Goosebumps! empty img src error events in Firefox

07 Sep 2016

Itā€™s almost Septemberween, which means itā€™s that time of the year we gather ā€˜round our spinning MacBook fans and share website ghost stories:

The tale of the disappearing burger and lobster site content (i.e., webcompat bug 2760).

Chapter 1.

Once upon a time (that time being the present), there was this burger and lobster ā€œrestaurantā€ called, um well, burger and lobster. In Firefox, as of today, the site content never renders ā€” you just end up with some fancy illustrations (none of which are burgers or lobsters).

Opening devtools, youā€™ve got the following mysterious stacktrace:

Error: node is undefined
compositeLinkFn@http://www.burgerandlobster.../angular-1.2.25.js:6108:13
compositeLinkFn@http://www.burgerandlobster.../angular-1.2.25.js:6108:13
nodeLinkFn@http://www.burgerandlobster.../angular-1.2.25.js:6705:24
(...stack frames descend into hell...)
jQuery@http://www.burgerandlobster.../jquery-1.11.1.js:73:10
@http://www.burgerandlobster.../jquery-1.11.1.js:3557:1
@http://www.burgerandlobster.../jquery-1.11.1.js:34:3
@http://www.burgerandlobster.../jquery-1.11.1.js:15:2

Cool, time to debug AngularJS.

But it turns out that leads nowhere besides the abyss, AKA funtions that return functions that compose with other functions with dollar signs in themā€¦ and the bug is elsewhere. Besides, Chrome has a similar error, and the page works there. Just a haunted node, maybe.

Dennis Schubert discovered that adding a <base href="/"> fixes the site, which happens to be required by Angular is later versions for $locationProvider.html5Mode. But this bug has nothing to do with pushState or history, or even SVG xlink:hrefs, all of which the <base> element can affect.

Another (dead) rabbit hole, another dead end (spooky).

At some point, all your debugging tricks and intuitions fail and itā€™s time to just page a thousand lines of framework-specific JS into your brain and see where that leads. Two hours later, if youā€™re lucky, you notice something weird like so:

var illustArr = [
  {
    "url": "/Assets/images/illustrations/alert-man.png",
    "x": "2508",
    "y": "2028"
  },
...
(bunch of similar objects objects, then...)
  {
    "url": "",
    "x": "",
    "y": ""
  };

And you recall a method that dispatches a allImagesLoaded event which tells the app itā€™s OK to load the rest of the page content. It looks like this:

b.allImagesLoaded = function() {
  d += 1,
  d === a.imageArr.length && $("body").trigger("allImagesLoaded")
}

But it only does that once itā€™s counted that all images have loaded (or fired an error event):

l.loadImage = function(a) {
  var b = $(document.createElement("div"))
    , c = $(document.createElement("img"));
  [...]
  c.attr("src", a.url),
  [...]
  c.bind("load error", function(e) {
      $(this).addClass("illustration--show"),
      h.allImagesLoaded()
  })
}

So yeah, that looks fishy. And thatā€™s why Firefox gets stuckā€”it doesnā€™t fire error events when img.src is set to the empty string, which is required per HTML. Hereā€™s a small test case, which also demonstrates why the <base href="/"> fixed the pageā€”itā€™ll fire an error event when requesting an image from the site root (and eventually barf on the HTML, I guess).

Anyways, the Gecko bug for that is Bug 599975. That will probably get fixed soon.

Epilogue.

So whatā€™s the moral of this ghost story? There is none. Septemberween is cruel that way.