This document describes the current state of editor embeddability, problems with the existing implementation, some possible embedding scenarios that we need to deal with, and an embedding solution that will fulfill them. Finally, steps towards that final solution are described.
Here are some embedding scenarios that editor needs to deal with. Note here that I use the term 'Composer' to mean an HTML-savvy compose widget that does rich text editing, and 'Editor' to mean a plain text editor (as well as the underlying technology for Composer).
<htmlarea> is intended as a shorthand for a rich-text multiline text widget embedded in an HTML document, and is not meant to infer that future versions of Mozilla will support this specific tag.
Composer embedded in a XUL application
Developers need to embed composer widgets in their XUL applications, by using the
<editor> tag as we do today. They should need to do as little work as possible to get basic editing functionality, be able to have any number of
<editor>s per window, and control whether those
<editor>s are in HTML or plain text mode.
Composer embedded in a native application
In this application, the
<iframe> on which the editor lives is embedded directly in the native application; this is equivalent to embedding the browser via nsIWebBrowser, but instead having an editable document. The composer chrome (toolbars etc) may be implemented by the embedder using native widgets, or using some amount of XUL. That chrome needs to be configurable - dockable floating toolbars, toolbar shared between composer widgets, or 1 per widget.
This type of embedding requires that the composer code is agnostic to where its UI is coming from; communication between the core editor and the UI needs to go through one or more interfaces that insulate the editor from its host application. (The current
nsEditorShell makes assumptions about the hosting XUL document, which need to be broken.)
Composer embedded in a web page (
IE 5 supports the
<HTMLArea> element; if Mozilla is to support something similar, editor needs to be embeddable to the extent that this can be done. It is likely that we'd use XBL to implement this type of widget, as is the plan for other form controls.
As is the case for composer embedded in a native application, there is a requirement here that the composer UI be configurable, such that it can be displayed either as a toolbar at the top of the
<htmlarea>, a floating palette, or a top-level toolbar.
The current composer architecture was created while other parts of Mozilla were still under development, and as a result it suffers from a number of shortcomings, and anachronisms. This section describes its major faults.
Editor incorrectly rooted
The editor in a composer window is current owned by the
nsEditorShell, which in turn is created, owned and destroyed by the
nsEditorBoxObject. The box object is a layout structure that is owned by content nodes, and survives frame destruction/recreation. The box object also has a reference to the docShell for the editor frame. XBL creates an
nsEditorBoxObject for each
<editor> tag is simply a
<iframe> on which an editor is created; in all other respects, it behaves like a XUL
The problem with this ownership model is that there can be only one editor per
<editor> tag, yet the document loaded in the
<iframe> may itself contain multiple
<iframe>s (consider a frameset document, or a document itself containing an
<html:iframe>). Currently, composer does not deal gracefully with such documents.
One editor per window limitation
The current composer window XUL/C++ architecture has grown up with the assumption that there can be just one
<editor> tag per window. At window construction time, we get the editorShell from the
<editor> element, and put that into
window.editorShell. This assumption was short-sighted, and must be fixed.
Editor assumes XUL document structure
There is C++ and JS code in the editor that assumes that the editor is living in a XUL document, and that there are XUL document nodes out there whose attributes can be tweaked to change the state of the UI (e.g. the inline style buttons). This needs to be changed to allow embedders to provide their own, possibly native UI. Editor needs to call through one or more interfaces when communicating with the UI.
Editor requires design changes such that the embedding applications above can be fulfilled, and these changes will need to address the current problems. Briefly, the embedding objectives are:
<editor>should get you a working editor in a XUL application
- Should be able to have multiple
<editor>s per XUL window
- Should be able to embed an editable content frame in a native application
- Embedders should be able to supply their own chrome (toolbars etc).
Meeting these objectives will also solve the following existing composer problems:
- Composer should handle frameset documents
- Composer should handle documents with
- Composer shouldn't rely on certain XUL document structure
Fixing editor rooting
As described above, editor rooting needs to be changed so that an editor lives on top of an nsDocShell, rather than hanging off the
nsEditorBoxObject. There is a docShell for each editable
<iframe>. This will involve:
- Making a new interface,
nsIEditorFrame, that is implemented by
nsDocShellor a related class. You should be able to QI from
nsIDocShellto one of these; if this succeeds, it indicates that the frame is editable.
nsIEditorFrameshould contain methods for getting the editing session, and doing some generic editor-related stuff (probably common to HTML and plain text editing). A subset of
nsIEditorShellwill probably move into this interface. (This would be analogous to the
nsIWebNavigationinterface used for a browser.)
- When we have one editor per docShell, loading a frameset document, or a page with an
<iframe>in composer, will instantiate more than one low-level editor. We need a concept of an "editing session" - a single top-level document which is editable, and which may embody more than one editor. This interface will be called
nsI????. High-level UI and emebedding code should deal with this editing session interface, without knowledge of the underlying editors. The editing session will forward commands to the individual editors depending on focus, and mediate undo/redo between them.
- Alternative solution: Rather than having multiple editors in this scenario, we could have a single editor, which is capable of saving and restoring state such that it can be transferred between the various subdocuments being edited. That state would have to include the document, undo stack, and typing state. The implementation of the editing session would be responsible for swapping out the editor's state on focus changes etc.
More than one editor per window
Current Composer clients in the Mozilla codebase all assume that there is only one
<editor> tag per window. They all need to be able to deal with multiple editors. Fixing this would require JS changes of the following kind:
- Standardize the way that clients get at the editorShell (or its replacement, post-embedding work) from the window.
- Ensure that focus changes between editors update the window's notion of the "current" editor. If we continue to use
window.editorShell, then this needs to be updated on focus changes.
- Ensure that each editor is properly constructed on window setup, and torn down on window destruction.
- Ensure that window close tests (e.g. calling
window.tryToClose) appropriately consult each editor for state.
Insulating editor from the chrome
Composer needs to not know anything about the UI that is driving it. The plan is to insulate Composer from the UI via a new interface that the embedder implements. Any UI that is now thrown up by Composer should go through this interface.
- Use a new interface,
nsIEditorUserInterface, to mediate communication between the editor and the UI. An embedder would need to implement this to get native toolbars and menus to work. In Composer, we'd have an implementation in JS that talks to the existing commands, and updates the XUL nodes.
- Fix existing JS and C++ code which explicitly addresses elements in the XUL document to go through </code>nsIEditorUserInterface</code>.
Steps to embedding
This section attempts to lay out an implementation plan, with the aim of keeping everything working as the various steps are taken. Some of these tasks can be done concurrently.
- Decide on how to implement editing session multiple-editor support
- Eliminate specific interdependencies between Composer and the XUL document, via
- Create bottleneck for getting to the focussed editor; ensure that focus changes update state
- Move editor ownership to docShell, creating
- Created the editing session API which copes with collections of editors (or make editor refocussable)
Where should file Open and Save logic live?
The embedder provides Open and Save dialogs if they want to. In Composer, we can pose these dialogs from JS (once some
nsIFile problems have been solved).
Does all Composer UI need to be replaceable?
There is a huge amount of Composer UI in the various dialogs for editing tables, links, images etc. Do we need to make it possible for an embedder to replace all of these with native UI?
Dialogs use the available editor APIs to get and set data, so can do all their work through existing APIs. If an embedder wants a fully native UI, then they'll have to code their own dialogs, and associated logic, but the APIs should already be available to them. This seems to be a non-issue.