diff --git a/src/extension-runners/firefox-android.js b/src/extension-runners/firefox-android.js index a44c5d4056..9ca556938b 100644 --- a/src/extension-runners/firefox-android.js +++ b/src/extension-runners/firefox-android.js @@ -138,6 +138,13 @@ export class FirefoxAndroidExtensionRunner { // push the profile preferences to the remote profile dir. await this.adbPrepareProfileDir(); + const stdin = this.params.stdin || process.stdin; + + if (isTTY(stdin)) { + readline.emitKeypressEvents(stdin); + setRawMode(stdin, true); + } + // NOTE: running Firefox for Android on the Android Emulator can be // pretty slow, we can run the following 3 steps in parallel to speed up // it a bit. @@ -157,6 +164,11 @@ export class FirefoxAndroidExtensionRunner { this.adbDiscoveryAndForwardRDPUnixSocket(), ]); + if (isTTY(stdin)) { + // Restore mode so Ctrl-C can be used to kill the process. + setRawMode(stdin, false); + } + // Connect to RDP socket on the local tcp server, install all the pushed extension // and keep track of the built and installed extension by extension sourceDir. await this.rdpInstallExtensions(); @@ -488,12 +500,30 @@ export class FirefoxAndroidExtensionRunner { log.debug(`Using profile ${deviceProfileDir} (ignored by Fenix)`); - await adbUtils.startFirefoxAPK( - selectedAdbDevice, - selectedFirefoxApk, - firefoxApkComponent, - deviceProfileDir - ); + const stdin = this.params.stdin || process.stdin; + + const handleCtrlC = (str, key) => { + if (key.ctrl && key.name === 'c') { + adbUtils.setUserAbortStartActivity(true); + } + }; + + if (isTTY(stdin)) { + stdin.on('keypress', handleCtrlC); + } + + try { + await adbUtils.startFirefoxAPK( + selectedAdbDevice, + selectedFirefoxApk, + firefoxApkComponent, + deviceProfileDir + ); + } finally { + if (isTTY(stdin)) { + stdin.removeListener('keypress', handleCtrlC); + } + } } async buildAndPushExtension(sourceDir: string) { @@ -562,9 +592,6 @@ export class FirefoxAndroidExtensionRunner { // TODO: use noInput property to decide if we should // disable direct keypress handling. if (isTTY(stdin)) { - readline.emitKeypressEvents(stdin); - setRawMode(stdin, true); - stdin.on('keypress', handleCtrlC); } diff --git a/src/util/adb.js b/src/util/adb.js index bb1342ac71..1ecffebbee 100644 --- a/src/util/adb.js +++ b/src/util/adb.js @@ -59,6 +59,10 @@ export default class ADBUtils { // while it is still executing. userAbortDiscovery: boolean; + // Toggled when the user wants to abort the Start Activity loop + // while it is still executing. + userAbortStartActivity: boolean; + constructor(params: ADBUtilsParams) { this.params = params; @@ -75,6 +79,7 @@ export default class ADBUtils { this.artifactsDirMap = new Map(); this.userAbortDiscovery = false; + this.userAbortStartActivity = false; } runShellCommand( @@ -351,20 +356,43 @@ export default class ADBUtils { // the following to: `${apk}/${apk}.${apkComponent}` const component = `${apk}/${apkComponent}`; - await wrapADBCall(async () => { + let exception; + wrapADBCall(async () => { await adbClient.getDevice(deviceId).startActivity({ wait: true, action: 'android.activity.MAIN', component, extras, }); - }); + }) + .then(() => { + exception = null; + }) + .catch((err) => { + exception = err; + }); + + // Wait for the activity to be started. + while (exception === undefined) { + if (this.userAbortStartActivity) { + throw new UsageError('Exiting Firefox Start Activity on user request'); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + if (exception) { + throw exception; + } } setUserAbortDiscovery(value: boolean) { this.userAbortDiscovery = value; } + setUserAbortStartActivity(value: boolean) { + this.userAbortStartActivity = value; + } + async discoverRDPUnixSocket( deviceId: string, apk: string, diff --git a/tests/unit/test-extension-runners/test.firefox-android.js b/tests/unit/test-extension-runners/test.firefox-android.js index 30f0ebea26..7b7c81b7cf 100644 --- a/tests/unit/test-extension-runners/test.firefox-android.js +++ b/tests/unit/test-extension-runners/test.firefox-android.js @@ -132,6 +132,7 @@ function prepareSelectedDeviceAndAPKParams( clearArtifactsDir: sinon.spy(() => Promise.resolve()), detectOrRemoveOldArtifacts: sinon.spy(() => Promise.resolve(true)), setUserAbortDiscovery: sinon.spy(() => {}), + setUserAbortStartActivity: sinon.spy(() => {}), ensureRequiredAPKRuntimePermissions: sinon.spy(() => Promise.resolve()), ...adbOverrides, }; @@ -989,4 +990,19 @@ describe('util/extension-runners/firefox-android', () => { consoleStream.stopCapturing(); }); }); + it('Stdin raw mode is set to false before setupForward', async () => { + const { params, fakeADBUtils } = prepareSelectedDeviceAndAPKParams(); + + fakeADBUtils.setupForward = sinon.spy(async () => { + fakeStdin.emit('keypress', 'c', { name: 'c', ctrl: true }); + }); + + const fakeStdin = sinon.spy(createFakeStdin()); + params.stdin = fakeStdin; + + const runnerInstance = new FirefoxAndroidExtensionRunner(params); + await runnerInstance.run(); + + assert.deepEqual(fakeStdin.setRawMode.lastCall.args, [false]); + }); }); diff --git a/tests/unit/test-util/test.adb.js b/tests/unit/test-util/test.adb.js index 5c4125f316..a538576526 100644 --- a/tests/unit/test-util/test.adb.js +++ b/tests/unit/test-util/test.adb.js @@ -1136,6 +1136,33 @@ describe('utils/adb', () => { wait: true, }); }); + + it('rejects an UsageError on setUserAbortStartActivity call', async () => { + const adb = getFakeADBKit({ + adbDevice: { + startActivity: sinon.spy(() => Promise.resolve()), + }, + adbkitUtil: { + readAll: sinon.spy(() => Promise.resolve(Buffer.from('\n'))), + }, + }); + const adbUtils = new ADBUtils({ adb }); + + adbUtils.setUserAbortStartActivity(true); + + const promise = adbUtils.startFirefoxAPK( + 'device1', + 'org.mozilla.firefox_mybuild', + undefined, // firefoxApkComponent/* + '/fake/custom/profile/path' + ); + + await assert.isRejected(promise, UsageError); + await assert.isRejected( + promise, + 'Exiting Firefox Start Activity on user request' + ); + }); }); describe('discoverRDPUnixSocket', () => {