Adding a New Menu Command

From MozillaWiki
Jump to: navigation, search

Here are the steps I used to add a context menu to the mailbox window pane and for responding when the user selects that menu.

The first step was to specify the text and access key (Windows mnemonic) of the menu:

 mozilla\mail\locales\en-US\chrome\messenger\messenger.dtd:
   ...
   <!ENTITY folderContextDiscoverSubFolders.label "Refresh Folder List">
   <!ENTITY folderContextDiscoverSubFolders.accesskey "f">


Second you need to specify the function that will be called when that menu item is chosen:

 mozilla\mail\base\content\mailWindowOverlay.xul:
   <menuitem id="folderPaneContext-discoverSubFolders"
       label="&folderContextDiscoverSubFolders.label;"
       accesskey="&folderContextDiscoverSubFolders.accesskey;"
       oncommand="MsgDiscoverSubFolders();"/>


Next you need to implement the associated function (in this case MsgDiscoverSubFolders()).

Implementations for menu handling functions obviously vary widely and I will not attempt to offer any sort of thorough explanation for all the different cases here. My primary interest was in how the JavaScript code ultimately called into the C++ code. This is generally (always?) done via calling the DoCommand() function for some class. The class used varies depending on what you are trying to accomplish and is crucial for determining that your command is routed to the correct location. Some of the options you see in the JavaScript files are dataSource.DoCommand() (which is what happens when you call DoRDFCommand()), gDBView.doCommand() and nsMessenger.DoCommand(). This latter case is a little less clear since the JS code generally calls into the nsMessenger class via a function other than DoCommand() and this function is what actually calls DoCommand(). (As a piece of personal commentary, I would find this whole thing a lot clearer if a single convention were used (i.e., if every JS function called into someone's DoCommand()) but that is perhaps too much to hope for in an open source effort like this.)

In the case of the refresh mailbox list command, I determined (eventually) that I should follow the model of certain other folder level operations like compact, rename folder and empty trash. So my JS function (stripped down for purposes of illustration) looks essentially like this:

 mozilla\mailnews\resources\content\widgetglue.js:
 mozilla\mail\base\content\widgetglue.js:
   function MsgDiscoverSubFolders()
   {
     messenger.DiscoverSubFolders(GetFolderDatasource(), resource);
   }


As suggested above, nsMessenger::DiscoverSubFolders() essentially just calls nsMessenger::DoCommand():

 mozilla\mailnews\base\public\nsIMessenger.idl:
   interface nsIMessenger : nsISupports {
   ...
     void DiscoverSubFolders(in nsIRDFCompositeDataSource db,
                             in nsIRDFResource folder);
   ...
   };
 mozilla\mailnews\base\src\nsMessenger.cpp:
   NS_IMETHODIMP
   nsMessenger::DiscoverSubFolders(nsIRDFCompositeDataSource* db,
                                   nsIRDFResource* folderResource)
   {
     rv = DoCommand(db, NS_LITERAL_CSTRING(NC_RDF_DISCOVERSUBFOLDERS), folderArray, nsnull);
     return rv;
   }


After much hand-waving (you can step through the code for yourself) we find ourselves in nsMsgFolderDataSource::DoCommand() which is the central processing area for many of these commands. (I don't offer any guarantee that for every command after the hand-waving you will end up in nsMsgFolderDataSource::DoCommand() since different commands are doubtless routed different directions.) A number of changes must be made to nsMsgFolderDataSource to support a new command. The following code excerpts show the lines of code I added but where in the file(s) they were added is left as an exercise for the reader. These changes are essentially a copy+paste+rename of every reference to an existing command:

 mozilla\mailnews\base\src\nsMsgRDFUtils.h:
   #define NC_RDF_DISCOVERSUBFOLDERS   NC_NAMESPACE_URI "DiscoverSubFolders"
 mozilla\mailnews\base\src\nsMsgFolderDataSource.h:
   static nsIRDFResource* kNC_DiscoverSubFolders;
 mozilla\mailnews\base\src\nsMsgFolderDataSource.cpp:
   nsIRDFResource* nsMsgFolderDataSource::kNC_DiscoverSubFolders= nsnull;
   ...
   rdf->GetResource(NS_LITERAL_CSTRING(NC_RDF_DISCOVERSUBFOLDERS), &kNC_DiscoverSubFolders);
   ...
   NS_RELEASE2(kNC_DiscoverSubFolders, refcnt);
   ...
   cmds->AppendElement(kNC_DiscoverSubFolders, PR_FALSE);
   ...
   (aCommand == kNC_DiscoverSubFolders) ||


The main command handling is in nsMsgFolderDataSource::DoCommand() so we need to add the new command to the if/else if statement:

 mozilla\mailnews\base\src\nsMsgFolderDataSource.cpp:
   nsMsgFolderDataSource::DoCommand()
   {
     ...
     else if ((aCommand == kNC_DiscoverSubFolders))
     {
       rv = folder->DiscoverSubFolders(nsnull, window);
     }
     ...
   }


You also need to implement the folder method for the nsMsgDBFolder class. In the case of a good number of these commands (including the folder level operations I emulated) this method is empty. Actions that do operate on the message database do have meaningful content in their methods, but for folder level operations this is a place-holder function which is never actually reached. My new handler method looks exactly like this:

 mozilla\mailnews\base\util\nsMsgDBFolder.cpp:
   NS_IMETHODIMP
   nsMsgDBFolder::DiscoverSubFolders(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
   {
     return NS_ERROR_NOT_IMPLEMENTED;
   }


Returning NS_ERROR_NOT_IMPLEMENTED is an indication that the actual implementation is done in a subclass. In my case the implementation is here:

 mozilla\mailnews\imap\src\nsImapMailFolder.cpp:
   nsImapMailFolder::DiscoverSubFolders(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
   {
     ...
   }

The remaining details are specific to the refresh mailbox list feature which is not of general interest.