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:
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.)
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.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!!!
Today's Dilbert is probably an old joke, but a good one.
This morning, I noticed something funny about how I approach various operating systems.
When Microsoft Windows Vista was released, I stayed way the hell away. I have a (nearly) perfectly functional Windows XP box, deliberately overbuilt when I bought it so it would last a while. The news reports about Vista were less than flattering, and ditto the blogs. Despite the occasional BSOD, which I now accept with mild grumbling, Windows XP is sufficient for my needs and the last thing I want to do is install an operating system (again) - and all the necessary build software - for building Mozilla. With Vista Service Pack 1 not too far off, I'm still convinced of the rightness of my decision.
Apple's Macintosh OS X 10.5, code named Leopard, was released a few days ago. At first I was tempted to go get it, but then I noticed my laptop's antivirus expires in a couple months. So again, I'm taking a wait-and-see approach, letting the AV program run out before upgrading. Again, the machine is adequate to my needs. I haven't fired up my Mac Mini in a little while, probably because I don't have a monitor dedicated for it or a desk big enough. (I'm lazy on that front.)
The Fedora Project will release the Fedora 8 edition of Linux in seven days. Yesterday, I pre-ordered a copy of it. Two USA dollars plus shipping and handling, from an online vendor. (When Fedora 7 was released, I set my box to downloading it - first by FTP, which died a couple times, and then by Bittorrent... but in deciding to be generous to others and allow for twice the upload amount as the download amount, it took three days to reach that 2.000 factor. Thanks a lot, Comcast.)
Here's the part I don't rationally grok. With Mac, and especially with Windows, I perceive a fair bit of pain with upgrading the operating system. With Linux, it's not even a question - I like it, and I actually look forward to the latest & greatest.
I had some rough experiences with Kanotix, a Debian-based system, but primarily because I couldn't get LILO to default to Windows at the time. I have had some good experiences with Fedora in the past, and I still do. Maybe it's because I don't hear nearly as much bad stuff about Linux in general that I'm willing to trust it more. Maybe it's because I can get copies of a Linux-based OS dirt-cheap. (Legally, too. I don't do pirated software.) Maybe it's because building Mozilla has the lowest barrier to entry on Linux - almost everything comes with the default OS installation. (The debuggers are extremely painful to use, though.) Maybe it's because the malware community fears offending the (very smart, very persistent, and very loud!) Linux-based developers community. Maybe it's because of the frequent updates Fedora offers - average one set every week, I've noticed - far more often than the Two Titans.
I don't know what it is, really. Ultimately, I don't think I care. Fedora's brand of Linux is one I simply don't have any cause to question. With Windows or Mac, I'm 90-95% sure that I'm at fault when something unexpected happens. With Linux, it's 99.9%, at least. I simply take for granted that Linux Really Does Work... and for a developer, that's saying a lot.
(It's not 100%... see http://sourceware.org/cgi-bin/gnatsweb.pl issue 2353. Granted, I reported it last night, but...)