Mozilla Mail exposes many of it's data structures to RDF through a few datasources. This allows exposure of mailnews-specific data to user interface using RDF Templates.
You should learn about RDF before reading this document or you will be hopelessly confused.
The root resource for all accounts, folders and messages is the RDF Resource named msgaccounts:/. From this resource, you can follow a number of arcs to find servers, folders, and finally messages. Eventually we'll probably hang mail filters, annotations, etc, off of nodes in the graph. Here is an example of how this might be set up:
In this tree-style representation of an RDF graph, I've made arcs italic and resources bold.
msgaccounts:/ +-- http://home.netscape.com/NC-rdf#child --> | imap://alecf@imap.mywork.com | +-- http://home.netscape.com/NC-rdf#IsServer --> "true" | +-- http://home.netscape.com/NC-rdf#child --> | imap://alecf@imap.mywork.com/INBOX | +-- http://home.netscape.com/NC-rdf#TotalMessages --> "4" | +-- http://home.netscape.com/NC-rdf#IsServer --> "false" | +-- http://home.netscape.com/NC-rdf#MessageChild --> | | imap_message://alecf@imap.mywork.com/INBOX#1 | +-- http://home.netscape.com/NC-rdf#MessageChild --> | | imap_message://alecf@imap.mywork.com/INBOX#2 | +-- http://home.netscape.com/NC-rdf#MessageChild --> | | imap_message://alecf@imap.mywork.com/INBOX#3 | +-- http://home.netscape.com/NC-rdf#MessageChild --> | imap_message://alecf@imap.mywork.com/INBOX#4 | etc... +-- http://home.netscape.com/NC-rdf#child --> | mailbox://alecf@pop.mywork.com | +-- http://home.netscape.com/NC-rdf#IsServer --> "true" | +-- http://home.netscape.com/NC-rdf#child --> | mailbox://alecf@pop.mywork.com/INBOX | +-- http://home.netscape.com/NC-rdf#TotalMessages --> "2" | +-- http://home.netscape.com/NC-rdf#IsServer --> "false" | +-- http://home.netscape.com/NC-rdf#MessageChild --> | | mailbox_message://alecf@pop.mywork.com/INBOX#1 | +-- http://home.netscape.com/NC-rdf#MessageChild --> | mailbox_message://alecf@pop.mywork.com/INBOX#2 | etc...
There are of course many more properties that are exposed via RDF, but this should give you a feel for it.
Mail does not link the UI to datasources in a "traditional" manner.
Instead of having a singleton datasource that is shared across all UI components, we have per-view datasources. This allows each template-based widget to maintain view/window-specific data with each datasource. For example...??? (sorting? what else do we store?)Datasources are created when each window's JavaScript is loaded by declaring the datasource variables in the source javascript as global variables. In the document's onload= handler the datasources are attached to their respective widgets by setting the database property on each RDF template's parent element.
Answering Queries
Mail uses RDF Resource Factories to attach mail-specific information to RDF resources. (The details of RDF Resource Factories will be left to RDF documentation for now.) From an RDF Resource, it is possible to QueryInterface() to the appropriate mail/news object, and then access information from there.
For example, the folder pane needs to display the number of messages in the INBOX. Information for this column is queried when the tree's RDF Template calls the folder datasource's GetTarget() method. The query's target is the resource named mailbox://alecf@pop.myisp.com/INBOX and the property node is named http://home.netscape.com/NC-rdf#TotalMessages. This is basically what happens, behind the scenes:
var target = RDF.GetResource("mailbox://alecf@pop.myisp.com/INBOX"); var property = RDF.GetResource("http://home.netscape.com/NC-rdf#TotalMessages"); var resultNode = dataSource.GetTarget(target, property, true);
In the folder datasource's GetTarget(), target would be QueryInterfaced to a nsIMsgFolder. To get the total messages, the datasource would then call nsIMsgFolder.GetTotalMessages(). Finally, it would convert the result of this call to an RDF Literal, and pass it back through the return parameter of GetTarget().
An example of how this might work inside the datasource:
var msgCountArc = RDF.GetResource("http://home.netscape.com/NC-rdf#TotalMessages"); function GetTarget(target, property, unused) { var folder = target.QueryInterface(Components.interfaces.nsIMsgFolder); if (property == msgCountArc) { var msgCount = folder.GetTotalMessages(false); var result = RDF.GetLiteral(msgCount.toString()); return result; } }
Asynchronously notifying RDF
When a mail object's data changes and the data is reflected in RDF by notifying all of the observers that RDF has registered with the datasource.
In the example of mail folders, each folder datasource first registers itself with the mail session as a nsIFolderListener because it wants information about when a folder changes. Each template registers itself as an RDF observer. When a folder's contents or properties change, it tells the mail session to notify the folder listeners that the data has changed. The folder datasource then translates these property changes into OnAssert() or OnUnassert() calls to the observers.
The calling chain essentially looks like this:
Registration:
After reviewing this design, it might seem unnecessary to have the double-levels of notification/registration. Why can't folders directly notify the RDF Content observers when things change?
Here is the rational behind this design: