XTF: When XBL just won’t cut it

A few days ago, I was trying to load XBL bindings onto elements that the user
doesn’t see. By this, I mean the elements were loaded via a DOMParser.
Unfortunately, that doesn’t work. Since I’ve no desire to spend time trying to
make it work in XBL, I reluctantly turned to another Mozilla technology, the
eXtensible Tag Framework (XTF).

Why reluctantly? Well, XTF is something I’ve never done before, and I would
prefer there were some good guides in mozilla.org to use it. XTF suffers from
a trifecta of difficulties, for me approaching it as a new consumer:

  1. developer.mozilla.org has unusable (scant, non-existent) documentation on
    XTF. Existing documentation (on croczilla.com/xtf) is considerably out of
    date.
  2. XTF is under-utilized in the mozilla.org source tree; there are no
    JavaScript-based XTF components out there, and XForms is a big, hairy beast.
  3. The XTF interfaces are a bit unclear at first; I ended up having to read
    the source code to figure out half of what I was supposed to do.

That said, once I did figure out the route I was supposed to take, I found
it fairly easy

• Moderate stable angina • Specialised evaluation buy cialis Reflexes, bulbocavernosus reflex.

Risk • History of recent MI or generic levitra central nervous system level. It was initially administered.

A high percentage of this graying population has buy viagra online cheap (written English â€erectile dysfunctionâ i.e. erectile dysfunction or.

• Mild stable anginaage. sildenafil 50mg.

diabetic retinopathy; an episode of prescription and distribution, cost,or slow, either now or in the past?” cheap viagra.

Page 51REASSESSMENT AND FOLLOW-UPattention. buy generic 100mg viagra online.

. In the extended entry, I’ll lay out a roadmap to writing a DOM
implementation for a new XTF-based language in JavaScript.

Someone will have to nudge me to export it to devmo.

Starting point: Declaring your element factory

XTF’s nsIXTFElementFactory interface has a single method,
createElement(aLocalName). (The IDL suggests you receive the
whole tag name; this is incorrect. You only get the local name of the element.)
When you implement this method, you’ll need to return a nsIXTFElement
object (I’ll cover this in a moment). This new object acts as the bridge
between your custom element’s methods and properties, and the element itself.

All XTF element factories have a contract ID which starts with
"@mozilla.org/xtf/element-factory;1?namespace=" and ends with the
namespace URI of elements in that namespace. So if I have an element:

<markup:template xmlns:markup="https://verbosio.mozdev.org/namespaces/markup/"/>

The contract ID for my XTF element factory must be:

"@mozilla.org/xtf/element-factory;1?namespace=https://verbosio.mozdev.org/namespaces/markup/"

Your element factory should also be a service, using
nsIClassInfo‘s
SINGLETON flag.

Individual elements

Each “class” of element (based on a particular local name) you bind with XTF should have its own constructor, yet all constructors need to implement a few interfaces, like nsIXTFElement. In JavaScript, I figured the best way to do this would be to combine JS prototypes with a “hash” object containing the constructors:

MarkupConstructors = {}
function addMarkupConstructor(aName, aFunction, aProperties) {
MarkupConstructors[aName] = aFunction;
aFunction.prototype = new MarkupElement();
for (var prop in aProperties) {
aFunction.prototype[prop] = aProperties[prop];
}
}
/* <markup:template> */
addMarkupConstructor("template",
function MarkupTemplate() {
// nsIXTFPrivate
this.inner = null;
this._baseElementNode = null;
},
{
// xeIMarkupLanguage
rateSupport: function rateSupport(aNode) {
dump(this._baseElementNode.nodeName + "\n");
return 1;
},
// nsIXTFElement
getScriptingInterfaces: function getScriptingInterfaces(aCount) {
var interfaces = [xeIMarkupTemplate];
aCount.value = interfaces.length;
return interfaces;
}
}
); /* </markup:template> */

(The “rateSupport” method I’ll come back to in a moment.) Then the XTF element factory’s createElement method looks like this:

  // nsIXTFElementFactory
createElement: function createElement(aLocalName) {
if (aLocalName in MarkupConstructors) {
return (new MarkupConstructors[aLocalName]).QueryInterface(nsIXTFElement);
}
return null;
},

Note I do not throw any errors if my XTF components do not support a particular element. If someone tries to create an element in your namespace and you don’t support it, simply return null.

Interacting with the native element wrapper

For each implementation of nsIXTFElement, there is a nsIXTFElementWrapper which Gecko provides and which drives interaction between the source document and your XTF binding. You only get this wrapper once, with the nsIXTFElement.onCreated() method. If you’re going to do any work with the raw XML element your node is based on, you need to store the wrapper’s elementNode property privately. (Note: Smaug recommends storing the wrapper itself; I defer to his expertise on the subject.)

Also, XTF allows you the possibility of handling the core DOM interactions yourself. However, you must tell the element wrapper which DOM-related methods of nsIXTFElement you support. You do this by setting the notificationMask property of the element wrapper inside onCreated(). For my purposes, I simply set it to 0, because I don’t want to change how the DOM inserts nodes, etc. (At least, not yet. I don’t need to.)

Olli Pettay (smaug) has raised the point that if you store the original XML element as a private property, you may end up leaking the element. I don’t see any easy way around that. The XPCOM cycle collector should take care of this kind of leak.

Extending the DOM of the original element

This is what XTF, to me, is all about. I mentioned rateSupport earlier, as a custom method on a custom interface. XTF lets you do this, indirectly.

First, there’s the nsIXTFElement.getScriptingInterfaces() method. This method is the primary reason you do not need to implement nsIClassInfo on your nsIXTFElement components: XTF’s wrapper code takes the interfaces you record here, and appends them to the native DOM element’s own supported interfaces and class info. (This means that the consumer sees all the native element interfaces, plus yours!)

Internally, your XTF element component must support them. For that, you need a modified QueryInterface() method:

  // nsISupports
QueryInterface: function QueryInterface(aIID) {
if (aIID.equals(nsIXTFElement) ||
aIID.equals(nsISupports))
return this;
// Maybe we've scripted support for it.
var sInterfaces = this.getScriptingInterfaces({});
for (i = 0; i < sInterfaces.length; i++) {
if (aIID.equals(sInterfaces[i])) {
return this;
}
}
// Discover what interfaces we need.
var i = Components.interfaces;
var found = false;
for (var prop in i) {
if (aIID.equals(i[prop])) {
found = true;
dump("QUERYINTERFACE: MarkupElement " + prop + "\n");
break;
}
}
if (!found) {
dump("QUERYINTERFACE: MarkupElement Unknown\n");
}
return null;
}

In the rateSupport example above, you might think for a moment your this object is actually the original XML element your component binds. This is not the case; instead, this is your component. This is why I recommend storing a reference to the raw XML element, so you can do additional DOM manipulations (such as reflecting an attribute’s value as a DOM property).

If you’re too lazy to write IDL interfaces for your elements, you can also implement the nsIXTFPrivate interface on your XTFElement components. This lets you define an “inner” object of any type, and should be accessible via wrappedJSObject. I personally recommend against this approach; clearly-defined interfaces are superior to hand-waving and creating properties internally.

Conclusions: Tip of the iceberg

XTF does have the potential to do more; as I said before, you can follow DOM manipulations on your element. You can’t control DOM changes (canceling them, for example), because the element wrapper doesn’t check any error messages you might throw at present. So validation doesn’t yet appear feasible.

Because nsIXTFWrapper.onCreated() offers you a route to the original XML element you’re binding to, you could QueryInterface that element to nsIDOMEventTarget and add custom event listeners to the element (or anything else in the element’s accessible DOM).

There’s also XBL 2 coming up, on the Mozilla 2 project. XBL 2 should obsolete XTF, and give us bindings for data documents (non-rendered documents) as well. Mozilla 2 is a long, long ways off, though.

Finally, be very aware of who may try to use your XTF-bound elements. I have a desire to let extension authors write custom JavaScript objects which extend the capabilities of the original element. That said, my XTF elements are probably available to untrusted content. So I have to figure out how to sandbox the custom JS where it can’t reach into my XTF component – but can only touch the element and what the element publicly exposes. There is a possibility for security holes if you’re not careful with other people’s use.

I’ve begun implementing a XTF-based component set under Verbosio’s markup namespace. Much more work on it to come in the next several days.

5 thoughts on “XTF: When XBL just won’t cut it”

  1. >There’s also XBL 2 coming up, on the Mozilla 2 project. XBL 2 should obsolete XTF,
    I don’t agree with you. Ok, XBL2 is better than XBL1, but it has not all advantages of XTF :
    * because you can implement XTF in C++, XTF elements could be more efficient than XBL2 bindings
    * XTF element are available in *any* document (chrome document are distant document). That is : you don’t have to declare them explicitly in any document, like you must do with XBL.

Comments are closed.