Dan
Dan

Reputation: 11079

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?

Upvotes: 8

Views: 1634

Answers (4)

Alex Craft
Alex Craft

Reputation: 15356

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)
}

Upvotes: 0

Aintaer
Aintaer

Reputation: 2141

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:

Upvotes: 6

Maen
Maen

Reputation: 10698

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!

Upvotes: 0

David Fari&#241;a
David Fari&#241;a

Reputation: 1594

  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?

Upvotes: 0

Related Questions