Finding undocumented capabilities for Mozilla extensions

Recently I looked for a way to get access to a fully assembled email just before it is sent. After crawling through the Mozilla Developer Network for quite some time I realized that there seems to be no support provided for this. So I wondered whether it would be possible anyways, even if the documentation suggests otherwise, and started tracing the sending process in Thunderbird.

In the following I explain how I found an undocumented way to intercept the sending process immediately before emails are sent. The used approach can be applied to any Mozilla-based application.

Getting started

First the very part of the user interface where the course of action to be interfered starts must be identified. A valuable tool for this is the DOM Inspector extension (available for Firefox, Thunderbird and SeaMonkey). It can be launched from the tools menu and allows to inspect an opened composer window via File -> Inspect Chrome Document. The tool lists the DOM of the selected chrome document and also shows the loaded overlays and scripts. This saves a lot of time which would be required for manually inspecting the XUL files.

Composer window loaded in DOM Inspector

The DOM structure of a composer window in DOM Inspector.

When a structural element from the inspected XUL file is selected, DOM Inspector highlights the corresponding element in the user interface by a red rectangle drawn around it. This way the triggers for particular actions can easily be found. The screenshot on the left shows the details for the button which triggers the email sending process. On the right the attributes of the button and their values are listed. The oncommand attribute is the one of interest. It says that clicking the button triggers the JavaScript call to goDoCommand('cmd_sendButton').

Following the control flow

Now we know that goDoCommand('cmd_sendButton') is the part of the implementation that starts the sending process (or more precise that it is one way to do so). Next we need to find its implementation, so we have a look at the scripts that are loaded into the composer window. DOM Inspector presents a list of all scripts loaded in a window at the top in the window node. To find the implementation of goDoCommand we could manually inspect all loaded script files.

Tracing with MXR

Or, more convenient, we let Mozilla-Cross-Reference (MXR) search for us. But the result for the search string ‚goDoCommand‚ reveals a lot of results, so to get less results and because we look for a function declaration, ‚function goDoCommand‚ limits the number of results to currently five. Comparing the names of the found files with the loaded files indicates that we find the function declaration in globalOverlay.js. But what we see is that this is a rather general function which delegates the actual work to another function doCommand. To locate doCommand’s implementation we again consult MXR with ‚function doCommand‚. The result is not unique, but ‚MsgComposeCommands.js‚ turns out to be the file we want.

Manual code execution

Now we need to manually execute doCommand with the parameter 'cmd_sendButton' as found earlier. The function checks whether the command is enabled and if so either calls SendMessageLater or SendMessage. As the latter function suggests to trigger the actual sending process, I searched its code which again is in MsgComposeCommands.js just like the GenericSendMessage function that is called from it. GenericSendMessage is the first method which contains some more logic than deciding what other function to call. So I read its code in order to understand what it does. The descriptive variable and function names in the code facilitate its understanding in addition to the comments. A lot of „stuff“ happens here and then in line 2154 I recognized the firing of an event which I already stumbled upon in the MDN, the compose-send-message event which according to the documentation allows to access the body of an email before it is written to the actual email:

  1. var event = document.createEvent('UIEvents');
  2. event.initEvent('compose-send-message', false, true);
  3. var msgcomposeWindow = document.getElementById("msgcomposeWindow");
  4. msgcomposeWindow.setAttribute("msgtype", msgType);
  5. msgcomposeWindow.dispatchEvent(event);

But as the final email is not built yet at this point in time, I couldn’t use it for my purpose. So I continued reading the code which lead to another interesting line of code– seems like we’re getting closer to the assembly process.:

  1. gMsgCompose.SendMsg(msgType, getCurrentIdentity(), currentAccountKey,
  2.                                                                  msgWindow, progress);

To find the implementation of gMsgCompose’s SendMsg method I just clicked on the method’s name and got this result. The third entry caught my interest because the filename nsMsgCompose.cpp is similar to the object’s name on which the method is called. In fact, it really is the corresponding implementation file and SendMsg is declared in line 1064.

Diving into Thunderbird’s core

Now the path leads into the guts of Thunderbird. For a vast amount of functionalities it is not required to leave the comfortable domains of XUL and JavaScript because a lot of built-in functions of Gecko, of which a lot is written in C++, is reflected in JavaScript via XPCOM. Here we reached the boundary, the gMsgCompose object is a JavaScript reflection of the nsMsgCompose class defined in nsMsgCompose.cpp. From here on the goal is to follow the control and data flow within the core implementation and keep an eye out for a possibility to intercept it. Depending on what you are looking for, this could be a variable which carries a value of interest or an event which let’s you interfere the flow at an interesting state of execution. If one is not familiar with reading C++-Code it takes a little getting used to it, but I got along with this in a short time. Throughout the course of SendMsg the email is still not assembled, but it turns out that in line 1181an event is fired which could maybe be observed:

  1. mProgress->OnStateChange(nsnull, nsnull, nsIWebProgressListener::STATE_START, NS_OK);

But as the email is not assembled yet, I didn’t put effort into getting a handler attached for this event. It seems pretty challenging, if even possible, because the the object which receives this event is created just before the SendMsg call is made to which it is passed. Finally the control is forwarded to _SendMsg in the same file.

Getting closer to the target

Again some things are done, amongst one is the instantiation of the MsgSend object which will finally carry out the assembling  of the mail. The part essential for my goal starts in line 999:

  1. // Create the listener for the send operation...
  2. nsCOMPtr<nsIMsgComposeSendListener> composeSendListener =
  3.                    do_CreateInstance(NS_MSGCOMPOSESENDLISTENER_CONTRACTID);
  4.  
  5. // [...]
  6.  
  7. nsRefPtr<nsIMsgCompose> msgCompose(this);
  8. composeSendListener->SetMsgCompose(msgCompose);
  9. composeSendListener->SetDeliverMode(deliverMode);
  10.  
  11. if (mProgress)
  12.     {
  13.         nsCOMPtr<nsIWebProgressListener> progressListener =
  14.                                     do_QueryInterface(composeSendListener);
  15.         mProgress->RegisterListener(progressListener);
  16.     }
  17.  
  18. // this is a cast which keeps composeSendListener with a
  19. // reference as nsIMsgSendListener
  20. nsCOMPtr<nsIMsgSendListener> sendListener = do_QueryInterface(composeSendListener);
  21. rv = mMsgSend->CreateAndSendMessage(
  22.       m_composeHTML ? m_editor.get() : nsnull,
  23.       identity,
  24.       accountKey,
  25.       m_compFields,
  26.       PR_FALSE,                           // PRBool           digest_p,
  27.       PR_FALSE,                           // PRBool           dont_deliver_p,
  28.       (nsMsgDeliverMode)deliverMode,      // nsMsgDeliverMode      mode,
  29.       nsnull,                             // nsIMsgDBHdr     *msgToReplace,
  30.       m_composeHTML?TEXT_HTML:TEXT_PLAIN, // const char      *attachment1_type,
  31.       bodyString,                         // const char      *attachment1_body,
  32.       bodyLength,                         // PRUint32        attachment1_body_length,
  33.       nsnull,                             //                 *attachments,
  34.       nsnull,                             //                 *preloaded_attachments,
  35.       nsnull,                             // nsMsgSendPart          *relatedPart,
  36.       m_window,                           // nsIDOMWindowInternal   *parentWindow;
  37.       mProgress,                          // nsIMsgProgress         *progress,
  38.       sendListener, // listener
  39.       mSmtpPassword.get(),
  40.       mOriginalMsgURI,
  41.       mType);

The composeSendListener variable which is handed in to the CreateAndSendMessage call on the MsgSend object in mMsgSend as sendListener (fourth parameter from the bottom) will help. Let’s have a closer look at this.

The undocumented event: onStartSending

In the call from above, sendListener is of type nsIMsgSendListener. Having a look at its definition reveals that it consists of six events of which onStartSending is one. Its name and even more its comment suggest that this is exactly what I was looking for all the time:

This method is called only once, at the beginning of a message send operation.

Searching a hook to attach an own listener

As found, the object behind sendListener is composeSendListener. And on that object SetMsgCompose(msgCompose) was called where msgCompose is a reference to this. The file in which this code resides is ‚nsMsgCompose.cpp‚ and defines the nsMsgCompose class. Having a look at the corresponding header file ‚nsMsgCompose.h‚ yields that this class implements the nsIMsgCompose interface. In this files several interfaces are defined, the definition of the nsIMsgCompose interface begins in line 137. The good news are placed in line 314, there a method addMsgSendListener is defined. As the method declaration is not prevented from being reflected into JavaScript by a [noscript] prefix like e.g. this one, it should be no problem attaching an own MsgSendListener to gMsgCompose in the compose window.

Getting attached

The next steps are straightforward. A reference to the gMsgCompose object is required and then a listener implementing the nsIMsgSendListener interface can be added. Because Thunderbird recycles the compose window after the first use I encountered somewhat strange effects with listeners directly attached to it in its onload event. Thus I used an observer for the mail:composeOnSend event via the observer service in an overlay for the main window (messenger.xul):

  1. // this resides in code executed when the onload event for messenger.xul is triggered
  2. var myOnSendListener = {
  3.     observe:
  4.         function(subject, topic, data){
  5.             // thunderbird sends a notification right before the mail gets assembled
  6.             // doing stuff in the compose window is tricky as they reuse compose windows
  7.             // subject is a reference to the compose window
  8.             subject.gMsgCompose.addMsgSendListener(new MyMsgSendListener());
  9.         }
  10. };
  11. var observerService = Components.classes["@mozilla.org/observer-service;1"]
  12. .getService(Components.interfaces.nsIObserverService);
  13. observerService.addObserver(myOnSendListener, "mail:composeOnSend", false);

Finding the assembled email

The constructor for MyMsgSendListener must of course be accessible from the above code. The following snippet shows how it can obtain a reference to the assembled mail:

  1. function MyMsgSendListener(){
  2.     this.onStartSending =
  3.         function(aMsgID,aMsgSize){
  4.             // when this event is fired, thunderbird has assembled the
  5.             // mail it is to send in an temporary .eml file in the temp directory
  6.             // first fetch temp directory (has nsIFile interface)
  7.             var tmpDirFile =
  8.                      Components.classes["@mozilla.org/file/directory_service;1"].
  9.                          getService(Components.interfaces.nsIProperties).
  10.                              get("TmpD", Components.interfaces.nsIFile);
  11.             // then check for the newest .eml file
  12.             var entries = tmpDirFile.directoryEntries;
  13.             var assembledMail = null;
  14.             while(entries.hasMoreElements()){
  15.                 var entry = entries.getNext();
  16.                 entry.QueryInterface(Components.interfaces.nsIFile);
  17.                 // check whether the current file has .eml extension
  18.                 if ((/.eml$/i).test(entry.leafName)){
  19.                     if(assembledMail == null)
  20.                         assembledMail = entry;
  21.                     else{
  22.                         // the assembled mail should be the newest .eml file
  23.                         if(assembledMail.lastModifiedTime < entry.lastModifiedTime)
  24.                             assembledMail = entry;
  25.                     }
  26.                 }
  27.             }
  28.             // assembledMail now carries a reference to the assembled mail
  29.         };
  30.     this.onProgress = function(aMsgID, aProgress, aProgressMax){};
  31.     this.onStatus = function(aMsgID, aMsg){};
  32.     this.onGetDraftFolderURI = function(aFolderUri){};
  33.     this.onStopSending = function(aMsgID, aStatus, aMsg, aFile){};
  34.     this.onSendNotPerformed = function(aMsgID, aStatus){};
  35. }

When the user triggers the sending process the onStartSending handler will be triggered and search the assembled email in the temporary files directory (this is OS independent). The variable assembledMail will carry a nsIFile reference to it when its done. To read the email’s contents the File I/O documentation can be consulted.

Some remarks

For the above approach to detect the assembled email please note that:

  1. This may take its time for a temp directory with a lot of files
  2. The identified file may be the wrong one in a multi-user setting (simultaneous sending of mails)
  3. Thunderbird will send the file after the onStartSending event was handled as it is

The last point is important if the file was modified – Thunderbird does no further checks to verify the compliance of the mail with the RFC 5322 standard.

Summary

If you miss a way to intervene the control or data flow within a Mozilla application at a certain point by an extension then digging in the source code of that application may reveal a possibility to do so. To be successful, one must consequently follow the control and data flow subsequent to an triggering action. MXR and MDN are useful tools to find out where the respective code resides and what types of objects are around.

Comments (1)

TimoMärz 1st, 2012 at 16:49

Thanks for the work you´ve done.
Absolutely that what i´ve looked for.
Had to use the ability to pipe the email just to an file in the moment it will be sent.
Also very good described, as i am a newbie to program Thuderbird addons.

Best regards,

Timo

Leave a comment

Your comment

(required)