You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



You are here: Editor project page > Editor Command Handling

Editor UI Command Handling

In this document:

Introduction

This document describes how commands are handled, and command states are update in the Composer UI in Mozilla. It also alludes to some future changes in design to allow for easier embeddability.

Note that composer's command handling architecture is similar to that of other modules (browser, mail/news), but is probably more "designed" than either of those. Because composer has a lot of context-sensitive state, its UI needs to update efficiently, and the command handling architecture grew with this in mind.

Command specification in the XUL

Command handling in composer involves the following pieces:

  • XUL Command sets

    <command> nodes in XUL are grouped under <commandset>s both for ease of overlay merging, and to group them into functional sets, so that related commands can be updated in groups. Most of the editor <commandset>s are contained in editorOverlay.xul.

    <commandset>s have a number of attributes that are used to update commands at the appropriate time:

    • id -- identifies this commandset.
    • commandupdater -- set to "true" to say that this is a commandset.
    • events -- specifies what kinds of events this commandset wants to know about. Comma-separated list of events to respond to, when someone calls UpdateCommands() with one of these events.
    • oncommandupdate -- the handler that will be called to update the commands.

    The command update handler can do anything needed to update the commands. The way this works in Composer is that the command update handler simply iterates through the child nodes of the commandset, calling goUpdateCommand() on each one. For an example, see goUpdateComserMenuItems() in ComposerCommands.js.

  • XUL Command nodes, and observers of those nodes

    For each command (where a command represents some user-level action, like "Make bold") there exists a <command> node in the XUL document, which usually lives under the appropriate <commandset>. The commands are identified by a string such as "cmd_bold". Since <command>s are simply <broadcaster>s, other XUL nodes (e.g. buttons, menu items) observe these commands in order to be notified about state changes (recall that broadcaster nodes simply propagate one or all of their attributes onto their observers when they change).

    There are two bits of state which are maintained on the command node that matter; its "disabled" attribute (which controls whether the command is enabled in the UI, and its "state" attribute. For a simple command like "Bold", the state attribute is either on ("true") or off ("false") For more complex commands like "paragraph style", the state attribute contains an identifier for the current style.

  • Command handlers ("commands")

    A command handler is an object in C++ or JavaScript which implements nsIControllerCommand and has been registered with command manager to handle a specific command (identified by its string, like "cmd_bold"). The command handler has to implement just two methods; IsCommandEnabled(), and DoCommand(). The command handler normally talks to the underlying editor implementation to discover if it is enabled, to update its state (which happens in the IsCommandEnabled() call), and to execute the command.

    Composers has both C++ (in nsComposerCommands.cp) and JS (in ComposerComands.js) commands. These are all registered through nsIControllerCommandManager.

  • Command manager

    The command manager accepts the registration and removal of commands, allows for finding the command handler for a given command, and forwards calls to IsCommandEnabled and DoCommand through to the relevant command handler. It is described by nsIControllerCommandManager. The command manager uses a fast hash table lookup to map command names to handlers.

    Composer has two command managers. The first, which is a singleton, and shared with text widgets, handles basic text editing commands (cut/copy/paste, selection movement etc). The second contains composer-specific commands, and is instantiated for each composer that is created. This difference is important; since the editor command manager is a singleton, and shared between all editing instances, its commands must be stateless. Composer commands registered with the composer controller, on the other hand, may store state.

    Note: two distinct, but functionally equivalent interfaces should be used to make this difference more evident.

  • Controllers

    Controllers are a higher-level XUL mechanism for command dispatching. Composer's controllers simply wrap its two types of command managers, relying on the command manager's fast lookup for command name to handler mapping.

How commands are dispatched

So what happens when the user clicks a button in the UI? We'll use the Bold button as an example. Here are the relevant bits of XUL (somewhat edited for brevity):

 <command id="cmd_bold" state="false" oncommand="goDoCommand('cmd_bold')"/>
...
 <button id="boldButton" observes="cmd_bold"/>

Note, first, that since the <button> observes the <command>, it gets the 'oncommand' attribute. So clicking the button causes goDoCommand('cmd_bold') to be executed. This is a JS function in globalOverlay.js which does essentially this:

function goDoCommand(command)
{
    var controller = 
    top.document.commandDispatcher.getControllerForCommand(command);
    if ( controller && controller.isCommandEnabled(command))
        controller.doCommand(command);
}

Using the magic of XUL (Explain how this works), this finds the appropriate command dispatcher, gets its controller, and calls its doCommand() method with the command name. This will call into one of the controllers described above, which forward the call to the command manager, which finds the command and calls its DoCommand() method. Simple!

In this case, "cmd_bold" is actually handled by a C++ command implemented as a nsStyleUpdatingCommand and registered on the composer controller. The C++ commands use a class hierarchy designed to maximize code-sharing between like commands. The DoCommand method is on the base class nsBaseStateUpdatingCommand, and it calls the derived class's ToggleState method, which is where we finally call into nsIEditorShell to change the text property. It then forces an immediate update of the UI by calling UpdateCommandState(), and returns.

Things get a little more complicated for multi-state commands (like align, or paragraph state). Their problem is that the series of calls through DoCommand() have no provision to send state along with the command name. Because of this, we have to stash state on the command node before executing the command, and retrieve it when we need to finally call into the editor shell. See the JS function doStatefulCommand() in editor.js, and the implementation of one of the nsMultiStateCommands to see how this works.

Updating Command state

Somewhere, at some time, somebody wants to update the state of commands in the UI to enable or disable items, or ensure that they reflect the state of the selection (e.g. whether we are in bold right now). That's done through a call to UpdateCommands() which is a method on WindowInternal (and hence any 'window' object in JS), with a particular string which describes what kind of update event this is.

Again through the magic of XUL, one or more <commandset>s are found that respond to the event specified in the UpdateCommands() call, and their oncommandupdate handler is called. For commandsets in composer, this will generally call goUpdateComposerMenuItems() which simply iterates through the child nodes of the commandset, calling goUpdateCommand() on each one. goUpdateCommand(), another function from globalOverlay.js, finds the controller for the command, calls its IsCommandEnabled(), and then sets or clears the "disabled" attribute on the command node as appropriate. The composer commands use the call to IsCommandEnabled to update their state, again using methods of the various command classes to call into nsIEditorShell for current state information, and setting this in the "state" attribute of the XUL command nodes.

How this has to change for embedding

The current command update and dispatch mechanism has evolved in a XUL application, and therefore has some reliances on XUL that need to be eliminated. Currently, the command themselves assume that they are working in a XUL document, and grab onto XUL nodes, getting and setting attributes on those nodes. This has to stop; composer may be embedded in a native application with no XUL.

The solution will be to introduce a layer between the command handlers, and the UI which they respond to and update. This layer will be an interface with methods to get and set the appearance of UI elements (both semantic and enabled state), and respond to command execution gestures.

See the embedding command handling document for the current design for this.

Maintained by the editor team: mozilla-editor@mozilla.org