From b1b28044d1c7195e18223850f18bfc7aa7ca8aa6 Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Wed, 22 Apr 2020 12:22:20 +0200 Subject: [PATCH] fix: use getRoot request to support Firefox 77+ (#1886) --- src/firefox/remote.js | 57 ++++++++++++++------ tests/functional/fake-firefox-binary.js | 8 +-- tests/unit/test-firefox/test.remote.js | 71 +++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 26 deletions(-) diff --git a/src/firefox/remote.js b/src/firefox/remote.js index 2a16e0944e..6d6338f462 100644 --- a/src/firefox/remote.js +++ b/src/firefox/remote.js @@ -86,27 +86,50 @@ export class RemoteFirefox { }); } + getAddonsActor(): Promise { + return new Promise((resolve, reject) => { + // getRoot should work since Firefox 55 (bug 1352157). + this.client.request('getRoot', (err, actors) => { + if (!err) { + if (actors.addonsActor) { + resolve(actors.addonsActor); + } else { + reject(new RemoteTempInstallNotSupported( + 'This version of Firefox does not provide an add-ons actor for ' + + 'remote installation.')); + } + return; + } + log.debug(`Falling back to listTabs because getRoot failed: ${err}`); + // Fallback to listTabs otherwise, Firefox 49 - 77 (bug 1618691). + this.client.request('listTabs', (error, tabsResponse) => { + if (error) { + return reject(new WebExtError( + `Remote Firefox: listTabs() error: ${error}`)); + } + // addonsActor was added to listTabs in Firefox 49 (bug 1273183). + if (!tabsResponse.addonsActor) { + log.debug( + 'listTabs returned a falsey addonsActor: ' + + `${tabsResponse.addonsActor}`); + return reject(new RemoteTempInstallNotSupported( + 'This is an older version of Firefox that does not provide an ' + + 'add-ons actor for remote installation. Try Firefox 49 or ' + + 'higher.')); + } + resolve(tabsResponse.addonsActor); + }); + }); + }); + } + installTemporaryAddon( addonPath: string ): Promise { return new Promise((resolve, reject) => { - this.client.request('listTabs', (error, tabsResponse) => { - if (error) { - return reject(new WebExtError( - `Remote Firefox: listTabs() error: ${error}`)); - } - if (!tabsResponse.addonsActor) { - log.debug( - 'listTabs returned a falsey addonsActor: ' + - `${tabsResponse.addonsActor}`); - return reject(new RemoteTempInstallNotSupported( - 'This is an older version of Firefox that does not provide an ' + - 'add-ons actor for remote installation. Try Firefox 49 or ' + - 'higher.')); - } - + this.getAddonsActor().then((addonsActor) => { this.client.client.makeRequest({ - to: tabsResponse.addonsActor, + to: addonsActor, type: 'installTemporaryAddon', addonPath, }, (installResponse) => { @@ -120,7 +143,7 @@ export class RemoteFirefox { log.info(`Installed ${addonPath} as a temporary add-on`); resolve(installResponse); }); - }); + }).catch(reject); }); } diff --git a/tests/functional/fake-firefox-binary.js b/tests/functional/fake-firefox-binary.js index 7a1f13ac4c..d399a7190c 100755 --- a/tests/functional/fake-firefox-binary.js +++ b/tests/functional/fake-firefox-binary.js @@ -3,8 +3,8 @@ const net = require('net'); const REPLY_INITIAL = {from: 'root'}; -const REQUEST_LIST_TABS = {to: 'root', type: 'listTabs'}; -const REPLY_LIST_TABS = {from: 'root', addonsActor: 'fakeAddonsActor'}; +const REQUEST_ACTORS = {to: 'root', type: 'getRoot'}; +const REPLY_ACTORS = {from: 'root', addonsActor: 'fakeAddonsActor'}; const REQUEST_INSTALL_ADDON = { to: 'fakeAddonsActor', type: 'installTemporaryAddon', @@ -37,8 +37,8 @@ function getPortFromArgs() { } net.createServer(function(socket) { socket.on('data', function(data) { - if (String(data) === toRDP(REQUEST_LIST_TABS)) { - socket.write(toRDP(REPLY_LIST_TABS)); + if (String(data) === toRDP(REQUEST_ACTORS)) { + socket.write(toRDP(REPLY_ACTORS)); } else if (String(data) === toRDP(REQUEST_INSTALL_ADDON)) { socket.write(toRDP(REPLY_INSTALL_ADDON)); diff --git a/tests/unit/test-firefox/test.remote.js b/tests/unit/test-firefox/test.remote.js index 899958fcb5..b8309aa081 100644 --- a/tests/unit/test-firefox/test.remote.js +++ b/tests/unit/test-firefox/test.remote.js @@ -225,9 +225,9 @@ describe('firefox.remote', () => { describe('installTemporaryAddon', () => { - it('throws listTabs errors', async () => { + it('throws getRoot errors', async () => { const client = fakeFirefoxClient({ - // listTabs response: + // listTabs and getRoot response: requestError: new Error('some listTabs error'), }); const conn = makeInstance(client); @@ -236,24 +236,33 @@ describe('firefox.remote', () => { .catch(onlyInstancesOf(WebExtError, (error) => { assert.match(error.message, /some listTabs error/); })); + + // When getRoot fails, a fallback to listTabs is expected. + sinon.assert.calledTwice(client.request); + sinon.assert.calledWith(client.request, 'getRoot'); + sinon.assert.calledWith(client.request, 'listTabs'); }); it('fails when there is no add-ons actor', async () => { const client = fakeFirefoxClient({ - // A listTabs response that does not contain addonsActor. + // A getRoot and listTabs response that does not contain addonsActor. requestResult: {}, }); const conn = makeInstance(client); await conn.installTemporaryAddon('/path/to/addon') .then(makeSureItFails()) .catch(onlyInstancesOf(RemoteTempInstallNotSupported, (error) => { - assert.match(error.message, /does not provide an add-ons actor/); + assert.match( + error.message, + /This version of Firefox does not provide an add-ons actor/); })); + sinon.assert.calledOnce(client.request); + sinon.assert.calledWith(client.request, 'getRoot'); }); it('lets you install an add-on temporarily', async () => { const client = fakeFirefoxClient({ - // listTabs response: + // getRoot response: requestResult: { addonsActor: 'addons1.actor.conn', }, @@ -265,6 +274,58 @@ describe('firefox.remote', () => { const conn = makeInstance(client); const response = await conn.installTemporaryAddon('/path/to/addon'); assert.equal(response.addon.id, 'abc123@temporary-addon'); + + // When called without error, there should not be any fallback. + sinon.assert.calledOnce(client.request); + sinon.assert.calledWith(client.request, 'getRoot'); + }); + + it('falls back to listTabs when getRoot is unavailable', async () => { + const client = fakeFirefoxClient({ + // installTemporaryAddon response: + makeRequestResult: { + addon: {id: 'abc123@temporary-addon'}, + }, + }); + client.request = sinon.stub(); + // Sample response from Firefox 49. + client.request.withArgs('getRoot').callsArgWith(1, { + error: 'unrecognizedPacketType', + message: 'Actor root does not recognize the packet type getRoot', + }); + client.request.withArgs('listTabs').callsArgWith(1, undefined, { + addonsActor: 'addons1.actor.conn', + }); + const conn = makeInstance(client); + const response = await conn.installTemporaryAddon('/path/to/addon'); + assert.equal(response.addon.id, 'abc123@temporary-addon'); + + sinon.assert.calledTwice(client.request); + sinon.assert.calledWith(client.request, 'getRoot'); + sinon.assert.calledWith(client.request, 'listTabs'); + }); + + it('fails when getRoot and listTabs both fail', async () => { + const client = fakeFirefoxClient(); + client.request = sinon.stub(); + // Sample response from Firefox 48. + client.request.withArgs('getRoot').callsArgWith(1, { + error: 'unrecognizedPacketType', + message: 'Actor root does not recognize the packet type getRoot', + }); + client.request.withArgs('listTabs').callsArgWith(1, undefined, {}); + const conn = makeInstance(client); + await conn.installTemporaryAddon('/path/to/addon') + .then(makeSureItFails()) + .catch(onlyInstancesOf(RemoteTempInstallNotSupported, (error) => { + assert.match( + error.message, + /does not provide an add-ons actor.*Try Firefox 49/); + })); + + sinon.assert.calledTwice(client.request); + sinon.assert.calledWith(client.request, 'getRoot'); + sinon.assert.calledWith(client.request, 'listTabs'); }); it('throws install errors', async () => {