Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webkit - dynamically created stylesheet - when does it really load?

I have some code (it's actually not mine but the SlickGrid library) that creates a <style> element, inserts it into the DOM, then immediately tries to find the new stylesheet in the document.styleSheets collection.

In WebKit this sometimes fails. I don't actually have any idea what the circumstances are, but it's nothing that's consistently reproducible. I figured I could work around it by changing the code so the check for the StyleSheet object doesn't happen until the load event on the style element, like so:

  $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
  var rules = ...;// code to create the text of the rules here
  if ($style[0].styleSheet) { // IE
    $style[0].styleSheet.cssText = rules.join(" ");
  } else {
    $style[0].appendChild(document.createTextNode(rules.join(" ")));
  }
  $style.bind('load', function() {
          functionThatExpectsTheStylesheet();
  });

and functionThatExpectsTheStylesheet attempts to locate the actual stylesheet object like so:

    var sheets = document.styleSheets;
    for (var i = 0; i < sheets.length; i++) {
      if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
        stylesheet = sheets[i];
        break;
      }
    }

but sometimes even at that point, the stylesheet object is not found.

So, my question is this:

  1. Does the load event in fact not guarantee that the styleSheet object will be available? Is it a bug?
  2. If not, is there another condition that I can use or do I just need to do this using timeouts?
  3. Or is there some problem with the way I'm binding the load event and that's my problem?
like image 508
Dan Avatar asked Jul 19 '13 13:07

Dan


1 Answers

Dynamically loading CSS stylesheets is still an area filled with browser quirks, unfortunately. In Webkit, <style> and <link> elements will both fire load and error events when loading stylesheets. However, the load event itself means only that the stylesheet resource has been loaded, not necessarily that it has been added to document.styleSheets.

The require-css RequireJS loader deals with this issue by branching its loading mechanism based on userAgent sniffing (it is nearly impossible to feature-detect whether or not the <link> tag will fire its load event properly). Specifically for Webkit, the detection resorts to using setTimeout to find when the StyleSheet object has been attached to document.styleSheets

var webkitLoadCheck = function(link, callback) {
  setTimeout(function() {
    for (var i = 0; i < document.styleSheets.length; i++) {
      var sheet = document.styleSheets[i];
      if (sheet.href == link.href)
        return callback();
    }
    webkitLoadCheck(link, callback);
  }, 10);
}

So while the load event will fire on Webkit, it is unreliable for the purposes of being able to access the corresponding StyleSheet instance. Currently the only engine that supports stylesheet load events properly is Firefox 18+.

Disclosure: I am a contributor to require-css

References:

  • http://pieisgood.org/test/script-link-events/
  • https://bugs.webkit.org/show_bug.cgi?id=56436
  • https://bugs.webkit.org/show_bug.cgi?id=38995
  • https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link
like image 191
Aintaer Avatar answered Sep 27 '22 17:09

Aintaer