Implementing Tags

I’m writing an extension to implement a currently-unimplemented HTML tag in Mozilla/Firefox. I can’t say what it is; the smart among you may work it out, but if you do please keep it to yourselves :-)

My original plan was to define an XBL implementation of the tag and just bind it on. This plan is simple, but unfortunately it doesn’t work. Implementing the tag requires using Mozilla platform features accessed via Components.classes, and as far as I can see, there is no way of applying an XBL binding to a tag in the content area in such a way that the XBL code is privileged – even if the binding uses a chrome:// URL to bind.

So, after much head-scratching and help from Boris Zbarsky, the current, horrible implementation strategy is as follows:

  • Overlay browser.xul to add a script
  • That script registers a handler for window.onload
  • The onload handler adds a “_helper” member to the window.content.document object,
    which exposes useful methods for implementing <tag>

  • This document._helper object is visible from the content
  • Add a selector to ua.css (by appending to the file – ick) to bind my (unprivileged) binding to <tag>
  • The binding gets invoked when a <tag> is seen
  • The binding constructor also registers an onload handler, as the _helper
    object is not available at binding construction time (because the onload handler mentioned above hasn’t yet fired)

  • This onload handler eventually fires and calls methods on the now-present
    document._helper to deal with initial state of <tag>

  • Methods on the binding call methods on document._helper as necessary to deal with
    scripted changes to <tag>

What do you think? Am I insane? Is there a better way?

Possible improvements I don’t know how to do:

  1. Find a way to make the _helper object available permanently, even while the page is loading.
  2. Find a way to inject the binding rule without messing with ua.css.

28 thoughts on “Implementing Tags

  1. I would say to try using XTF, but I doubt that works in the HTML namespace, you would probably need a custom namespace.

  2. You can make a global object available by adding a component to the “JavaScript global property” category. Look at components/nsSidebar.js in Seamonkey for an example, it creates the global variable window.sidebar. I guess you can register the component when your browser.xul overlay starts up – no need to install it permanently.

    When doing so, please keep in mind, that it isn’t only your XBL code that gains access to a chrome script but any web page loaded in the browser. Every function provided needs to be secure and shouldn’t give away any sensible information.

    There is also another possibility for XBL to communicate with an extension though very ugly and inflexible. Adblock used to work this way and it created fake keyboard events that would bubble and get caught by the extension at the chrome level. The only way to pass parameters here is by key code, Adblock used several with values > 1000.

  3. This issue came up in the context of the linktoolbar, way back when. You might be able to find commentary in the “convert link toolbar to xbl” bug whose number I no longer have memorized (yes, I did used to – it may even still be “assigned” to me), or perhaps one of the other linktoolbar bugs (there was a metabug that you could use as a starting point).

    After discussing this with hyatt we came to the conclusion that the right solution long-term was that XBL code should run in the security context of *whatever caused the CSS file to be bound to the page*, rather than the context of the page as it is now, or the context of the CSS file as I had originally proposed. This means that for most stylesheets nothing changes because they’re bound by or tags in the page itself, but now things like ua.css, html.css and userContent.css can get special treatment because they get bound to the page by internal rules so they can get high privileges.

    Unfortunately it seems like this common sense approach was never implemented, and so the same problem gets to bite a new generation of bug…

  4. That should be “they’re bound by <style> or <link> tags in the page itself”.

    Your comments apparently aren’t protected against HTML injection?

  5. Funnily enough, I was having a chat with someone about Mozilla extensions this afternoon, about similar issues.

    There seem to be a number of problems with the way extensions work, as I see it. I’m sort of worried about versioning – extensions that have rotted as new versions of software have come out are legion, and I fear that even post-1.0 this versioning problem will still exist (i.e., will changes be made post-1.0 which break extensions? Maybe UI being moved, functions changing..)

    But other real issues revolve about how you add functions – adding new bits of code into existing functions is tough (should work a bit like Aspect Oriented programming), there are problems depending on libraries like jslib (again a versioning thing), and in general things can seem pretty difficult.

    I tried writing support for List-Id et al. into Thunderbird; I’m not sure it’s possible. Having a context-sensitive item in the right-hand menu (i.e., examines the header in the mail and makes the option selectable or not) looks exceedingly hard (I couldn’t think of a way to do it when I last looked – List-Id: is not a cached header, so you’d have to setup a sink etc. I think).

    Is there a better way? Maybe to add specific interfaces to extend functionality in a cleaner way? (I even find developing extensions hard – it’s like writing an extension for a program in patch file format).

  6. Regarding wmlbrowser: it currently operates by intercepting WML content as it comes in, then converting with XSLT before sending the transformed page as HTML to the normal browser component. None of that really applies here I don’t think?

    There did used to be an XBL implementation but I don’t remember exactly how far it got. But I don’t think I was trying to do anything privileged in the XBL bindings.

  7. Finding the right security scheme for XBL is hard and one of the not-so-frequent topics on the mozilla2.0 telefone confs. Though Benjamin tries to raise the point every now and then. But it’s painful, and thus we end up with yet another call and no comments on his proposal.

    Basically, even assigning the security context of the CSS domain may not suffer. That would keep you from implementing security sensitive stuff like browser. Security checks need to be done on a method by method scale, at least IMHO.

    Gerv, if you add a property to the global namespace, be sure to call it something like __SomeCompany_secret_helper, to avoid naming conflicts. And do add close security reviews for it, of course. I personally would prefer the category manager approach. I got that working once for RDF scriptability (which never landed), look at RegisterRDF in https://bugzilla.mozilla.org/attachment.cgi?id=130590

  8. Had the same issue when, during the nscp era, I coded image resizing in Nvu in XBL. The easiest way to solve the problem is to extend the behavior of the browser element itself and have, onload as you do it, a ref to the browser element passed to the document. That way, a bound element in the unprivileged document can ask the privileged one to do something he can’t.

  9. Axel, you’ll note that my proposal *didn’t* suggest to use the security context of the CSS file, and that it already takes into account the reason why that isn’t sufficient.

    Hyatt agreed, at the time, that it was sufficient for security. If you know of any reason why it isn’t, please do let me know, because I’m genuinely curious.

  10. Regarding the second request (modify global CSS rules without writing to disk): rue has already essentially solved the problem, though it’s not in any publicly available extension. See my blog — basically, when your extension loads, use inIDOMUtils::getCSSStyleRules(window.document.documentElement) to get access to the nsIDOMCSSStyleSheet object for resource://gre/res/html.css and then add your rules.

    The technique works fine on my machine, but is currently dependent on DOMi or a custom component implementing DOMi’s code. If you could help remove that dependency, I’d be quite grateful…

  11. Stuart: your <style> and <link> tags don’t appear in the source – MT has stripped them out. So the blog is protected.

    In comments on this blog, some HTML is allowed – <b> (as above) and <blockquote> being the most useful.

  12. Ben: very cool, and it’s indeed annoying that it doesn’t work without DOMI.

    However, it has suggested to me another way which would suffice. In an onload handler for new content load (which I know how to do), I could do:

    window.content.document.styleSheets[0].insertRule(“mybindingrule”, 0);

    I hope that, because Mozilla reacts correctly to dynamic style changes, this will apply my binding when the load is finished – which is good enough for me. If there are no stylesheets, I suspect I can insert a dummy one using document.createElement(“style”).

  13. @Ben: nice trick but I don’t think you can eliminate the dependance on DOM Inspector. I looked into it and even DOM Inspector uses a pretty ugly hack to get to these style rules that are usually hidden deep inside gklayout.

  14. Wladimir: You’re right that the DOMi code is something of an ugly hack, though it does seem to have the distinct advantage of working… :-) We may or may not figure out how to get independent of the C++ code itself, but it would certainly be feasible to extract the one function from the DOMi code and compile that — I’ve already got an (untested-but-successfully-compiled) .so XPCOM component that seems to work right on Linux… the corresponding .dll is proving rather more intractable to compile. We actually don’t need every single style source, just the nsIDOMStyleSheet corresponding to “resource://gre/res/html.css”. That could and probably should make the necessary function much simpler, though cross-platform XPCOM in extensions itself still isn’t as easy as it could/should be.

    Gerv: Yeah, at one point (before rue’s elegant-but-needy code), I had a routine set up to add a node with the appropriate text content to the of every new window, before onload; it worked, but ’twas something of an ugly hack.

  15. An update: despite the elegance of the dynamic-stylesheet-insertion method, the issues with distributing compiled XPCOM are large enough to make writing to ARes/html.css (or UChrm/userContent.css) more attractive.

    I’m actually surprised that the XPCOM DOMi components aren’t installed for a default .exe install — I’d think they’d just disable the menuitems/key bindings and leave the XPCOM intact… oh well.

  16. I hope that, because Mozilla reacts correctly to dynamic style changes, this will apply my binding when the load is finished – which is good enough for me.

    Turns out that I was wrong about that. It seems Firefox completely ignores dynamically-added -moz-binding rules. <sigh> It’ll respect ones in the original page source, but not identical ones added via the DOM. It’ll respect rules like p { color: red; }, but not -moz-binding rules.

    So the options are rely on DOMI (can’t do that), distribute compiled XPCOM as a replacement (don’t want to do that) or append to ua.css. I may not even be able to do the latter, because I haven’t yet found out how to run JavaScript as part of the install process, now install.js has been replaced by install.rdf.

  17. install.js is still supported though. And will remain to be, IIRC, as there are valid usecases which just don’t work with install.rdf.

    Sadly, it may hork all the sweetness of EM.

    I wonder if we could make the ua.css be some kind of wildcard, like we do for ua.css. Do you have to work on older builds?

    Note, modifying ua.css requires that your extensions is installed in the application, not in the profile (which will be more frequent, right?). So you’d have to hack userContent.css, probably. Well, depending on installation.

    You may be able to register a component for startup, and check if your modification is done, and prune it afterwards or something.

    Or don’t prune it, as crashes make new profiles, or existing profiles may be edited and remove your additions and you end up with non-working stuff.

    YIKES, I pitty you there.

  18. Axel: are you sure install.js still gets executed? I understand that people are making XPIs which work with both Mozilla and Firefox by having both install.js and install.rdf, with Mozilla reading one and Firefox reading the other. Am I mistaken? Or is there some trick here I haven’t noticed?

    I wonder if we could make the ua.css be some kind of wildcard, like we do for ua.css. Do you have to work on older builds?

    That first sentence doesn’t seem to make sense… but yes, it has to work on older builds. Certainly Firefox 1.0, and almost certainly Netscape 7.

    The right fix going forward is the stylesheet access API being discussed in bug 179006, as Wladimir points out.

    Gerv

  19. Gerv: have you tried adding your stylesheets asynchronously through window.shouldLoad, rather than directly? That’s one of the less-intuitive tricks that Adblock uses…

  20. Gerv:
    Aaron Boodman just dug up a “before onload” event, and we’re using it in Greasemonkey.

    window.document.getElementById(‘appcontent’).addEventListener(“DOMContentLoaded”, greaseLoad, false);

    This does indeed fire before the content’s onload.

  21. Wow… um, yeah, that last post from me should have been “window.setTimeout” not shouldLoad…

    Jeremy: there are posts from Andyed mentioning this back in May of 2003, but still… it looks pretty neat/promising. I’ll have to investigate this.