Mail event system

Warning: The content of this article may be out of date. It was imported from mozilla.org and last updated in 2002.

Mozilla mail requires an event system to notify different subsystems that data has changed. This document describes the system that events are passed amongst the mail objects.

For example, when a folder gets a new message, its total message count increases. The folder pane needs to know that this changed so that it can update the message count so the user can see it.

Interfaces

The key interfaces here are:

  • nsIFolder.idl is an interface for storing a hierarchy of objects with properties. This interface is not (should not be) mail-related. nsIFolders each store individual lists of folder listeners which are maintained with addListener() and removeListener(). When a notification is fired on a folder, all of the folder's folder listeners and the mail session itself is notified.
  • nsIFolderListener.idl is an interface that an object can implement if it is interested in listening to changes on single folder or on all folders. If the object just needs to be notified about one folder, it should call that folder's addListener method. If the object wants to know about all notifications on all folders, then it should register with the mail session.
  • nsIMsgMailSession.idl is a service that acts as a broadcaster for folder notifications. The mail session receives all notifications from all folders, and then forwards it on to any nsIFolderListeners that have registered themselves. The nsIMsgMailSession interface actually only allows adding and removal of listeners - the notifications happen through the nsIFolderListener interface as well.

Methods

Each event type has a two methods associated with it:

  • Notify*<event> - in the nsIFolder interface, and nsIMsgMailSession interface.
  • OnItem*<event> - in the nsIFolderListener interface. The mail session implements this interface.

Where

<event>

is the type of event, such as

BoolPropertyChanged

or the generic

Event

.

Sample control flow

Here is an example of a possible flow of control when a new message is added to a folder. In this example, there is a dialog open that shows properties for this folder including the message count. This dialog is a listener on this particular folder.

  1. A message is added to an nsImapMailFolder containing 4 messages.
  2. Because the number of messages in the folder have increased, this change in total message count needs to be broadcast to the world. The folder calls NotifyIntPropertyChanged on itself with the atom that represents "TotalMessages":
    this->NotifyIntPropertychanged(kTotalMessagesAtom, 4, 5);.
  3. NotifyPropertyChanged broadcasts this event to each its nsIFolderListeners by calling OnItemIntPropertyChanged on each listener:
    listener->OnIntPropertyChanged(this, kTotalMessagesAtom, 4, 5);
    • The dialog is one of these folder-specific listeners. In its implementation of OnIntPropertyChanged, it uses this information to update the message count in the dialog.
  4. NotifyPropertyChanged then broadcasts this event to the mail session:
    mailSession->OnIntPropertyChanged(this, kTotalMessagesAtom, 4, 5);
  5. The mail session rebroadcasts this information to each of the global listeners that has been registered with it. For each global listener, it calls OnIntPropertyChanged:
    listener->OnIntPropertyChanged(folder, kTotalMessagesAtom, 4, 5);
    • The folder datasource is a listener on all folders and receives this notification. In its implementation of OnIntPropertyChanged, it notifies RDF (via the nsIRDFObserver system) that the message count has changed. RDF handles the notification by updating the message counts in the folder pane.

Events

At the time this document is being written, these are the current events:

nsIFolder nsIFolderListener
NotifyItemAdded OnItemAdded
NotifyItemRemoved OnItemRemoved
NotifyItemPropertyChanged OnItemPropertyChanged
NotifyItemIntPropertyChanged OnItemIntPropertyChanged
NotifyItemBoolPropertyChanged OnItemBoolPropertyChanged
NotifyItemUnicharPropertyChanged OnItemUnicharPropertyChanged
NotifyItemPropertyFlagChanged OnItemPropertyFlagChanged
NotifyItemEvent OnItemEvent
NotifyFolderLoaded OnFolderLoaded
NotifyDeleteOrMoveMessages OnDeleteOrMoveMessages

Sample code

In this example, a listener will be set up to be notified when the message count changes in a folder:

// our variable to know if the listener fired
var listenerHasFired = false;
var totalMessagesListenerHasFired = false;

// the listening function that will react to changes
function myOnIntPropertyChanged(item, property, oldValue, newValue) {
  listenerHasFired=true;

  var propertyString = property.GetUnicode();

  dump("OnIntPropertyChanged has fired with property + " +
    propertyString + "!\n");
  if (propertyString == "TotalMessages") {
     totalMessagesListenerHasFired=true;

     //now show us visually
     var folder = item.QueryInterface(Components.interfaces.nsIMsgFolder);
     dump("The folder " + folder.prettyName + " now has " +
       newValue + " messages.");
  } else if (propertyString == "TestProperty") {
     dump("Recieved integer test property fired on folder " +
       folder.prettyName + " with values " + oldValue + " and " +
       newValue + "\n");
}

// set up the folder listener to point to the above function
var folderListener = {
  OnItemAdded: function(parent, item, viewString) {},
  OnItemRemoved: function(parent, item, viewString) {},
  OnItemPropertyChanged: function(parent, item, viewString) {},
  OnItemIntPropertyChanged: myOnIntPropertyChanged,
  OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) {},
  OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue) {},
  OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) {},
  OnItemEvent: function(item, event) = {},
  OnFolderLoaded: function(aFolder) = {}
  OnDeleteOrMoveMessagesCompleted: function( aFolder) = {},
}

// now register myself as a listener on every mail folder
var mailSession = Components.classes["component://netscape/messenger/services/session"].
  getService(Components.interfaces.nsIMsgMailSession);

mailSession.addListener(folderListener);

// now test to see if integer stuff is firing at all
// let's say "folder" is a folder we know about

// first we need an atom to play with
atomService = Components.classes["component://netscape/atom-service"].
  getService(Components.interfaces.nsIAtomService);

var testPropertyAtom = atomService.getAtom("TestProperty");

// now fire the test notification
folder.NotifyIntPropertyChanged(testPropertyAtom, 0, 100);

// Now we would do some operations to change the message count, such
// as copying a message into this folder or something. Then we could
// verify that our listener fired by checking if listenerHasFired and
// totalMessagesListenerHasFired are true

// this is left as an exercise for the reader.
      

Future plans

The notification system has two duplicate methods which could be implemented with OnItemEvent/NotifyItemEvent:

FolderLoaded

, and

DeleteOrMoveMessagesCompleted

. Both of these could be done by making atoms for each of these events and just firing it with NotifyItemEvent.

Completed

There are some of redundant methods between the nsIMsgMailSession and the nsIFolderListener interfaces. nsIMsgMailSession also contains a number of other methods that are completely unrealted to folder notification. It would make sense to collapse the nsIMsgMailSession simply into an object that implements the the nsIFolderListener interface to receive notifications from the folders. The notification functions should probably go to an nsIFolderBroadcaster interface or something, since they need to know what folder is being modified.


Alec Flett

Last modified: Fri Mar 31 12:22:03 PST 2000