The logjam has officially broken. This week, I spent most of my time adding little usability features (like opening a file when you double-click on it in a directory tree) and doing some behind-the-scenes clean-up of the code.
I also added a new feature which any XUL developer should find interesting: a chrome directory viewer. This is different than Gijs Kruitbosch's Chrome List extension, in that mine examines the chrome registry of an outside application, and lists alternate locales & skins as well. (Not that there's anything wrong with Chrome List; Firefox extension developers should take a good look at it.)
To do this, I added yet another Verbosio interface, for xeIChromeDocumentPack. Whether I needed this interface or not isn't entirely clear; I didn't find any other interface on mozilla.org which matched the needs.
As I'm still waiting for bonsai.mozdev.org (they've planned it for 2nd Quarter 2007), I really lost track of all the changes I'd done this week. The list, harvested from cvs log and in the extended entry, is rather impressive, even to me.
Coming up: support for editing DTD's, virtual:// revisited, and a fix for a Verbosio data loss bug.
In no particular order, here are my commit messages from 02/19/2007 (the last blog entry) to 02/24/2007:
I don't recall seeing anyone on planet or feedhouse mentioning this, so I thought I would:
Mitchell Baker and the Firefox Paradox
Because all I did was notice the article, I'm going to turn off comments for this entry.
Problem: You have a XPCOM component which handles files of a particular
type (*.xul). You have another component which wants a handler
for foo.xul. How do you get the first component from the
second?
Solution 1: Hard-code the first component's contract identifier ("@foo.mozdev.org/blah/blah/xul;1"); into the second component's source code as a constant. Unfortunately, this is not very scalable (*.js, *.css, *.rdf, *.in, *.xml, *.txt...).
Solution 2: Use the Gecko Category Manager, and define a category for all your file handling components.
Incidentally, this is a real-world scenario for me; I have had to solve this exact problem. The Gecko category manager is a common way for components to look up the contract ID's of other components.
The category manager stores information in a format like this:
"@mozilla.org/xpath-generator;1"
"@mozilla.org/browser/feeds/result-writer;1""service,@mozilla.org/extensions/manager;1"The Roman numerals here indicate category names. Each category can store different properties, indexed by some property name.
Often (but not always, as the Extension Manager example shows) a property value will be a component contract ID. This gives a simple way for one component to list itself where other components can find it.
First you need a category name. In my case, I'll choose "Verbosio document wrappers by file type".
Second, your component's module should register the component's contract
ID in the category manager. This happens in the registerSelf()
call. I've given an example of this near the end of "Exposing
XPCOM Components in JavaScript, part one". In this case, my code would
say:
var Module = {
// nsIModule
registerSelf: function registerSelf(compMgr, fileSpec, location, type) {
// Module registration code goes here.
// Category registration code.
var catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
catman.addCategoryEntry("Verbosio document wrappers by file type",
"xul",
"@verbosio.mozdev.org/document-wrappers/xul;1",
true, /* Persist this entry */
true); /* Replace existing entry */
},
// ...
}
We already have a category name, and presumably all the relevant components are using it. Let's try to find a match for "myApp.xul".
TestComponent.prototype = {
// sample method to get a component
getComponentContractID: function getComponentForFile(aPath) {
// aPath == "myApp.xul"
var dotIndex = aPath.lastIndexOf(".");
var catEntry = aPath.substr(dotIndex + 1);
const catman = Components.classes["@mozilla.org/categorymanager;1"]
.getService(Components.interfaces.nsICategoryManager);
const catName = "Verbosio document wrappers by file type";
return catman.getCategoryEntry(catName, catEntry);
}
};
This is just sample code (nsISupports is only the foundation interface), but it illustrates the point. By storing the contract ID in the category manager, we can look up the contract ID later for something relevant, and then create a component for the relevant item.
Gradual progress lately. I'm still building pieces I need to make Verbosio a relatively smart editor.
First, as Myk Melez talked about Komodo Edit, and Robert Accettura followed suit, I decided to get Komodo Edit 4.0. It just came out of beta. Like it did for Myk and Robert, it has replaced my standard text editing environment(s). I won't reiterate the features they pointed out; suffice it to say I'm pretty pleased with it so far.
However, it's not enough to make me stop working on Verbosio. I have several features in Verbosio's design plan that I don't see Komodo Edit doing. Komodo Edit is a vast improvement on what I have had at my disposal until now, and in some ways will still be better than what I alone can put out. By no means has Komodo Edit missed the boat; what they've done is fantastic, especially for a free product. I simply think that when I put Verbosio 0.1 out, it's going to open a few eyes too.
Enough about Komodo Edit. What else have I been doing?
Today I implemented a components test engine for Verbosio. Testing should be the first aspect of any software development, and also the last (albiet not the only). Alas, I found myself repeating the same steps over and over again in the user-interface, which could be automated into a script. So I wrote that script.
The nice thing about test engines is they make finding bugs a bit easier. I ended up fixing a few bugs that I didn't know I had. More importantly, I have a way to check for components-based regressions with a simple menu item.
I would have liked to make these tests xpcshell-based, but there's a little problem with that right now. (Actually two, since my test component relies on chrome code; I'm not particularly pleased about that, but for the moment it'll do.)
Oh, and I finally have my base URI map functionality worked out, at least for XUL applications. To explain, if I have two locations for a file:
Then they share a common path (tools/ecma-debug). The prefixes for these paths are "base URI's". The base URI's which this file maps to are:
When a XUL file references chrome://verbosio/content/verbosio.dtd, I then know to get /home/ajvincent/verbosio/objdir/testing/chrome/content/verbosio/verbosio.dtd instead.
There could be other base URI's in a map, as well. Think of a website; a base URI could be http://foo.mozdev.org/ for a /home/ajvincent/foo/htdocs/ directory.
Earlier today, I realized I need to search the local file system from Verbosio. There's really no such capabilities in native mozilla.org code. So, I built a basic file search capability on my own, and checked it in to Verbosio.
See the Full Article section for more details. (I also introduce a new way to implement multiple XPCOM components in a single JavaScript file.)
Local file system searches in Verbosio (patch) (requires JSLib, and my ECMA debug script).
Here's some sample code using the file search service:
Simply put, you can search for properties of files and directories (which JSLib's File and Dir classes implement), as well as the contents of files for a specific string.
You can specify more than one set of criteria. Everything within a set of criteria must match for the file search to accept a file. However, if a file passes one or more sets of criteria on the file search, the file search accepts the file. (Logical AND tests within a criteria set, logical OR tests between criteria sets.) So you can do different searches simultaneously.
The first argument of searchDirectory is the local path of the file you want to search. The second argument is true if you want to search sub-directories as well, or false if you don't.
The filterProperty method of a search criteria set lets you define a property name and the value it must have (as a string). The filterContents method requires that any matching file have the first argument (a string) in its contents.
Whenever a contents match is included, you can get the lines where the match happens. Each result has a getLineNumbers() method, which returns an object with line numbers you can feed into getLine(aLineNumber).
Finally, because there may be multiple criteria sets, each result also has a criteriaIndex property, indicating which criteria the result matched first (in order of their being added to the search service).
As for the new JavaScript component structure: I really have gotten tired of having a bunch of related JavaScript components duplicate the same boilerplate code over and over again, when they can all share the same space. So I created a new JavaScript component template, and organized it so the module code comes first. Then I added a special addConstructor() method to the module code, so individual components could just call that one line, and the module would recognize them. Since Gecko parses the entire component file before trying to call NSGetModule, everything still works. Best of all, I now have a simple template to drop new JavaScript components into the file.
Feedback, including suggestions for improvement, is welcome!
I had an idea late last night: what if I could position UI for configuring a XUL element without moving or resizing that element? This would give a decidedly WYSIWYG feel to editing XUL interfaces.
The above link is a demo of one way this might work. In essence, I'm emulating the <xul:popup/> element, but with XUL controls included and working. I'd like your feedback on it, including good practices for opening and closing the "context" box, and accessibility support.
There's also some strange happenings with shift+tab which I'd like help figuring out.
UPDATE: The shift-tab issues are bug 306058.
While I tried to clean up source code editing in Verbosio, I ran into a serious problem: mozdev bug 16341. Basically, the XML document I was editing was pulling in DTD entities from the Verbosio application, not from the document's intended chrome URL's. In other words, Verbosio was getting confused about where to get source code from.
This little mistake is not easy to fix, unless Verbosio knows something about the context it's working in. This goes back to the question of "stand-alone" editing versus "repository" editing (which I tried, and failed, to define in a prior blog entry). Stand-alone editing means a bunch of files which are not related to each other. Repository editing refers to files which reference one another and/or link to one another, so edits to one affect other files as well.
For XML files which refer to other files (like localization DTD files), stand-alone editing cannot work. The entities in external files must load, or the XML document is not well-formed.
(Yes, I know Mozilla does strange things with external entities, that don't follow the XML spec. Deal with it. I have to.)
I had gone for stand-alone editing first, figuring that was going to be very simple. Now it's a stand-alone complex (pun intended) and a major (pun again intended) design flaw.
The solution, for now, is to implement a whole bunch of features I didn't want to implement this early. More importantly, I have to break stuff to fix stuff, again. This will give me basic support for one type of repository editing: XUL applications and extensions.
I had hoped to have Verbosio 0.1 ready in time for OSCON 2007 in Portland, OR. I seriously doubt that's going to happen now, but it might be halfway usable by then.
It's not all gloom and doom. Today I checked in the first big piece of the new approach: a file-system tree viewer. I'm almost certain I could have used a RDF datasource for this (rdf:files), but I needed this in a hurry, and RDF in XUL is something I still don't fully get. So I went with what I knew. If anyone wants to base this on RDF, I'll take patches.
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...