JavaScript - Dynamic Resources Loading-Error Handling

Posted at

Some time ago I've wrote the article HTML5 Image Loading Error Handling, although quite simple solution, foucsing on self-contained, more simple solution using an inline event-handling to hide images with specific width/height set, to avoid dirtying up the UI with "empty squares".

I've recently embedded a YouTube video (which uses an iframe), I've noticed that for some reason it wasn't shown on mobile devices, although it should show the "poster"-of the video the very least, nothing was shown but an empty frame,

So I've thought I could reuse the previous solution for iframe-elements too,
and to make it easier to use I'll make it dynamic,
- but will KEEP using an inline-event-handlers instead of settings various handlers to the DOM/node representative of the element.



In short, we'll be looking for IMG or IFRAME elements that were not already configured with inline-onerror/ontimeout attribute,
and write a little code to hide it, in-case of a loading-error. Simple.

Array.prototype.forEach.call(document.querySelectorAll('img:not([onerror]),iframe:not([onerror])'),function(e){e.setAttribute("onerror","this.style.display=\\\\\\"none\\\\\\";");});

And:
Array.prototype.forEach.call(document.querySelectorAll('img:not([ontimeout]),iframe:not([ontimeout])'),function(e){e.setAttribute("ontimeout","this.style.display=\\\\\\"none\\\\\\";");});


Loading those two lines can be done using a SCRIPT tag, but I can do even better:
instead of:
<script type="text/javascript">
Array.prototype.forEach.call(document.querySelectorAll('img:not([onerror]),iframe:not([onerror])'),function(e){e.setAttribute("onerror","this.style.display=\\\\\\"none\\\\\\";");});
Array.prototype.forEach.call(document.querySelectorAll('img:not([ontimeout]),iframe:not([ontimeout])'),function(e){e.setAttribute("ontimeout","this.style.display=\\\\\\"none\\\\\\";");});
</script>


Which is a "BLOCKER - block" (first "block" as in "you shall not pass", second "block" as a lego-block or a code-block, ..a chunk essentially..)

There is a nice little trick which is to base64-encode the content and use a script as if loading a remote resource, with the addition of async and defer (so the script will load after the page has done loading, and it is not really important in the loading order to be blocking the main thread :] ).

This is the end result
<!-- add ONERROR and ONTIMEOUT attribute to all IMG and IFRAME elements (if not already have one) -->
<script async defer type="application/javascript" src="data:application/javascript;charset=UTF-8;base64,QXJyYXkucHJvdG90eXBlLmZvckVhY2guY2FsbChkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdpbWc6bm90KFtvbmVycm9yXSksaWZyYW1lOm5vdChbb25lcnJvcl0pJyksZnVuY3Rpb24oZSl7ZS5zZXRBdHRyaWJ1dGUoIm9uZXJyb3IiLCJ0aGlzLnN0eWxlLmRpc3BsYXk9XFxcIm5vbmVcXFwiOyIpO30pOwpBcnJheS5wcm90b3R5cGUuZm9yRWFjaC5jYWxsKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ2ltZzpub3QoW29udGltZW91dF0pLGlmcmFtZTpub3QoW29udGltZW91dF0pJyksZnVuY3Rpb24oZSl7ZS5zZXRBdHRyaWJ1dGUoIm9udGltZW91dCIsInRoaXMuc3R5bGUuZGlzcGxheT1cXFwibm9uZVxcXCI7Iik7fSk7Cg=="></script>




You can [also] click the following link to see that your browser open up the tab and showing the javascript's plain text (which is kind'a cool..)
data:application/javascript;charset=UTF-8;base64,QXJyYXkucHJvdG90eXBlLmZvckVhY2guY2FsbChkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdpbWc6bm90KFtvbmVycm9yXSksaWZyYW1lOm5vdChbb25lcnJvcl0pJyksZnVuY3Rpb24oZSl7ZS5zZXRBdHRyaWJ1dGUoIm9uZXJyb3IiLCJ0aGlzLnN0eWxlLmRpc3BsYXk9XFxcIm5vbmVcXFwiOyIpO30pOwpBcnJheS5wcm90b3R5cGUuZm9yRWFjaC5jYWxsKGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ2ltZzpub3QoW29udGltZW91dF0pLGlmcmFtZTpub3QoW29udGltZW91dF0pJyksZnVuY3Rpb24oZSl7ZS5zZXRBdHRyaWJ1dGUoIm9udGltZW91dCIsInRoaXMuc3R5bGUuZGlzcGxheT1cXFwibm9uZVxcXCI7Iik7fSk7Cg==






Including a BASE64 content directly in your web-page might not be great :/
If you wish to, this PHP snippet will render the same result:


<!-- add ONERROR and ONTIMEOUT attribute to all IMG and IFRAME elements (if not already have one) -->

<?php
  $src = <<<SRC
(function(window, document, elements){
  "use strict";
  window.NodeList.prototype.forEach = window.Array.prototype.forEach;
  
  elements = document.querySelectorAll('img:not([onerror]),iframe:not([onerror])');
  elements.forEach(function(element){ element.setAttribute("onerror", "this.style.display=\\\\\\\\\\"none\\\\\\\\\\""); });
  
  elements = document.querySelectorAll('img:not([ontimeout]),iframe:not([ontimeout])');
  elements.forEach(function(element){ element.setAttribute("ontimeout", "this.style.display=\\\\\\\\\\"none\\\\\\\\\\""); });
}(
  self
, self.document
, null
));
SRC;
  $src = base64_encode($src);
?><script async="async" data-pagespeed-no-defer="true" pagespeed_no_defer="true" type="application/javascript" src="data:application/javascript;charset=UTF-8;base64,<?php print($src); unset($src);/*cleanup*/ ?>"></script>


- The BASE64 is rendered on server-side, so your HTML will still look the same.

- Note the additional escaping of \\ and ", this is due to passing those characters through the additional PIPE of the PHP-engine.