javascripthtmlcsscssom

Why are styles invisible, and style tags not re-appendable after using insertRule of CSSOM


If I've added styles with CSSOM using insertRule I've noticed two things.

The added styles don't appear in the html while viewing in Firebug ever.

The added styles don't work if the style tag is appended (ex: moved from head to body) to another element (Happens in Firefox, and Chrome).

If the styles are added after the tag is appended then they do work. They still don't show in Firebug. When appending the sheet has to be reassigned (regetted?) which makes it appear even stranger.

For not showing in Firebug it could be a quirk with Firebug, but regular styles that are not added dynamically show up.

For the styles not working after the append I'm wondering if this is the standard because this happens in Firefox, and Chrome. Looking at the standards I didn't see anything about this. Unless I just don't understand them.

var style = document.createElement('style'),
    sheet = style.sheet;
document.head.appendChild(style);
//When line 6 is un-commented the styles work
//if line 8 is also commented out.
//document.querySelector('.container').appendChild(style);
//Get the sheet when line 6 is un-commented
sheet = style.sheet
sheet.insertRule('.test{color: red}');
document.querySelector('.container').appendChild(style);
//styles don't apply after the previous line

Edit for clarity:

If you append a <style></style> tag to the <head></head> of html you can apply styles with style.sheet.insertRule(styleString), and added styles will apply for the document.

If you have already appended that <style></style> to the <head></head> like <head><style></style></head>, and attempt to append <style></style> somewhere else like <div><style></style></div> all the styles are lost, and do not apply again.

Is this normal?

The code flow:

Works:

  1. append <style> any where
  2. add styles with style.sheet.insertRule(styleString)

Doesn't work:

  1. append <style> any where
  2. add styles with style.sheet.insertRule(styleString) to <style>
  3. append same <style> somewhere else

My other issue is that styles added to <style></style> don't show up in Firebug even if they have applied to the document.

Edit more clarity:

If I reappend the style element without having modified the stylesheet, the styles remain:

var style = document.querySelector('style');
document.head.appendChild(style);
* {color: red}
<p>Hello world</p>

But if I have altered the stylesheet with JS, the changes are undone:

var style = document.querySelector('style'),
    sheet = style.sheet;
sheet.insertRule('* {color: red}', 0);
document.head.appendChild(style); //Comment this line out, and this works.
/* CSS rules will be inserted with JS */
<p>Hello world</p>


Solution

  • This is because <style> elements only have a sheet property when they are appended to the doc.
    Hence, when you do move it, or remove it from the document, their sheet property is set back to null. Every rules you applied to it using the insertRule method will have disappeared since they're not inside the <style>'s innerHTML.


    Relevant part of the specs :

    The update a style block algorithm for CSS (text/css) is as follows:

    1. Let element be the style element.

    2. If element has an associated CSS style sheet, remove the CSS style sheet in question.

    3. If element is not in a Document, then abort these steps.

    4. If the Should element's inline behavior be blocked by Content Security Policy [...] then abort these steps. [CSP]

    5. Create a CSS style sheet with the following properties: ...

    As you can see, each time the <style> element is updated, a new CSS style sheet is created, (only if .3 and .4 are not blocking the process).


    Small demo :

    var style = document.createElement('style');
    console.log('before being appended : '+ style.sheet)
    document.body.appendChild(style); 
    console.log('after being appended : '+ style.sheet)
    document.body.removeChild(style);
    console.log('after being removed : '+ style.sheet)