Using the object element to dynamically embed Flash SWFs in Internet Explorer

This is a journey into the madness of Internet Explorer.

Yes, there is a happy ending. Jump to the end of the post if you just want the solution and don’t care about how we got there.

The Scenario

You want to embed a Flash SWF into your HTML document using the object element, and you need to be able to do it using JavaScript. (Let’s pretend SWFObject doesn’t exist, ok?)

If you were using HTML markup without JavaScript, embedding a Flash SWF using an object element would be pretty straightforward:


<object id="mySWF" width="550" height="400" 
        data="mymovie.swf" 
        type="application/x-shockwave-flash">
   <param name="flashvars" value="dog=woof&cat=meow" />
</object>

Microsoft designed Internet Explorer’s object to work a bit differently, so the markup for Internet Explorer becomes:


<object id="mySWF" width="550" height="400"
        classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
   <param name="movie" value="mymovie.swf" />
   <param name="flashvars" value="dog=woof&cat=meow" />
</object>

The key differences are:

  • Internet Explorer requires the classid attribute instead of type
  • Internet Explorer requires the param name="movie" child node instead of the data attribute in the object

Annoying, perhaps, but workable. If you’re working with hard-coded markup — what SWFObject refers to as static publishing — you can drop in some conditional comments and be done:


<!--[if IE]>
<object id="mySWF" width="550" height="400"
        classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
   <param name="movie" value="mymovie.swf">
<![endif]-->

<!--[if !IE]>-->
<object id="mySWF" width="550" height="400"
        data="mymovie.swf"
        type="application/x-shockwave-flash">
<!--<![endif]-->

   <param name="flashvars" value="dog=woof&cat=meow" />

<p>Fallback content for people without Flash Player</p>

</object>

That’s nice, but I need to use JavaScript

On the surface, using JavaScript to recreate the HTML markup appears to be a trivial task; we just need to add conditional logic to handle the two small differences between Internet Explorer’s object element and the object element used by the other browsers:


var target_element = document.getElementById("replaceMe"),
   obj = document.createElement("object"),
   isMSIE = /*@cc_on!@*/false;

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

if (isMSIE) {            

   //IE requires the 'classid' attribute
   obj.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
   
   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);

} else {

   //Non-IE browsers require the 'type' attribute
   obj.setAttribute("type", "application/x-shockwave-flash");
   
   //Non-IE browsers require the 'data' attribute
   obj.setAttribute("data", "test.swf");

}

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Seems simple enough, right? Not so fast. If you try running this in Internet Explorer (6-9), you’ll notice the SWF fails to load, and IE will behave as though it’s stuck trying to load a file — the “loading” icon never goes away.

Issues with Microsoft’s proprietary classid attribute

It turns out Internet Explorer’s object does not like having the classid appended after the object has been created. This is very similar to the well-known IE bug for adding the name attribute to certain form elements. For the form element bug, Microsoft’s solution is to include the name attribute in the createElement string:

Attributes can be included with the sTag as long as the entire string is valid HTML. To include the NAME attribute at run time on objects created with the createElement method, use the sTag.

Microsoft provides the following example:


var newRadioButton = document.createElement("<INPUT TYPE='RADIO' NAME='RADIOTEST' VALUE='First Choice'>")

What happens if we use this solution for the classid issue?


var node_name = (isMSIE) ? "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />" : "object";
var obj = document.createElement(node_name);

This works like a charm… in IE 8 and lower. Unfortunately, IE 9 chucks a wobbly. Microsoft, in their wisdom, decided that angled brackets should no longer be valid inside the createElement method. This is actually excellent news: Internet Explorer 9 is behaving like other browsers, so let’s just use a try/catch to target IE versions prior to IE9, and use standard W3C code for IE9:


var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj;

if (isMSIE) {

   try {
      
      //For IE 8 and lower
      obj = document.createElement("<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />");

      //IE requires the 'movie' <param>
      var param_movie = document.createElement("param");
      param_movie.setAttribute("name", "movie");
      param_movie.setAttribute("value", "test.swf");
      obj.appendChild(param_movie);
      
   } catch (e) {
      
      //Let IE9 and higher fall through and use the standard browser markup
            
   }
   
}

if (!obj) {

   obj = document.createElement("object");
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
   
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Still works in Internet Explorer 8 and lower, but fails in IE 9. Okay, perhaps IE9 still requires the classid attribute and ‘movie’ param. Let’s fork the code some more and try it out.


var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj;

if (isMSIE) {

   try {
      
      //For IE 8 and lower
      obj = document.createElement("<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />");
      
   } catch (e) {
      
      //IE9 doesn't support classid in createElement, so let's add it afterward
      obj = document.createElement("object");
      obj.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
      
   }

   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);
   
} else {

   //Standard browsers
   obj = document.createElement("object");
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
   
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Are you ready for the surprise? This doesn’t work in IE9, either. Adding classid to the object after it has been created simply will not work in any version of Internet Explorer. Microsoft removed their own recommended workaround — adding the attribute in the createElement string — to standardize their browser, yet they failed to provide full support for the related W3C standards being used by their competitors.

In their announcement of the createElement change for IE9, Microsoft recommended using the W3C standard setAttribute method, yet it fails for classid. *grumble*

To their credit, Microsoft also provided a second workaround consisting of strings and innerHTML:


var parent=document.createElement("div");
parent.innerHTML="<div id='myDiv'></div>";
var elm=parent.firstChild;

For our purposes, it would look like this:


function createIeObject(){
   var div = document.createElement("div");
   div.innerHTML = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'></object>";
   return div.firstChild;
}

var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj = (isMSIE) ? createIeObject() : document.createElement("object");

if (isMSIE) {
   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);
} else {
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

This feels like it’s getting us somewhere. Unfortunately, the SWF still doesn’t load. What else can we try? What if the ‘movie’ param were added at the same time the object is created and classid attribute is specified in Internet Explorer?


function createIeObject(url){
   var div = document.createElement("div");
   div.innerHTML = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'><param name='movie' value='" +url + "'></object>";
   return div.firstChild;
}

var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj = (isMSIE) ? createIeObject("test.swf") : document.createElement("object");

if (!isMSIE) {
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

EUREKA! The SWF now displays as it should in all versions of Internet Explorer, and IE no longer behaves as if it’s stuck trying to load something. The takeaway is that Internet Explorer’s proprietary classid attribute and ‘movie’ param need to be created together or all is lost.

Why not use innerHTML all the way?

Many developers will say: Why not use innerHTML for the whole thing? After all, SWFObject 2.0 through 2.2 uses string building and innerHTML for the entire object creation in Internet Explorer:


el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';

In SWFObject, the attributes (att) and parameters (par) are added to the string via string concatenation. This is functional, but means that a completely different workflow — one devoid of standard W3C techniques — is required for Internet Explorer versus all other browsers.

For example, if there are many param nodes to add to the object, they will have to be generated as a long string of params in IE, while the fork for the non-IE browsers will all use a createElement/appendChild combination. In my opinion, it would be best to keep the code simple and use the same code for handling attributes and parameters in all browsers. This is ideal for maintenance, security, and file size.

Using the proposed IE solution allows us to modify the object via W3C techniques — we can add parameters and attributes and they all function as expected. The only difference between IE and other browsers is the creation of the initial object, but once it’s set up, IE’s object behaves the same in all browsers.

The obligatory poke at Microsoft

This is a perfect example of why there is not much love for Internet Explorer (and by extension, Microsoft) in many web developer circles. A task that should be trivial is utterly complicated, and requires hours of troubleshooting and experimentation just to achieve some basic functionality.

It’s my sincere hope that Internet Explorer 10+ will use the standard object code, but it probably won’t, since IE will probably still rely on ActiveX for the Flash Plugin. Falling short of using the standard object element, I hope that IE 10+ will at least allow us to add the classid attribute using setAttribute, and also allow us to add the movie param using createElement and appendChild.

Sniffing Internet Explorer via JavaScript

I’ve been reviewing bug submissions for the SWFObject project and was reminded of a big problem with SWFObject 2.2: the JavaScript technique it uses for detecting Internet Explorer does not work in Internet Explorer 9.

SWFObject 2.2 currently uses an IE detection technique proposed by Andrea Giammarchi in 2009:


var isIE = !+"v1";

It’s a hack that relied on Internet Explorer’s unique handling of vertical spaces (v). Now that it has stopped working, I decided to take a quick look at what others are doing.

One of my favorite approaches is Dean Edwards’ “sniff” technique, which takes advantage of Microsoft’s conditional compilation.


var isIE = /*@cc_on!@*/!1;

It’s 4 years old, but still works like a charm. The only problem is that some JavaScript compressors and optimizers (including YUI Compressor and Google Closure) have a hard time with the inline comment and strip it out. It makes the code difficult to maintain, because it requires editing post-compression.

One way to get around the compressor issue (as pointed out by a commenter on Dean Edwards’ post) is to wrap the conditional compilation statement in an eval() function:


var isIE = eval("/*@cc_on!@*/!1");

Since eval() is evil, I won’t use this approach.

One of the most long-standing methods of detecting Internet Explorer is to examine the userAgent string:


var isIE = /msie/gi.test(navigator.userAgent);

However, this isn’t foolproof, as most browsers allow you to change the userAgent string at will. For example, I changed Safari’s userAgent string to report itself as Internet Explorer 8, and /msie/gi.test(navigator.userAgent); returned true!

So far, the only test I’ve found that seems to fit the bill is the old navigator.appName test:


var isIE = navigator.appName === 'Microsoft Internet Explorer';

Is it perfect? Not a chance, but it has a lot of upside: it works in IE9, doesn’t get damaged when compressed, doesn’t rely on any hacky tricks (as fun as they might be), and isn’t affected by spoofed userAgent strings.

What are the Frameworks using?

jQuery uses userAgent sniffing, and includes a warning that it’s unreliable. MooTools uses a combination of properties from the navigator object, including navigator.userAgent and navigator.platform. It’s interesting to note that MooTools previously used feature detection to infer the browser brand, but changes introduced by Firefox 3.6 prompted the MooTools team to switch to a userAgent-based detection method instead. (Nicholas Zakas wrote an interesting blog post about MooTools’ prior detection technique.)
Dustin Diaz’s Bowser detection script uses userAgent exclusively.

Why sniff in the first place?

Before you get down on me for even talking about sniffing, relax, I agree with you. I much prefer feature detection to browser sniffing; this is especially important with our quickly changing browser landscape: HTML5, web storage, geolocation, etc. (Modernizr is a great tool for modern feature detection.)

But Internet Explorer always makes us do things we’re not quite comfortable doing. Sometimes it’s not very easy to detect IE’s support for a given issue. For example, IE is notorious for not allowing JavaScript developers to set the name attribute of form elements via setAttribute; it requires including the name in the createElement invocation. Craziness!

So we carry on. I use feature detection for just about everything, but once in a while I need a global “are you Internet Explorer?”, and browser sniffing fits the bill.