With XPIDL, XPCOM components can be very strict about what they allow.
Simply put, if you try to pass in an argument to a XPCOM component, and the
component requires a type that your argument doesn’t support, it won’t work.
In C++, your program won’t compile; in JavaScript, XPConnect throws
exceptions. This is a good thing.
Many of the basic data structures – nsIArray, nsIPropertyBag, etc. – take
a nsISupports
argument. This covers most objects you construct
and work with. This is all well and good… unless you want to pass in a raw
type such as a number, a true
or false
value, a
string, etc. Then you’re stuck.
Fortunately, all is not lost. There is an interface named
nsIWritableVariant
, which XPCOM provides for wrapping native
types in an nsISupports
object. (There are type-specific
interfaces like nsISupportsPRBool
as well, but
nsIWritableVariant
is an one-size-fits-all solution.) Even more
interesting, its read-only companion, nsIVariant
, is “magical”
to XPConnect: JavaScript receives its values as native types, not as
nsIVariant
objects.
Thus, nsIVariant
turns the strengths of XPIDL barriers so
that they are no longer fighting you, but working with you. This is what I am
calling judo with nsIVariant
. Read on in the extended entry for more details.
Creating a variant
In C++, there’s a number of methods for setting types. If you have a
PRUint8
, you can call setAsPRUint8(val)
, for
example. The rest of the methods are pretty self-explanatory, but two methods
are also interesting: setAsISupports()
and
setAsInterface()
. These two allow you to wrap any
nsISupports
object in a variant.
In JavaScript, almost all the methods are available, but there’s a nice
shortcut: the setAsVariant()
method. The beauty of this is that
you can pass in any JavaScript value to this – XPConnect converts
the argument into a nsIVariant
for you. This has special,
“magical” implications.
Retrieving a variant
In C++, you have to work a little bit. First, you have to get the data
type (call GetDataType()
on the variant). This will tell you
which (if any) methods of nsIVariant
to call.
In JavaScript, it’s even easier. If the interface you’re accessing
specifies the argument is a nsIVariant
, then you don’t have to
do anything – XPConnect will convert it for you. This is more of the
“magical” nature of these variants. If the interface specifies
nsISupports
, then you’ll get back a nsISupports
object which implements nsIVariant
. You can get the native value
by querying the object for nsIVariant
, as shown below:
js> var variant = Components.classes["@mozilla.org/variant;1"].createInstance(Components.interfaces.nsIWritableVariant); js> variant.setFromVariant(2) js> variant [xpconnect wrapped nsIWritableVariant @ 0xe27280 (native @ 0xe0e6c8)] js> variant.QueryInterface(Components.interfaces.nsIVariant) 2 js> typeof variant object js> typeof variant.QueryInterface(Components.interfaces.nsIVariant) number
It’s worth noting that, technically, XPConnect is violating the rules of
nsISupports
here. The QueryInterface()
method
should technically always return an object that you can call
QueryInterface()
on again. This is not the case for
nsIVariant
:
js> variant.QueryInterface(Components.interfaces.nsIVariant).QueryInterface(Components.interfaces.nsISupports) typein:8: TypeError: variant.QueryInterface(Components.interfaces.nsIVariant).QueryInterface is not a function
On the other hand, it is consistent with JavaScript object
identities:
js> var variant = Components.classes["@mozilla.org/variant;1"].createInstance(Components.interfaces.nsIWritableVariant); js> var func = function() { dump("Hello World!\n"); } js> variant.setFromVariant(func) js> variant [xpconnect wrapped nsIWritableVariant @ 0xe27280 (native @ 0xe0e6c8)] js> variant.QueryInterface(Components.interfaces.nsIVariant) function () { dump("Hello World!\n"); } js> variant.QueryInterface(Components.interfaces.nsIVariant)() Hello World! js> variant.QueryInterface(Components.interfaces.nsIVariant) == func true js> variant.QueryInterface(Components.interfaces.nsIVariant) === func true
I had thought about introducing a number of nsIVariant
-based
interfaces to parallel the basic data structures, but while working on this
article, I realized… why? The whole point of
nsIVariant
is to wrap changeable values in a
nsISupports
structure. Since these data structures work with
nsISupports
naturally, it doesn’t make any sense to add an extra
layer of abstraction.
With a little XPCOM trickery involving Components.Constructor, you can make this even simpler to use:
var Variant = Components.Constructor(“@mozilla.org/variant;1”, “nsIWritableVariant”, “setFromVariant”);
var v = new Variant(5);