wrappedJSObject
is a property sometimes available on XPConnect wrappers. When available, it lets you access the JavaScript object hidden by the wrapper.
There are two kinds of XPConnect wrappers that support the wrappedJSObject
property:
This article focuses on the latter kind of wrappers, which hide any properties or methods on the component that are not part of the supported interfaces as declared in xpidl.
The rest of this article demonstrates what XPConnect wrappers do and how wrappedJSObject
can be used to bypass them.
To see how the wrappedJSObject
property works, we need an example XPCOM component implemented in JS. See How to Build an XPCOM Component in Javascript for details on creating one.
For simplicity we omit the component registration code. Suppose we register the component below with the @myself.com/my-component;1
contract.
// constructor function HelloWorld() { }; HelloWorld.prototype = { hello: function() { return "Hello World!"; }, QueryInterface: function(aIID) { if (!aIID.equals(Components.interfaces.nsISupports) && !aIID.equals(Components.interfaces.nsIHelloWorld)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } };
Now let's get a reference to our component. In this example we use getService
, but as long as we get the reference from XPCOM, our component gets wrapped by XPConnect in the same way:
var comp = Components.classes["@myself.com/my-component;1"].getService();
If we try to call the hello()
method we defined in our component implementation, we get:
> comp.hello(); TypeError on line 1: comp.hello is not a function
This happens because, as we mentioned earlier, comp
is not the HelloWorld
JS object itself, but an XPConnect wrapper around it:
> dump(comp); [xpconnect wrapped nsISupports]
The idea of these wrappers is to make the JavaScript-implemented XPCOM components look just like any other XPCOM component to the user. This also makes the public interface of the component clearer and provides protection for the component's internal data.
Calling QueryInterface
on the wrapper works, because it is defined in the nsISupports
interface, and the wrapper knows the underlying object implements nsISupports
:
> comp.QueryInterface(Components.interfaces.nsIHelloWorld); [xpconnect wrapped (nsISupports, nsIHelloWorld)]
As you can see, the QueryInterface
call also made the wrapper know about the other interface our component supports. Assuming the nsIHelloWorld
interface defines the hello
method, we can now call it:
> comp.hello() Hello World!
While this behavior is nice for production code as it forces you to clearly define the interfaces that should be used to access the component, it's inconvenient to write the interfaces (and recompile each time you change them) when working on a prototype of the component.
wrappedJSObject
XPConnect lets you bypass its wrappers and access the underlying JS object directly using the wrapper.wrappedJSObject
property if the wrapped object allows this.
More specifically, as XPConnect source comments say, you can get comp.wrappedJSObject
if three conditions are met:
comp
really is an XPConnect wrapper around a JS object. Wrappers around non-JS objects don't have this property.wrappedJSObject
property that returns a JS object.nsIXPCSecurityManager
allows access (see the source code comments for details; this is usually not an issue for Mozilla extensions and applications)This means that in order to access the JS object implementing our component directly, we must modify the component. For example:
function HelloWorld() { this.wrappedJSObject = this; };
Now we can get the component directly:
var comp = Components.classes["@myself.com/my-component;1"] .getService().wrappedJSObject;
This is a real JS object:
> comp [object Object]
So we can access any property on it:
> comp.hello(); Hello World!
This functionality can be used for quick prototyping, as well as to painlessly pass arbitrary JS values to the component (which can be used for sharing complex JS data in particular).