31

I have an HTML page where several JavaScript, CSS and images files are referenced. These references are dynamically injected and user can manually copy the HTML page and the support files to another machine.

If some JS or CSS are missing, the browser complains in the console. For example:

Error GET file:///E:/SSC_Temp/html_005/temp/Support/jquery.js

I need somehow these errors reported back to me on the inline JavaScript of the HTML page so I can ask user to first verify that support files are copied correctly.

There's the window.onerror event which just inform me that there's a JS error on the page such as an Unexpected Syntax error, but this doesn't fire in the event of a 404 Not Found error. I want to check for this condition in case of any resource type, including CSS, JS, and images.

I do not like to use jQuery AJAX to verify that file physically exists - the I/O overhead is expensive for every page load.

The error report has to contain the name of the file missing so I can check if the file is core or optional.

Any Ideas?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Santoo
  • 643
  • 2
  • 7
  • 17
  • 2
    A local file does not generate a 404 error, since that's an HTTP error code. A HTTP server is needed to send HTTP things like errors. Do you want to check for file existence? – pimvdb Dec 14 '11 at 12:46
  • I need to verify this when html page loads in the browser. Is this possible? – Santoo Dec 14 '11 at 12:58
  • I *believe* `onerror` also works on a ` – pimvdb Dec 14 '11 at 13:01
  • I added a bounty on this question. A generic answer that could explain a way to catch these errors (especially when the page is running on a web server) would be great. – Brad Feb 27 '15 at 17:54
  • I believe you're looking for [this](http://www.w3schools.com/jsref/event_onerror.asp) (you can either attach the event to the head with document.getElementsByTagName("HEAD")[0]..., or inline a function call for each element html) – Patrick Webster Feb 27 '15 at 22:29
  • If you're using jquery, $(this) while inside the error function will return the attributes of that element, meaning your filename. Try console.log($(this)) in that context to look at what all is there. – Patrick Webster Feb 27 '15 at 22:44
  • @PatrickWebster That doesn't work in all cases. See my related question here: http://stackoverflow.com/q/28751709/362536 – Brad Feb 28 '15 at 22:00
  • @Brad That's a fair point, but please see my response to that question. The onerror() will work when actual error statuses exist. I'm not sure what kind of custom stuff HTTP2 will hold, but for now if an image comes back as 200 OK, a browser has to assume there wasn't an error and you are receiving precisely what was requested, at the resource location known to be canonical for that resource. – Patrick Webster Mar 01 '15 at 13:29
  • this post looks like it addressed your question with naming a specific file http://stackoverflow.com/a/10556743/4597917 – Da Rod Mar 03 '15 at 21:43

4 Answers4

66

To capture all error events on the page, you can use addEventListener with the useCapture argument set to true. The reason window.onerror will not do this is because it uses the bubble event phase, and the error events you want to capture do not bubble.

If you add the following script to your HTML before you load any external content, you should be able to capture all the error events, even when loading offline.

<script type="text/javascript">
window.addEventListener('error', function(e) {
    console.log(e);
}, true);
</script>

You can access the element that caused the error through e.target. For example, if you want to know what file did not load on an img tag, you can use e.target.src to get the URL that failed to load.

NOTE: This technically will not detect the error code, it detects if the image failed to load, as it technically behaves the same regardless of the status code. Depending on your setup this would probably be enough, but for example if a 404 is returned with a valid image it will not trigger an error event.

Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
  • I've been searching for this solution, off and on, for months. Many thanks. Is there a way to get the error code, also? – Victoria May 09 '15 at 18:31
  • @Victoria [Not from the error event or DOM node](https://stackoverflow.com/questions/8108636/how-to-get-http-status-code-of-img-tags). The only way to do this would be to make another request to the URL using AJAX, but this will only be possible if the URL satisfies [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) or [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS). – Alexander O'Mara May 09 '15 at 18:47
  • Thanks again... one can spend a long time searching for answers that don't exist! – Victoria May 09 '15 at 21:01
  • 4
    This event handler does not fire if an returns a 404 AND a valid image. The element doesn't trigger onerror, and thus nothing bubbles up. – mpoisot Jun 11 '15 at 16:30
4

you can use the onload and onerror attributes to detect the error

for example upon loading the following html it gives alert error1 and error2 you can call your own function e.g onerror(logError(this);) and record them in an Array and once the page is fully loaded post is with single Ajax call.

<html>
    <head>
        <script src="file:///SSC_Temp/html_005/temp/Support/jquery.js" onerror="alert('error1');" onload="alert('load');" type="text/javascript" ></script>
    </head>
    <body>
        <script src="file:///SSC_Temp/html_005/temp/Support/jquery.js" onerror="alert('error2');" onload="alert('load');" type="text/javascript" ></script>
    </body>
</html>
Zeronex
  • 434
  • 1
  • 3
  • 8
4

I've put together the code below in pure JavaScript, tested, and it works. All the source code (html, css, and Javascript) + images and example font is here: on github.

The first code block is an object with methods for specific file extensions: html and css. The second is explained below, but here is a short description.

It does the following:

  • the function check_file takes 2 arguments: a string path and a callback function.
  • gets the contents of given path
  • gets the file extension (ext) of the given path
  • calls the srcFrom [ext] object method that returns an array of relative paths that was referenced in the string context by src, href, etc.
  • makes a synchronous call to each of these paths in the paths array
  • halts on error, and returns the HTTP error message and the path that had a problem, so you can use it for other issues as well, like 403 (forbidden), etc.

For convenience, it resolves to relative path names and does not care about which protocol is used (http or https, either is fine). It also cleans up the DOM after parsing the CSS.

var srcFrom = // object
{
    html:function(str)
    {
        var prs = new DOMParser();
        var obj = prs.parseFromString(str, 'text/html');
        var rsl = [], nds;

        ['data', 'href', 'src'].forEach(function(atr)
        {
            nds = [].slice.call(obj.querySelectorAll('['+atr+']'));
            nds.forEach(function(nde)
            { rsl[rsl.length] = nde.getAttribute(atr); });
        });

        return rsl;
    },

    css:function(str)
    {
        var css = document.createElement('style');
        var rsl = [], nds, tmp;

        css.id = 'cssTest';
        css.innerHTML = str;
        document.head.appendChild(css);
        css = [].slice.call(document.styleSheets);

        for (var idx in css)
        {
            if (css[idx].ownerNode.id == 'cssTest')
            {
                [].slice.call(css[idx].cssRules).forEach(function(ssn)
                {
                    ['src', 'backgroundImage'].forEach(function(pty)
                    {
                        if (ssn.style[pty].length > 0)
                        {
                            tmp = ssn.style[pty].slice(4, -1);
                            tmp = tmp.split(window.location.pathname).join('');
                            tmp = tmp.split(window.location.origin).join('');
                            tmp = ((tmp[0] == '/') ? tmp.substr(1) : tmp);
                            rsl[rsl.length] = tmp;
                        }
                    });
                });

                break;
            }
        }

        css = document.getElementById('cssTest');
        css.parentNode.removeChild(css);
        return rsl;
    }
};

And here is the function that gets the file contents and calls the above object method according to the file extension:

function check_file(url, cbf)
{
    var xhr = new XMLHttpRequest();
    var uri = new XMLHttpRequest();

    xhr.open('GET', url, true);

    xhr.onload = function()
    {
        var ext = url.split('.').pop();
        var lst = srcFrom[ext](this.response);
        var rsl = [null, null], nds;

        var Break = {};

        try
        {
            lst.forEach(function(tgt)
            {
                uri.open('GET', tgt, false);
                uri.send(null);

                if (uri.statusText != 'OK')
                {
                    rsl = [uri.statusText, tgt];
                    throw Break;
                }
            });
        }
        catch(e){}

        cbf(rsl[0], rsl[1]);
    };

    xhr.send(null);
}

To use it, simply call it like this:

var uri = 'htm/stuff.html';    // html example

check_file(uri, function(err, pth)
{
    if (err)
    { document.write('Aw Snap! "'+pth+'" is missing !'); }
});

Please feel free to comment and edit as you wish, i did this is a hurry, so it may not be so pretty :)

0

@alexander-omara gave the solution.

You can even add it in many files but the window handler can/should be added once.

I use the singleton pattern to achieve this:

some_global_object = {
  error: (function(){
     var activate = false;
     return function(enable){
        if(!activate){
           activate = true;
           window.addEventListener('error', function(e){
              // maybe extra code here...
              // if(e.target.custom_property)
              // ...
           }, true);
        }
        return activate;
     };
  }());

Now, from any context call it as many times you want as the handler is attached only once:

some_global_object.error();
centurian
  • 1,168
  • 13
  • 25