XUL Persistent State and Observers

30 Dec 2006

Neil Deakin

This document describes a means of maintaining state in a XUL document, as well as observing changes to an application's state.

1.1 Application storage

The appStorage window attribute may be used to retrieve a StorageList object which provides a per-domain Storage area. Unlike the storage areas provided by the globalStorage, the data within an appStorage is only available until the application terminates. The Storage objects retrieved in this manner otherwise operate identically.

1.2 Chrome usage of storage

Both the appStorage and globalStorage StorageList objects may take a chrome URL to retrieve a storage area for a particular chrome package identifier.

When calling the StorageList's namedItem method, if the domain argument begins with the string 'chrome://', then a chrome storage area is retrieved. If the domain of the calling script is not a chrome domain or otherwise doesn't have access to the chrome domain, then a security exception must be thrown. Otherwise, the chrome package id is determined by finding the first slash character (U+002F) after the 'chrome://' prefix and taking all characters between those exclusively. If the slash character does not exist, then all characters between the 'chrome://' prefix and the end of the string exclusively are used. The storage area for that package id is then returned. This effectively means that characters after the first slash have no effect on which storage area is returned.

Chrome storage areas behave identically to other storage areas.

Chrome will likely need a means of storing objects other than strings.

When a chrome storage area is changed, a storage event is fired on all other documents containing chrome URLs. The domain of this event is the string 'chrome://' concatenated with the chrome package id as calculated above.

1.3 Observing changes

The XUL <observes> element may be used to observe changes to a chrome storage area, as well as DOM attribute changes to another element.

The type DOM attribute on the <observes> element is used to specify the type of storage area to listen for changes to. Possible values for this attribute are:

1.3.1 Determining the observing element

Regardless of the type specified for the <observes> element, it may need to determine the element that will receive the change, called the observing element. If the parent node of the <observes> element is an <observesset> element, then the observing element is the <observes> element itself. Otherwise, it is the parent node of the <observes> element.

1.3.2 Storage observers

When the type attribute on an <observes> element is set to either session, app or global, the observer listens to changes to a storage area. It only listens to changes to a storage area of the appropriate type. Changes to storage areas of other types are not examined.

The domain attribute is used to filter the observer such that it only listens to changes to the storage area of a particular domain. For chrome storages, the domain attribute should be set to the string 'chrome://' concatenated with the chrome package id to listen to.

The key attribute may be used to filter the observer such that it only listens to changes to particular keys. The observer will ignore changes to other keys. The value of the key attribute should be the key to listen to.

The attribute attribute specifies the name of the attribute to modify on the observing element when the storage data changes. When the value of the key and domain is changed, the value of the specified attribute is set to that value. If the attribute attribute is not specified, no attribute is set.

Here are some examples of observers:

<observerset onbroadcast="dosomething();">
  <observes type="global" domain="www.example.com"/>
  <observes type="app" domain="chrome://testapp" key="seen-once"/>
  <observes type="app" domain="chrome://testapp" key="seen-once"
            attribute="value"/>
<observerset>

The first observer listens to all changes to the global storage for the domain 'www.example.com'. If any key within global storage changes, either by being set, modified or removed, the observer is notified of the change. This example uses the broadcast event to listen for changes, as explained in a later section. Note that there is no means of determining which key was actually changed.

The second observer listens to the application storage for the chrome package 'testapp'. However, as the key attribute has been set, it is only notified of the changes to the key 'seen-once'. Within the broadcast event listener, the value of this key could be examined.

The third observer is similar to the second except that the value attribute of the <observes> element is set to the value of the 'seen-once' key whenever it is changed.

This next example allows a button to be hidden when the key 'hidedetails' is modified in global storage.
<button id="details-button" label="Details...">
  <observes type="global" domain="chrome://testapp"
            key="hidedetails" attribute="hidden"/>
</button>

<button label="Hide" oncommand="globalStorage['chrome://testapp'].hidedetails = true"/>

1.3.3 Element observers

When the type attribute on an <observes> element is set to element or not specified, the observer is used to listen for changes to the attributes of another element within the same document.

The element attribute specifies the id of another element within the same document. If the id matches the id of the <obseves> element itself, or doesn't match the id of any element within the document, the <observes> element has no effect and observes nothing. If the value of the element attribute is modified, then the observer must stop observing the element specified by the old value and begin observing changes to the new element. Inserting an <observes> element into a document causes it to begin observing changes the the specified element, while removing one fron a document causes it to stop observing changes.

If the element attribute refers to an id that doesn't match an element within the same document, the observer does nothing. Inserting an element matching this id does not cause the observer to begin observing changes.

The attribute attribute specifies the name of the attribute to listen for changes to. When the value of the attribute of the element being observed (specified by the element attribute) changes, the same attribute on the observing element should be set to the same value. If the attribute is removed, then the same attribute on the observing element element should be removed. Thus, after a change to this attribute to the element being observed, the methods hasAttribute and getAttribute should return the same values for both the observing element and the observed element. Changing the attributes directly on the observing element does not cause the observed element to change in any way.

If the attribute attribute is set to the value *, or is not specified, then all attributes are observed except the id, ref and persist attributes. This functions as with observing a single attribute, except any attribute modified on the observed element causes the corresponding attribute on the observing element to be set to the same value.

1.3.4 Broadcaster element

The <broadcaster> element is a convenient placeholder when several element observers wish to observe an attribute change. Any attribute is valid on the <broadcaster> element. Typically, several observers will observe a broadcaster so that a change made to the attributes of a broadcaster will propagate to all of the other elements.

1.3.5 Observes attribute

The observes attribute applies to any XUL element. It is a shorthand for defining a child <observes> element with the attribute attribute set to *. The value of the observes attribute is used as the value for the observer's element attribute. When using the attribute form, the element becomes the observing element.

If the observes attribute is set or modified on an element, then it will begin observing the element with the id in the same document specified by the value of the attribute. If an element with this id does not exist in the document, then the observer does not observe anything. Adding an element to the document with this id does not cause the observe to start observing that element.

Removing the observes attribute from an element causes the observer to stop observing that element.

Thus, the following two examples are equivalent:

<button label="Show" observes="show-toolbar"/>

<button label="Show">
  <observes element="show-toolbar"/>
</button>

1.3.6 Broadcast event

The broadcast event, which bubbles, is not cancellable, and has no default action is fired on the <observes> element whenever a change is made that affects the observer. The broadcast event is fired only when using the <observes> element and is not fired at all when using the shorthand observes attribute.

In this example, the dosomething() method will be called when the show-images key is changed with the application storage area for the 'sample' chrome package.

<observes type="app" domain="chrome://sample"
          key="show-images" onbroadcast="dosomething();"/>

2.1 Commands

Add stuff about commands here.

3.1 Persistent attribute store

XUL provides a automatic persistent storage which is more convenient when storing the state of the attributes of an element. The persistent attribute store saves the state of attributes when they are changed, and restores them again when the document with the same URI is loaded again.

The persist attribute on an element specifies a space-separated list of attribute names that should be persisted for that element. If the element does not have an id, then the persist attribute has no effect. Otherwise, whenever an attribute contained within the list of attributes specified by the persist attribute is changed on that element, the new value is saved in the persistent attribute store in such a way that the value can be restored later as described below. If an attribute is removed, an indication of this should also be stored.

When the URI of the document of an element with a persisted attribute is later loaded again (whether the same document or another document), the value of that attribute is set to the persisted value. This should also restore the state of removed attributes such that the element should not have the attribute. The process of restoring persisted attributes is done during or after parsing the document, but before the load event is fired on the document. Persistence also applies to a generation element created from a template when it is inserted into the document. Other than the elements existing in the document when it is parsed, or template generation elements, elements inserted later do not have persisted attributes applied. Persisted attributes are restored regardless of whether the attribute to restore is contained with the list specified by the persist attribute when restoring it.

Adding, changing or removing the persist attribute on an element only affects later attribute changes to the element and does not change the persistent attribute store. (unless the persist attribute is itself contained within the list of attributes to persist, in which case it should be stored as with any other attribute).

In order for this to work, the persistent attribute store will need to store the document's URI, the id of the element, the attribute name and the attribute value.