Skip to content

Commit

Permalink
Stop lighthouse run when multiple tabs to the same origin are found (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
wardpeet authored and brendankenny committed Oct 28, 2016
1 parent 5b4b1c5 commit 48a524d
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 9 deletions.
2 changes: 2 additions & 0 deletions lighthouse-cli/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ function showRuntimeError(err: LightHouseError) {
function handleError(err: LightHouseError) {
if (err.code === 'ECONNREFUSED') {
showConnectionError();
} else if (err.message.toLowerCase().includes('multiple tabs')) {
console.error(err.message);
} else {
showRuntimeError(err);
}
Expand Down
6 changes: 6 additions & 0 deletions lighthouse-core/gather/connections/cri.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class CriConnection extends Connection {
connect() {
return this._runJsonCommand('new').then(response => {
const url = response.webSocketDebuggerUrl;
this._tabId = response.id;

return new Promise((resolve, reject) => {
const ws = new WebSocket(url);
ws.on('open', () => {
Expand Down Expand Up @@ -88,6 +90,10 @@ class CriConnection extends Connection {
sendRawMessage(message) {
this._ws.send(message);
}

getCurrentTabId() {
return Promise.resolve(this._tabId);
}
}

module.exports = CriConnection;
16 changes: 16 additions & 0 deletions lighthouse-core/gather/connections/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,22 @@ class ExtensionConnection extends Connection {
getCurrentTabURL() {
return this._queryCurrentTab().then(tab => tab.url);
}

getCurrentTabId() {
return this.queryCurrentTab_().then(currentTab => {
return new Promise((resolve, reject) => {
chrome.debugger.getTargets(targets => {
const target = targets.find(target => target.tabId === currentTab.id);

if (!target) {
reject(new Error('We can\'t find a target id.'));
}

resolve(target.id);
});
});
});
}
}

module.exports = ExtensionConnection;
74 changes: 74 additions & 0 deletions lighthouse-core/gather/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ class Driver {
return this._connection.sendCommand(method, params);
}

/**
* Get current tab id
* @return {!Promise<string>}
*/
getCurrentTabId() {
return this._connection.getCurrentTabId();
}

evaluateScriptOnLoad(scriptSource) {
return this.sendCommand('Page.addScriptToEvaluateOnLoad', {
scriptSource
Expand Down Expand Up @@ -177,6 +185,72 @@ class Driver {
});
}

getServiceWorkerRegistrations() {
return new Promise((resolve, reject) => {
this.once('ServiceWorker.workerRegistrationUpdated', data => {
this.sendCommand('ServiceWorker.disable')
.then(_ => resolve(data), reject);
});

this.sendCommand('ServiceWorker.enable').catch(reject);
});
}

/**
* Rejects if any open tabs would share a service worker with the target URL.
* @param {!string} pageUrl
* @return {!Promise}
*/
checkForMultipleTabsAttached(pageUrl) {
// Get necessary information of serviceWorkers
const getRegistrations = this.getServiceWorkerRegistrations();
const getVersions = this.getServiceWorkerVersions();
const getActiveTabId = this.getCurrentTabId();

return Promise.all([getRegistrations, getVersions, getActiveTabId]).then(res => {
const registrations = res[0].registrations;
const versions = res[1].versions;
const activeTabId = res[2];
const parsedURL = parseURL(pageUrl);
const origin = `${parsedURL.protocol}//${parsedURL.hostname}` +
(parsedURL.port ? `:${parsedURL.port}` : '');

let swHasMoreThanOneClient = false;
registrations
.filter(reg => {
const parsedURL = parseURL(reg.scopeURL);
const swOrigin = `${parsedURL.protocol}//${parsedURL.hostname}` +
(parsedURL.port ? `:${parsedURL.port}` : '');

return origin === swOrigin;
})
.forEach(reg => {
swHasMoreThanOneClient = !!versions.find(ver => {
// Check if any of the service workers are the same (registration id)
if (ver.registrationId !== reg.registrationId) {
return false;
}

// Check if the controlledClients are bigger than 1 and it's not the active tab
if (!ver.controlledClients || ver.controlledClients.length === 0) {
return false;
}

const multipleTabsAttached = ver.controlledClients.length > 1;
const oneInactiveTabIsAttached = ver.controlledClients[0] !== activeTabId;

return multipleTabsAttached || oneInactiveTabIsAttached;
});
});

if (swHasMoreThanOneClient) {
throw new Error(
'You probably have multiple tabs open to the same origin.'
);
}
});
}

/**
* If our main document URL redirects, we will update options.url accordingly
* As such, options.url will always represent the post-redirected URL.
Expand Down
27 changes: 20 additions & 7 deletions lighthouse-core/gather/gather-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ const path = require('path');
* 1. Setup
* A. driver.connect()
* B. GatherRunner.setupDriver()
* i. beginEmulation
* ii. cleanAndDisableBrowserCaches
* iii. clearDataForOrigin
* i. checkForMultipleTabsAttached
* ii. beginEmulation
* iii. cleanAndDisableBrowserCaches
* iiii. clearDataForOrigin
*
* 2. For each pass in the config:
* A. GatherRunner.beforePass()
Expand Down Expand Up @@ -85,12 +86,20 @@ class GatherRunner {
static setupDriver(driver, options) {
log.log('status', 'Initializing…');
// Enable emulation based on flags
return driver.beginEmulation(options.flags)
return driver.checkForMultipleTabsAttached(options.url)
.then(_ => driver.beginEmulation(options.flags))
.then(_ => driver.enableRuntimeEvents())
.then(_ => driver.cleanAndDisableBrowserCaches())
.then(_ => driver.clearDataForOrigin(options.url));
}

static disposeDriver(driver) {
// We dont need to hold up the reporting for the reload/disconnect,
// so we will not return a promise in here.
log.log('status', 'Disconnecting from browser...');
driver.disconnect();
}

/**
* Navigates to about:blank and calls beforePass() on gatherers before tracing
* has started and before navigation to the target page.
Expand Down Expand Up @@ -240,10 +249,8 @@ class GatherRunner {
options.url = urlAfterRedirects;
});
})
.then(_ => GatherRunner.disposeDriver(driver))
.then(_ => {
log.log('status', 'Disconnecting from browser...');
return driver.disconnect();
}).then(_ => {
// Collate all the gatherer results.
const computedArtifacts = this.instantiateComputedArtifacts();
const artifacts = Object.assign({}, computedArtifacts, tracingData);
Expand All @@ -258,6 +265,12 @@ class GatherRunner {
});
});
return artifacts;
})
// cleanup on error
.catch(err => {
GatherRunner.disposeDriver(driver);

throw err;
});
}

Expand Down
6 changes: 6 additions & 0 deletions lighthouse-core/test/gather/fake-driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ module.exports = {
beginEmulation() {
return Promise.resolve();
},
checkForMultipleTabsAttached() {
return Promise.resolve();
},
reloadForCleanStateIfNeeded() {
return Promise.resolve();
},
enableRuntimeEvents() {
return Promise.resolve();
},
Expand Down
3 changes: 3 additions & 0 deletions lighthouse-core/test/gather/gather-runner-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ function getMockedEmulationDriver(emulationFn, netThrottleFn, cpuThrottleFn) {
enableRuntimeEvents() {
return Promise.resolve();
}
checkForMultipleTabsAttached() {
return Promise.resolve();
}
cleanAndDisableBrowserCaches() {}
clearDataForOrigin() {}
};
Expand Down
99 changes: 99 additions & 0 deletions lighthouse-core/test/lib/drivers/driver-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@ const assert = require('assert');
const connection = new Connection();
const driverStub = new Driver(connection);

function createOnceStub(events) {
return (eventName, cb) => {
if (events[eventName]) {
return cb(events[eventName]);
}

throw Error(`Stub not implemented: ${eventName}`);
};
}

function createSWRegistration(id, url, isDeleted) {
return {
isDeleted: !!isDeleted,
registrationId: id,
scopeURL: url,
};
}

function createActiveWorker(id, url, controlledClients) {
return {
registrationId: id,
scriptURL: url,
controlledClients,
};
}

connection.sendCommand = function(command, params) {
switch (command) {
case 'DOM.getDocument':
Expand All @@ -34,6 +60,9 @@ connection.sendCommand = function(command, params) {
return Promise.resolve({
nodeId: params.selector === 'invalid' ? 0 : 231
});
case 'ServiceWorker.enable':
case 'ServiceWorker.disable':
return Promise.resolve();
default:
throw Error(`Stub not implemented: ${command}`);
}
Expand Down Expand Up @@ -83,3 +112,73 @@ describe('Browser Driver', () => {
assert.equal(opts.url, req3.url, 'opts.url matches the last redirect');
});
});

describe('Multiple tab check', () => {
it('will fail when multiple tabs are found with the same active serviceworker', () => {
const pageUrl = 'https://example.com/';
const swUrl = `${pageUrl}sw.js`;
const registrations = [
createSWRegistration(1, pageUrl),
];
const versions = [
createActiveWorker(1, swUrl, ['unique'])
];

driverStub.getCurrentTabId = () => Promise.resolve('unique2');
driverStub.once = createOnceStub({
'ServiceWorker.workerRegistrationUpdated': {
registrations
},
'ServiceWorker.workerVersionUpdated': {
versions
}
});

return driverStub.checkForMultipleTabsAttached(pageUrl)
.then(_ => assert.ok(false), _ => assert.ok(true));
});

it('will succeed when service worker is already registered on current tab', () => {
const pageUrl = 'https://example.com/';
const swUrl = `${pageUrl}sw.js`;
const registrations = [
createSWRegistration(1, pageUrl),
];
const versions = [
createActiveWorker(1, swUrl, ['unique'])
];

driverStub.getCurrentTabId = () => Promise.resolve('unique');
driverStub.once = createOnceStub({
'ServiceWorker.workerRegistrationUpdated': {
registrations
},
'ServiceWorker.workerVersionUpdated': {
versions
}
});

return driverStub.checkForMultipleTabsAttached(pageUrl)
.then(_ => assert.ok(true), _ => assert.ok(false));
});

it('will succeed when only one service worker loaded', () => {
const pageUrl = 'https://example.com/';
const swUrl = `${pageUrl}sw.js`;
const registrations = [createSWRegistration(1, pageUrl)];
const versions = [createActiveWorker(1, swUrl, [])];

driverStub.getCurrentTabId = () => Promise.resolve('unique');
driverStub.once = createOnceStub({
'ServiceWorker.workerRegistrationUpdated': {
registrations
},
'ServiceWorker.workerVersionUpdated': {
versions
}
});

return driverStub.checkForMultipleTabsAttached(pageUrl)
.then(_ => assert.ok(true), _ => assert.ok(false));
});
});
9 changes: 7 additions & 2 deletions lighthouse-extension/app/src/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,16 @@ document.addEventListener('DOMContentLoaded', _ => {
}, selectedAudits);
})
.catch(err => {
let {message} = err;
if (err.message.toLowerCase().startsWith('another debugger')) {
let message = err.message;
if (message.toLowerCase().startsWith('another debugger')) {
message = 'You probably have DevTools open.' +
' Close DevTools to use lighthouse';
}
if (message.toLowerCase().includes('multiple tabs')) {
message = 'You probably have multiple tabs open to the same origin.' +
' Close the other tabs to use lighthouse.';
}

feedbackEl.textContent = message;
stopSpinner();
background.console.error(err);
Expand Down

0 comments on commit 48a524d

Please sign in to comment.