Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop the trace when multiple tabs are found. #639

Merged
merged 14 commits into from
Oct 28, 2016

Conversation

wardpeet
Copy link
Collaborator

@wardpeet wardpeet commented Sep 8, 2016

Fixes #601
I check if multiple service workers got found and it throws an error. I check in the cli and the extension on this error and show the a good message.

let activeServiceWorkers = [];
registrations
.filter(reg => !reg.isDeleted)
.filter(reg => parseURL(reg.scopeURL).hostname === parseURL(pageUrl).hostname)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to check the origin is the same, not just the hostname

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I think @paulirish had something about using just hostname to do some limited redirect handling. One issue is port number...e.g. our smoketest on localhost:whatever might alert on this even though one of them has a SW and one doesn't

@@ -145,7 +145,7 @@ lighthouse(url, flags, config)
if (err.code === 'ECONNREFUSED') {
console.error('Unable to connect to Chrome. Please run Chrome w/ debugging port 9222 open:');
console.error(' npm explore -g lighthouse -- npm run chrome');
} else {
} else if (err) {
console.error('Runtime error encountered:', err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a bad idea. thanks

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some quick nits

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename tab to target to keep things clearer?

Copy link
Member

@paulirish paulirish Sep 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes ^^. for all three on this line.

 const target = targets.find(target => target.tabId === currentTab.id);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

return Promise.all([getRegistrations, getVersions, getActiveTabId]).then(res => {
const registrations = res[0].registrations;
const versions = res[1].versions;
const activePageId = res[2];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still should be activeTabId here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


const multipleTabsAttached = ver.controlledClients.length > 1;
const oneInactiveTabIsAttached = ver.controlledClients.length === 1 &&
!ver.controlledClients.find(sw => sw === activePageId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename sw to client

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

message = 'You probably have DevTools open.' +
' Close DevTools to use lighthouse';
}
if (message.toLowerCase().startsWith('unable to kill')) {
message = 'You probably have multiple tabs open.' +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably have multiple tabs open to the same origin or something like that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@wardpeet
Copy link
Collaborator Author

Updated

@wardpeet wardpeet force-pushed the sw/warn-tabs branch 3 times, most recently from 6dc888a to a58fef2 Compare September 17, 2016 09:09
@wardpeet
Copy link
Collaborator Author

updated, sorry for the many reviews!

@paulirish
Copy link
Member

In action screenshot:
image


let swHasMoreThanOneClient = false;
registrations
.filter(reg => !reg.isDeleted)
Copy link
Member

@paulirish paulirish Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nuke this isDeleted filter. Even if it's deleted (aka unregistered) it can come back from the dead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow didn't know that

});

if (swHasMoreThanOneClient) {
throw new Error(
Copy link
Member

@paulirish paulirish Sep 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we'll need to call driver.disconnect around now, so we dont keep the debugger attached.

Also, Is there any other cleanup we need to do?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will check if anything else needs to be done.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only needed to disconnect the driver. Couldn't find anything else. I will have a look at what happens when we close the trace in between.. (shouldn't be part of this PR)

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, a few more remaining things I've added. Sorry we were slow this week, lots of stuff going on.

I'll leave checkForMultipleTabsAttached for @paulirish to check, he's the one who's had his head in there :)

.catch(err => {
if (err.code === 'ECONNREFUSED') {
showConnectionError();
} else if (err.message.toLowerCase().includes('multiple tabs')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want this down in handleError()

}

/**
* Checks all serviceworkes and see if any duplications are running
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

service workers

@@ -44,7 +44,7 @@ const Config = require('./config/config');
*/

module.exports = function(url, flags, configJSON) {
return new Promise((resolve, reject) => {
return (new Promise((resolve, reject) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to add these parens here?

@@ -62,7 +62,7 @@ module.exports = function(url, flags, configJSON) {

// kick off a lighthouse run
resolve(Runner.run(driver, {url, flags, config}));
});
}));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

@@ -251,8 +260,7 @@ class GatherRunner {
// We dont need to hold up the reporting for the reload/disconnect,
// so we will not return a promise in here.
driver.reloadForCleanStateIfNeeded(options).then(_ => {
log.log('status', 'Disconnecting from browser...');
driver.disconnect();
GatherRunner.disposeDriver(driver);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move the log message back here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? I feel it's ok in dispose because of another source runs dispose we still want to show the log disconnecting because it's tightly coupled to disconnect?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how you have it is probably fine

})
// cleanup on error
.catch(err => {
GatherRunner.disposeDriver(driver);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that way you can have an error specific log here. Even as simple as `log.log('status', 'Disconnecting from browser after error...'); or something

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice job adding this here. We should have been doing this for other errors already

@@ -91,9 +92,17 @@ class GatherRunner {
}).then(_ => {
// Clears storage for origin.
return driver.clearDataForOrigin(options.url);
}).then(_ => {
// Check Service Workers running
return driver.checkForMultipleTabsAttached(options.url);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like setupDriver has a weird order. Feel free to punt to a followup PR, but I feel like the multiple tabs check should come before clearingDataForOrigin, for instance (why bother if the SW clear isn't going to work anyways?). Not sure of optimal order, though.

});
});
});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want to switch the outer Promise and this.queryCurrentTab_ here, to put this.queryCurrentTab_() into the promise chain so any rejections in it will be handled. Something like:

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);
    });
  });
});

@brendankenny
Copy link
Member

oh, he already approved checkForMultipleTabsAttached, I missed that

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few more comments on the tests :)

@@ -80,4 +83,123 @@ describe('Browser Driver', () => {
assert.notEqual(opts.url, req1.url, 'opts.url changed after the redirects');
assert.equal(opts.url, req3.url, 'opts.url matches the last redirect');
});

it('will fail when multiple tabs are found with the same active serviceworker', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be helpful to surround these tests with a nested
describe('Multiple tab check', () => { to make it clear what the it in these are, since these are just tests for checkForMultipleTabsAttached, not end to end tests.

.then(_ => assert.ok(true), _ => assert.ok(false));
});

it('will succeed when old sw are deleted', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this test still what you want given what @paulirish said about deleted service workers?

.then(_ => assert.ok(true), _ => assert.ok(false));
});

it('will succeed when only one sw loaded', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/sw/service worker

});

function createOnceStub(events) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move these to the top of the file with the other helper functions

@wardpeet
Copy link
Collaborator Author

@brendankenny could you look over this :)

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Ward. Somehow I missed your update commits and thought this PR was just chilling. Thanks for pinging me and staying on this :) Looking good, just some last comments.

@@ -196,6 +196,13 @@ function lighthouseRun(addresses) {
}

return lighthouseRun(addresses);
})
.catch(err => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately I can't remember at all now, but why is this catch necessary and errors won't be handled by handleError?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prolly just a mistake

return new Promise(resolve => {
this.once('ServiceWorker.workerRegistrationUpdated', data => {
this.sendCommand('ServiceWorker.disable');
resolve(data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add what's been added to getServiceWorkerVersions to handle promise rejections.

this.sendCommand('ServiceWorker.disable')
  .then(_ => resolve(data), reject);

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

@@ -90,11 +91,19 @@ class GatherRunner {
.then(_ => {
return driver.cleanAndDisableBrowserCaches();
}).then(_ => {
// Check if multiple tabs of origin are open
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I now think this check definitely be moved up first. No point in getting started if it's not going to work out. Maybe just:

return driver.checkForMultipleTabsAttached(options.url)
  .then(_ => {
    // Enable emulation if required.
    return options.flags.mobile && driver.beginEmulation();
  }).then(_ => {
    return driver.cleanAndDisableBrowserCaches();
  }).then(_ => {
    // Clears storage for origin.
    return driver.clearDataForOrigin(options.url);
  });

?

@@ -251,8 +260,7 @@ class GatherRunner {
// We dont need to hold up the reporting for the reload/disconnect,
// so we will not return a promise in here.
driver.reloadForCleanStateIfNeeded(options).then(_ => {
log.log('status', 'Disconnecting from browser...');
driver.disconnect();
GatherRunner.disposeDriver(driver);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how you have it is probably fine

})
.forEach(reg => {
// Check if any of the service workers are the same (registration id)
// Check if the controlledClients are bigger than 1 and it's not the active tab
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move these comments down to each step


const multipleTabsAttached = ver.controlledClients.length > 1;
const oneInactiveTabIsAttached = ver.controlledClients.length === 1 &&
!ver.controlledClients.find(client => client === activeTabId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: since you know it's length 1, clearer to just write this line as ver.controlledClients[0] !== activeTabId;?

}

/**
* Checks all service workers and see if any duplications are running
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something like Rejects if any open tabs would share a service worker with the target URL.?

@brendankenny
Copy link
Member

hmm, smoke test is broken, saying it's hitting Runtime error encountered: Error: Not implemented in Driver.getCurrentTabId

* @return {!Promise<string>}
*/
getCurrentTabId() {
return Promise.reject(new Error('Not implemented'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the issue is here. Driver no longer extends ExtensionConnection or CriConnection, so I believe driver's getCurrentTabId needs to call this._connection.getCurrentTabId()

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)

);
}

return res;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to return anything here

log.log('status', 'Disconnecting from browser...');
return driver.disconnect();
}).then(_ => {
// We dont need to hold up the reporting for the reload/disconnect,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this section doesn't exist anymore after #816. I think you just want

.then(_ => GatherRunner.disposeDriver(driver))
.then(_ => {
  // Collate all the gatherer results.
  ...

.then(_ => driver.enableRuntimeEvents())
.then(_ => driver.cleanAndDisableBrowserCaches())
.then(_ => driver.clearDataForOrigin(options.url));
}

static disposeDriver(driver) {
log.log('status', 'Disconnecting from browser...');
driver.disconnect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should copy the

// We dont need to hold up the reporting for the reload/disconnect,
// so we will not return a promise in here.

comment up here so it's clear why driver.disconnect(); isn't being returned

@wardpeet
Copy link
Collaborator Author

@brendankenny fixed latest nits :)

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! 🎊 🎉 🎈 🍰

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants