how to fix the cryptic "Script error." in javascript ?

This tech post is related to a nagging JavaScript error that I debugged almost a year ago. If you look at any javascript error report, you will see a cryptic error polluting the reports under the name "Script error." without any information about the error. This happens in Firefox, Safari, and Chrome when an exception violates the browser's same-origin policy - i.e. when the error occurs in a script that's hosted on a domain other than the domain of the current page. This tech post details how you can fix this error and decrypt the error message.

Background

The "Script error." happens in Firefox, Safari, and Chrome when an exception violates the browser's same-origin policy - i.e., when the error occurs in a script that's hosted on a domain other than the domain of the current page. This is a very common scenario when the javascript served on a webpage is hosted on a CDN (Content Delivery Network) such as Akamai where the domain of the javascript file is different from the webpage that is including the JS and running it.

This behavior is intentional. It prevent scripts from leaking information to external domains. For an example, imagine that you accidentally visited a site called evilsite.com that serves a script pointing to your bank's home page such as "mybank.com/index.html".

<script src="mybank.com/index.html"></script>

Please note that the script tag is pointing to an html file rather than JavaScript. This will result in a script error, but the error might be interesting because it might tell us if you are logged in or not. Imagine that if you're logged in, the error spitted out might be something like "Welcome Ravi.. is undefined", whereas if you're not logged in, it might be "Please Login ... is undefined" or something similar.

If evilsite.com does this for the top 20 or so bank institutions, they'd have a pretty good idea of which banking sites you visit, and enables it to target you with a superior phishing page. This is one such example as to why browsers shouldn't allow any data leak across domains.

Source: StackOverflow user broofa's explanation of "Script error." at Cryptic-Script-Error SO Post.

Why does the browser say "Script error." instead of something meaningful ?

The browser is trying to hide the original error source and everything related to it. Surfacing anything meaningful would go against the very purpose of issuing the "Script error." message. See Webkit source that checks for origin (Chrome/Safari) and Firefox source that handle this error.

Source: StackOverflow user broofa's explanation of "Script error." at Cryptic-Script-Error SO Post.

How do I fix the "Script error." ?

This is where CORS (Cross Origin Resource Sharing) comes into play. Cross-origin resource sharing (CORS) is a mechanism that allows many resources (e.g., fonts, JavaScript, etc.) on a web page to be requested from another domain outside the domain from which the resource originated. In particular, JavaScript's AJAX calls can use the XMLHttpRequest mechanism. Such "cross-domain" requests would otherwise be forbidden by web browsers, per the same origin security policy.

Enabling CORS on the server and client side fixes the issue in Chromium, Chrome and Firefox as of October 2014. No such on Safari :(.

http://enable-cors.org/ has listed the steps to enable CORS on both client and server side in a simple manner. I will list the same for you.

Server

Make sure that the server serving your static JavaScript files adds Access-Control-Allow-Origin header to it's response.

Access-Control-Allow-Origin: *

You can also whitelist domains instead of allowing every site with "*". As per Access-Control-Allow-Origin header W3 spec, the value should either be a single origin or the string "null" or "*".

Access-Control-Allow-Origin: http://mysite.com

If your files are served by a CDN, you need to contact them to serve this header with JavaScript files.

Client

Set the crossorigin attribute in the script tag to anonymous.

<script src="http://someotherdomain.com/js/test.js" crossorigin="anonymous"></script>

See CORS Settings attributes W3 spec for more details on the crossorigin attribute and all the values it can take. It is important to note that this attribute has no effect on browsers that don't support CORS, see CanIUseCors to check which browsers support it. Also, some browsers like Chrome expect the Access-Control-Allow-Origin to be set in the response header when it sees the crossorigin attribute on the script tag. If this header is missing , it will refuse to the load the script.

Example

Setup

I have setup two external javascript files (think of it as CDN-ed, although they are powered by Google App Engine internally) namely,

  • no-cors.js that has no Access-Control-Allow-Origin header in it's response

  • cors.js that has Access-Control-Allow-Origin header in it's response

The contents of both the javascript files are exactly the same and is served from https://enablecors.appspot.com/ domain, while the webpage using it is under http://ravikiranj.net. The window.clickMe is trying to call an undefined function that should trigger an error. This function is called when the user clicks the Click Me! button in the test webpage that also remains the same across both tests except for the script URL it's pointing to and the crossorigin attribute.

I have modified the window.onerror function that is described in detail at MDN - window.onerror to showcase the error message, error stack, file and line number on the webpage. The two examples shown below are iframed urls pointing to http://ravikiranj.net/hacks/cors-demo/no-cors.html and http://ravikiranj.net/hacks/cors-demo/cors.html respectively.

HTML

<!DOCTYPE html>
<html>
    <head>
        <title>CORS Example</title>
        <style type="text/css">
        div {
            border: 1px solid #C0C0C0;
            padding: 10px;
            border-radius: 10px;
            margin: 5px;
        }
        .btn {
            display: block;
            margin: 10px 5px;
        }
        </style>
    </head>
    <body>
        <div id="MSG">Error Stack</div>
        <div id="STACK">Error Stack</div>
        <div id="FILE">File</div>
        <div id="LINE_NO">Line Number</div>
        <input type="button" class="btn" onclick="window.clickMe();" value="Click Me!" />
        <script src="https://enablecors.appspot.com/assets/js-cors/cors.js" crossorigin="anonymous"></script>
        <!--<script src="https://enablecors.appspot.com/assets/js/no-cors.js"></script>-->
    </body>
</html>

CDN JS

window.onerror = function(errorMsg, file, lineNo, colNo, errStack) {
    "use strict";
    var msg = errorMsg ? errorMsg : "null"
    , stack = errStack ? errStack : "null, only Chrome/Chromium/Firefox based browsers support errorStack"
    , errFile = file ? file : "null"
    , errLineNo = lineNo ? lineNo : "null"
    , msgElem = document.getElementById("MSG")
    , stackElem = document.getElementById("STACK")
    , fileElem = document.getElementById("FILE")
    , lineElem = document.getElementById("LINE_NO")
    ;
    if (msgElem && stackElem && fileElem && lineElem) {
      msgElem.innerHTML = msg;
      stackElem.innerHTML = stack;
      fileElem.innerHTML = errFile;
      lineElem.innerHTML = errLineNo;
    } else {
      alert("Unable to find div ids MSG, STACK, FILE, LINE_NO on page");
    }
};
window.clickMe = function(e) {
    "use strict";
    if (e) {
      e.preventDefault();
    }
    var l = "test";
    l.callme();
};

"Script error." demo without CORS

Actual JS Error with CORS enabled


As you can see from the above, Enabling CORS on both the server and the client side has indeed fixed the issue. To make sure that you are indeed serving the header, you can use Chrome's Network tab to inspect them as below.

chrome dev tools screenshot showing cors headers in net tab

Comments

Comments powered by Disqus