8

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?
Dan
  • 10,990
  • 7
  • 51
  • 80

4 Answers4

6

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:

Aintaer
  • 2,131
  • 17
  • 15
0
  1. I think the load event could be triggered earlier than the css stylesheet. The issue would happen maybe 1 of 10 times, but its still not best practice and the most clean way.

  2. setTimeout is surely a good approach, as it can be guaranteed that your script loads the css first. However, since only the tag of the stylesheet, means, the link to it is created, the css need to be downloaded first, so whatever approach of these two you use it is never 100% guaranteed that your css is loaded before the load event fires.

To be 100% sure that everything runs in correct order is, to make a synchronous fileread via AJAX, or an asynchronous AJAX call with a helper function which checks if the CSS is ready (this way you dont freeze the Browser but still have the ability to check wheter the file is loaded or not). Another way would be to just link the stylesheets manual: You set the stylesheets first and then comes the code in the header of your HTML file.

I didnt look so deep into the load events, but it could be that the DOMContentLoaded event is only firing as CSS is loaded (ill research on this one)

Look here for similiar question: jQuery event that triggers after CSS is loaded?

Community
  • 1
  • 1
David Fariña
  • 1,536
  • 1
  • 18
  • 28
  • I'm not quite following - what could be more synchronous than creating a `style` element and appending this to the DOM myself? Also I forgot to mention - the code I'm talking about is generally not run at page load, but later. – Dan Jul 19 '13 at 14:07
  • And just to be clear (maybe it wasn't clear enough in my question) - I am not loading an external stylesheet here. I'm creating an internal one with the actual rules text generated with JS code. – Dan Jul 19 '13 at 14:08
0

I don't claim to have a solution, neither an explanation of the Webkit loading timing, but I recently had a similar problem, but with JS : I needed to be absolutely sure that the jQuery framework was loaded before I include other libraries of my own, which depended on jQuery.

I then noticed that the load event of jQuery wasn't fired when I expected it.

For what it's worth, here how I solved my problem :

var addedHead = window.document.createElement('link');

addedHead.async = true;
addedHead.onload = addedHead.onreadystatechange = function () {

    if (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') 
    {   
        callback();
        addedHead.onload = addedHead.onreadystatechange = null;
    }
};
//And then append it to the head tag

You could give it a try!

Maen
  • 10,603
  • 3
  • 45
  • 71
  • Again, this is not a `link` element, it's a `style` element with inline rules. There's no external resource involved. – Dan Jul 22 '13 at 17:56
0

Had same need, using detection for style's rule to check if stylesheet has been loaded.

// Load CSS dynamically. There's no way to determine when stylesheet has been loaded
// so we usingn hack - define `#my-css-loaded {position: absolute;}` rule in stylesheet 
// and the `callback` will be called when it's loaded.
var loadCss = function(url, cssFileId, callback){
  // CSS in IE can be added only with `createStyleSheet`.
  if(document.createStyleSheet) document.createStyleSheet(url)
  else $('<link rel="stylesheet" type="text/css" href="' + url + '" />').appendTo('head')

  // There's no API to notify when styles will be loaded, using hack to 
  // determine if it's loaded or not.
  var $testEl = $('<div id="' + cssFileId + '" style="display: none;"></div>').appendTo('body')    
  var checkIfStyleHasBeenLoaded = function(){
    if($testEl.css('position') == 'absolute'){
      $testEl.remove()
      callback()
    }else setTimeout(checkIfStyleHasBeenLoaded, 10)
  }
  setTimeout(checkIfStyleHasBeenLoaded, 0)
}
Alex Craft
  • 13,598
  • 11
  • 69
  • 133