Skip to content

Commit

Permalink
fix: use getRoot request to support Firefox 77+ (#1886)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rob--W committed Apr 22, 2020
1 parent e0d4c3c commit b1b2804
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 26 deletions.
57 changes: 40 additions & 17 deletions src/firefox/remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,50 @@ export class RemoteFirefox {
});
}

getAddonsActor(): Promise<string> {
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<FirefoxRDPResponseAddon> {
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) => {
Expand All @@ -120,7 +143,7 @@ export class RemoteFirefox {
log.info(`Installed ${addonPath} as a temporary add-on`);
resolve(installResponse);
});
});
}).catch(reject);
});
}

Expand Down
8 changes: 4 additions & 4 deletions tests/functional/fake-firefox-binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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));

Expand Down
71 changes: 66 additions & 5 deletions tests/unit/test-firefox/test.remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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',
},
Expand All @@ -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 () => {
Expand Down

0 comments on commit b1b2804

Please sign in to comment.