These features include anonymous content, the command dispatcher, the controller system, and the configurable key binding system.
The above features have emerged as critical requirements for the Mozilla application. Unfortunately the design of these features has been haphazard at best. The design has also been particularly difficult because it necessarily involves non-standard extensions to CSS, the DOM, and HTML.
The purpose of this document is to provide a concrete proposal for a redesign of the current system in order to enable the full range of functionality we need for the Mozilla application. In the following sections, I will weave together these different features into a new language, XBL, that can exist apart from XUL and that can be used with either XUL or HTML.
The Structure of an XBL Document
An XBL file is a well-formed XML file.
The root element of the file is a <bindings> tag. The
namespace of XBL is
http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl.
An XBL file consists of zero or more bindings, each declared as a child of the root element with a <binding> tag.
<bindings xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
...
</binding>
<binding>
...
</binding>
...
</bindings>
|
CSS and XBL
An element in XML (in particular XUL and HTML elements)
can obtain bindings from a specific XBL file. The
location of this file is specifiable using CSS. A new property, behavior,
can be used.
textarea {
behavior: url("chrome://global/content/textAreaBindings.xml");
}
|
The bindings found in the XBL file will all be applied to any elements that match the style rule. In the above example, a bindings file is defined that will apply to all HTML textarea elements.
Note: There is currently a working draft afoot for adding behavioral extensions to CSS. This proposal complements that draft through its support of the behavior property. One possible implementation of behaviors is HTC, HTML Components. This implementation, however, is not generalizable to other namespaces and does not support the extra capabilities that XBL offers. XBL, therefore, is simply another kind of behavioral implementation that CSS can reference.
The <binding> Element
The binding element is used to dynamically bind new
information to another XML element (e.g., a XUL or HTML element).
Each binding has three components.
The following sections cover each component of a binding in more detail.
The <content> Element
The content element is used as a child of the binding element to
indicate additional content that can potentially be built underneath the
bound element. A binding can have only one content element as a child.
This content can be either anonymous, which means that it is not really present in the bound element's document, or it can be explicit, which means that the content elements are actually cloned from the binding file and inserted into the bound element's document as children of the bound element.
The explicit attribute placed on the content element is used to specify whether or not the content is explicit or anonymous. If the value is true, then the content is assumed to be explicit. If the value is false or if the attribute is not present, then the content is assumed to be anonymous.
The content itself is declared normally, just as it would appear if it were really declared underneath the bound element.
|
Example: Scrollbars on different operating systems can have varying
structures. On the Windows operating system a scrollbar can be implemented
using an up button, a scrollbar thumb, and a down button. On the BeOS,
however, a scrollbar consists of a pair of up/down buttons, a scrollbar
thumb, and a second pair of up/down buttons.
By making the scrollbar's content anonymous, and by moving the anonymous content itself into an XBL file, CSS can then be used to point to two different XBL files on the different operating systems.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
<content>
<xul:titledbutton class="upButton" oncommand="parentNode.lineUp();"/>
<xul:thumb class="thumb" .../>
<xul:titledbutton class="downButton" oncommand="parentNode.lineDown();"/>
</content>
...
</binding>
...
</bindings>
|
Anonymous Content and the DOM
Although anonymous content is not really contained in the document, it can be styled by
style sheets loaded by the document. It can also reference JavaScript variables and
functions defined within the global script object that corresponds to the document.
Events that occur on anonymous content are not visible to the bound element or to its document. Within the anonymous content tree itself, events bubble normally and are also capturable by other anonymous content.
It is possible for anonymous content to be bound to another XBL file that can itself contain anonymous content. In this case, the nested anonymous content is invisible to the outer anonymous content. This nesting can be taken to an arbitrary level.
| Example: The scrollbar thumb itself can be implemented using two springs and a titledbutton. This content could be defined in an XBL file bound to the thumb element. It is invisible to the thumb that it is bound to, and in turn the thumb is invisible to the scrollbar that it is bound to. |
Exclusionary Construction of Content
By default, content is only built if the bound element has no child content already.
In certain situations, however, it is desirable to build anonymous content even when the
element already has children. The excludes attribute can
be used in this case. Its value is a comma-separated list of child tags that
should be ignored during the check regarding whether or not anonymous content
should be constructed.
|
Example:: The menu element in XUL can contain template or observes
elements as children. It can also contain a menupopup element as a child. If,
however, no content is specified other than those three children, then the contents
of the menu must still be constructed anonymously.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
<content excludes="template,observes,menupopup">
<xul:titledbutton class="menu-left"/>
<xul:titledbutton class="menu-text"/>
<xul:spring flex="1" class="menu-spring"/>
<xul:titledbutton class="menu-accel"/>
<xul:titledbutton class="menu-right"/>
</content>
...
</binding>
...
</bindings>
|
Inheritance of Attributes
In some cases attributes that are set or changed on the bound element should be
reflected into the binding's content. The inherits
attribute can be used on specific elements within the content binding.
This attribute is a global
attribute, and therefore its namespace must be explicitly qualified as XBL.
The value of the attribute is a comma-separated list of attributes that should
inherit from the bound element. These attributes always remain in sync with
the bound element; if the attribute's value changes on the bound element, then
the anonymous content is also updated to reflect the new value.
In some cases the attribute on the bound element is not the same as the attribute on the anonymous content that should be set. In this case, an additional attribute, mapto, can be specified on the anonymous content. Its value is a comma-separated pair that indicates that the first attribute in the pair should be mapped to the second attribute in the pair.
|
Example:: The menu element in XUL supports the value attribute as
a means of specifying the menu item's text. It also supports the acceltext
attribute as a means of specifying the accelerator text for a menu item. Because
titledbutton elements are used for both, the value attribute
must be specified on both. The menu text button can inherit the value from the
bound element. The menu accelerator button must inherit from acceltext and
map that attribute to its value attribute.
<xbl:bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<xbl:binding>
<xbl:content excludes="template,observes,menupopup">
<xul:titledbutton class="menu-left"/>
<xul:titledbutton class="menu-text" xbl:inherits="value"/>
<xul:spring flex="1" class="menu-spring"/>
<xul:titledbutton class="menu-accel"
xbl:inherits="acceltext" xbl:mapto="acceltext,value"/>
<xul:titledbutton class="menu-right"/>
</xbl:content>
...
</xbl:binding>
...
</xbl:bindings>
|
Sometimes different types of content should be constructed based on the attributes found on the bound element. XBL supports this capability through the usage of rules. If the content varies in this way, then instead of placing the elements directly underneath the content tag, the elements are placed under rule tags, one for each different rule, and each of these rule elements is placed underneath the content tag.
Attributes that are specified on a rule element are compared with attributes found on the bound element. If the value specified in the rule matches the value of the attribute on the bound element, then the rule is said to be matched.
The first matched rule is always the one used to construct content. Note that even if a rule is matched that content might not be constructed, since the bound element must still have no children (and/or only have children that are not excluded from the content check).
Rule elements support the excludes attribute, allowing this check to vary from rule to rule.
|
Example:: A treecell in XUL constructs different anonymous content
if an indent attribute is set to true
on the cell. In one case, a <treeindentation> element needs
to be inserted into the cell. Otherwise it doesn't.
<xbl:bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<xbl:binding>
<xbl:content excludes="template,observes">
<rule indent="true">
<xul:treeindentation>
<xul:titledbutton class="tree-icon" flex="1" xbl:inherits="value,src">
</rule>
<rule>
<xul:titledbutton class="tree-icon" flex="1" xbl:inherits="value,src">
</rule>
</xbl:content>
...
</xbl:binding>
...
</xbl:bindings>
|
The <interface> Element
The interface element is used to specify additional functions that can be
invoked on the bound element, as well as additional properties that
can be set or retrieved on the bound element.
A binding element can have only one interface
element child.
Methods
An interface element defines zero or more methods and zero
or more properties. Each method is specified
using the method tag, and each
property is specified using the property tag.
The name attribute
can be used on either of these two elements to indicate the name of the method
or property that will
be added to the bound element.
|
Example: A scrollbar element in XUL can have methods defined on it
using XBL that allow for scrolling up and down, either by a line or by a page.
It can also have a property that represents the current index of the scrollbar.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
<content>
<xul:titledbutton class="upButton" oncommand="parentNode.lineUp();"/>
<xul:thumb class="thumb" .../>
<xul:titledbutton class="downButton" oncommand="parentNode.lineDown();"/>
</content>
<interface>
<method name="lineUp">
...
</method>
<method name="lineDown">
...
</method>
<property name="currentIndex"/>
</interface>
</binding>
...
</bindings>
|
Method and property names are case-sensitive. The convention to use in Mozilla: the first letter of method and property names should always be lowercase (just as with JavaScript methods and properties).
Parameters
Arguments to methods can be specified using the <argument> tag.
The argument tag can also take a name attribute. In the
implementation of the function the value of this attribute can be used to refer
to a variable that is bound to the value of the argument passed in to the method.
The type of an argument does not need to be specified. Method arguments in XBL are of polymorphic type.
|
Example: A scrollbar can define a scrollTo method that requires
a single argument, a number indicating the line to scroll to.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
...
<interface>
<method name="scrollTo">
<argument name="index"/>
...
</method>
...
</interface>
...
</binding>
...
</bindings>
|
Method Implementations
The body tag is used to specify the implementation of a particular
function. It contains a script that is executed when the method is invoked.
The type attribute can be used to specify the language
of the script.
|
Example: The scrollbar defines an implementation for the scrollTo
method.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
...
<interface>
<method name="scrollTo">
<argument name="index"/>
<body>
<![CDATA[
if (index == currentIndex) return; ...
]]>
</body>
</method>
...
</interface>
...
</binding>
...
</bindings>
|
The method body is evaluated in the context of the bound element. Thus properties of the element can be referred to by name (as is done for the currentIndex property in the example above).
Properties
The property tag is used to specify a property in XBL. By default the
property can be set or retrieved on the element. The name
attribute is used to specify the name of the property.
The readonly attribute if present and set to true indicates that the property can be inspected on the bound element but not changed.
The value attribute on the property element is a script that evaluates to an initial value for the property.
|
Example: The scrollbar initializes its current index to 0.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
...
<interface>
...
<property name="currentIndex" value="0"/>
...
</interface>
...
</binding>
...
</bindings>
|
Interfaces and XPCOM
An interface element can point to an XPCOM interface and to a specific
implementation of that interface.
When this is done, no methods or properties are specified in the
XBL. Instead they are picked up from the XPIDL description that corresponds to
the XPCOM interface.
[Note: Only interfaces that have had XPT files generated for their descriptions can be used with XBL.]
The name attribute can be used to point to a symbolic name for the interface. When this is done, the interface is retrieved from the interfaces object that is accessible from the Components object. The iid attribute can be used to point directly to a specific interface ID.
Implementations can be referred to using the progid attribute, which refers to a progid, or a specific implementation can be directly referred to using the classid attribute.
It is permissible to specify an XPCOM interface and to define the implementation inline using <method> and <property> tags. Note that it is not permissible to point to an XPCOM implementation without also specifying either an interface name or an IID.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
...
<interface name="nsIController" progid="component://netscape/editor/controller"/>
...
</binding>
...
</bindings>
|
The <events> Element
The final component of a binding is the <events> element. This element
is used to define new event handlers that should be invoked when events occur on
the element. The events element contains zero or more event
elements as children.
Each element corresponds to a single event handler that will be invoked when the
event is matched.
Event Elements
The type attribute specifies the type of event that is being
fielded. Its name must correspond to a valid DOM event type, such as click
or keypress. A comma-separated list of event names can
be given if, for example, the event should fire on more than one action, e.g., when
the return key is pressed or when the mouse is clicked.
The button attribute can be used to restrict a mouse event to a specific button. It has values of left, middle or right.
There are several modifier keys supported. They each have separate attributes. If set, then on a mouse or key event, these modifier keys must be down for the event to fire. The value of a modifier key attribute is true if the modifier key must be down. The modifier key attributes include shift, control, alt, command, and meta.
The key attribute can be used to stipulate that on a key event the key must be the primary key used. The keycode attribute works in a similar fashion, but it allows for the selection of unusual keys like space or home. The virtual key codes can be found in the Mozilla source. Note: Include link to Mozilla source file.
The capturer attribute, if set to true, indicates that the event should only fire during the capturing stage of DOM event flow.
The value attribute contains the event handler script that should be invoked when the event element is matched.
|
Example: A list box in HTML moves the selection up and down when the appropriate
arrow keys are pressed. XBL event elements can be used to install this behavior
on a list box.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
...
<events>
...
<event type="keypress" keycode="vk_up"
value="moveDown()"/>
<event type="keypress" keycode="vk_down"
value="moveUp()"/>
...
</events>
...
</binding>
...
</bindings>
|
Event Redirection
An event element can be used to map one event to another event. When this
happens, a new event is synthesized from the old event and fired into the DOM.
The <mapto> element is used to perform the redirection.
It is a child of the event element, and it can have all the attributes
that an event element can. These attributes specify the conditions
of the new event and provide a hint as to how the fields of the event
object should be filled in.
The new event fires on the bound element and moves through the capturing and bubbling stages just as the old event does. An event element that redirects can still have a value attribute specified. This code executes prior to the firing of the new event. This can be useful in cases where it is desirable to prevent the continued processing of the old event.
|
Example: Dialog boxes attempt to close when the ESC key is pressed. An
XBL file bound to all dialog windows can be used to ensure that the ESC key
causes a close handler to fire.
<bindings xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.also.xbl">
<binding>
...
<events>
...
<event type="keypress" keycode="vk_escape">
<mapto type="close"/>
</event>
...
</events>
...
</binding>
...
</bindings>
|
This mapping capability is quite powerful. It can be used to perform key remapping. For example a Dvorak keyboard layout could be implemented within Mozilla by supplying a set of redirects on the outermost XUL window.
Other examples of event redirection include platform-specific bindings. For example, CONTROL+click on the Mac could be remapped to a right mouse button click. Finally modifier keys can be mapped to other modifier keys, thus allowing for platforms to choose modifier keys as appropriate for specific bindings.
Event Cascading
A bound element can have multiple bindings attached to it.
The presence of multiple bindings introduces a new form of sideways event flow
(in addition to the bubbling and capturing flows that already take place in
the DOM).
When an event goes into the DOM, as each element is hit, the bindings of that element are traversed in order to see if an event is matched. It is possible that more than one binding can supply a valid event element. For example, an element can contain two bindings that both indicate that they wish to handle the click event.
For both capturing and bubbling, the bindings of an element are traversed in order. By default all bindings are checked. The event is said to cascade through the bindings on the element.
An event handler in one binding may choose to prevent subsequent bindings from seeing the event. This is especially common in the case of command bindings. Note: Supply a link to the Command Dispatching document here.
The preventCascade method may be invoked on the event itself to stop the flow of the event through the rest of the bindings. Note that since the bindings are potentially walked twice, if a capturer prevents a cascade, this will only stop other capturers from seeing the event. Non-capturing bindings that are hit during the bubbling phase will still see the event unless preventBubble is invoked on the event.
Importing Bindings
One XBL file can import bindings from another XBL file using a processing instruction.
The instruction is xbl-import, and it takes a src
attribute that points to the bindings to be loaded.
This feature allows bindings to be reused by different elements that also wish to define additional bindings of their own. Most importantly, it can be used in conjunction with chrome URLs to include platform-specific event redirects and bindings unique to a given operating system.
The Bindings Array
All elements in the DOM can have their bindings inspected. An array of bindings
(nsIBindings)
is accessible from the bound element. Each object in the array implements the
nsIBinding interface. The IDL for the
nsIBindings array is shown below.
#include "nsISupports.idl"
#include "nsIBinding.idl"
[scriptable, uuid(A5ED3A01-7CC7-11d3-BF87-00105A1B0627)]
interface nsIBindings : nsISupports {
void insertBindingAt(in unsigned long index, in nsIBinding binding);
nsIBinding removeBindingAt(in unsigned long index);
nsIBinding getBindingAt(in unsigned long index);
void appendBinding(in nsIBinding binding);
void removeBinding(in nsIBinding binding);
unsigned long getBindingCount();
void preventCascade();
};
|
Programmatic Manipulation of Bindings
The nsIBindings object can be programmatically manipulated using
the methods on the object. Specifically bindings can be appended, inserted, inspected and
even removed completely.
Although the contents of the bindings array can be altered, care should be taken in doing so. In particular, if style rules are being used to cause an element to point to different bindings as those rules are matched and unmatched, then programmatic manipulation of the array should be avoided.
For example, if a hover rule on an element causes different bindings to be used, then any changes made to a binding array will be lost when the user mouses over the element.
Reflection
The nsIBinding object provides a powerful capability to the DOM.
Specifically the methods and properties defined by the binding can be inspected using
reflection.
The IDL for the nsIBinding interface is shown below.
#include "nsISupports.idl"
#include "domstubs.idl"
[scriptable, uuid(D5B61B82-1DA4-11d3-BF87-00105A1B0627)]
interface nsIBinding : nsISupports {
readonly attribute DOMElement bindingElement;
};
|
The method and property elements for a binding can be obtained by examining the children of the bindingElement property of the nsIBinding object. In the case of XPCOM interfaces, the method elements are constructed using the information found in the XPT file as a guide. Note: This means that XPCOM must support reflection of methods and properties. A glue layer constructs the elements dynamically from the reflected information.
When a binding points to an XPCOM interface, that interface should always be obtainable by doing a QueryInterface on the nsIBinding object. This means that any implementation that is intended for use via a binding interface must support aggregation. It must be possible to QueryInterface the implementation back to an nsIBinding.
In the case where the XPCOM interface has been specified for a binding, but the implementation is declared inline using XBL elements, a wrapper implementation is built around the elements that supports the QI between the nsIBinding and the specified interface.
Cascading of Method Invocations
Methods and properties defined on bindings are invoked and manipulated by making those calls
directly on the bound element itself. For example, if a binding defines a
doCommand function for a XUL tree widget, then the function can be invoked directly
from the tree element.
For methods, the IDL for the DOM element is first checked. If there is no match, then the bindings are searched in order for any methods that match the invoked method. Note that it is possible for multiple bindings to have matching methods. When this occurs all of the matched methods are invoked in order. In other words, a cascading of method invocations occurs.
It is possible for a method to halt the cascade by calling the preventCascade method on the bindings property of the bound element. Once this method has been called, no subsequent methods will be invoked.
The invocation of methods directly from the bound element is not possible using C++. For this reason, the bindings are made accessible from C++ and the binding objects can be QueryInterfaced directly to their appropriate interfaces, and methods can be invoked that way. When this tactic is applied, however, the cascading will not occur, since the method was invoked on the binding instead. In the C++ case, therefore, cascading, if desired, has to be done "by hand."
Setting/Retrieval of Properties
When a property is inspected, the element itself is checked first. If the property is not
found, then the bindings are searched in order. The first binding that provides a match
is used to obtain the current value of the property.
When a new property is set on the element, the element is checked first. If the property does not currently exist on the element, then the bindings are searched in order. The first binding that has the property defined gets the property set. If no bindings match, then the property is set on the element and not on any of the bindings.
The DOM Window and the Bindings Array
Bindings that are applied to the body element in HTML or to the window element
in XUL are not placed on those elements. Instead they are applied to the DOM window that
contains the XUL or HTML document. The DOM window has a bindings
array just as DOM elements do, and it also supports programmatic manipulation of the bindings
themselves.