- Author: Susan D. Tiner
- Last Updated Date: 11/23/05
Statement of Purpose
The purpose of this article is to provide a high-level technical overview of the architecture of the extensible, object-based Mozilla application framework by examining what happens when the user performs a simple user interface (UI) action such as clicking a link in the Contents Panel of the Help Viewer window shown below. The article focuses on the architecture of the overall framework supporting the Mozilla application suite, not the architecture of the individual applications themselves.
Prerequisites
This article assumes you have access to Mozilla sources and are familiar with the directory structure of the source tree. The code samples in the article are based on Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9a1) Gecko/20051104 SeaMonkey/1.1a from a new source tree checked out 11/04/05. The build ID is 2005110409. The Help Viewer files referenced in the article are located in
/seamonkey/extensions/help/
This article also assumes you are familiar with the JavaScript and C++ programming languages, object-oriented programming (OOP) terminology and design concepts, the Microsoft® Component Object Model COM), and the CORBA OMG Interface Definition Language (IDL).
Organization
This article first covers some conceptual groundwork, then walks through a user scenario to illustrate how key Mozilla architectural components work together to support a simple Help Viewer UI action.
Framework Layers
At a high level, you can think of the Mozilla framework as consisting of a suite of applications, each of which is organized into three basic layers depicted in Figure 1.
Figure 1.
User Interface (UI) Widgets and Services
The X Window System Toolkit defines the term widget as a pre-defined object that encapsulates both the command actions and data associated with a graphical user interface (GUI) control. The various X Toolkit implementations provide a set of widgets for UI controls such as menus, command buttons, dialog boxes and scroll bars. The Mozilla XPToolkit module provides a similar set of facilities for building cross-platform UI controls implemented as XML User Interface Language (XUL) packages. A XUL package consists of
- a XUL description of the UI widget
- Cascading Style Sheets customizing appearance
- JavaScript services implementing the UI behavior
A package (also known as chrome) is really just a bundling of a set of UI widgets and associated services implementing a particular application feature. Chrome and Extensions are examples of packages.
Content (DOM)
The Mozilla Document Object Model (DOM) specifies a logical tree structure for storing UI and document content, and provides an API (Application Programming Interface) for accessing and manipulating the content. An application such as the browser may have one or more open documents accessible via DOM APIs. When an HTML, XML, SVG or other type of document is loaded, the NGLayout engine (also known as Gecko) parses the contents into a DOM tree, and handles the layout and rendering of the document pages. The NGLayout engine also parses XUL UI controls into a DOM tree and handles rendering of the UI.
Core Services
Modules such as the NGLayout engine comprise the core application services available to
- other core modules, and
- XUL packages
Core application modules are implemented as a set of one or more XPCOM (Cross-Platform COM) objects.
XPCOM Object Model
Before getting into the details of the UI example, it's helpful to have a basic understanding of the XPCOM object model. Mozilla architectural modules are comprised of groups of related XPCOM objects that provide services to and access services from each other through dynamically queryable interfaces.
Object Model At a Glance
Mozilla dynamically loads and integrates core modules and XUL packages into the runtime environment on startup. Once loaded, a module or package has full access to the XPCOM objects of all the other modules in the runtime environment. The ability to dynamically integrate modules and packages at startup is a powerful benefit of the object model, which defines a module as a set of one or more XPCOM objects called components. These objects provide services to client packages and modules and access services provided by other modules through dynamically queryable interfaces. Figure 2 shows this structure.
Figure 2.
In the diagram above, each horizontal line extending from an object and terminated by an open circle indicates an interface to the object. The client object does not need to know anything about the internal structure or implementation of the provider object in order to take advantage of its services. The client simply queries the provider for a particular service, and if available, accesses that service through an interface defined in XPIDL (Cross-Platform IDL), derived from the CORBA IDL. The XPIDL interface description is independent of the programming language used to implement the object itself. For example, a JavaScript object or C++ class could both implement the same XPIDL interface.
Scripting languages such as JavaScript cannot directly access XPCOM components, but access them indirectly using XPConnect. XPConnect also provides a required layer for XPCOM components written in a scripting language.
Let’s consider a Resource Description Framework (RDF) example, where RDF is a framework for describing and interchanging metadata, that is, information about information. In this example, we’re interested in the RDF sub-graph underlying the Contents Panel widget within the Help Viewer window. Suppose the client already has an nsIRDFDataSource
object representing a sub-graph of an RDF graph and calls nsIRDFDataSource.GetTarget(resource, NC_LINK, true)
to obtain an nsIRDFNode
representing a specific node in the graph, in this case a link to a Help Viewer document page. The client is completely unaware of which C++ class (or other language) actually implements nsIRDFNode
; it only interacts with the IDL interface.
The client would like to ask the nsIRDFNode
whether its supports nsIRDFLiteral
. An nsIRDFNode
must be an nsIRDFLiteral
or an nsIRDFResource
. The link is a literal, so the client wants nsIRDFLiteral
. Since all XPCOM interfaces inherit the base interface nsISupports
, the client can ask whether nsIRDFNode
supports nsIRDFLiteral
by calling nsIRDFNode.QueryInterface()
, a method in the nsISupports
interface.
[scriptable, uuid(00000000-0000-0000-c000-000000000046)] interface nsISupports { void QueryInterface(in nsIIDRef uuid, iid_is(uuid),retval] out nsQIResult result); [noscript, notxpcom] nsrefcnt AddRef(); [noscript, notxpcom] nsrefcnt Release(); };
The uuid
parameter to QueryInterface()
is the IID uniqely identifying the interface.
If nsIRDFNode
doesn't support nsIRDFLiteral
, it returns null
and it's up to the client to choose an alternate course of action.
The XPCOM component keeps track of all interface pointers currently held by its clients using an internal reference count it increments via client calls to AddRef()
. It is up to the client to call Release()
when it no longer needs the interface. Release()
decrements the reference count and then the object becomes a candidate for destruction.
JavaScript handles its own garbage collection, so it doesn’t need to call AddRef()
and Release()
.
Inside an XPCOM Component
Figure 3 shows a more detailed view of an XPCOM component.
Figure 3.
As noted earlier, the component-specific interfaces of an XPCOM component all inherit from nsISupports
. Likewise, an XPCOM component must implement
nsIFactory
, which provides a mechanism for creating an object without having access to the class declaration for that object,nsIModule
, which provides a mechanism for registering and unregistering the XPCOM component, as well as for accessing the underlying class objects implementing the IDL interfaces, andNSGetModule()
, the entry point used to load the XPCOM component.
XPCOM components also link with a set of XPCOM utilities, such as strings, smart pointers, basic assertion and condition checking. These utilities are referred to as XPCOM glue.
At startup, the core application component identifies and loads all of the different XPCOM components comprising the core application services and builds a central registry it uses to generate instances of XPCOM components and instances of interface implementation classes on demand.
XPCOM in Summary
To summarize, the XPCOM object model
- specifies the structure of XPCOM components,
- builds a central registry based on the XPCOM components loaded at startup,
- generates XPCOM component instances on demand using the registry, which specifies the supported interfaces, their corresponding implementation objects, and the
nsIFactory
interface, - provides API facilities clients can use to dynamically create XPCOM components, and
- specifies the mechanism clients use to query an XPCOM component for one of its interfaces, and to release the interface when it’s no longer needed.
JavaScript Client Example
Suppose the JavaScript service in Figure 2 is getLink()
in Help.js, which responds to the user clicking on a link in the Contents Panel within the Help Viewer window by obtaining the link from the Contents Panel elements stored in a DOM tree. As a client, getLink()
begins by obtaining the XULElement
in the DOM tree representing the Contents Panel.
var tocTree = document.getElementById("help-toc-panel");
The document object is a XULDocument
, and tocTree is the Contents Panel XULElement
. Using the database
attribute of the XULElement
, getLink()
obtains an nsIRDFCompositeDataSource
, which presents the individual datasources
of the XULElement
as a single RDF graph.
var tocDS = tocTree.database;
NsIRDFCompositeDataSource
inherits from nsIRDFDataSource
.
Help.js declares an nsIRDFService
as a compile time constant at the beginning of the file.
const RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
The Components
object is made available to JavaScript via XPConnect; it serves as a bridge connecting JavaScript and XPCOM.
getLink()
uses the nsIRDFService
to obtain an nsIRDFResource
for the requested link.
var resource = RDF.GetResource(rdfID);
nsIRDFCompositeDataSource
inherits from nsIRDFDataSource
, which supports a GetTarget()
method for obtaining the nsIRDFNode
for a given resource and property.
var link = tocDS.GetTarget(resource, NC_LINK, true);
Now that getLink()
has the nsIRDFNode
for the link, it can call QueryInterface()
to check whether the nsIRDFLiteral
is supported.
link = link.QueryInterface(Components.interfaces.nsIRDFLiteral);
If nsIRDFLiteral
is supported, getLink()
uses it to return the value of the link. Otherwise it returns null
.
if (link) return link.Value; else return null;
The document page for the link is displayed in the Help Viewer window.