Adding Nodes to the DOM with Style
June 29th, 2010
8Extension developers who want to overlay HTML on pages face two style-related challenges: preventing page styles from affecting the added HTML, and ensuring that the added HTML is visually over top of the rest of page. This blog post will show you how to use XBL to add nodes to the page with style, complete with a working demo.
Short Version
Play with this extension. It adds the row of seats from MST3K to your browser in a way that is interesting to browser extension developers.
The Cascade Problem
One of the first bugs I worked on in extension development was that the elements that extension was adding to a page would look strange of some pages and fine on others. What was happening was that CSS rules on those pages were being applied to our markup.
The first thing I researched while trying to fix this problem was how the cascade in CSS works—when an attribute for an element is specified twice, which version wins? It’s a short read that I’d encourage everyone mucking about with CSS to skim at least once. The important lesson for me at the time was: user !important rules win.
“Great,” I thought, “I’ll just put all of our rules in a user sheet and mark them !important.” And then I did (See the Stylesheet Service for how). Aside from being crazy, there are two reasons this didn’t work:
1. I left out attributes. If I set border and the author sheet sets border-left, it still wins for border-left.
2. I was relying on children inheriting my rules. If I set border on a parent and the author sheet sets border on all elements, it still wins for border on the children of my element.
My next solution was to add rules to my user sheet for every element in my overlay with a list of every attribute set to -moz-initial !important except for the rules I wanted to change. I nicknamed this solution “screaming at the top of your lungs all of the time.” And it sort of worked. I had to pick ridiculous (or, as I said at the time, “improbable”) ids for every element to avoid altering elements on the page, I had made it impossible to ever change the look of the extension, the error console was a avalanche of css errors, but the elements looked just the way I wanted them to on every page.
What I really needed was a way to tell Firefox “don’t apply the page styles to this html.” If you ever find yourself angrily tacking margin-left, margin-right, margin-top… to an inline style on an iframe, you probably need a way to do that, too. More on that later.
The Z-Index Problem
A red div and a green div are absolutely positioned with the same top, left, height, and width. Which color will be on top when:
- Red is after green in the dom, they are in the same stacking context, and neither have z-index set?
- Red is after green in the dom, they are in the same stacking context, and they have the same z-index set?
- Red is after green in the dom, they are in the same stacking context, and green has a higher z-index than red?
- Red and green are in different stacking context’s, red’s context has a higher z-index, and green has a higher z-index than red?
- Red is after green in the dom, they are in the same stacking context, and that context is a xul stack element?
Insert comic turntable squeak here.
Having “solved” the CSS problem, I moved on to the next style problem: how to make sure my element is over top of arbitrary page elements. In my experience, you can get 80% there by just setting z-index: 50 for the div you want to be over top of the page. Few elements have a z-index in the wild, and when they do developers seems to stick to single digits.
My favorite example of the other 20% is the dropdown menu on Yahoo! Finance. The z-index for these divs is set to about 2000. Why 2000? Who knows! But there’s an easy fix: up your z-index from 50 to 2001. If anyone asks why you chose that number, make a joke about Stanley Kubrick to distract them and then run.
Obviously, 2000 is not the highest z-index in use on websites, so your qa lab is going to come back with some nasty bugs about ads for weight loss overlaying your UI. My brilliant plan on hearing this was to skip the intermediary escalation of z-indexes and jump to the highest z-index possible in firefox. The problem with this is that, if I can look up the highest z-index, so can Joe Geocities Page. Having picked the highest z-index I could, I moved on to solving the problem of what to do with elements that are the same z-index as me.
Since my extension waited until the page loaded and then called document.body.appendChild, my element would be later in the dom then those other nodes, so I’d beat them—yay! Unless the page added elements to the dom after load, which since the whole “DHTML” fad in the 90s had become incredibly common.
If CSS wouldn’t help us, it was time to escalate to Javascript. I considered continually moving my div to the bottom of the list of the body’s children. I considered looping over all the other elements on the page and making their z-indexes smaller than mine. In short, I considered madness.
What I really needed was a way to tell the page “this html should go on top of all of the other markup.” If you have a tab opened to the w3c page for “stacking context” to follow a wild hunch, you probably need a way to do this as well.
A Wacky Problem Calls for a Wacky Solution
Mystery Firefox Theater 3000 is a trivial example of a solution to the cascade and z-index problem.
how does this help with the cascade?
The solution to our first problem is to put our html inside of a XBL binding with the attribute inheritstyle set to false. There are two places on MDC that mention inheritstyle. Don’t blink or you’ll miss them:
- https://developer.mozilla.org/en/XBL/XBL_1.0_Reference/Anonymous_Content#Binding_Stylesheets
- https://developer.mozilla.org/en/XBL/XBL_1.0_Reference/Elements#binding
mft3k’s content.js waits for pages to load and applies a binding to the html node of each page (the reason I chose the html node will be explained when I come back to the z-index problem). binding.xml#seats has inheritstyle=“false” and adds some html around the body of the page. The rules specified in stylesheet.css are not exhaustive lists of attributes set to !important, there is no need for inline styles, and they can make use of the normal cascade. The only gotcha I’ve encountered with inheritstyle is that it doesn’t seem to work for font-related styles, but adding a nice * {color: -moz-initial !important, … other font-related styles … } to the top of my binding’s stylesheet seems to do the trick. Limiting the pain to some font-bleeding problems is a huge win.
how does this help with the z-index problem?
Here’s where things get down-right devious. As noted above, the body is now a child of some of my binding’s dom nodes. I give the parent of the page body and my div a display of -moz-stack, which causes it to act like a XUL stack. Since my seats div comes after the body as a child of the stack, it will always be displayed on top of the body. The stylesheet has some extra rules to make sure the body’s height and width are what you’d expect, and we’re done. The major gotcha here is that plugins like Flash wildly ignore the rules about who’s on top on Windows and Linux. I’ll have to leave this one as an exercise to the reader, and please turn in your homework when you’re done :)
Thanks and Conclusion
I’d like to say thanks to:
- Mike Kaply for taking in interest in my work and encouraging me to share it.
- Mitchell Johnson for hacking out some code for me and suggesting the name.
The source for mft3k is BSD licensed. Use it, make it better, and brag about it to me.
Tagged with: extension, css, xbl, mst3k
Related Posts
Author
8 Comments Leave a comment
I started asking myself literally last week why extension authors (especially the big players, like the Firebug guys) aren’t using XBL instead of injecting elements into the page and possibly causing observer effects.
I really don’t know understand why you call it a wacky solution. It has the shadow tree for not mucking up the DOM and was designed for encapsulation.
Jorge: Aside from making a html:div act like a xul:stack with -moz-stack, xul is mercifully absent from my hack. I will be sure to look into that bug though, thanks!
Colby Russell: It is oddly uncommon, but I’m certainly not the first developer to stumble across that corner of MDC. For example, if think the guys at Flashblock make excellent use of XBL + observers. I hope that releasing a cheesy demo like mft3k will help spread the word.
Regarding the wackiness, you may be right. It’s only bizarre until everyone starts doing it :)
Ryan: Flashblock actually goes further with encapsulation by making it such that none of the methods are available to content. I don’t know if I’d call it a good use of XBL, though, because the way it’s done is by not creating any methods—all of the “methods” are actually functions defined in a closure of the constructor, and they work because everything happens either in the constructor or by being invoked by an event listener (which has access because it’s also set in the constructor).
That’s by no means a slam on Ted, Ted, Jesse, or any of the others involved. I say that I wouldn’t call it a good use of XBL just because the approach isn’t very XBLly, since none of methods, fields, or handlers are actually specified in XBL. But that was the only way to achieve that with XBL+JS (at least at the time it was originally written, I think), and it was just a deficiency of XBL. The HTML5 video controls are actually specified in XBL, too, without exposing the implementation to content, but I don’t know how it achieves that, since I’ve only ever accidentally stumbled upon it while browsing without the intent of figuring out how it works. I believe nsISecurityCheckedComponent is involved somehow, for what it’s worth.
Note that the planned removal of remote XUL also plans to remove remote XBL, so it might indeed break your solution.
I wonder if something involving a <panel> (similar to the autoscroll marker or the Test Pilot status messages) with noautohide=true will work. It used to not play well with transparency, but that might have been fixed now…
All of the things in this article are more than familiar to me … I am planning on migrating the Firebug inspector to a transparent XUL panel and SVG / canvas.
Great article.
Mike Ratcliffe: I wasn’t talking about XUL panels, I’m talking about using XBL instead of observably adding stuff via (W3C) DOM methods.
I increased MFT3K’s maxVersion today to 4.* and tried in out on Minefield 4.0b5pre. It ran without changes! Luckily, the removal remote XUL have not nerfed this approach to adding dom nodes!
Thanks to Jorge for bringing the remote XUL change to my attention, and Mook for clarifying that remote XBL was also on the chopping block!
Leave a comment
Allowed Tags
_emphasis_
*strong*
??citation??
-deleted text-
+inserted text+
^superscript^
~subscript~
@code@
Add code using a GIST
gist: gistid
I’m curious if this approach will continue to work when remote XUL is eliminated (https://bugzilla.mozilla.org/show_bug.cgi?id=546857), assuming this approach requires mixed XUL and HTML content in a web page.
Reply to comment