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.



Command handling for embedders



Introduction

The current web browser embedding API used to execute and get state for browser commands, nsIWebNavigation.idl, lists available commands explicitly, with essentially a "do" and "get status" method for each command (reload, go forward, go back etc.). Interfaces such as this which give the embedder direct access to execute and query the state of each command allow for interfaces that are easy to read and implement, but have a number of drawbacks:

  • Interfaces get unwieldy for command-rich components such as editor/composer
  • No way for the embedded app to notify the embedder when commands need to be updated, so the embedder has to infer update times from other activities, or use an eager update strategy.

The solution is a generic 'DoCommand' and 'IsCommandEnabled' type API, such as nsICommandHandler.

Internally, we have interfaces such as nsIController (an object which acts as a dispatch point for a set of commands that are handled by a window) and nsIControllerCommand which is an interface via which you can implement a single command in JS or C++ (to be registered and dispatched by a nsIControllerCommandManager).

Requirements

So we need a simple set of interfaces to provide the following functionality for embedders:

  • Execute a command via a generic DoCommand method
  • Query the enabled status of a command via a generic IsCommandEnabled method
  • Ask whether a command is supported, via a generic IsCommandSupported method
  • Pass command-specific parameters to the DoCommand method (e.g. make bold, set paragraph to be h4)
  • Query command-specific state of a command (e.g. is the current selection bold?)
  • Register and unregister a listener that gets called when one or more commands need to be updated

Interfaces

I propose the following interfaces (names are subject to change if there are conflicts with existing interfaces).

nsICommandManager

An interface through which embedders (and XUL applications) can exectute commands, get command state, and register listeners for command udpates.

  • GetInterface (or QI?) from nsIWebBrowser
  • The embedder may also implement this, to allow for command notifications to pass from embedded (XUL) chrome out to the embedder. Need some strategy for QIing the emedding container to see if they want this.
[scriptable, uuid()]
interface nsICommandManager : nsISupports
{
    void        addCommandObserver(in nsIObserver aCommandObserver, in wstring aCommandToObserve);
    void        removeCommandObserver(in nsIObserver aCommandObserver, in wstring aCommandObserved);
    
    boolean     isCommandSupported(in wstring aCommandName);
    
    boolean     isCommandEnabled(in wstring aCommandName);
    wstring     getCommandState(in wstring aCommandName);
    void        doCommand(in wstring aCommandName, in wstring aCommandParams);

}

The command state string returned by getCommandState() reflects the current state of stateful commands (like Bold, or Paragraph style). The contents of the string vary depending on the command, and will be documented on a per-command basis. Some commands (e.g. Back, Reload) don't have corresponding state, and this call will return an empty string.

Similarly, commands that have state will require a non-empty string in the aCommandParams argument to the doCommand() call. This will likely one of the state strings returned by getCommandState().

Note: nsICommandHandler.idl will be obsoleted by these changes.

nsIObserver Implementation

The nsIObserver interface is used to notify embedders that commands states have changed, so that they can change the appearance of applicable UI elements as appropriate. The observer will be called, usually eagerly, when command state changes, so implementations should have low overhead. Registering command observers is optional; UI could just call nsICommandManager::isCommandEnabled() and nsICommandManager::getCommandState() on each command it cares about.

The embedder should register an nsIObserver using nsICommandManager::addCommandObserver() for each command that they wish to get update notifications for. The same observer may be registered for more than one command. Unregistering on a command does not affect notifications for other commands on which the observer was registered. To register on all commands (or, similarly, unregister for all comments on which the observer was registered), pass NULL or an empty string in the aCommandToObserve/aCommandObserved parameter.

Parameters to the Observe() call on nsIObserver for a command update notification:

[scriptable, uuid()]
interface nsIObserver : nsISupports
{
    void Observe(   in nsISupports aSubject,           // The nsICommandManager calling this Observer
                    in wstring     aTopic,             // Name of the command being updated
                    in wstring     someData );         // Command state (if available)
};

Note: the command state passed as the last argument to Observe() may be expensive to compute, and often the embedder won't care. Maybe not send this, so the embedder must call nsICommandManager::getCommandState() if they need it?

How to make or get one:

  • Embedder implements this interface.

Implementation notes

Currently we have a command dispatcher for xul, the nsIDOMXULCommandDispatcher, which manages command updater objects (XUL document nodes that have the commandupdater attribute set to "true", and a list of nsIControllers, which handle individual commands.

This command dispatcher needs to be made independent of XUL. Its focus-related duties have already been moved into nsIFocusController, and what remains can be morphed into an implementation of the nsICommandManager described above.

Once that is done, the various places in the JS (and C++) that look like this (note that the isCommandEnabled() call is not strictly necessary here, but is included for illustration):

var controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
if (controller.isCommandEnabled("cmd_copy")
  controller.doCommand("cmd_copy");

can be changed to call the new nsICommandManager:

if (window.commandManager.isCommandEnabled("cmd_copy"))
    window.commandManager.doCommand("cmd_copy");

Questions:

  • What about the cool nsIControllerCommand/nsIControllerCommandManager that allows you to write an individual command as an object that supports the nsIControllerCommand interface? Should this be exposed to embedders? Should all commands someday be written this way?

Simon Fraser