05.05.10

New Mozilla developer feature: Components.utils.getGlobalForObject(obj)

Suppose you’re an extension developer implementing some sort of event listener-like interface corresponding to browser windows. You’d like listeners to stick around as long as the original browser window is open, so when the browser window’s unload event fires when the browser window is closed, you want to remove the listener. Further, for simplicity, you’d like to be able to reuse the same interface as the DOM uses: EventTarget.addEventListener(eventName, listener, bubbles). But if you do that, how do you know what browser window to associate with a listener? One possibility is that you could associate event listeners with windows if you could determine the global object that corresponded to the event listener in question. (Assume arguendo that you control all use of listeners, so every listener is straightforwardly created in the window with which it’s associated — no shenanigans passing listeners across windows.) This isn’t the only situation where one might wish to know the global object, but it does happen to be a somewhat common one.

Is it possible to determine the global object corresponding to an arbitrary object? You can through a convoluted sequence of actions involving prototype hopping using Object.getPrototypeOf and walking the “scope chain” for the object (using an obscure Mozilla-specific feature whose details I omit). More simply, if the object in question is a DOM node, you could use node.ownerDocument.defaultView, which, while quite understandable, is still DOM-specific. But wouldn’t it be much better if there were some simpler, universal way to determine this value, rather than skirting the edges of feature intentionality?

Firefox nightlies now implement support for a new method available to extensions and other privileged code: Components.utils.getGlobalForObject(obj). It’s designed to support the specific need of determining the window where an object was created. (XPCOM components of course have a global object that isn’t a window, and that global object will be returned by the method in those circumstances.) Its functionality needs little explanation:

/* in the global scope */
var global = this;
var obj = {};
function foo() { }
assertEq(Components.utils.getGlobalForObject(foo), global);
assertEq(Components.utils.getGlobalForObject(obj), global);

If you’re using the previously-noted method involving prototype- and scope-chain-hopping, you should change your code to use Components.utils.getGlobalForObject(obj) instead. This new method is much simpler and clearer, and upcoming changes mean that the old method will no longer work in future nightlies and releases. The next release is still several months out, so you should have plenty of time to adjust.

As always, you can experiment with a bleeding-edge version of Firefox that supports Components.utils.getGlobalForObject(obj) by downloading a nightly from nightly.mozilla.org. (Don’t forget to use the profile manager if you want to keep the settings you use with your primary Firefox installation pristine.)

A last note: the method as currently implemented in nightlies suffers from a small bug when used with objects from unprivileged code — objects from scripts in web pages, that sort of thing. It’s fixed in the TraceMonkey repository, and the adjustment should make its way into the mozilla-central repository (and thus into nightlies) in short order. If you only use the method on objects from privileged scripts, I don’t believe you’ll encounter any problems.

5 Comments »

  1. More importantly, I guess that you can access the global object of a module that way – something that I need to do occasionally in unit tests. So I’ll be able to throw away my hacks using __parent__ eventually. Not to mention that a function inside a module doesn’t have a proper way to access its own global object, there is no global variable like “window” there. Very nice and thank you!

    Comment by Wladimir Palant — 06.05.10 @ 00:24

  2. More importantly, I guess that you can access the global object of a module that way

    We should prevent that. JS modules explicitly define what should be exported. The rest should be private.

    Comment by Dao — 06.05.10 @ 00:49

  3. Making the method not work on objects from modules is an interesting idea. I doubt it would truly be effective; I’m aware of at least one other method to determine global objects, one that’s more obscure than the scope-chain-walking hack while at the same time more understandable, and I don’t think we could disable that method because it would break the web.

    Comment by Jeff — 06.05.10 @ 11:06

  4. The exported symbols are a convention, not a security mechanism. I don’t see the point enforcing that somebody using a module cannot access its global object at any price. As I mentioned, the fact that accessing it is possible is pretty useful for my unit tests – I want to have access to private objects there without exporting them.

    Comment by Wladimir Palant — 06.05.10 @ 12:57

  5. […] by exposing a method somewhere to directly retrieve the corresponding global object. We have recently added such a method to support this use case: […]

    Pingback by Where's Walden? » SpiderMonkey change du jour: the special __parent__ property has been removed — 07.05.10 @ 16:23

RSS feed for comments on this post. TrackBack URI

Leave a comment

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>