Application Services / Application Core

 

Table of Contents


Overview


XUL is an XML based grammar for specifying the  static GUI. An Application Service is the code that both has access to the GUI elements and the code for doing the specified work. The definition of an cross platform (XP) application is a small kernel that is able to load the static definition of the UI, the XUL or multiple XUL files, and the Application Service(s), the code for processing the XUL. The AppRunner is available today for this purpose.

NOTE: There is already a notion of a Service Manager in the Mozilla code base. So there is no confusion, throughout the rest of this document the term "Application Core" or AppCore will be used in the place of Application Service.

As stated earlier, the AppRunner has no application specific knowledge, it merely loads a XUL file and one of associated AppCores, then "hooks" them together and an application specific instance is born.
 

What is an AppCore?

An AppCore is one more objects that implements a set of functionality required for a specific type of application. For example, the mail application would conceivably have an MailAppCore that is capable of grabbing the data out of the UI and sending a message. The AppCore may be written entirely in JavaScript (JS), entirely in C++, or combination of the two (see Figure #1).
 
 


Figure #1


 


An AppCore is divided into two distinct pieces, the "glue" code and the application specific code. The "glue" code has knowledge of the UI and sits between the static UI elements and the application specific code. The application specific code's role is to know a little as possible about the GUI and only provide core application functionality. Figure #1 shows a line from the XUL to the JavaScript and then from the JS to the App. Specific Code (ASC). This path illustrates a user manipulating a UI element which causes a UI event to be fired. The event is processed in the JavaScript and then a call is made into the application specific code.

The line between the XUL and the ASC illustrates direct calls from the GUI into the ASC and notifications from the ASC to the UI (this later path to UI from the ASC is discouraged).

In the Mail example, consider a very simple UI. Text fields for the address, subject and the body of the message; and a single "Send" button for sending the contents of the UI to the specified address. The JS code for the XUL would contain two functions, a MailSendMsg function and a MailSent function. The user would fill in the message fields and then press "Send." The Send button invokes a XULCommand that executes the MailSendMsg function in the JS. The JS MailSendMsg function grabs the data out of the UI and makes a call into the ASC function called SendMail.

When the ASC's SendMail completes, it makes a JS call to invoke the MailSent function. The MailSent function can then do any type of notification to the GUI (i.e. like clearing the form).
 
 


Figure #2


 

How does it actually work?

Let's continue with the MAil example.

The Mail example loads an initial XUL file MailAppShell.xml at start up. This file defines three IFRAMEs: the toolbar area, the "content" area, and the status bar area. In this example, the content area will contain an HTML file defining a form for entering in a Mail message (see figure #3)
 



Figure #3

Next, we define the toolbar. The "Send" button defines a XULCommand to be executed when the user presses it. The XULCommands are also defined in the XUL files and they describe the JavaScript string they are to execute.
 

  <xul:toolbar>
   <html:button cmd="nsCmd:MailSendMsg" style="background-color:rgb(192,192,192);">
     <html:img src="resource:/res/toolbar/Mail_SendMsg.gif"/><html:BR/>Send
   </html:button>
   .
   .
   .
 </xul:toolbar>

  <xul:commands>
    <xul:command name="nsCmd:MailSendMsg" onCommand="SendMailMessage()"/>
    .
    .
    .
  </xul:commands>
 

The XULCommand nsCmd:MailSendMsg defines a JavaScript string that is to be executed when the button is pressed. This JS function will ask the AppCoreManager for a MailAppCore by a unique name. If the MailAppCore doesn't exist yet, it will create one. Then it gets the data out of the form and invokes the SendMail function on the MailAppCore object.

function SendMailMessage()
{
  var appCore = AppCoresManager.Find("MailCore");
  if (appCore==null) {
    appCore = new MailCore();
    if (appCore != null) {
      appCore.Init("MailCore");
     appCore.MailCompleteCallback("MailSent()");
     appCore.SetWindow(window);
     appCore.SendMail(document.forms[0].elements[1].value, document.forms[0].elements[2].value, document.forms[0].elements[4].value);
    }
  } else {
   appCore.SendMail(document.forms[0].elements[1].value, document.forms[0].elements[2].value, document.forms[0].elements[4].value);
  }
}
 
 

Next, we will define the GUI
 

<form ENCTYPE="text/plain" onSubmit="return submitForms()"></center>
<TABLE bgcolor="#C0C0C0">
<TR>
<TD>
<fieldset width="100%" height="100%" > 
<legend align=left>&nbsp;Info&nbsp;</legend>
<TABLE width="100%" height="100%">
  <TR>
    <TD>Address:</TD>
    <TD width="100%"><input type="text" name="addrTo" ></TD>
  </TR>
  <TR>
    <TD>Subject:</TD>
    <TD><input type="text" name="subject" id="subject"></TD>
  </TR>
</TABLE>
</fieldset>
</TD>
</TR>
<TR>
<TD>
<fieldset width="100%" height="100%" > 
 <legend align=left>&nbsp;Message&nbsp;</legend>
 <TEXTAREA cols=40 rows=15 name="mailbody"></TEXTAREA>
</fieldset>
</TD>
</TR></TABLE>
</form>

The MailAppCore code is as follows:

NS_IMETHODIMP
nsMailCore::SendMail(const nsString& aAddrTo, const nsString& aSubject, const nsString& aMsg)
{
  if (nsnull == mScriptContext) {
    return NS_ERROR_FAILURE;
  }
  printf("----------------------------\n");
  printf("-- Sending Mail Message\n");
  printf("----------------------------\n");
  printf("To: %s  \nSub: %s  \nMsg: %s\n", aAddrTo.ToNewCString(),
                                           aSubject.ToNewCString(),
                                           aMsg.ToNewCString()); // these ToNewCString's leak
  printf("----------------------------\n");

  if (nsnull != mScriptContext) {
    const char* url = "";
    PRBool isUndefined = PR_FALSE;
    nsString rVal;
    mScriptContext->EvaluateString(mScript, url, 0, rVal, &isUndefined);
  }
  return NS_OK;
}
 
 

Summary
The toolbar XUL loads a button with a specified XUL command: nsCmd:MailSendMsg. The "content" area of the window contains a simple HTML form for entering in the message data. The content area also contains JavaScript for processing the toolbar XUL. When the user presses on the "Send" button it issues a XULCommand that executes a JS function, in this case the JS function is "SendMailMsg". This JS function creates a MailAppCore object and invokes a method to send the message. The SendMail native method then executes a JS function as a callback to the GUI to allow it to clean up after the message is sent.
 

How do I create an Application Core?

In order to create and use a native code from JavaScript we must do several things:


In the Mail example, the MailAppCore is a single object with a JavaScript interface. The interface is defined with IDL and then run though the IDL compiler to create the nsIDOMxxx interface and the necessary "stub" functions needed to "hook" up the interface to a "real" implementation (see figure #6). Below, next to Figure #6, is the IDL for the MailAppCore.
 

Figure #6

interface MailCore : BaseAppCore
{
/* IID: { 0x18c2f980, 0xb09f, 0x11d2, \
    {0xbc, 0xde, 0x00, 0x80, 0x5f, 0x0e, 0x13, 0x53}} */

  void MailCore();

  void SendMail(in wstring addrTo, in wstring subject, in wstring msg);
  void MailCompleteCallback(in wstring script);
  void SetWindow(in Window win);

};
 

The C++ implementation will also be derived from the nsIDOMxxx interface that is automatically generated from the IDL compiler. Here is the C++ implementation with the new interface added:

class nsMailCore : public nsBaseAppCore,
                public nsIDOMMailCore
{
  public:

    nsMailCore();
    ~nsMailCore();

    NS_DECL_ISUPPORTS
    NS_IMETHOD    GetScriptObject(nsIScriptContext *aContext, void** aScriptObject);
    NS_IMETHOD    Init(const nsString& aId);
    NS_IMETHOD    GetId(nsString& aId) { return nsBaseAppCore::GetId(aId); }

    NS_IMETHOD    MailCompleteCallback(const nsString& aScript);
    NS_IMETHOD    SetWindow(nsIDOMWindow* aWin);

    NS_IMETHOD    SendMail(const nsString& aAddrTo, const nsString& aSubject, const nsString& aMsg);

  protected:

    nsString            mScript;

    nsIScriptContext   *mScriptContext;
    nsIDOMWindow       *mWindow;
};

The methods Init, GetId, and GetScritpableObject are defined in the IDL's base class BaseAppCore.

Here is a brief description of some of the methods:
 
Method Name
Function

Init

This method sets the name of the object and registers with the AppCoreManager

GetId

Returns the objects name

MailCompleteCallback

Sets the JavaScript string that will be executed after the message is sent

SetWindow

Sets the WebShell window into the object, this is needed so the object can execute JS

SendMail

This sends the message

NOTE: It is important that the name of the IDL object matches the name of the implementation object (except for the "ns" prefix). The IDL compiler generates stub code for the mapping of the JS function calls to an instance of the implementation object. If the names are different you will get a compiler error.

Your implementation object must have a standard factory for creating instances of it.
 

Registering the JS object into the External name space

The Mozilla service manager contains a service for registering the JS object into the JS script name space. The following code snippet shows how to get the service and register a new script name space:

    /***************************************/
    /* Add us to the Javascript Name Space */
    /***************************************/

    nsIScriptNameSetRegistry *registry;
    nsresult result = nsServiceManager::GetService(kCScriptNameSetRegistryCID,
                                                   kIScriptNameSetRegistryIID,
                                                  (nsISupports **)&registry);
    if (NS_OK == result)
    {
        nsAppCoresNameSet* nameSet = new nsAppCoresNameSet();
        registry->AddExternalNameSet(nameSet);
        /* FIX - do we need to release this service?  When we do, it get deleted,and our name is lost. */
    }

The nsAppCoresNameSet object implements the nsIScriptExternalNameSet interface and it provides two important functions:

  1. The AddNameSet method enables it to register one or more objects into the name space.
  2. The InitializeClasses method provides an easy way for the JS objects to initialize their classes at start up.

Here is a small portion of the code for adding it's name to the name space:

NS_IMETHODIMP
nsAppCoresNameSet::AddNameSet(nsIScriptContext* aScriptContext)
{
    nsresult result = NS_OK;
    nsIScriptNameSpaceManager* manager;

    result = aScriptContext->GetNameSpaceManager(&manager);
    if (NS_OK == result) {
        result = manager->RegisterGlobalName("MailCore", kMailCoreCID, PR_TRUE);
        .
        .
        .

        NS_RELEASE(manager);
    }
    return result;
}

Here is a snippet showing the initialization of the external class:

NS_IMETHODIMP
nsAppCoresNameSet::InitializeClasses(nsIScriptContext* aScriptContext)
{
    nsresult result = NS_OK;

    result = NS_InitMailCoreClass(aScriptContext, nsnull);
    .
    .
    .
    return result;
}

The External Objects DLL

The DLL containing the external object(s) has a method NSGetFactory where a CID is passed in and the appropriate factory is passed back. So each scriptable object needs it's factory. This method looks as follows:

extern "C" NS_EXPORT nsresult
NSGetFactory(const nsCID &aClass, nsISupports* serviceMgr, nsIFactory **aFactory)
{
    if (aFactory == NULL) {
      return NS_ERROR_NULL_POINTER;
    }

    *aFactory = NULL;
    nsISupports *inst;

    if ( aClass.Equals(kMailCoreCID) ) {
      inst = new nsMailCoreFactory();
    } else {
      return NS_ERROR_ILLEGAL_VALUE;
    }

    if (inst == NULL) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    nsresult res = inst->QueryInterface(kIFactoryIID, (void**) aFactory);
    if (res != NS_OK){
      delete inst;
    }

    return res;

}
 

How does this all fit together?

Until COMConnect is implemented, the objects that are in the external DLL must be registered into the nsRespository like all other Mozilla objects. Then the NameSet object for the objects must be added to the ScriptNameSetRegistry service. Now the application is ready to execute. When JS starts up it checks the registry and initializes all the classes (this is when the InitializeClasses method gets called.)

When JS encounters the new object in the script it looks it up in the external name set registry and is then able to create an object of this kind.


Conclusion

This document provides the initial approach to providing ApplicationCore objects to a XUL application using native code and scriptable interfaces. There are many unresolved issues that must be addressed. A few of these are as follows: