March 22, 2008

XUL Trees and Objects: ClassTreeView

I love XUL trees. I even smoke them from time to time. But what I don't like is trying to build a hierarchy of objects in them - even though that's probably the best use for them.

Imagine that you want to show this tree of objects, with properties of each object horizontally, and the objects themselves laid out vertically, indented and illustrated to show which objects have which parent objects. DOM Inspector does this with DOM nodes all the time. My chrome registry viewer code does something similar for files (file systems are tree-like), and when you want to see the properties of an object, JS object inspection is usually through a tree. Even Venkman uses trees to show you functions in a file or webpage.

Still, for every different object tree I've come across, there's a different view that has to be built. Usually it's custom-built for that tree. So you've got two options: build your own view, from scratch, every time... or build a XUL tree DOM and let Gecko's own tree utilities show it to you.

Believe it or not, I've tried both approaches... and finally decided to roll my own baseline solution. (If someone else has done this before, please let me know. It's best to have this in a common place.) More details in the extended section.

nsITreeView

Here's the kind of image that comes to mind every time I've looked at the nsITreeView interface:

nsITreeView's heart

Seriously, what in the name of (it's Easter weekend, I don't care to push my luck) what in the world is this? At least half the methods on it deal with row-specific or cell-specific details. Couldn't the people who work with this just given me a nsITreeRow interface and a getRow() method on the tree view?!?

I mean, it feels so dirty. It feels... procedural. Not object-oriented at all. How do you map a row in this tree to an object? You don't through this interface - because there's no row to grab.

It's also an interface with a lot of methods on it to implement. Yeeeuck. It's not immediately obvious which ones you need, and when. I've never truly understood this tree view stuff, even with XULPlanet's tutorial lending a hand.

<xul:treeitem/> and friends

I do understand treeitems, treechildren, treerow, and the like, though. It's DOM! It's something we already have! It's easy to inspect, to debug! It's familiar!

This was my preferred method of building trees for five years. Never mind the various people who kept telling me that tree views were so much better, ignoring me when I said, "No, make this a DOM set of trees, that doesn't need a lot to understand." It's simple. I like simple.

Until two or three months ago, when I discovered nsTreeContentView. For those of you who don't know about it, this is what takes your pretty DOM-based XUL tree and converts it into a nsITreeView object.

So here's what happens: My app spends a lot of time crafting this oh-so-nice, fifteen-levels-deep, memory-crushing, CPU-melting DOM fragment. My program then appends it, forcing nsTreeContentView to go to work, ripping that fragment apart and creating... a nsITreeView that I did everything to avoid dealing with, and which you just waited far too long for. Not to mention the bootstrapping I have to put on top of that DOM tree to bind each row to some object.

There are a few things that are more efficient than this approach...

Starting over

So finally I said, "enough of this." If I'm going to build a tree for objects, I'm going to do it right. I'm going to create a generalized tree view component, reading from both the tree and from objects, to show the object hierarchy. I'm going to define a very simple, and very flexible, API that my component's users can build in and make it all work.

The first concept is that tree columns define what the cells show. So let's just go ahead and define a propertyname attribute to stick on each <xul:treecol/>. element. We still need those, anyway. For more complex properties, define a fallback by allowing the tree's author to set a function on the column element. I do this through the DOM 3 UserData API.

The second concept is that there's a 1:1 mapping between rows and objects. That is, for each row, there should be exactly one object, and vice versa. So, at least internally, we need a TreeRow class to store a reference to the original objects.

The third concept is that the tree view needs to know how to get child objects of a given object. To do that, the tree view requires you to pass in a function which takes an object and returns an array of objects, which you say are children of that object.

The fourth concept is to provide a way to add top-level objects to this tree. Every DOM tree has a root node, every file system has a few items at the top. A generic tree view class can't know about them beforehand, so you have to tell it about them.

Put these four together, and you have enough to build a generic algorithm, a generic "class tree viewer".

ClassTreeView

Source Code

Sample chrome code (copied, altered from the XUL Tutorial on developer.mozilla.org)

testTreeView.xul

Element and MatterState represent (in this case) two similar classes which live in the same tree - so really, you'd only need one of them. One column has a propertyname attribute on it. The other one (further down in the init() function) has a cellGetter function on it. The getObjectChildren() function tells the tree view how to go from one object to its children. Everything else is just raw data, initializing the tree and the tree view, and adding top-level objects to the tree. It's really just that simple, and pretty light-weight.

Now, I will admit this ClassTreeView is not complete. For instance, it's read-only, and doesn't yet support progress meters, check boxes, etc. I don't need those at this point, and to me it's just more complexity (see nsITreeView for details). As a starting point for showing a true object hierarchy, though, it's good enough. If you know tree views and want to finish this, patches accepted!

API-wise, it's a "best guess". I designed this to work based on my needs and understanding. Maybe there are better approaches - but for this particular problem, I think it's a good start.

The one true weakness to ClassTreeView is that content web pages cannot use it. The reason for that is buried at the end of the nsITreeView.idl file: the nsINativeTreeView interface. So perhaps I will someday rewrite this in C++ code - after I figure out C++-to-JavaScript interfaces, and with a lot of reviews to make sure I get it right - and I'll make it available to the Web. (Then again, you don't see a whole lot of XUL on the Web... maybe for reasons like this.)

Posted by WeirdAl at 7:24 PM | Comments (4)

November 24, 2007

Beyond clones: synchronized DOM nodes

For years, I've tried to answer a simple question: How do you show the same data in two places at once, in Mozilla? In particular, DOM nodes. The simple answer is to clone the data you have in one location, and place the clone in another. This is not enough when the data changes in one location; I want it to change in the other, too.

Possibilities include:

  • A split view of the same document, particularly for editing
  • My latest attempt at a scrollable content object model (XBL at work, again)
  • Correcting markup in one section of code, and having it update a dependent section later automatically. Think slide shows, where two slides are nearly identical - and a semantic relationship exists between them. Also think steps of a theorem proof.

An ideal solution might involve a special layout frame for transposing the data, but that's more work in an area I'm unfamiliar with. A solution I can do (more expensive memory-wise, but at least workable) involves setting DOM mutation listeners. Read the extended entry for more details, and a ZIPped source directory for my own synchronizer - if you don't mind some technobabble.

In short, there are three parts to keeping a pair of nodes synchronized: managing mutation listeners, detecting changes and reflecting them. The mutation listeners exist for the sole purpose of handling the latter two parts.

Gecko 1.9 actually makes most of this pretty easy. With the UserData interfaces of DOM Level 3 Core, I can define references between nodes in one DOM subtree, and equal nodes in a second DOM subtree. DOM Level 2 Traversal helps find every single node in each tree, so I can create the references. DOM Level 2 Events support means I can detect when a change happens and act on it. DOM Level 2 Range provides a way to watch over whole sets of DOM nodes - providing I define another specialized mutation listener to watch over the contents of the range. DOM 3 Core's isEqualNode method (and the xpcshell test harness) means I can test the scripted synchronization and ensure that it really did update correctly. The primary event listener that keeps the two trees synchronized is about 130 lines of JavaScript.

The fun begins when you realize all the little ways a synchronizer can go wrong.

For instance, say your synchronizer is watching Node1-Node2 and Node2-Node3. If you're not careful, a user could define another relationship, Node1-Node3. The result is that Node1 updates Node2, which updates Node3, which updates Node1 again... an endless loop.

Or, say one of the nodes you're watching is a document fragment. Bad idea. The moment you tell the DOM to append that document fragment to a node, the children of that document fragment will be appended instead. (That's how document fragments work, and it's why I've always thought of document fragment nodes as a type of DOM clipboard.) The result is that the DOM removes those nodes from the document fragment. The synchronizer then removes the equivalent nodes from the paired node, and you've got data loss.

Another nasty scenario is when one node you're synchronizing contains the matching node. They can't possibly be equal under that scenario, and it all falls apart. (If the node pairs you're watching aren't equal, or the range pairs aren't equal, what's the point of trying to synchronize them?)

Finally, if you attach the same mutation listener to both trees, you'd best make sure that when one node mutates and it causes the second node to mutate, that it doesn't again cause the first to mutate.

Thus, validating the arguments for synchronization is extremely important. If you want to watch ranges of nodes (as I do), the complexity required for validation is then squared.

It also makes sense that if you want to start synchronization at one point, you'll eventually want to stop it at another. So providing API for detaching the synchronizer from a node pair or range pair is a good idea.

Ranges can change their positions too - so when you start watching a range, you really want to watch a clone of the range which you own (and never change by yourself).

Fortunately, I've covered all of these. (I think.)

DOM Synchronizer, source code

I should probably make a XPI out of this at some point, but I wanted to put this out for feedback as-is. This is a XPCOM component, implemented in JavaScript, for doing the grunt work of synchronization. It has four important methods: attachByNodes(), detachByNodes(), attachByRanges(), and detachByRanges(). You would pass in the pairs of nodes or ranges to watch or to stop watching as the arguments.

However, the DOM Synchronizer must also work - and when it stops working, you need a way to find out. For this, I also included a fifth method, hasInternalError(). (In the case of an internal error, all synchronization stops and the four primary methods would all throw an exception.)

The ZIP file also contains a XPCShell test case which beats up on the DOM Synchronizer a bit. I probably need to write more tests for the validation routines...

This code has been tested and implemented for Firefox 3.0 beta 1 and Gecko 1.9b1 equivalents. Known bugs:

  • ###!!! ASSERTION: Event listener manager hash not empty at shutdown! - this one, as far as I can tell, isn't my fault directly. It happens after the test finishes running, but my test logs show all my event listeners have been accounted for and removed. I need some investigation of that, and I should probably file a bug.
  • When a page unloads, and it contains a DOM node the synchronizer watches, by definition the node goes away. The synchronizer still keeps a reference to it, though... and I don't know how it will react, especially when the matching node hasn't been unloaded yet. I suspect memory leaks at the least, and possibly even crashes.

I'm sure there are a bunch of possibilities for this synchronizer that I haven't thought of yet, but it's a start. I'd appreciate it if anyone still reading this could take a look at it...

UPDATE: The assertion failure is gone on trunk. So are a ton of noisy NS_WARNING calls. Yay!!!

Posted by WeirdAl at 11:14 PM | Comments (4)

October 17, 2007

Why aren't we using Components.Exception?

Components.Exception on LXR

A long-standing custom in Mozilla code for XPCOM components in JavaScript is to throw Components.results.NS_ERROR_FAILURE or something like it. However, Components.Exception is more useful, particularly since it lets us component authors define our own error messages to go with the error code. Plus, it's been around for quite a while.

It's actually pretty useful, I've found. I'm wondering if we should add this to a list of to-do's for Mozilla 2.

Posted by WeirdAl at 7:54 PM | Comments (2)

October 7, 2007

SVG snap-to-grid, part two

Updated

I spent another six hours on this, and I'm fairly pleased with the results. The bulk of the code is now in a new SnapGrid.js library file. The features:

  • Keyboard- and mouse-driven commands
  • Step-by-step defining of an object (in this case, a line)
  • A semi-generic, objects-within-objects library design. (Doing it right is much harder than it looks, and I'm pretty sure I'm not doing it right!)
  • Of course, a nice big grid, and a nice big dot showing you where the snap-to-grid will snap to.

It's another proof-of-concept, but this time for something much smaller than Verbosio. Hopefully, if I've designed it right, I can rapidly expand it to cover other shapes and, more importantly, the SVG path element...

Posted by WeirdAl at 12:19 AM

October 5, 2007

SVG snap-to-grid, part one

Last night, I was thinking I'd really like to have a JavaScript-based SVG editor. There are some really freaking sweet examples out there. But there's something missing from all of these, which surprises me in retrospect: SVG snap-to-grid support.

Freehand drawing is nice... unless you want precision. Sadly, the human hand (mine, at least) isn't that accurate with distilling individual pixels. If I can get close enough with the mouse on a zoomed-in view, though, and I can see a grid indicating pixel coordinates, maybe the application can round off my mouse position to the nearest real point, and give me a visual indicator. Like this snap to grid example, which I put together in about two hours.

Right now, if you load the example, you can place a point by hitting a key on the keyboard. This is in keeping with the idea that controls should be accessible without the mouse. Unfortunately, I haven't gotten into maneuvering points with key strokes. Nor have I factored in any kind of zoom & pan capabilities - linear algebra is not one of my strengths.

Over the next few days, I hope to turn this code into an actual class, similar to a Dojo-style library. I might even make it a XBL binding. Ultimately, by being able to precisely identify coordinates, I could plug coordinates directly into shapes such as SVG paths (Bezier curves made simple), rectangles, ellipses, etc. I could even build interfaces to create classes of polygons. (I want my <svg:triangle/>!)

You might wonder why I'd work on yet another SVG editor, when there are so many out there. There are two reasons. First I didn't see any examples that would really be compatible with running inside a Mozilla framework, besides the ones above. They're usually stand-alone apps. The examples above are exceptions to that rule, but (this is the second reason), the first two are not explicitly licensed under an open-source software license (no explicit license, and Creative Commons, respectively). Mark Finkle's work is under the MIT License, but it's HTML based. Nothing wrong with that. It just strikes me as unnecessary if the only platform you're targeting is Mozilla. Mark's targeting IE, too. My use case is simply different.

On the other hand, if I keep it as a JS-based constructor (instead of XBL), that would make it easier to port to other platforms, such as RichDraw.

Food for thought.

Posted by WeirdAl at 8:22 AM

August 29, 2007

Extensions for every XULRunner Application (and Firefox, and Thunderbird, and...)

With many thanks to Dave Townsend and Robert Strong for reviews, Mozilla trunk builds now support extensions for "the toolkit" - meaning any XULRunner app which uses the toolkit. This will also be true for the next milestone, and (barring something really unusual) Mozilla 1.9 and the next major XULRunner release.

I can think of several extensions which would be good candidates:

The bug for DOM Inspector has a pretty simple example of how you'd add support for the toolkit to an extension. It's less than ten new lines, added to install.rdf.

There's also a nice side benefit possible here: extensions for another application - say, Songbird - which don't need to be for just that application could port easily over to Firefox with the toolkit target.

So what determines if an extension should be for "the toolkit"? First, you need module owner or author's approval for that extension. :-) Second, it shouldn't be specific to a given product or written for a given application - for instance, an extension for browser history doesn't qualify. On the other hand, if it adds more tools (a new SQLite database, for example, or a spell-checking dictionary - thanks for the idea, KaiRo) or capabilities without requiring that the app be a browser, a mail client, a multimedia player, etc., then it should be fine.

I'd invite fellow developers to start experimenting with this, and generate feedback. Any bugs to add "the toolkit" as a supported application should be marked as depending on bug 299716. Also, any other missing support for toolkit extensions we need bugs on, depending on bug 299716. We need eyeballs!

P.S. Congratulations on document.elementFromPoint(x, y), Mr. Karel. That's very useful, too.

Posted by WeirdAl at 11:51 AM | Comments (5)

August 3, 2007

XPath Generator hits the trunk

After eighteen months of "try, try again", the first version of XPath Generator code has landed in Mozilla 1.9 "trunk" code. It is currently off by default.

I'm quite pleased with this. This is the first complete feature I've driven from initial API drafts to a final implementation in Mozilla CVS.

So how do you use it? First, you need a build with ac_add_options -enable-extensions=default,xpath-generator. Once you have it, your chrome code should be able to call:

var generator = new XPathGenerator();
generator.addNamespace("html", "http://www.w3.org/1999/xhtml"); // optional, but recommended
var xpath = generator.generateXPath(targetNode, contextNode);

nsIXPathGenerator details two currently supported flags for the generator's searchFlags property. IGNORE_ID_TYPE_ATTRS means that the XPath should not stop when it finds an ancestor with an ID attribute. USE_DESCENDANT_AXIS produces a single-step (in the case of attributes, double-step) XPath beginning with descendant::.

XPath Generator objects also provide their own namespace resolvers, which as I note above can take new namespace declarations via the generator's addNamespace() method.

Sadly, this implementation excludes support for attributes in the XPath (foo[bar="baz"]); based on review feedback it didn't seem necessary for the initial implementation.

So why is this feature off by default? It's new. Aside from myself, I don't know anyone actively using my XPath Generator right now. Because it's so new and untried, we (myself and the reviewers) decided we should not expose the generator to web pages just yet. For that reason, we left it off by default as well. Finally, because it wasn't on by default, it didn't land for the 1.9a7 milestone.

If you are a developer and you see yourself needing or wanting XPath Generator, you have two options. (1) Start playing with it now, and see what improvements you'd suggest (patches welcome). Your experiences with XPath Generator - including actually using it - will carry weight in any attempt to get it approved for Firefox 3. (Popular opinion & votes will not.)

(2) Package XPath Generator as an extension and submit it to AMO, and include it with your own extension as a multi-item package. If you're going to do the latter, please contact me and let me know. Ideally, it would be submitted only once.

Either way, I really would like to find out how useful you find XPath Generator in your own work.

In the meantime, I need to work on making it and other extensions available to all Gecko-based apps...

Oh, by the way, there's also a new do_parse_document() function available for XPCShell tests. If you have a particularly slow Mochikit test that doesn't need a window, you might want to rewrite your test to work using this new function. (Come to think of it, there could be a significant speed-up in total test execution, if we converted many tests and if text/html for DOMParser landed.)

(Now why do my most significant events take place on weekends...)

Posted by WeirdAl at 3:02 PM

June 6, 2007

Debugging xpcshell tests (and what they actually test)

Scenario: You're working on a mozilla.org trunk bug, and you've got a xpcshell testcase. Great. Except that it's failing. What you want to do is to set a breakpoint in your C++ code to figure out why the test fails, but to do that you need your debugger attached to xpcshell. Since the xpcshell test harnesses usually run straight through, and run without user interaction, you never have a chance to attach to the right xpcshell.

Until today.

After I got tired of fighting this scenario a couple dozen times, I decided to write a patch which could let you load a given xpcshell test, and manually run the test. Instead of trying to figure out the full list of JS files your test needs, now you can debug the one test.

From your objdir, you run "make SOLO_FILE=(filename) -C (target directory) check-interactive". This launches xpcshell and loads your testcase. You can then attach a debugger and set your breakpoints. Example: make SOLO_FILE=test_import.js -C js/src/xpconnect/tests check-interactive

js>_execute_test(); runs the test. (You still need to name your test function in each file "run_test()".)

js>quit(); exits the xpcshell.

Gory details in bug 382682.

Posted by WeirdAl at 12:23 PM | Comments (1)

May 15, 2007

Got boilerplate?

How many times do you see code like this:

var factory = {
  createInstance: function createInstance(outer, iid) {
    // ...
    if (outer != null) {
      throw Components.results.NS_ERROR_NO_AGGREGATION;
    }

    return (new FooComponent()).QueryInterface(iid);
  }
};
var Module = {
  registerSelf: function registerSelf(compMgr, fileSpec, location, type) {
    // ...
  },
  getClassObject: function getClassObject(compMgr, aCID, aIID) {
    // ...
  },
  // ...
}

It's everywhere in Firefox and XULRunner-based applications. But not for long...

Robert Sayre resurrected bug 238324, which implements a JavaScript code sharing system. This means common code which gets repeated ad nauseum (and risks bugs from duplication) can be reduced to a much smaller boilerplate. Fifty to sixty lines of JS reduce to fifteen:

Components.utils.import("rel:XPCOMUtils.jsm");

var NSGetModule = XPCOMUtils.generateNSGetModule([
  {
    className:  BarComponent.prototype.classDescription,
    cid:        BarComponent.prototype.classID,
    contractID: BarComponent.prototype.contractID,
    factory: XPCOMUtils.generateFactory(
      function barCtor() {
        return new BarComponent();
      },
      [Components.interfaces.nsIClassInfo]
    )
  }
], null, null);

By the way, the above code also means you don't have to implement QueryInterface() either. (Yes, you do - classinfo interface flattening doesn't work without it, and many others will be broken. Oops.) Expect much code bloat reduction from this in Gecko 1.9, including Firefox and Thunderbird.

This is just the tip of the iceberg. I find myself writing a lot of boilerplate JS code for enumerators, and a bit of arrays. Maybe we can eliminate that, too. Or perhaps other shortcuts for implementing nsIClassInfo or other interfaces. With XUL/JavaScript preprocessing, maybe I could even contribute my design-by-contract library to mozilla.org now - though that could mean preprocessing a lot of component files too. I'm not sure we want to do that.

Sure, all the above could be done via the JavaScript subscript loader, and in fact, that's how the common code gets imported. That route is slow, though. Now it's standardized through C++.

If you can come up with other frequently repeated code, file bugs for code redux under Core -> XPConnect and dependent on bug 238324. Feel free to cc me too; I'll be very happy to implement it, if someone else doesn't beat me to it!

Posted by WeirdAl at 9:30 PM | Comments (4)

May 9, 2007

Testcase redux: Mozilla tools?

One of my "favorite" phrases in working on bugs is "reduced testcase". It's not always easy to do that, though, and I'm starting to think about ways to do that. I'm posting this now to get some feedback.

Please read the extended entry for details (it's a bit technical).

In Verbosio right now there's a strange cross-platform layout bug. I've got an iframe with a yellow background on top of another iframe. Windows shows the yellow background; Linux and Mac don't. This sounds like a good need for a layout reftest. Unfortunately, to get to the strange bustage, you have to go through about five steps. Reducing that to a simple reftest does not sound easy.

I'm personally thinking we could do something about that:

  1. Serialize the document, taking anonymous content into the real world (and replacing the bound nodes with XUL boxes).
  2. Load the transformed document source into a new window (Test Window A)
  3. Use DOM Inspector on Test Window A to remove DOM nodes which don't affect the bustage. (If you remove something that does affect the bustage, DOM-I has a nice undo feature.)
  4. Serialize the document in Test Window A again and save it as the first part of the reftest.
  5. Build another XUL document with only <xul:box/> elements, appropriate styling and text by script, which appears identical to Test Window A. Load the new document into Test Window B.
  6. Verify Test Window B renders identically to Test Window A.
  7. Serialize Test Window B's document as the second half of the reftest.

That's a long-winded way of saying "Record, reduce, generate a comparison document, save."

For building a xpcshell-based testcase and reducing it, there might be other possibilities. Shane Caraveo's Casper record/replay harness could be a starting point, along with Venkman for tracing:

  1. Use Casper to record your actions.
  2. Replay those actions step by step, with Venkman running this time.
  3. Record calls from the user interface into component code, including what the arguments were. (Where an argument isn't a literal value like a string or a number, retrace the recording to find out how the object was created, and record the object's creation too.) Each call is a "step".
  4. Store the sequence of recorded steps as the testcase.

In short, this means "Record, reduce, save." Not too different from the procedure I suggest for a layout reftest generator tool.

I tried pitching the above xpcshell test idea to Silver several weeks ago, but I don't think I was very clear then. I'm not even sure I'm clear now. Also, for Verbosio, I can't use xpcshell on my custom components because they're not native to XULRunner (see bug 359830 for details). But I have a test engine component which does basically the same thing.

Please let me know what you think. Are tools like this feasible? Do you have better ideas for implementing them? Can you think of similar tool designs for reducing testcases? Are you willing to write tools like these?

Posted by WeirdAl at 1:47 PM

April 21, 2007

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. 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="http://verbosio.mozdev.org/namespaces/markup/"/>

The contract ID for my XTF element factory must be:

"@mozilla.org/xtf/element-factory;1?namespace=http://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.

Posted by WeirdAl at 5:38 PM | Comments (5)

April 5, 2007

WYSIWYG for multiple XML languages?

Just a few moments ago, I noticed a post in mozilla.dev.tech.mathml for a WYSIWYG MathML editor. If you factor in tools like TinyMCE for HTML editing, and Mark Finkle's SVG RichDraw tool, you can't help but be amazed, as I am. (No word on the licensing behind the MathML tool; I've just asked in the newsgroup.)

Just to see efforts like this for individual languages impresses me greatly. Plus, since they work in web pages, there's no reason they can't work in chrome pages. Web pages operate under a lot more restrictions than chrome.

How nice it would be to integrate all of these into a common platform, so that you'd have WYSIWYG XHTML + MathML + SVG!

This is precisely the sort of thing I'm building Verbosio for: people who develop great tools for individual markup languages could drop them into Verbosio as extensions and have them working together. That's the vision behind my Verbosio work.

Where's WYSIWYG in Verbosio? A little ways off. I don't think true WYSIWYG will make Verbosio 0.1, although I'd very much want it; I simply have to concentrate on minimal functionality first. (I have other editing-what-you-see plans first.)

All the same, I'd love to start a community discussion between these individual projects and see what I can do to integrate these tools and help them.

My hat's off to all of you.

Posted by WeirdAl at 10:30 PM | Comments (1)

March 17, 2007

Design by contract in JavaScript, part 3: XPCOM class invariants

In the last DBC-JS article, I wrote about precondition and postcondition support for JavaScript in general. At the time, I lamented the inability to implement any kind of invariant checking. Today, I created a way to implement class invariants for JavaScript-based XPCOM components, courtesy of mozilla.org's nsIScriptableInterfaceInfo interface. (A class invariant is something that must be true when public methods finish executing without errors, in my interpretation.)

Read on for more details, including why it can't work reliably for non-component JS.

Wrapping a whole component's public code

The nsIClassInfo interface, which I described in some detail, has a useful getInterfaces() method. This method reports the public interfaces a component implements. With nsIScriptableInterfaceInfo, you can go one level deeper and determine all the methods, readonly attributes, and settable attributes of a component as well.

The mozilla.org example for this is the (alas, poorly documented) nsInterfaceInfoToIDL.js file and its doInterface() method. Mozilla code (with webservices enabled, which almost every Mozilla build has - Camino being a possible exception) can use this component to show you what an interface would look like, "decompiled" back into XPIDL.

What IDL files give us are the public methods and properties of a component. (IDL calls properties "attributes".) If you know what they are named (which nsIScriptableInterfaceInfo provides) and what type they are (method, settable property or readonly property - also available from nsIScriptableInterfaceInfo), you can construct a JavaScript object with matching public methods and properties.

Getters, setters and methods

Mozilla's JavaScript has long supported __defineGetter__ and __defineSetter__ for assigning a generic object ways to get and set a JavaScript property. (See the Core JavaScript 1.5 Guide for details.) Since you can also assign methods directly to an object, and the property assignment methods themselves take functions as the second argument, all you need is the name of the function you're calling and a new function to actually call it:

this.__defineGetter__(m.name, this._createGetter(m.name));

In the above example, this._createGetter(m.name) actually returns a function.

Enforcing a class invariant

The whole point of this is to call a generic invariant() method on the component:

    var contract = {
      precondition: function invariant_precondition() {
        if (contract.method_precondition) {
          contract.method_precondition.apply(this, arguments);
        }
      },

      method_precondition: null,
      body: aFunc,
      method_postcondition: null,

      postcondition: function invariant_postcondition() {
        if (contract.method_postcondition) {
          contract.method_postcondition.apply(this, arguments);
        }
        base.invariant();
      }
    }

This contract, processed through the getContractFunction() function, sends each individual property getter, property setter, and method call in an interface through the class invariant check after the original component has finished the user's initial request. The method_precondition and method_postcondition calls refer to (presumably private!) methods on the component for additional DBC assertion checks.

Returning the invariant-wrapped component

To wrap the component in the invariant contract, a couple extra changes are necessary. In the component's factory code, the component itself isn't returned; the invariant contract is:

      createInstance: function createInstance(outer, iid) {
        loadSubscripts();
        if (outer != null)
          throw Components.results.NS_ERROR_NO_AGGREGATION;
        var component = new aConstructor();
        var ecmaDebug = getDebugPref();
        var retval = ecmaDebug ?
                     new InvariantContract(component) :
                     component;
        return retval.QueryInterface(iid);
      }

Obviously, QueryInterface still must be supported on the invariant contract:

  // nsISupports
  QueryInterface: function QueryInterface(aIID) {
    return (this._baseComponent.QueryInterface(aIID)) ? this : null;
  },
  
  toString: function toString() {
    return "[InvariantContract " + this._baseComponent.toString() + "]";
  }

So the real component is silently stored as a property of the invariant contract (what the user receives). Thanks to the interface info duplication and the __defineGetter__, __defineSetter__ and method definition work, the invariant contract essentially duplicates and wraps around the real component. The real component can work totally oblivious to the existence of the invariant contract. Ultimately, the end-user should see no difference between the real component and the contract wrapped around it. (Unless, of course, the contract is broken and a bug exists.) It might execute a bit slower, but debug code shouldn't show up for the end-user anyway.

Modifying XPCOM components to support class invariants

First, every XPCOM component which needs an invariant must implement an invariant() method:

DocumentWrapper.prototype = {
  // ...
  // Design-by contract invariant
  invariant: function invariant() {
    if (this._isInitialized) {
      dump("*** Entering invariant for XUL document wrapper\n");
      assert(this.wrappedDocument, "No wrapped document?");
      assert(this.selectionRange, "No selection range?");
      assert(this.pathToDocument, "No path to document?");
      assert(this.txmgr, "No transaction manager?");
      assert(this.wrappedSource, "No wrapped source?");
      assert(this.publicURI, "No public URI?");
      dump("*** Exiting invariant for XUL document wrapper: success\n");
    }
  },
  // ...
}

You may notice the invariant requires an initialization value be true. This is because creating a component and initializing it for use are two different things. In this respect it isn't a true class invariant, but within the restrictions of XPCOM it's quite reasonable. (XPCOM does have error codes specifically for NOT_INITIALIZED and ALREADY_INITIALIZED. Using them is strongly recommended.)

Second, you have to load the ECMA Debug script I wrote. Currently it lives at chrome://verbosio/content/tools/ecma-debug.js, but I think I'll move it (for Verbosio) to resource://app/ecma-debug.js. You can always find the latest version of the script in Verbosio's CVS repository. The code is under the GPL/LGPL/MPL tri-license.

Third, you have to modify your factory code as I showed above to return the invariant contract pseudo-component.

Fourth, to actually run the invariant contract, you have to set the javascript.debug preference to true.

Why couldn't this work with normal JavaScript?

Of course, you can define getters, define setters, set methods, and wrap around any JavaScript object. For most of JavaScript, it would work fine. However, arrays make this impossible.

With arrays, a user can set a property on the array and nobody ever finds out about it. For example, suppose your object says:

{
  a: 3,
  b: [4, 5, 6]
}

You can write code to watch the "a" property. You can write code to watch the "b" property (to make sure people don't replace it). But you can't write code to watch the elements of the "b" property (the array). If I say yourObj.b[3] = 7, there's no way your code can check for it in every case.

Sure, you can write code to watch for someone setting the '3' property on "b". But what if I set yourObj.b[1000000] = Infinity? It's simply not supported. Bug 312116 would fix this, but I wouldn't hold my breath. Similarly, properties of properties are hard to keep an eye on.

So why does this work in XPCOM? The answer is simple, and surprising. XPIDL doesn't define an primitive array attribute type for properties. Oh, yes, there's nsIArray, but that's an object with a length property, not a plain array value. XPIDL also lets you return an array from a method, but methods are okay. Bottom line: XPIDL asks you to create the array from scratch each time - and because this means your original component doesn't expose its native arrays to the world, you're protected. (You can still return a native array if you want that directly belongs to your component and affects how the component works, but that's not generally accepted practice.)

Conclusion

Given that I was able to implement this in a single afternoon, I'm really quite surprised to see there is no equivalent for C++-based XPCOM components. I would have at least expected to see a NS_INVARIANT macro established in nsDebug.h, but there's no such animal.

I do believe that design-by-contract is very important - as important as automated testing and documentation. Used in tandem with automated testing, it can make the code more robust. Contracts, when broken, define a bug - either in the code the contract checks, or in the contract itself. DBC adds reliability and assurance that things work. I'd very much like to see DBC included as a standard part of Mozilla 2 code, and in debug code written for XULRunner applications such as Firefox or Verbosio.

Thanks for reading!

Posted by WeirdAl at 9:40 PM | Comments (1)

February 1, 2007

Cool tip of the day: Help systems for XULRunner apps

Creating a Help Content Pack

I wish I'd found this earlier. This looks like a ready-made framework for creating a help system for a XULRunner application.

UPDATE: I wonder if there's support for an extension to add pages to the application's help system...

Posted by WeirdAl at 9:41 PM

November 17, 2006

Exposing XPCOM components in JavaScript, part three

Generally speaking, untrusted content (such as a web page) can't do anything with most XPCOM components, including creating them. There are of course exceptions to this policy - DOM objects, for example, are glorified XPCOM components with clearly defined interfaces for public use. For a web page to use a component, however, including calling on any methods or properties, the component has to explicitly tell Mozilla what is allowable and what is not. The nsISecurityCheckedComponent interface defines how that is done.

If your component doesn't implement nsISecurityCheckedComponent, the most you'll be able to do with the component is create instances of it, and that's only if your component has the DOM_OBJECT flag set on it, as I described in part two of this series. You won't be able to do anything with those instances, not even access any of its properties. So understanding nsISecurityCheckedComponent is also important.

The rest of this article is in the extended section.

Security Levels

First, it's important to understand the levels of permissions your component can grant. Thankfully, they're fairly simple. They are the same levels (both name and capability) as Configurable Security levels.

Let's say foo is a nsFoo component instance. Calls to methods of foo, getting and setting properties of foo, and even accessing foo itself require the component state that the content page may do something -- that it has access. If foo wishes to deny access to a property, method, or foo itself, it would return "noAccess". If foo wants to grant access, it would return "allAccess". "sameOrigin" refers to the same-origin security policy, but I'm not sure it's useful in the context of a XPCOM component; the component has no base URI that I'm aware of. (I could be wrong, and I expect to update this paragraph based on responses from the experts.)

It's worth noting that these strings are not case-sensitive to Mozilla; "AllAccess" and "allAccess" are equivalent.

Permissions control by methods

There are four methods which nsISecurityCheckedComponent defines. The first is canCreateWrapper. Mozilla calls this method if your component didn't declare itself a DOM object via the nsIClassInfo interface. The first argument is an interface identifier, or IID, which the component implements. (Remember QueryInterface and nsIClassInfo.interfaces? It's the same thing here, so you can check the interface using aIID.equals(Components.interfaces.nsIFoo), and return a particular policy based on that.)

The second method is canCallMethod. Mozilla calls this method to find out if your component will allow untrusted content to call a named method. Its first argument is again an IID, and its second argument is the method name.

The third method is canGetProperty. Mozilla calls this method to find out if your component will allow untrusted content to get the value of a property. Its first argument is an IID (do you see a pattern here?), and its second argument is the name of the property.

The fourth method is canSetProperty. Mozilla calls this method to find out if your component will allow untrusted content to set the value of a property. Its first argument is an IID, and its second argument is the name of the property. Note this method does not get an argument reflecting the value of the property; it's still your code's responsibility to validate whatever the user wants to set the value to.

Example code

Here's a sample I've extracted from my XPathGenerator bug's latest patch. This code doesn't check IID's, because the values which untrusted content can reach these methods define.

  // nsISecurityCheckedComponent
  canCreateWrapper: function canCreateWrapper(aIID)
  {
    return "AllAccess";
  },

  // nsISecurityCheckedComponent
  canCallMethod: function canCallMethod(aIID, methodName)
  {
    return "AllAccess";
  },

  // nsISecurityCheckedComponent
  canGetProperty: function canGetProperty(aIID, propertyName)
  {
    switch (propertyName) {
      case "searchFlags":
      case "resolver":
        return "AllAccess";
    }

    return "NoAccess";
  },

  // nsISecurityCheckedComponent
  canSetProperty: function canSetProperty(aIID, propertyName)
  {
    if (propertyName == "searchFlags") {
      return "AllAccess";
    }

    return "NoAccess";
  },

The canGetProperty allows users to get the resolver property, but not to set it. Strictly speaking, I could do that also by writing a getter into the component and not a setter for the resolver, but this adds another layer of defense.

In one respect, I'm probably being too casual here; I may want to deny untrusted pages from finding out what the component will allow. For instance, imagine an untrusted page trying to call canCallMethod(Components.interfaces.nsISecurityCheckedComponent, "canCallMethod"). This would have the result of calling it once from Mozilla's native code (which is all right), and then again from the untrusted page. Speaking from a security perspective, this is information untrusted pages don't need, and what they don't need, they shouldn't be allowed to find out. I should probably restrict based on at least the method name (since a user could pass in an interface for the first argument that doesn't have the method named in the second argument).

Components which are services probably should return "NoAccess" for all calls to canSetProperty. The reason is simple: such a component publicly available could have a value set by a web page that persists after the page is gone, and even into your privileged code. This is risky from a security perspective, and also introduces the possibility of unpredictable results from later calls on the service.

Wrapping up your component - and this series

In implementing nsISecurityCheckedComponent, remember that you should add the interface to the listings in your implementations of nsISupports.QueryInterface and nsIClassInfo.interfaces. These two report what's available to the public; nsISecurityCheckedComponent reports what's permissible.

I've also filed bug 360207, because at this time Mozilla has a bug with JavaScript getters and setters with regards to nsISecurityCheckedComponent. Even if the canGetProperty and canSetProperty methods return "allAccess", Mozilla will veto untrusted pages from calling on the getters and setters you may have defined. Be aware of this bug; hopefully by the time you read it, this bug will be fixed.

As a final note, I should emphasize nsISecurityCheckedComponent is not a frozen interface! This means it is liable to change without warning in the future, so be on the lookout.

Commentary on this and the other two articles in this series (Registering your component as a global and nsIClassInfo explained) is always welcome. Corrections are accepted, but I will make changes to update these articles without notice. Thanks!

Posted by WeirdAl at 12:34 AM | Comments (4)

November 13, 2006

Exposing XPCOM components in JavaScript, part two

Whenever a XPCOM component is exposed to a public web page, the page knows nothing about what that component is, what interfaces it implements. Thus, Mozilla code treats it as if it implements only nsISupports, with the handy-dandy QueryInterface() method... and nothing else.

This means web pages would have to QI for every interface they needed, and they would know nothing about what's available. However, by implementing one more interface, no more instanceof operators would be needed, and you'd have all the properties and methods of the object that you thought you'd have. That interface is nsIClassInfo.

The rest of this article is in the extended section.

From the nsIClassInfo overview:

nsIClassInfo is an interface that provides information about a specific implementation class, to wit:

  • a contract ID and class ID through which it may be instantiated;
  • the language of implementation (C++, JavaScript, etc.);
  • a list of the interfaces implemented by the class;
  • flags indicating whether or not the class is threadsafe or a singleton; and
  • helper objects in support of various language bindings.

From the standpoint of a public component, the most important part of nsIClassInfo is the getInterfaces() method. This method returns an array of interfaces the component supports, much like what QueryInterface checks for. (In theory, you could have QueryInterface call getInterfaces() and do its lookups based on that, but it probably isn't necessary.) In JavaScript, a sample getInterfaces method would look like:

  // nsIClassInfo
  getInterfaces: function getInterfaces(aCount) {
    var array = [Components.interfaces.nsIDOMXPathNSResolver,
                 Components.interfaces.nsISecurityCheckedComponent,
                 Components.interfaces.nsIClassInfo];
    aCount.value = array.length;
    return array;
  },

Two things to note about this method. First, the aCount variable is really an "out" parameter, defined in the nsIClassInfo interface to refer to the length of the array. In JavaScript, that parameter becomes the value property of an aCount object. It's just another quirk of XPIDL.

The other thing to note is the nsISupports interface is not listed. This is per the nsIClassInfo interface's inline documentation.

Another important part of nsIClassInfo is the flags property. I freely admit to not understanding most of the flags, but SINGLETON should be turned on if your component is a service, and off if it is not. (Note: Declaring your component as both a service and a member of the "JavaScript global constructor" category probably won't work too well.) DOM_OBJECT is also useful for granting content pages without special privileges access to your component.

The getHelperForLanguage method returns a nsISupports object, if so desired, that can QI to nsIXPCScriptable. For JavaScript-based components, nsIXPCScriptable is not scriptable, so don't bother - just return null. The helpers are optional anyway.

The others are well-documented in the nsIClassInfo interface already. For JS-based components:

+  // nsIClassInfo
+  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,

The classDescription property is just a string describing the component for humans. The classID and contractID should match the first and third arguments of the registerFactoryLocation calls in your factory code, respectively.

You may have noticed nsISecurityCheckedComponent above. As I'll discuss in the third part of this series, it becomes important to include support for it, so that Mozilla knows what your component's users can do with the component (like, what the component was designed to do).

Posted by WeirdAl at 12:25 PM | Comments (2)

November 10, 2006

Exposing XPCOM components in JavaScript, part one

Several months ago, I wrote about my solution for sending messages from content to chrome. Today, I'm starting a three-part follow-up, talking about how component authors can provide their components to web pages. This first part is about letting users touch your component. Part two will focus on nsIClassInfo, which eliminates the need for QueryInterface or the instanceof operator by web pages. Part three will focus on nsISecurityCheckedComponent, which regulates what users can do with your component.

Normally, of course, this isn't done; most XPCOM components should work behind-the-scenes and not be available to web pages. There are exceptions. For instance, the chromeMessenger above is explicitly for web pages to send messages where it normally wouldn't be able to (but the application still needs someone to listen for that message). Another is my XPathGenerator code, which I thought had to be C++-based. Fortunately, I discovered earlier this week that isn't the case.

The rest of this article is in the extended section.

If you look in nsIScriptNameSpaceManager.h, you find no definition for a nsIScriptNameSpaceManager class, but several strings. These strings (or more accurately, their values) are used in the nsICategoryManager interface as category names. The categories expose components to the public.

To add your component as an object accessible to any window, you use the category "JavaScript global property". On the other hand, if you want to make your component a constructor, (and your component returns constructors), you would use "JavaScript global constructor". (Part of the problem was I assumed from the XMLExtras code that I had to use "JavaScript DOM class", which led to a wild goose chase into the fiery pits of hell, as "word" would put it.)

For adding my chrome messenger, I used the following:

var Module = {
  registerSelf: function registerSelf(compMgr, fileSpec, location, type) {
    // ...

    // Add chromeMessenger constructor
    var catman = Components.classes["@mozilla.org/categorymanager;1"]
                           .getService(Components.interfaces.nsICategoryManager);
    catman.addCategoryEntry("JavaScript global property",
                            "chromeMessenger",
                            contractId,
                            true,  /* Persist this entry */
                            true); /* Replace existing entry */

  },

  // ...
};

The contractId variable above was the contract identifier for my chromeMessenger component, "@manyone.net/chrome-messenger;1";. Reference my article announcing mnChromeMessenger.js for details on how people can use it.

The code for adding a constructor object is similar:

var Module = {
  registerSelf: function registerSelf(compMgr, fileSpec, location, type) {
    // ...

    var catman = Components.classes["@mozilla.org/categorymanager;1"]
                           .getService(Components.interfaces.nsICategoryManager);

    catman.addCategoryEntry("JavaScript global constructor",
                            "XPathGenerator",
                            XPathGenerator_contractId,
                            true,  /* Persist this entry */
                            true); /* Replace existing entry */

  },
  // ...
};

Users would then be able to create XPathGenerator objects simply by calling var gen = new XPathGenerator(). It couldn't be simpler for them.

The next article will talk about nsIClassInfo, which you can get a brief introduction to in mozilla.org's nsIClassInfo Overview.

Posted by WeirdAl at 10:55 PM | Comments (2)

October 25, 2006

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;
Posted by WeirdAl at 1:21 PM | Comments (1)

September 12, 2006

XBL 2.0 as a Last Call Working Draft

I must have been asleep at the wheel, again. Last Friday, the XBL 2.0 specification became a Last Call Working Draft.

I'm personally pleased to see the design behind the new <implementation/> element. It would mesh very nicely with my precondition/postcondition article of a few days ago. It would also be nice to have support for <xbl:script/>. I will probably spend an hour or so reading over the specification in closer detail this week, in particular trying to understand this public/private object stuff.

Posted by WeirdAl at 11:27 PM

September 1, 2006

Design-By-Contract in JavaScript, part 2

Two years ago, I wrote an article describing a design-by-contract library for JavaScript. This morning, I realized I wasn't using it anywhere in my new projects. This really bothers me - a tool that not even the inventor will use?

I did a little thinking, and realized one of the big problems was in object construction, where I literally couldn't use it. The old DBC-JS file had the approach of requiring users to call a separate function to execute the contract: var root_3 = applyContract(getSquareRoot, this, 3); instead of this.getSquareRoot(3).

Ease of use beats functionality hands-down. Firefox taught us that for user interfaces, and this demonstrates the same for actual code. So obviously I had a design flaw in my design code.

Enter one of the little used features of JavaScript: you can define functions inside functions. The inner functions are treated as local variables. Moreover, any local variables in the outer functions are available to the inner functions.

To spare feedreaders from unwanted technobabble, I'll continue the rest of this on a secondary page.

Observe the following function:

Note the getContractFunction actually returns a function, not an object or an ordinary value. If I have code like this:

Then the only call a user of sqrtObject has to make is sqrtObject.sqrt(9). No applyContract() mess!

Suppose, though, you want your contract function to be a constructor. For that, the inner function of getContractFunction() is even simpler:

Contracts are thus simplified enormously. Here's some sample code to demonstrate this new design-by-contract library:

ecmaDebugTest.js

Here's the updated ecmaDebug.js file, with the new contract code. It also includes my custom NS_ASSERTION firing function for debugging purposes when Venkman just isn't enough. Other features: toString and toSource have been redirected to the contract's body (so you can see what the function's really supposed to do). Also there is a toContractSource() and toContractString() which deliver the whole contract object in source and string form, respectively.

ecmaDebug2.js

Feedback welcome!

Posted by WeirdAl at 4:17 PM | Comments (6)

July 6, 2006

Accessible extensions?

There's been a thought rattling around in my brain for a while, but when my hand started hurting a bit today, I thought a bit more.

Many of the Firefox extensions I use don't have keyboard shortcuts. Or, for that matter, menuitems inserted by overlay into the main toolbar.

I'm not intending to point fingers (I'm guilty of not considering a11y with tinderstatus), but there are some popular extensions out there that could really use some accessibility love. Including several that I use on a daily (and in one case, hourly or even more frequently ;-) ) basis.

On the other hand, there is the danger of two or more extensions picking the same key combination to do something. I am acutely aware of that problem; I face the same sort of issue at a higher level of magnitude with Verbosio. We might need some kind of functionality through Firefox's Extension Manager to override assigned alternate UI's to their core functions. (What to do for extensions missing UI I haven't figured out yet.)

Thoughts? I'd be willing to design & patch for this, but I want some community feedback (and if I do patch, a general assurance that it will get fast reviews). Is this worth doing? If so, how would you want it to work?

Posted by WeirdAl at 2:44 PM | Comments (6)

May 25, 2006

Sending messages from content to chrome (part 3)

UPDATE: This code has not been seriously audited for security holes, and may introduce vulnerabilities. Also, when I wrote it I did not take into account work on Gecko 1.9. Please be aware this code has not gone through rigourous testing.

Several months ago, I came around with a question: how should I send information from a content page to chrome. I wasn't able to do it by DOM events as bz suggested, so when I did a little thinking, I came up with a solution for ManyOne Networks, Inc., my employer, that I wasn't able to talk about at the time.

That time has passed. Regretfully, ManyOne has decided not to release the product I was working on, but they've no qualms about releasing the source code behind it. Including my special message-passing code.

dust-source.tar.bz2 > components/mnIChromeMessenger.idl and components/mnChromeMessenger.js .

How a webpage uses it:

  1. You create a JS-based object containing all the data you want passed upstairs.
  2. You include a window property, which is the top window you can reach.
  3. You wrap this object in another JS object (which I refer to as wrapper), as its wrappedJSObject:
    /**
     * Wrap a JavaScript object for passing to components code.
     */
    function ObjectWrapper(object) {
      this.wrappedJSObject = object;
    }
  4. Call chromeMessenger.send(wrapper).

This does some minor sanity-checking on the wrapped object (basically, making sure all but a couple properties are numbers, strings, boolean or undefined), and then dispatches a message to Mozilla's observer service, with a topic of "content-message".

Now, it's entirely up to the chrome and XPCOM code to write observers to listen for this particular topic of message. One idea we had was to use this for opening new tabs by script. You'll find in the tarball's chrome/portlets/content/navigatorOverlay.js at line 448 a procedure by which we did this. There are some weaknesses in the scheme (notably, this code can't be used to directly return a window object of the new browser to the caller, and there's no concept of tabbrowser's maximum tab count - thus inviting tabbrowser spam), but overall, it would do what we needed it to do.

Personally, I'd like to see mozilla.org review and adopt this code somewhere - say, in extensions/chrome-messenger, or perhaps in the toolkit if the Aviary or Firefox teams want it. I wrote this code for commercial use, and we tested it pretty thoroughly on our company's product. It works beautifully.

I'm thinking seriously about submitting a bug to get this code checked in, but I'd like your feedback on where in the source it should live, and who wants this capability. If there's not enough interest, then I won't file a bug and we'll just leave it on the sidelines, maybe as a XPI on addons.m.o.

P.S. This is my 300th blog entry. Nice coincidence that it should be a useful one.

Posted by WeirdAl at 2:32 PM | Comments (5)

May 6, 2006

Text styling by Mozilla editor? (part 2)

So I got thorougly roasted on my original idea. Which is actually good, compared to the deafening silence I've seen when I post other ideas. :-) Believe me, I prefer discussion on my ideas. In this case, it's helped me abort a bad approach - which is why I post my ideas.

In short, regexps are impractical in the context I had in mind. I refused to give up on this until Daniel Brooks (aka db48x) showed me "the regular expression that parses email addresses".

So scratch regexp's.

What I (very roughly) need for XML documents, as I understood db48x's explanation, is:

  1. Support when parsing the source of the XML document for start and end points in the source text corresponding to the XML tags & attributes of the document.
  2. An algorithm for converting those boundary points into their equivalent contentDocument-in-the-text-editor boundaries (which would be much easier if I store the boundaries as line number + column number)
  3. A way to specify the classes for each set of boundary points
  4. A way to specify the CSS stylesheet for each class.

I was very concerned about performance, but apparently that's a non-issue.

The first and third items on the list involves probably some XML parser hacking. Of course, I've never hacked our expat before. :-) The second item is straight mathematics. The fourth I could probably just apply to the editor's contentDocument, or perhaps hack nsPlaintextEditor to support nsIEditorStylesheets.

For other types of source code (such as C++, JS, etc.), I'd need the same types of constraints as for XML, but not the same constraints. Some way to create a common set of XPIDL interfaces would really be cool, but I'm not at that stage yet. I'm still in the brainstorming-and-learning phase. (Maybe working backwards, from end-of-document to start, may mean not recalculating for offsets and new DOM nodes as iteration continues.)

Here's the transcript of our conversation: #developers @ irc.mozilla.org on syntax highlighting

As always, your feedback in helping me clarify and organize these thoughts is welcome - as long as you're informative. (I can take rudeness, but not without references to back it up.)

Posted by WeirdAl at 9:49 AM | Comments (5)

May 5, 2006

Text styling by Mozilla editor?

Let's face it: when you're editing plaintext in Mozilla, you don't get any nice text highlighting. It's just not supported. Having edited raw text (XML documents, JavaScripts, CSS files, you get the idea) many times, I've often wanted some support for syntax highlighting, in a simple but very flexible way.

There are really two parts to the problem: (1) Figuring out the method by which text is highlighted (or otherwise "styled"), and (2) Figuring out how to determine the groups of text which are highlighted.

Call me crazy, but the second part I feel can only be answered by regular expressions, like ECMAScript's.

One idea I had involves reusing the CSS stylesheet mechanism, and a new Mozilla-specific CSS selector (which no one's proposed yet):

-moz-regexp("/foo/") {
  color: #ff0000;
}

The above stylesheet would be applied to a <xul:editor type='text/plain'/>'s content document, and through some internal magic, would apply the CSS rules to the appropriate text.

Another, somewhat more convoluted, would involve implementing a new XPath function regexp-match("/foo/"), and XSLT stylesheets to transform the source text into a HTML document, similar to what XML pretty printing in Mozilla does. Then use a <xul:editor type='text/html'/> to display the document for editing. When practical, the editor would grab the post-processed + edited text and re-run it (or at least the changed portion) through the XSLT stylesheet to re-highlight the changes.

A time delay to allow the user to finish typing before repainting the screen would be entirely acceptable. When you factor in DOM mutation events, it's practically a necessity.

I'm wondering how in the world to do this. If someone could write up a proposal on doing this, it'd make for a really great feature for Gecko 1.9. Any Summer of Code '06 takers? Anyone else willing to do this?

Does anyone understand what I'm talking about here on the technical side? :-)

UPDATE (at 3 am) : Okay, I just had a nice 45 minute discussion with Daniel Brooks (aka db48x). Combine that with comments 2-4 on this blog entry, and I've got some rethinking to do. New blog entry coming with Daniel's conversation post-cogitation. In the meantime, your comments are still welcome. (For those of you wanting to preview the conversation, find some #developers moznet logs, roughly from 2:00 am to 3:00 am Pacific time, 06 May 2005.) For now, I'm going to sleep.

Posted by WeirdAl at 4:42 PM | Comments (6)

April 6, 2006

Disabling XUL by section

I've often wanted to set one attribute on a XUL element and have that forcibly disable all the descendants of the XUL element at once. Given:

<xul:vbox sectiondisabled="true">
  <xul:textbox value="foo"/>
</xul:vbox>

the textbox should be disabled, in my opinion. (Or something like this.) So today I finally wrote up a script that could do this for me:

disableSection.js

I also learned about nsIDOMXULControlElement. My work in XUL Widgets could only benefit from implementing this.

There's a couple advantages to this. I can "nest" disabled sections now, so if a inner section is no longer disabled, the outer section still takes precedence. Also, if I replace my usage of disabled with sectiondisabled (on the same node), then I don't have to worry about accidentally and incorrectly setting a disabled property.

I still have to figure out how to handle <xul:command/> elements and command dispatchers, but by and large this is a big improvement.

Feedback welcome.

Posted by WeirdAl at 12:20 PM | Comments (1)

March 13, 2006

Just when things start to look good...

My new best friend, smaug ;-), posted a serious cleanup of DOM events code recently (see bug 234455 for details), thus allowing me to considerably simplify a patch for a bug that's blocked me on Verbosio for months (bug 201236). That one looks like it's going to be finished up fairly soon, too, so I was a happy camper.

Until this evening. Since Verbosio potentially can edit XUL applications, I figured I'd try a new approach: start with your most basic XUL editor possible. Specifically, a textbox and a script to take that textbox's contents, and replace the source code of the document with the textbox's value. The idea was to see if I could start to slowly build a more complex editor using the basic editor itself as I went.

That didn't work, for one simple reason: I couldn't get the source code of the script inside the XUL document. So I did a little digging, and discovered there were no child nodes for the script element...

*insert ex-sailor's swearing here*

Bug 330426. I'm back where I started -- worse, actually, since this is in a part of the code where I have no references to compare to. Other than the DOM specs, and they're the only justification I have for filing a bug.

Posted by WeirdAl at 10:33 PM

March 4, 2006

XPath Generator: One little oversight...

One of the reasons I even thought about writing a native XPath Generator for Mozilla-based applications is a need to take a XPath for a DOM tree and translate it into a path for a DOM Inspector-like node tree. Unfortunately, as my mind has just now started wandering back towards my Verbosio application (for which I would use this XUL tree), I realized I had completely forgotten to include any functionality in the IDL or in the implementation for creating a XPath that would be friendly to this.

Specifically, I wanted a XPath that looks like: /node()[5]/node()[7]/node()[11]. I didn't think about that when writing the extension, though. Fortunately, I had a lot of foresight in allowing for extensibility. So, pending reviews to land the xpathgen code (which I don't expect right away), I'll submit a new bug to to "GET_CHILD_INDEXES_ONLY".

I might argue that I own the spec and I can do what I want there, but I don't buy that. I'm doing this for the community, so even when I suggest a new idea for the spec, I make sure to run it by someone else for approval.

UPDATE: Or maybe not. Collecting adjacent text & cdata nodes under XPath makes it not so useful.

Posted by WeirdAl at 10:52 AM | Comments (1)

February 11, 2006

Writing C++ is like working out

I've just completed a first-draft, unstable, patch implementing a XPath generator in native mozilla.org code. (It is highly unstable, so unless you're a developer willing to help fix bugs in it, don't use it.) It's been a marathon for me, writing a component from scratch with (until now) only peripheral experience in C++. I'm sure there are many in #developers who can attest to the large number of first-timer mistakes I've begged for help on.

As for the title of this blog entry, I'm pretty sure it's accurate. First, you start off with a lot of pain. Read the "Full Article" link for a list.

  • Writing the XPIDL file (which was actually something I've done a lot, and was painless)
  • Write minimal (read: NS_ERROR_NOT_IMPLEMENTED) XPCOM components.
  • Copy the C++ header class which implements the IDL from a generated nsI*.h file.
  • Copy the C++ implementation class too. (XPIDL is very nice about that.)
  • Write a XPCOM module file, based on nsXMLExtrasModule.cpp. (This let me add the XPath generator as a global constructor.)
  • Write a basic functionality testcase for when things were generally supposed to work.
  • Implement a custom nsIDOMXPathNSResolver constructor.
  • Finally learn how nsISecurityCheckedComponent works. (Hint: You implement it on objects which are available to unsecure code, but don't implement nsIDOMClassInfo.)
  • Borrow some little functions, and then rename them (because it's a Good Idea) for internal use.
  • Read and reread multiple times the DevMo XPCOM Strings guide, and realize that type does matter.
  • Found out about AppendInt for strings. Nice and neat.
  • Read and reread multiple times the DevMo XPCOM Hashtables guide, and realize that it's very incomplete. Would someone care to document how you write functions to pass into EnumerateRead?
  • Designed, on paper at least, an approach to the attribute search storage problem using nsVoidArray. Read a guide by bz which DevMo didn't link to (but mozilla.org did). Later found out the document was quite obsolete (nsVoidArray is deprecated, according to bz, for nsTArray, whatever that is), and that hash tables in theory would be better.
  • Designed a new approach to the attribute search storage problem using nsDataHashtable.
  • Argued with #developers about creating hash keys for the hash tables. I had an algorithm ready to go, I didn't see a simpler or better one, so I ran with mine.
  • Started running into many kinds of problems:
    • Compile errors
    • Linking errors because a file wasn't included
    • Linking errors because the Makefile.in didn't tell the process to include a directory the file was in
    • Linking errors trying to use nsContentUtils.h (that didn't last very long; you have to be in tier_9 for that) or some other "off-limits" file like nsXMLNameSpaceMap.h
    • Crashes when the code compiles cleanly.
    Each one of these, I pretty much begged #developers for help in fixing. There were many.
  • Figured out how to really check a node's local name is valid.
  • Copied code for writing to the console service. (I might as well explain to the user why I returned an error.)
  • Learned that C++ code can be put into IDL files (for contract id and CID purposes), but it's generally not a good idea.
  • Wrote some sanity-checking assertions into my first attempt at hash table usage, and discovered that I was doing things wrong very early on.
  • Learned how to translate a Mozilla string into debug console output. Having that in the strings guide would have been nice to know.
    #ifdef DEBUG
    const char* realHash = NS_LossyConvertUTF16toASCII(hashKey).get();
    printf("hashKey:      '%s'\n", realHash);
    #endif
  • Assert + return error code, or console message + return error code, is highly useful, but verbose.
  • Wrote "PR_MACRO" code, based on nsDebug.h, to take that verbose code and reduce it to something more readable.
  • Lots of NS_ENSURE_SUCCESS, lots of assert+return for internal errors, lots of console message + return for user errors. Probably too much NS_ENSURE_SUCCESS. I'm trying to write as defensively and proactively as I can.
  • Figured out when to use *, when to use &, when to use nsCOMPtr, and when to just refer to the value directly. Especially in passing from one function to another.
  • Realized (after reading ROC's diatribe, and later, Brendan Eich's response in agreement, that not every function should return nsresult. Only the ones that call on other functions that return nsresult. This led to some later changes to internal code.
  • Fixed my testcase when I realized I totally botched it. (Even now, it's pretty incomplete; it doesn't handle document fragment nodes.)
  • Designed, ahead of time, capability for people to extend XPathGenerator to cross anonymous content boundaries and contentDocument-to-frameElement boundaries. (XPath doesn't support it, so XPathGenerator doesn't either, but the extensibility is there.)
  • Wrote the first-ever C++ implementation of a nsIDOMNodeFilter class for mozilla.org native code. Up until now, I'd been borrowing code from everywhere. This time, there was literally no code to borrow. That was frustrating. (Fortunately, I'd written several node filters in JavaScript, and knew generally what to do by now.)
  • Worried as my code just grew and grew and grew. This morning, the C++ file exceeded 1,000 lines. This evening, it was over 1,600. I tried analyzing the code to see where I could abstract stuff into new functions, but the efficiency gain was (at the time) minimal at best.
  • Discovered the "joy" of hash table enumeration. *sarcasm alert*. Fixing the problems from that alone took almost the entire day, and I only solved it with specifically biesi's and shaver's help.
  • Figured out a way to "return" nsresult when the function type won't let you do that. Very important.
  • Found out about "friend declarations", a little too late to do anything worthwhile about it for this first patch.
  • Diagrammed out, roughly, the algorithms for generating individual steps.
  • Posted the patch, and then spent well over an hour figuring out just what I've done on this. There's probably two or three things I've already forgotten.

All this, just for the first draft. An experienced C++ developer reading this will probably chuckle ruefully at having gone down this sort of road before. What makes this so much harder for me is that this is literally my first attempt at writing a major C++ based tool. Until now, my major tools have been chrome or JavaScript-based tools. I've done smaller fixes in C++ code before (I implemented Attr.isId specifically for the XPath generator). But for me, this is a giant leap. Believe me, I'm not Superman.

As for the working-out analogy: You start out with a lot of pain. The exercises are not fun. When you go home at the end of the first day, you're sore, tired, and thinking you don't want to go back. But you go back anyway, because you know it's the right thing to do.

A few days later, you're doing better. You're starting to get a feel for how it's done. The workouts start becoming easier. You come back with better abilities, and you don't have to ask for help on every little thing. You start to experiment, try to lay down some patterns to follow.

Finally, it becomes so routine that you look for ways to branch out and do other stuff. You don't have to ask for help; you just do it, and you feel good. Really, really good. Pretty soon, you're casually talking with other people just starting out, giving them little tips from your own experience where you made a big mistake and paid for it.

That's where the analogy ends for me; I haven't gotten further in my gym workouts than this third stage, so I can't explain how it feels. On C++, I'm probably at the second stage. There are probably four or five other stages in the pipeline, some in between the stages I list here, but you get the idea. It's a lot of work. But ultimately, it does pay off.

With that, I'm going to bed. I think that, when all is reviewed and super-reviewed, I will finally go get the CVS checkin privileges which I would need to land this patch. It's just time.

Posted by WeirdAl at 11:51 PM | Comments (4)

February 7, 2006

Sending messages from content to chrome? (part 2)

A few weeks ago, bz helped me solve one of the issues blocking content-to-chrome messaging. Unfortunately, as I have just discovered, that's not enough.

I tried giving my little nsIDOMEvent object a few JS properties from content. Little things, like a string (evt.foo = "bar"). But w