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

DBW: use navigator.sendBeacon in beforeunload/pagehide handlers #871

Open
ebidel opened this issue Oct 31, 2016 · 11 comments
Open

DBW: use navigator.sendBeacon in beforeunload/pagehide handlers #871

ebidel opened this issue Oct 31, 2016 · 11 comments
Labels
needs-complete-audit-proposal https://github.com/GoogleChrome/lighthouse/blob/master/docs/new-audits.md new_audit P2

Comments

@ebidel
Copy link
Contributor

ebidel commented Oct 31, 2016

How to check:

because user agents typically ignore asynchronous XMLHttpRequests made in an unload handler.

To solve this problem, analytics and diagnostics code have historically made a synchronous XMLHttpRequest call in an unload or beforeunload event handler to submit the data. The synchronous XMLHttpRequest blocks the process of unloading the document, which in turn causes the next navigation appear to be slower. There is nothing the next page can do to avoid this perception of poor page load performance, and the result is that the user perceives that the new web page is slow to load, even though the true issue is with the previous page.

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon

@olingern
Copy link
Contributor

olingern commented Nov 11, 2016

@ebidel I looked at this for a while today, and wanted to ping you about a couple of things.

filename for the audit?

  • uses-sendbeacon
  • no-xhr-beforeUnload-pagehide

Watching network requests
Since I'm still new to the codebase, I wanted to ask a couple of questions about implementing the checks for this.

  1. I see that, if I define an beforeUnload event, it's available to me through PageLevelEventListeners on the artifacts object thats passed into audit. It seems like the best way to implement this would be, check for beforeUnload or pagehide events, if present - monitor for sync xhrs or fetch like you mentioned before.
  2. I'm still searching around the codebase for example on hooking into events. It seems that if either of the two closing events are present, then I should dispatch a close event and listen for sync xhrs / fetch. Do you have anywhere in the codebase where you could link me to an example of invoking events? As for monitoring, it seems that NetworkRecorder inside of lighthouse-core/lib will be of most help for this.

Cheers

@ebidel
Copy link
Contributor Author

ebidel commented Nov 11, 2016

"uses-sendbeacon" sgtm.

For the network stuff, it looks like something clever like this could work. I didn't realize you could dispatching beforeunload yourself works!

// Inject this into page in beforePass
const observer = new PerformanceObserver(list => {
  const xhrsRequests = list.getEntries().filter(entry => {
    // Note: fetch() does not have initiatorType - https://crbug.com/649571
    return entry.initiatorType === 'xmlhttprequest';
  });
  if (xhrsRequests.length) {
    console.log(xhrsRequests);
  }
}).observe({entryTypes: ['resource']});

// Example of page code that you should add to lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html
window.addEventListener('beforeunload', e => {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://www.chromestatus.com/features', false);
  xhr.send();
});

// Inject into page in `afterPass`
window.dispatchEvent(new CustomEvent('beforeunload'));
window.dispatchEvent(new CustomEvent('pagehide'));

You could wrap the observer and dispatching part in a function that gets injected into the page. Example. You might need to play around with delaying dispatching the events.

This would't tell you that the XHR was sync (unless you also grep the console output for the warning chrome throws when sync xhrs are used), but I don't think it would hurt to always suggest navigator.sendBeacon for all requests made in beforeunload and pagehide handlers.

@igrigorik
Copy link

Does this need to be instrumented in the page itself? Instead, could you look at the initiator field in the trace event log and detect any !beacon requests initiated after unload?

PerfObserver does not guarantee synchronous delivery of these events, and since page is being unloaded, there is high likelihood that you may miss requests with the above pattern -- e.g. inject an image beacon and allow page to continue unloading, etc.

@ebidel
Copy link
Contributor Author

ebidel commented Nov 14, 2016

Just looking at the --save-artifacts output from running against https://googlechrome.github.io/samples/beacon/....`navigator.sendBeacon` requests don't seem to show up in our network trace. Perhaps this is because they're not captured during the pass (e.g. the request is made after the fact in a pagehide handler).

The other thing we miss out on with using the network trace is line/col/source info:

screen shot 2016-11-14 at 10 06 57 am

@ebidel
Copy link
Contributor Author

ebidel commented Nov 14, 2016

@igrigorik I also found that navigator.sendBeacon requests don't show up in PerformanceObserver. Is that known? Should they?

@igrigorik
Copy link

Yes, they should. Sounds like a bug. If you have a test page.. can you please file a bug?

@olingern
Copy link
Contributor

@ebidel @igrigorik is this something I can tackle without navigator.sendBeacon exposed, or should I hold off until the bug is fixed?

@ebidel
Copy link
Contributor Author

ebidel commented Nov 22, 2016

@olingern the bug with PerformanceObserver doesn't apply here. We don't care about pages using navigator.sendBeacon. They're doing the right thing. We only care about pages that are using XHR on page unload :)

I'd experiment with the technique I highlighted in #871 (comment) to see if we catch PerformanceObserver callbacks in unbeforeunload. They're not guaranteed to be delivered on page unload, but I believe they were when I tried this out quickly.

Definitely add some tests to https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-cli/test/fixtures/dobetterweb/dbw_tester.html and let us know.

@paulirish paulirish added needs-complete-audit-proposal https://github.com/GoogleChrome/lighthouse/blob/master/docs/new-audits.md and removed good first issue needs-priority labels Mar 8, 2018
@paulirish
Copy link
Member

Need to get a complete new audit proposal on this one: https://github.com/GoogleChrome/lighthouse/blob/master/docs/new-audits.md

It's definitely worthwhile, but will take some work.

@paulirish
Copy link
Member

in m75, sync xhr in "page dismissal" is attempted to be removed.
https://bugs.chromium.org/p/chromium/issues/detail?id=827324

I'm pretty sure there is a console.log/violation we could use.

Seems good, though we'd have to dismiss the page during gathering. We could try to fire their handlers, though..

But if this removal sticks, then it already did our job for us.

@connorjclark
Copy link
Collaborator

window.addEventListener('beforeunload', function () {
   var request = new XMLHttpRequest();
request.open('GET', '/bar/foo.txt', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {
  console.log(request.responseText);
}
});
VM26:1 Uncaught DOMException: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'https://developer.mozilla.org/bar/foo.txt': Synchronous XHR in page dismissal.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-complete-audit-proposal https://github.com/GoogleChrome/lighthouse/blob/master/docs/new-audits.md new_audit P2
6 participants