Third-Party JavaScript Development: CSS Defensive Techniques

(In a previous article, I introduced a convenient method for shipping stylesheets with your third-party JavaScript application.)

When styling content in your third-party JavaScript application, you have a unique challenge to overcome: interference. The publisher may have used any number of techniques to define styles, and many of them threaten to modify the appearance of your content. In this article, I will to cover some of the specific threats and methods for defending against them.

Style Leak

Awareness of third-party web applications is still expanding, so it is prudent to assume publishers’ sites were not built with us in mind. This means, among other things, that their styles may “leak” into the nodes inserted by third-party apps. In some cases, this may be unintentional; consider the following example of a publisher document after your “CapnCrunch” application has inserted content:

<div id="content">
<h1>Publisher's title</h1>
<p>Publisher's paragraph</p>
<p>Publisher's paragraph</p>
<div id="capncrunch">
  <h1>Your widget's title</h1>
  <p>Please purchase Captain Crunch</p>
  <div id="capncrunch-footer">
    <a href="#">Crunchetize me</a>
  </div>
</div>

If the publisher wanted green paragraphs, she may have declared a CSS rule like #content > p { color: #bada55; }. This would be really cool for your application, but in reality, the publisher likely declared #content p { color: #bada55; }. It acheives the desired affect, except now your widget’s paragraphs will have an ugly (and somewhat intimidating) green hue.

This is not isolated to environments implementing bad practices. Consider CSS resets–a publisher may (or may not) declare simple rules like * { margin: 0; }, h1, h2, h3, h4, h5, h6 { font-size: 100% }, or * { box-sizing: border-box; } (more on that last one here). As a third-party JS application developer, you cannot make any assumptions about such rules.

So just what are we supposed to do about all these rules flying around all over the place?

Over-specifying

CSS rules are assigned a priority according to how they are specified (more on this here and here). Depending on your content’s structure, you may be able to boost your rules’ priority by being more specific than strictly necessary. Consider the markup from the example widget, reprinted below for your convenience:

<div id="content">
<h1>Publisher's title</h1>
<p>Publisher's paragraph</p>
<p>Publisher's paragraph</p>
<div id="capncrunch">
  <h1>Your widget's title</h1>
  <p>Please purchase Captain Crunch</p>
  <div id="capncrunch-footer">
    <a href="#">Crunchetize me</a>
  </div>
</div>

A rule like #capncrunch a { color: #001337; } will not necessarily take precedence over the publisher’s #content div a { color: transparent; }. The rule #capncrunch #capncrunch-footer a { color: #001337; } is far less susceptable to such nonsense. (The namespaced IDs in this example are intentional. This practice mitigates the risk of collisions with publisher styles.)

Obviously, rules like this are not strictly necessary; indeed, shorter rules are usually more efficient. The motivation here is not simply DOM element targeting (or even efficiency) but ensuring precedence. This is the method of choice for Disqus–you can read about it in these slides from Disqus engineer Anton Kovalyov.

You’re forgetting something !important

At this point, more battle-worn developers may be champing at the bit to point out that even overspecified styles can be overridden via the !important declaration. This is certainly the case, and it’s a “gotcha” no matter how you intend to style your markup. In those situations where a publisher is using !important, one option is to fight fire with fire: declare !important on your own rules. Since your styles are likely being defined at document.ready, your !important declarations will override the publisher’s.

But who wants to maintain stylesheets filled with redundant declarations? Not me. Luckily, the CSS build process (and corresponding build tool) which I described in an article last week is perfectly-situated to automate this process. All that’s required is an extension of the proposed syntax; let’s make it familiar-looking: !import_rule [rule name] !important and !import_fule [file name] !important. For example:

src/widget3.css

div.widget3-container {
  font-family: "Century Gothic", sans-serif;
  /* place below other elements */
  z-index: -1;
}
div.widget3-container h1 {
  color: #a00;
}

src/widget3.js

(function() {
  var styleElem =("<style>" + "!import_file widget3.css !important" + "</style>");
  // The rest of your application...
})();

…could be used to build the following JavaScript:

dist/widget3.js

(function() {
  var styleElem =("<style>" + "div.widget3-container { font-family: \"Century Gothic\", sans-serif !important;z-index: -1 !important; } div.widget3-container h1 { color: #a00 !important; }" + "</style>" );
  // The rest of your application...
})();

Although this will technically get the job done, I recommend avoiding !important whenever possible.

This approach doesn’t scale, and it breaks the “cascading” nature of CSS. The valid use cases are few and far between. Arguably, 3PJS is a new special case where the use of !important is acceptable, but I think it far better to engage the wayward publisher in a discussion about their structure and offer a more sane solution. If yours happens to be the only third-party application on the offending web site, it won’t be for long. Your advocacy of best practices may even save other 3PJS devs headaches. This is a tough job, so we all gotta stick together!

The iFrame Sandbox

There is a solution that protects you from the !important directive and does not amount to conflict escalation. One caveat: this approach is only useful if you are simply inserting new content (not modifying publisher content).

The contents of HTML iFrames do not receive the parent document’s styling. This means that, by inserting content into an iFrame within the publisher’s DOM, you can effectively “sandbox” your styles. Note that the sandbox works both ways–you don’t have to worry about the styles you define affecting the publisher’s page. This means you don’t have to namespace IDs and class names as in the previous examples.

Here’s how it’s done:

widget4.js

(function( window, document, undefined ) {
  var iframe = document.createElement("iframe"),
    iWindow,
    iDoc;
 
  document.body.appendChild( iframe );
  
  iWindow = iframe.contentWindow;
  iDocument = iWindow.document;
  
  iDocument.open();
  iDocument.write( /* your DOM content here */ );
  iDocument.close();
  
  /* the rest of your app... feel free to modify the iFrame's
  contents via the iDocument var. If you're using jQuery,
  remember to use the iFrame document as the second argument
  to the jQuery function, i.e.
  $("h1", iDocument).text("Hello World!");
  (see http://api.jquery.com/jQuery/)
  */
 })( this, this.document );

Some may be reading this and wondering, “Why not just define a src attribute on the iFrame?” In other words, why go through all the hastle of writing to the iFrame and managing the context when the browser will do it for free?

If the iFrame does not need to communicate with the parent page, this is certainly a good option. In many cases, the iFrame’s contents are at least partially dependent on the context in which they are included. By hosting the widget on your own domain, and including it elsewhere via the src attribute, you submit the document to cross-origin restrictions. There are solutions (most notable is the EasyXDM library), but at this point, setting the src is no longer saving you extra work.

In addition, the “sourceful” iFrame approach necessitates a separate web request for each widget. If there is more than one widget on the page, the resultant latency may not be acceptable for your application.

Lastly, even though the browser will likely cache those resources required by each iFrame, the JavaScript execution environments are isolated. This means JS libraries like jQuery will need to be evaluated in every iFrame.

Protect your Style

Hopefully, this has got you thinking defensively. 3PJS presents unique challenges; CSS is just one. If you’ve got your own approach to defensively defining presentation in your application, please share in the comments below!

This entry was posted by Mike Pennisi (@jugglinmike) on April 17, 2012 in JavaScript and Feature.

Comments

Author

This entry was posted by Mike Pennisi (@jugglinmike) on April 17, 2012 in JavaScript and Feature.

Recent from this Author

Related Posts