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

Make @bugsnag/plugin-inline-script-content more robust #528

Merged
merged 14 commits into from
May 13, 2019

Conversation

bengourley
Copy link
Contributor

@bengourley bengourley commented Apr 25, 2019

The approach to inline script handling changed in v4+ of the browser notifier. Compared with the old approach, the updated approach makes the experience better when it succeeds – providing surrounding code for the location of the error – but when it fails it causes bad grouping and confusing displays of surrounding code. It fails more often than the approach of its predecessor, which has a negative impact on the users of Bugsnag who monitor websites which make heavy use of inline scripts.

This changeset combines the approach from v3 and v4 to make the inline script handling more robust, as well as providing a good approximation of the surrouding code.

Outline of the combined approach

Using the implementation in v3 as a guide, the plugin-inline-script-content has been updated to trace execution of all callbacks back to their original script using the following algorithm:

  • initialise a variable lastScript
  • implementent a function getCurrentScript() which returns
    • document.currentScript if defined, otherwise…
    • lastScript if defined, otherwise…
    • the last item in document.scripts if the document is still loading
  • wrap every single piece of code than can schedule some asynchronous javascript with a function that updates lastScript before the async callback runs and unsets lastScript once it has completed (this list includes: setTimeout, setInterval, setImmediate, requestAnimationFrame and everything in this list)
  • in a beforeSend callback for every error
    • detect if the URL of the top stackframe matches the initial URL of the page OR if there is no stacktrace information at all – this indicates that it is an inline script. If it is an inline script…
    • call getCurrentScript() to get the last known script to be running before this error was caught. Attach the innerHTML value of the script object to report.metaData.script.content.

And using a similar implementation to v4, but without searching around for <script> tags using regexes, simple extracting the surrounding lines:

  • in the same beforeSend callback described above for every error, when we know we are dealing with an inline script…
    • get the innerText of the document.documentElement and pad it with a <!DOCTYPE html>-like first line. This is the best approximation of the raw html the browser received that it is possible to get with JS. Using the line number of the stackframe, extract the surrounding code (3 lines above and 3 lines below) and add this to the stackframe.code property. These lines may contain JS/HTML or a mix of both.

Testing

I have thoroughly tested this in a variety of browsers, comparing with the behaviour of the v3 notifier and of no notifier at all – paying particular attention to old IEs where the stacktrace information is – at best – limited. In the process I fixed a bug where we could have got a better stacktrace for handled errors (1805259).

The approach is water-tight (as this is what the old notifier did) but the codebase has moved on since then so this is new code and could break in new ways. This needs a lot of manual testing in addition to the end to end tests.

I manually verified the following behaviour:

  • wrapped functions (e.g. setTimeout) have the correct this when executing the callback
  • correct stacktraces for
    • various browsers (IE8,9,10,Chrome,FF)
    • handled/unhandled errors (handled errors could do with a pipeline change to improve grouping in old IEs)
    • sync / async / nested async functions

Versioning Release plan

I would be averse to anything but a major version bump for this changeset. In my opinion is is too wide reaching.

Rather than a major version bump (since this has no breaking API changes) we are going to stagger the rollout, first to npm, and then follow up with a CDN release.

Bundle size

To mitigate the known increase in bundle size due to this changeset I removed the deprecated config warnings (cb3e793). Even so, the bundle increase is rather significant:

- 11.69kB
+ 12.17kB

Todo

  • Maybe add an option to disable this plugin (this also will contribute to bundle inflation)
  • Add maze runner scenarios
  • Test calling timer fns with multiple arguments
  • Calling __proxy'd fns with bad arguments
  • Test addEventListener callbacks receive the correct this value
  • Truncate the number of lines in surrounding code a la fix(plugin-node-surrounding-code): Ensure surrounding code is truncated to sensible length #531
  • limit the metaData.script.content to something (but way more than 200 chars)

This commit combines the approach from v3 and v4 to make the inline
script handling more robust, as well as providing a good approximation
of the surrouding code.

In order to do this we wrap every single async entry point.
After all the other bugsnag plugins have been initialised, add the wrapping. If this is done
beforehand, some of the other obtrusive plugins (such as the breadcrumb listener for clicks) can
interfere with the inline script handling, incorrectly making it find the script where bugsnag was
initialised.
When the traced callback doesn't error, the original script value can be reset synchronously. In
some cases the asynchronous null-ing of the current script allowed the wrong script to be identified
and attached to reports when an error happened in the same tick as a wrapped callback. Also, while
none of the testing so far yeilded a concern about the wrapped callback returning the original
callback's return value, but it should operate as transparently as possible. This ensures the return
value is passed on.
@bengourley bengourley merged commit c1fbf12 into next May 13, 2019
@bengourley bengourley deleted the inline-script-improvement branch May 13, 2019 14:08
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.

2 participants