Cool tip of the day: Node filters in JS as objects

Typically in Mozilla usage, we use tree walkers like this:

const nsIDOMNodeFilter = Components.interfaces.nsIDOMNodeFilter;
function filter(aNode) {
if ((aNode.namespaceURI == "http://verbosio.mozdev.org/namespaces/foo/") && (aNode.localName == "foo")) {
return nsIDOMNodeFilter.FILTER_ACCEPT;
}
return nsIDOMNodeFilter.FILTER_SKIP;
}
var walker = document.createTreeWalker(rootNode, nsIDOMNodeFilter.SHOW_ELEMENT, filter, true);

The NodeFilter we pass in to the tree walker is usually just a JavaScript function. If you read the DOM 2 Traversal spec carefully, though, this isn’t exactly what the spec would indicate. (It makes a special exemption for JavaScript in the ECMAScript bindings.)

According to the spec, node filters are usually objects (not specifically functions) with an acceptNode(Node aNode) method. What this means is, if we shape the filter as an object, we can give special properties to the node filter.

For example, say you want to accept <foo/> and <bar/> elements, but you want to do something different in each case. You could write the filter to have an acceptedType property:

const nsIDOMNodeFilter = Components.interfaces.nsIDOMNodeFilter;
var filter = {
acceptedType: null,
acceptNode: function acceptNode(aNode) {
if (aNode.localName == "foo") {
this.acceptedType = "foo";
return nsIDOMNodeFilter.FILTER_ACCEPT;
}
if (aNode.localName == "bar") {
this.acceptedType = "bar";
return nsIDOMNodeFilter.FILTER_ACCEPT;
}
return nsIDOMNodeFilter.FILTER_SKIP;
}
}
var walker = document.createTreeWalker(rootNode, nsIDOMNodeFilter.SHOW_ELEMENT, filter, true);
while (walker.nextNode()) {
switch (filter.acceptedType) {
case "foo":
// do something
break;
case "bar":
// do something else
break;
default:
throw new Error("Unexpected type");
}
}

Note that this node filter doesn’t modify the document (no good node filter or tree walker should). It modifies itself in such a way that the code using the tree walker and node filter can detect that state change and act according to what the filter is signaling.

Also, don’t forget to prevent leaks by clearing the tree walker when you’re done with it:

walker = null;
filter = null;

One thought on “Cool tip of the day: Node filters in JS as objects”

  1. That looks very useful in some of the WF2 code…
    Can you use that in C++?
    (From Alex: Actually, you have to. JavaScript is the one bending the rules by allowing you to pass a function in. In C++ land, you must explicitly define a nsIDOMNodeFilter* object. Reference my xpathgen patch in bug 319768 for the only example I know of with a C++-based node filter. 🙂 )

Comments are closed.