How to inject STYLE into a page (a final word)

Is it a supported scenario to inject a style tag into the body like this?

<div id="injectionDiv">&nbsp;</div>

<script type="text/javascript">
document.getElementById("injectionDiv").innerHTML='<style type="text/css">.myClass{background-color:green;}</style><div class="myClass">&nbsp;</div>';
</script>

If so, then why isn't the style applied?

[548 byte] By [JohnSudds-MSFT] at [2008-1-10]
# 1

You can inject style via script, but it can be a little tricky.

Internally, Internet Explorer treats the <style> tag as a NoScope element, which means (according to a rather opaque comment in the source) that "no text in a textrun should point to it." Examples of other tags that have this attribute are HTML comments (<!-- -->) and SCRIPT tags. All NoScope elements are removed from the beginning of the parsed HTML before it is injected with innerHTML or insertAdjacentHTML. To prevent this from happening, you must include at least one scoped element at the beginning of the injected HTML.

So, using the example above, all we have to do is put the style block after the element it modifies.

document.getElementById("injectionDiv").innerHTML='<div class="myClass">&nbsp;</div><style type="text/css">.myClass{background-color:green;}</style>';

That said, you really should put all style rules in the HEAD for strict compliance with XHTML. Doing this can also be a little tricky because you cannot inject HTML into the HEAD or STYLE element directly. The best way to dynamically add styles to a page is to wait for the document to load, and then create the rule in a new style sheet.

window.onload = function()
{
document.createStyleSheet().addRule('.myClass', 'background-color:blue!important;');
}

JohnSudds-MSFT at 2007-10-3 > top of Msdn Tech,Internet Explorer Development,Internet Explorer Web Development...
# 2

Here's a second example that attempts to inject a SCRIPT tag, also considered to be NoScope by Internet Explorer, using innerHTML.

<div id="injectionDiv">&nbsp;</div>

<script type="text/javascript">
alert(document.documentElement.outerHTML);
document.getElementById("injectionDiv").innerHTML='<script defer>alert("hello");</' + 'script>';
alert(document.documentElement.outerHTML);
</script>

I have used an alert to show me the before and after effects. As it turns out, the source of the page doesn't change at all.

To work around this situation, just insert a dummy element (for example, a hidden input field) before the opening SCRIPT tag.

document.getElementById("injectionDiv").innerHTML='<input type="hidden"/><script defer>alert("hello");</' + 'script>';

JohnSudds-MSFT at 2007-10-3 > top of Msdn Tech,Internet Explorer Development,Internet Explorer Web Development...
# 3
WARNING: This post contains inaccurate information.
Please read follow-up post below.

One final post on this subject. There are limitations and caveats to what you can do with a styleSheet object through script, namely:

  1. You can create a styleSheet, but you cannot delete one.
  2. The addRule method simply adds new rule; it doesn't update existing rules or merge rules under the same selector.
  3. You can retrieve objects from the styleSheets collection by ID; however, you can neither create a STYLE element with an ID, nor change the ID after the element is created. (The ID attribute is read-only on styleSheet objects!)

Consider what happens when a page in a sub-frame adds a style rule to its parent page using a naive combination of createStyleSheet and addRule. Each time the sub-frame changed, it would add a duplicate rule to a new STYLE element. Since there's no easy way to detect whether the stylesheet has already been created, eventually IE returns a script error when it has had enough. Sub-optimal.

If I started with a known quantity of style rules that were applied in all cases, I should only create the style sheet once. Fortunately, I can use TITLE to get around the limitations of the ID attribute. The following script retrieves a style sheet by title and creates a new styleSheet if necessary.

function createCustomStyleSheet(t)
{
var ssa = document.styleSheets;
for (var idx=0; idx<ssa.length; idx++)
{
if (ssa[idx].title == t)
return ssa[idx];
}

// not found, create one
var ss = document.createStyleSheet();

// ADD RULES HERE

ss.title = t; // instead of ID, which is read-only
return ss;
}

Now, what if I wanted to remove rules dynamically? Since I cannot delete the styleSheet object, I could simply remove each rule one at a time, like this:

var cnt = ss.rules.length;
while(cnt--)
ss.removeRule(cnt);

This is where things get a little tricky. There used to be a cross-domain scripting vulnerability in IE6 that allowed you to load any random file whether it parsed as CSS or not, and then read it with the rules collection or cssText property. As a result, IE7 has been patched in such a way that it causes script errors if you attempt to read from those properties.

The following code works well in both IE6 and IE7.

function clearSheet(ss)
{
var bAccessDenied = false;

// This variation works on IE6
try {
var cnt = ss.rules.length;
while(cnt--)
ss.removeRule(cnt);
}
catch(e) {
// alert(e.description);
if((e.number&0xffff) == 5)
bAccessDenied = true;
}
// This variation works on IE7
if (bAccessDenied)
{
try {
while(true)
ss.removeRule(0);
}
catch(e) {
// alert("No more rules.");
}
}
return ss;
}

With these functions in place, I can now safely create and remove rules in a controlled way.

var ss = createCustomStyleSheet("localStyle"); // unique ID
clearStyleSheet(ss); // remove previously added rules
ss.addRule('.myClass', 'background-color:blue!important;');

As you can see, inline styles via injected HTML are still quite a bit easier to manage.

It's up to you to decide which approach to use.

JohnSudds-MSFT at 2007-10-3 > top of Msdn Tech,Internet Explorer Development,Internet Explorer Web Development...
# 4
I am attempting to use the document.createStyleSheet method, however it seems that everyother time I call it i get the follow javascript error.

Error Object Output
name "Error" String
number -2147024809 Double
description "Invalid argument." String
message "Invalid argument." String

Any Ideas or solutions would be greatly appreciated.

Thanks
Phillip

PhillipWhelan at 2007-10-3 > top of Msdn Tech,Internet Explorer Development,Internet Explorer Web Development...
# 5

I have learned some things since my "final word" post that I simply must share. I feel a little stupid for not figuring this stuff out sooner, but no better time than the present! (I thank Justin Rogers for setting me straight.)

I made several claims in my last installment to this thread that I now must RETRACT.

  1. You cannot delete stylesheet objects. WRONG.

Having retrieved a stylesheet object from the document, you can delete it with removeNode(true) in IE, or with the following DOM-compliant analog:

Code Snippet

// Remove an existing stylesheet by ID

var ss = document.getElementById('myStyleSheet');
if (ss) ss.parentNode.removeChild(ss);

  1. You cannot set the ID of stylesheet objects. WRONG.

It may seem obvious, but IE is the only browser that allows you to create a stylesheet object with the document.createStyleSheet() method. When it does so, the object is returned to you fully formed; in other words, the requisite HTML has already been added to the document in the HEAD section. The ID attribute is read-only if you access the object as a stylesheet object, but NOT if it's a regular DOM element.

Best to avoid createStyleSheet() altogether, and use the cross-browser friendly version here:

Code Snippet

// Create stylesheet object and append to HEAD

ss = document.createElement("STYLE");
ss.id = 'myStyleSheet';
document.documentElement.firstChild.appendChild(ss);

  1. You cannot change stylesheet contents with innerHTML. RIGHT! but only on IE...

For "security" purposes in IE, the innerHTML property of stylesheet objects is read-only. However, this is not the case universally. For a cross-browser approach, first try setting the rules all at once with innerHTML. If that fails, set the rules individually with addRule, as follows:

Code Snippet

var rules = '.myClass {background-color:#AAD2DE;} p { color:red} '

try
{
ss.innerHTML = rules;
}
catch(exc)
{

var parts = rules.split(/\s*[{}]\s*/);
for (var i=0; i<parts.length; i+=2)
ss.styleSheet.addRule(parts[i],parts[i+1]);
}

As you can see the resulting code is MUCH cleaner and easier to comprehend.

JohnSudds-MSFT at 2007-10-3 > top of Msdn Tech,Internet Explorer Development,Internet Explorer Web Development...
# 6

One more thing.

If you simply must inject a fully formed STYLE tag using a scoped innerHTML string as described above, avoid relative paths to resources such as background images. Until temporary elements are appended to the document, IE uses the context of the top-level document to download resources. Although it may be apparent to you which page is building the STYLE rules, an unexpected frameset can throw you a curve ball.

The solution is to always append the style element to the document BEFORE assigning it any rules.

JohnSudds-MSFT at 2007-10-3 > top of Msdn Tech,Internet Explorer Development,Internet Explorer Web Development...