Adblock Plus and (a little) more

Avoiding naming conflicts in overlays · 2009-02-16 23:12 by Wladimir Palant

XUL overlays are a great mechanism for extending existing functionality. However, there is a catch — any scripts loaded by an overlay are sharing the namespace with the scripts loaded by the original page and the scripts loaded by other overlays (typically from other extensions). So if extension Foo defines a global variable myGreatVar and extension Bar chooses to define a global variable myGreatVar as well, bad things will happen — both extensions will end up accessing the same variable and usually both extensions will behave erratically because of that. Same happens with functions, if two scripts define the same global function the second function definition will override the first. The situation is worst for constants — a constant cannot be redeclared meaning that one of the extensions will cause an error message.

Now this problem isn’t a new one and Mark Ziesemer wrote a nice article suggesting namespaces as a solution a while ago. This blog article even made it into AMO’s text block answers. Unfortunately, I think that these fake namespaces are an awkward and unnatural construct in JavaScript. You will have to spell out your namespace all the time and you will have to force your code into an object oriented writing style (which may or may not be desired). And of course you are still polluting the global namespace, and be it only for the namespace variable.

Instead you could make sure that all your variables and functions are declared inside a function. Those variables will be local to that function and not visible outside of it, so conflicts will be impossible. You can wrap all your code in a closure and let it execute when the “load” event is triggered:

window.addEventListener("load", function()
{
  const INCREMENT = 1;
  var myVariable = 8;

  function increaseMyVariable()
  {
    myVariable += INCREMENT;
    alert(myVariable);
  }

  document.getElementById("extension.foo.myButton")
                .addEventListener("command", increaseMyVariable, false);
}, false);

The only disadvantage of that approach: your functions won’t be visible to your XUL elements either, so inline events handlers in XUL won’t be possible. addEventListener will have to be used instead.

But what if you have much code that you would like to split up into multiple files? A closure cannot be shared between different files. But Firefox lets you create an independent namespace using Components.utils.Sandbox constructor (yes, that’s a misuse of a feature but I am not aware of another feature that would be more suitable). Once that namespace is created you can load scripts into it using mozIJSSubScriptLoader:

window.addEventListener("load", function()
{
  var namespace = new Components.utils.Sandbox();

  // Define global variables "window" and "document" for the new namespace
  namespace.window = window;
  namespace.document = document;

  // Load scripts into the namespace
  var subscriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                      .getService(Components.interfaces.mozIJSSubScriptLoader);
  subscriptLoader.loadSubScript("chrome://.../general.js", namespace);
  subscriptLoader.loadSubScript("chrome://.../utils.js", namespace);
}, false);

Here the disadvantage is that a sandbox is really an empty namespace, by default it doesn’t have variables like “window”, “document”, neither does it have the usual functions like alert() or XMLHttpRequest constructor. You can work around by setting the necessary variables on the sandbox before loading the scripts as in the example above. However, function setTimeout() for example generally doesn’t work from a sandbox and has to be replaced by nsITimer.

Tags:

Comment [6]

  1. Alex Vincent · 2009-02-17 00:07 · #

    Why use the sandbox at all? Why not just a raw JavaScript object, like my example from a few years ago?

    http://weblogs.mozillazine.org/weirdal/archives/008101.html

    (This is not a rhetorical question – I’m genuinely interested in the answer.)

    Reply from Wladimir Palant:

    A plain object isn’t a real namespace, it will still fetch global variables in the window – and if you want to access variables that you declared as “global” you will have to prefix them with “this”.

  2. Simon · 2009-02-17 00:07 · #

    I am not aware of another feature that would be more suitable
    A plain new Object {}, as used by viewSourceUtils.js ?

    You can work around by setting the necessary variables on the sandbox before loading the scripts as in the example above.
    Or set namespace.__ proto __ = namespace.window

    Reply from Wladimir Palant:

    A plain object isn’t a real namespace, it will still fetch global variables in the window – and if you want to access variables that you declared as “global” you will have to prefix them with “this”.

    Setting proto – nice idea, thanks. Will try that and see how it works out.

  3. Neil Rashbrook · 2009-02-17 02:15 · #

    When designing SeaMonkey’s new toolkit-enhanced prefwindow Mnyromyr decided to use the subscript loader to load the scripts into the context of one of the DOM nodes. This benefits from a feature that at one point in Gecko’s ancient past used to work on all nodes but nowadays only works in XUL which is that an inline event listener can call functions defined on any node in the element’s hierarchy. Unfortunately some of the preference elements simulate event dispatch with Function objects using only the node’s own scope, so to call the loaded functions you have to write awkward code such as document.getElementById(“foo”).bar();

  4. Vladimir Dzhuvinov · 2009-02-22 17:42 · #

    Good :-)

    How about the other similar problem – avoiding naming conflicts with XUL element IDs? ;-)

    Reply from Wladimir Palant:

    With element IDs the only solution IMO is to use a prefix that other extensions won’t use. Luckily, conflicting IDs are pretty rare from what I can tell.

  5. Vladimir Dzhuvinov · 2009-02-23 23:23 · #

    Thank you for the advice :-)

    I spent the last couple of days searching for relevant articles on the topic. Despite the recent proliferation and rising complextity of web apps and Firefox extensions, there are few good posts about Javascript namespacing and identifier naming in large markup files. Perhaps not more than a dozen. I’m currently developing a xulrunner product, and while it isn’t that complicated from software engineering point of view, it didn’t take more than 2-3 months of programming to notice that naming (as well as the partitioning of code in general) was beginning to turn into a serious problem :-)))

    As for XML ID prefixing, initially I wanted to use the ‘.’ as a separator char, but quickly learned that this complicates the CSS #ID selectors (everything that is not alphanum, dash or underscore has to be escaped). So now I’m using the commonly used ‘-’ to separate the prefix(es). And the ID names, oh dear, they keep getting longer and longer ~:-0

    Like “myAccountTabpanel-transactionsTree-creditedAmountCol” or “myAccountTabpanel-menuPanel-accountBalanceLabel” :-)

  6. mee · 2010-11-11 23:53 · #

    What changes would you make to the “extensions manager” such that it would be more of a manager and less of a catalog?

Commenting is closed for this article.