November 26, 2006

Verbosio progress, 11/26/2006

I'm continuing to build basic functionality for Verbosio, my extensible XML editor project. Remember when I said "Designing an editor is hard", not once, but twice? Well, hard problems just slow me down. They don't stop me. :-)

Right now, the following things work:

  • Opening a XML document.
  • A DOM Inspector-like tree view rendering.
  • Selecting a sequence of nodes in the view.
  • Opening the extensions manager and error console.
  • Context menu and editing menu for undo, redo, cut, copy, paste, delete (the items appear and show enabled/disabled status, that is).
  • XPath Generator from mozilla.org bug 319768 is now temporarily a Verbosio extension.
  • Recompiling IDL interfaces on-the-fly
  • Synchronizing between the source directories in the repository and the object directories where the XULRunner application lives.
  • Undoing and redoing transactions (what the user actually does).

The last is a "maybe", because I haven't got any fully-fleshed-out transactions to do or undo. The first transaction I'm attempting to implement is a delete transaction. In the world of single DOM nodes, a deletion is easy. In the world of a range of nodes (think selected hypertext), it gets more interesting. That is, deleting is easy, but undoing the deletion is more interesting. Especially since there are several possibilities with ranges which I have to consider.

My immediate to-do list for Verbosio looks like:

  • Finish implementing and testing the Delete command for XML documents.
  • Implement a rudimentary Copy command.
  • Implement a Cut command (Copy + Delete).
  • Implement a Paste command.

  • Implement saving and closing documents.
  • Make exiting the application check for unsaved documents.
  • Implement a "Preview mode" viewer for documents.

With luck, I could have the above done in a week. Beyond that, my longer-term to-do list for Verbosio includes:

  • Writing a basic XUL-based SVG editor (or finding one that's already out there and adapting it) for Verbosio.
  • Implementing markup templates into Verbosio itself, and the template generator.
  • Finishing the XBL language pack.
  • Adding chrome editing capabilities.
  • Editing a XUL application or extension as a complete project (what Verbosio calls a "document pack").
  • Creating back-up copies of files in the event of a crash.
  • Properly using the virtual:// protocol I created earlier.

Of the longer-term list, only the first two or three are requirements for a version 0.1 release of Verbosio. At this point, I'm down-scaling my lofty ambitions for the 0.1 release. It'll still be a good little editor if I meet the above targets, just useful enough for developers to start working with. For now, that will be satisfactory.

I don't like "satisfactory". I like "excellent". But after a couple years of slogging through this code and other projects, "satisfactory" will have to do for now.

P.S. I also bought a new MacBook on Friday, to replace the laptop that collapsed a few months ago.

Posted by WeirdAl at 7:11 PM | Comments (1)

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 16, 2006

Verbosio: Basic XBL validation, anyone?

Over the last several days, I've been working on getting some basic functionality working in Verbosio again, picking up where I left off several months ago. I realized as I was working on this that I had two separate interfaces, one of them exceedingly minor, that could and should be merged. So now, language packs (which describe a particular XML language) will also be able to do some basic validation of their contents.

As part of that, I started thinking about XBL, which I hope will be the first XML language Verbosio will be able to usefully edit. XBL documents, fundamentally, are a mix of XML and JavaScript. So to make sure a XBL document is "valid" (that is, readable by Gecko products), I can combine well-formedness checking in XML with syntax checking in JS... very, very carefully.

The result is a partially-written, untested JavaScript component which does a whole lot of legwork to handle implementation elements, and will soon do similar legwork on handler elements. One factor of the design (which complicates it, but will make it very cool when I get it working) is to figure out where a JavaScript syntax error is in the XBL document and generate a full error message, complete with source code line and exact line number. I'm fairly certain I can pull it off.

Here's a sneak peek at my "XBL language pack component". The function in question here is called "getFirstSyntaxError". Under normal circumstances (no error at all), it would return null. There are probably a dozen little bugs (and half a dozen big ones) lurking in there, but I think I'll make it workable in the next few days.

If I do succeed at this, it opens up a whole new avenue for Verbosio, as a proof of concept. XUL, XHTML, and SVG documents can all have inline scripts (though not in as complex a structure as XBL does). By mixing XML well-formedness with JavaScript syntax checking, Verbosio could help page authors fix simple bugs on the fly.

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

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)