var styletag = document.createElement("style");
document.head.appendChild(styletag);
styletag.appendChild(document.createTextNode('')); // This is needed to replicate issue
styletag.sheet.insertRule("#inner { background-color: red;} ");
setTimeout(()=>styletag.textContent='',1000)
<span id="inner">content</span>
This particular piece of code adds the appropriate styling to the #inner
element. Until the setTimeout
code runs and textContent
is reset to the empty string.
Then the stylesheet's rules (styletag.sheet.cssRules
) become empty and the background-color
is removed.
The third line is needed to replicate this issue for some reason.
var styletag = document.createElement("style");
document.head.appendChild(styletag);
// styletag.appendChild(document.createTextNode('')); // This is needed to replicate issue
styletag.sheet.insertRule("#inner { background-color: red;} ");
setTimeout(()=>styletag.textContent='',1000)
<span id="inner">content</span>
I could have understood if i used something like
styletag.textContent = "#inner { background-color: red;} "
instead of
styletag.sheet.insertRule("#inner { background-color: red;} ");
Here resetting textContent
to empty would remove the rules, which makes sense.
Maybe setting textContent
makes the browser re-evaluate the CSSRules
? But if so why is the issue only replicable when I append an empty text node to the <style>
tag?
This is quite complex and honestly quite surprising, but the gist of the issue is that,
A <style>
element will update its rules based on its content in 3 cases:
The element is popped off the stack of open elements of an HTML parser or XML parser.
The element is not on the stack of open elements of an HTML parser or XML parser, and it becomes connected or disconnected.
The element's children changed steps run.
In our case, the first one doesn't matter, since we're creating the element dynamically and not from a parser.
The second one is only of limited importance too, it happens when we append the element in the DOM, but we don't really care of what it was before. Only thing is, when in the DOM we have a first sheet
that got computed out of the current (empty) content.
Then we've got the element's children changed one. This algorithm is called when a node is inserted (step 9) or removed (step 21). So when you do append the empty TextNode
, this step is ran, and the <style>
will update its rules again, with no content, again... That update isn't really important either. What's important though is that now, our <style>
does have a child node.
Indeed, when running the textContent
setter steps with an empty string, the previous children of the target element are removed, no new node is added but the remove steps are still ran.
However when there is no previous child in the target element, no node is removed and no new node is added. Neither the insert steps nor the remove ones are run, and we never reach that children changed steps.
So it all boils down to how textContent = ""
runs differently when there is previous content or not.