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.
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).
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).
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)
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>
<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> |
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.
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 void MailCore(); void SendMail(in wstring
addrTo, in wstring subject, in wstring msg); }; |
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:
|
|
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 **)®istry);
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:
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 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.
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: