Friday, 14 December 2012

Catching Cross-Domain JS Errors

As I've mentioned before, most modern browsers do not provide access to error information in window.onerror for scripts loaded from across domains. This is a very severe restriction, and has limited the usefulness of Errorception to some extent.

Fortunately, a couple of months ago, Firefox landed a patch to add this feature, and this has already been shipped with the latest versions of Firefox. Chrome is expected to follow suit very soon, since this has already landed in Webkit.

Unfortunately, this doesn't work out of the box, and will require some tweaking of your server and markup. Fortunately, the changes you need to make are minimal.

On the server

You will need to enable CORS for the external JS file you load. The most minimal way to do this is to set the following HTTP header in the response for your JS file.

Access-Control-Allow-Origin: *

That's the only server-side change you need to make!

In the markup

Script tags have now got a new non-standard attribute called crossorigin. The most secure value for this would be anonymous. So, you'll have to modify your script tags to look like the following.

<script src="http://sub.domain.com/script.js" crossorigin="anonymous"></script>

Browser support

As of this writing, only Firefox supports reporting errors for cross-domain scripts. All WebKit browsers including Chrome is expected to support this very soon. This isn't a problem with IE at all, since IE already reports errors to window.onerror irrespective of the domain (yay, security!). Standardisation for the new attribute has been proposed though it hasn't gotten anywhere.

Update: Thanks to Matthew Schulkind for pointing out in the comments below: It appears that Firefox insists that if you are using the cross-origin attribute, the script file must be served with the access control HTTP header. If the access control header isn't present, the script simply doesn't get evaluated. This is a minor annoyance at development time, so I've filed a bug with Mozilla about this.

16 comments:

  1. Hi - could you clarify whether this is caused by the errorception snippet being on a different domain to the remotely-loaded script?

    In other words, can it be fixed by putting the errorception snippet as the first lines in the remote script, or will that not work?

    Thanks!

    ReplyDelete
    Replies
    1. I'm not sure I understood the question, Party Ark. That said, this post has very little to do with Errorception. Most browsers simply don't send enough error information to window.onerror if the error occurred in a JS file that's loaded from a different domain than the page.

      In other words, the manner of loading Errorception's snippet is of no consequence. You can (and should) continue to load the snippet as usual.

      If you still have questions and want to discuss over email, feel free to mail me at rakeshpai at errorception dot com.

      Delete
    2. > [Party Ark] could you clarify whether this is caused by the errorception snippet being on a different domain to the remotely-loaded script?

      This article is about the situation where you load your own JS files from a different domain than the html file. Specific:

      My Website is hosted at www.example.com/index.html . To speed up the whole thing, I host my JavaScript file(s) at cdn.example.com/script.js . This works fine.

      But if I want to track errors via the window.onerror Event Handler, then the information I do not get any information about errors that are caused by my cdn.example.com/script.js JavaScript file. Instead of "TypeError: test.missingFunction is not a function in script.js on line 42" I only get "Script Error in script.js on line 0".

      This Cross-Domain Discussion is about making the error information available. It does not matter, if you handle the error message on your own or use errorception for it.

      Delete
  2. First off, thanks for posting. I've found a lot of good info in this blog.

    I'm trying to add the crossorigin attribute to my site so I can decode the cryptic "Script error", but I'm now having trouble with script loading order. Everything works fine in Chrome and older version for Firefox (and of course IE), but on at least Firefox 18+, I start getting JS errors left and right because my scripts no longer load in a guaranteed order. This goes for inline <script> tags as well as dynamic tags.

    Something as simple as the following does not work:

    foo.js:
    window.foo = 3

    foo.html:
    <script type='text/javascript src='foo.js'></script>
    <script type='text/javascript>alert(window.foo);</script>

    Any idea how to fix this? Again, everything works 100% of the time without the crossorigin attributes added, no race conditions otherwise.

    ReplyDelete
    Replies
    1. Of course I realized after I posted this that I forgot to include the crossorigin='anonymous' attribute in the snippet, but it was definitely there in my test (otherwise everything worked).

      Delete
    2. Hi Matthew,

      Thanks for the detail you've provided. I've been able to replicate the issue. Here's my code: https://gist.github.com/4571053

      It turns out, Firefox insists that if you are setting the crossorigin attribute on the script tag, the script file should also have the access control HTTP header set correctly. If the access-control header isn't set, Firefox simply doesn't evaluate the script at all. (So, this isn't a problem with the load order - it appears that Firefox simply *doesn't* evaluate the file.)

      The solution obviously is to set the access control headers on the script file's HTTP response. I've verified that this makes the script work as you'd expect.

      I'm not sure if this behaviour in FIrefox is intentional. It's definitely a dev-time annoyance to make sure that you are sending the right HTTP headers or else your scripts will simply not evaluate. I've filed a bug about this with Mozilla: https://bugzilla.mozilla.org/show_bug.cgi?id=832587 For now though, setting the access-control header seems to make the problem go away.

      It isn't clear from your comment if you were sending the access-control header or not. Do let me know if even after setting the header your scripts don't load correctly.

      Delete
    3. Thank you!

      While I was serving up my own JS with the proper headers, I was also attempting to load jQuery from the Google CDN as well as some other 3rd party JS. Nobody else has CORS headers, so Firefox didn't load anything external.

      Delete
    4. Yes, for now, you won't be able to use the crossorigin attribute with jQuery if loaded from the CDN. I've just filed a bug with jQuery as well to help fix this. http://bugs.jquery.com/ticket/13267

      Delete
  3. Rakesh, I have few queries.

    Firefox: (19.0 beta)
    I see that Firefox has relaxed the need for crossorigin attribute for reporting the errors from x-origin scripts. Even without setting the attribute, I am able to get the actual error message. The location and the line number are not available in either case. Am I missing something?

    Chrome: (Version 26.0.1401.0 canary)
    Doesn't seem to be supporting crossorigin attribute for scripts. http://code.google.com/p/chromium/issues/detail?id=159566

    ReplyDelete
    Replies
    1. Hey Varunkumar,

      Regarding Firefox: Are you sure you are checking the values you get in the window.onerror function and not just what's printed in Firebug? In the conversation in the Firefox bug I filed, Boris cites the spec to say that the current behaviour of Firefox is intentional. By 'current', I mean Firefox 18/mac - my dev box. Even though stuff gets written to the console as you describe, window.onerror doesn't get any useful information if the attribute isn't present. I wouldn't expect this behaviour to change in FF19.

      Regarding Chrome: You are right, this doesn't currently work in Chrome. However, this has already landed in WebKit (https://bugs.webkit.org/show_bug.cgi?id=81438), so I don't think it should take too long for it to show up in Chrome. Thanks for the bug report link! Starred it.

      Delete
    2. I am not referring to Firebug output. Try this out -- https://gist.github.com/4701998 I am sourcing the file calculator.js from two different domains (www.varunkumar.me and slides.varunkumar.me). CORS access (allow access headers) is supported only by slides.varunkumar.me. Uncomment the script loading and try out different cases. Please let me know if you can decode the behaviour of the attribute.

      Btw, I am on Firefox 19.0 beta channel in Mac. I couldn't find any reference about this behaviour either.

      Delete
    3. Ah, thanks for the code, it helped narrow down the problem. The reason you aren't getting the line number and URL has nothing to do with CORS. It is because you are throwing a 'string' and not an 'Error' object. Though throw allows you to throw virtually any expression as an error, anything other than the Error object is practically useless. You don't get any meta-information about the error if isn't an Error object. @rauchg has written about this at length here: http://www.devthought.com/2011/12/22/a-string-is-not-an-error/

      Delete
    4. Thanks Rakesh. I will try sending an Error object.

      Delete
  4. I'm involved in developing a build system that automates optimization of static assets on web pages. One of the functionalities is to link to the assets on a CDN. My question is this: Is there any downside to defining this new attribute?

    I was thinking if it made sense to just have the build system add it automatically when making a CDN build. But only if it can't back fire.

    The patch to our build system can be found here: https://github.com/One-com/assetgraph-builder/commit/01c09e953a08a03b68b86fcbed33d01fcb995691

    ReplyDelete
    Replies
    1. Hi Peter,

      There are two aspects to this.

      1. The CDN will need to send the CORS header: Setting this header is not a problem at all. In fact, jQuery now sends this header from their CDN. With jQuery's reach, we can be sure that this isn't a problem.
      2. The attribute on the script tag. The only problem with this is (as discussed above), that if you are setting the attribute, you *will* need to send the CORS header for your JS file. If you set the attribute but don't send the CORS header, the script will not be evaluated.

      Hope this helps.

      Delete
  5. Thank you man, we've struggled over 4 hours with remote javasript in firefox!

    The solution is:


    Header set Access-Control-Allow-Origin "*"

    ReplyDelete