Back to Art & Logic Resources

Embedding Mozilla in Mac OS X Cocoa Apps

Table of Contents

Introduction
Mozilla Build
      1) Download and install Fink
      2) Download and install Orbit using Fink
      3) Download the SharedMenusFramework
      4) Pull Mozilla (Chimera) makefile
      5) Add options
      6) Build Mozilla
      7) Build embed.jar
NSBrowserView Build
      Optionally, build the test app for Cocoa
Linking HTML Content to your application
      Code
      Setup
      Problems
            Security Manager
            Round-trip communication
Licensing and Distribution
Conclusion

Introduction

“Embedding Mozilla” can mean many things. For our purposes, it does not mean “embedding Gecko”, the underlying engine. Furthermore, there are many “flavors” of Mozilla, one of which is named Chimera. Chimera is a standalone browser built in Cocoa, on top of shared Mozilla sources (which, in turn, are on top of Gecko), for Mac OS X. For our purposes, “embedding Mozilla” actually means “embedding Chimera”, or, more accurately, “embedding CHBrowserView, which is based on Chimera”.

It is worth noting that “Camino” is the Mozilla team’s next great OS X effort. When Camino is stable and embeddable as Chimera is, it will be the likeliest candidate for OS X Cocoa embedding. The magnitude of the difference between Camino and Chimera, while more than a simple name change, is difficult to estimate at this point, especially with respect to the embedding effort. Much of the underlying source base is identical (layers of it, in fact, are shared with OS X Carbon and other layers are shared even with Windows/*nix manifestations of Mozilla browsers). However, the Cocoa layer itself is undergoing significant transformation, likely resolving some of the issues called out later in this document.

It is also worth mentioning that Apple has been working on Safari, which consists of KDE's KHTML engine, WebCore (an Objective-C framework for processing and displaying web pages), and an unfinished Safari SDK (promised at WWDC 2003) which will purportedly make embedding a browser as simple or simpler than the procedure detailed in this document. What remains to be seen is whether or not Safari’s WebCore, JavaScriptCore, or SDK will include mechanisms for hooking web-page JavaScript code to the embedded browser or, ultimately, to the embedding application. If such mechanisms are provided and other factors such as performance are comparable, Safari will be well worth considering for full-scale browser embedding. Until then, Mozilla is the only well-known cost-effective option. However, one important general distinction between Chimera/Camino and Safari is that Chimera/Camino exposes a Cocoa programming interface, but not a Cocoa HTML GUI. Safari uses Cocoa widgets when rendering HTML.

Note that initial attempts to embed Mozilla in an OS X 10.1.x application were only moderately successful. For a variety of reasons, 10.1.x was being abandoned for 10.2, and it is recommended that these instructions be followed for 10.2+ only.

That said, this document intends to cover the necessary steps to embed NSBrowserView and hook it up to the embedding application; it intends to:

Mozilla Build

The process below will probably take about 5 hours to complete, if all goes well. Nearly 4 of those hours will be download and build steps that don't require much or any interaction.

Here are some references for the process. Note that where these references refer to Camino, they may or may not be accurate representations of what needs to be done for Chimera:

1) Download and install Fink

There are two ways to do this.

Due to a bug in Fink, it is very important that the Fink installer not be used more than once (it can cause severe system problems, kernel panic, system instability, etc.) Therefore, at the time of this writing, the following paragraph should not be followed to install Fink for 10.2 if you already have Fink for 10.1 (pre 0.4.0, that is). If you are in this situation, skip to the next paragraph that describes upgrading your Fink.

The most direct way is to install Fink for 10.2 according to http://fink.sourceforge.net/download/index.php and http://fink.sourceforge.net/news/jag-bootstrap.php. Follow steps 1-4 of the instructions at http://fink.sourceforge.net/news/jag-bootstrap.php. Try step 5, but you may not have any luck connecting to the sourceforge server (it’s very busy). Step 5 can be replaced by first typing

> fink scanpackages

to get a list of available packages and then

> sudo dselect

which launches a package selection utitlity. Follow the on-screen instructions to download orbit (the full instructions for the step 5 replacement are found at http://fink.sourceforge.net/download/index.php, step 4). Note that the “Download and install Orbit using Fink” section, below, may not be necessary if you follow this route.

Alternately, if you have a version of Fink for OS X 10.1, you will need to upgrade. Due to a bug in Fink, it is very important that the Fink installer not be used more than once (it can cause severe system problems, kernel panic, system instability, etc.). Therefore, at the time of this writing, the following process was required to upgrade:

a) Download:

> curl http://us.dl.sourceforge.net/fink/fink-0.4.0a-installer.dmg -O

b) Double-click the .dmg to mount, double-click the mounted volume, double-click the installer, and follow the instructions.

c) Edit your ~/.cshrc and/or ~/.tcshrc file, adding:

#!/bin/tcsh
source /sw/bin/init.csh

d) Source the file; e.g.:

> source ~/.cshrc

e) Get the update patches for OS X 10.2 (the following is from http://fink.sourceforge.net/news/jaguar.php):

Step 1: Install Mac OS X 10.2 and the OS X 10.2 Developer Tools. A binary option is not yet available for the 10.2 version of Fink.

Step 2: Obtain the files for the Fink upgrade. Download the Fink 0.11.1 archive. Double click on the archive to expand it (Stuffit Expander or OpenUp both work), then open a terminal window and “cd” into the fink-0.11.1 directory. All subsequent commands will assume that you are in the fink-0.11.1 directory.

Step 3: Install Fink. To do this, run this command from within the fink-0.11.1 directory.

> ./bootstrap.sh

Step 4: Add fink to your paths. Type:

> pico ~/.cshrc

A text editor will pop up. Enter this line:

source /sw/bin/init.csh

To get out of the editor, press control-O, return, control-X. Close the Terminal.app window and open a new one.

Step 5: Obtain updated fink packages. To do this, issue the command:

> fink selfupdate-cvs

You will need an active internet connection during this step, which will provide you with package descriptions for the 10.2 packages.

f) Finally, when all of this has been completed, you may remove the fink-0.11.1 archive, directory, and its contents.

2) Download and install Orbit using Fink

Issue the command:

> fink install orbit

Note that this will probably fail multiple times. Just continue to hit '3' to try another server. One of them should finally work, usually within 10 tries.

3) Download the SharedMenusFramework

This can be found at ftp://ftp.url-manager.com/pub/SharedMenusCocoa.sit.bin. Unstuff it and place the framework (SharedMenusCocoa/build/SharedMenusCocoa.framework) into /Library/Frameworks. Double check all the of symbolic links in the framework. I found that Headers, Resources, Current, and SharedMenusCocoa were all pointing to the wrong places. Fix these if necessary.

4) Pull Mozilla (Chimera) makefile

Issue the commands:

> setenv CVSROOT :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot

You may wish to do this in any number of other ways, but note that if you’re in a CVS tree, such as one you’ve set up with MacCVS Pro, you may have difficulties. Refer to CVS docs to work through any problems.

> cvs login

Use the password 'anonymous' (w/o quotes) - you’ll only need to login once

> cvs co -r CHIMERA_M1_0_1_BRANCH mozilla/client.mk

It is important that you pull the Chimera branch, specifically. Alternately, you may wish to try the Camino branch to see if the similarities are close enough to enable Camino embedding.

5) Add options

Get into the mozilla tree:

> cd mozilla

In this directory, create a file called .mozconfig (note the leading period). In that file, put the following:

ac_add_options --enable-default-toolkit=cocoa
ac_add_options --disable-tests
ac_add_options --enable-crypto
ac_add_options --disable-mailnews
ac_add_options --disable-ldap
ac_add_options --disable-accessibility
ac_add_options --disable-jsd
ac_add_options --enable-plaintext-editor-only
ac_add_options --disable-mathml
ac_add_options --enable-extensions=cookie,xmlextras,universalchardet
ac_add_options --with-system-zlib

Optionally, add this to enable compiling on several processors:

mk_add_options MOZ_MAKE_FLAGS=-j4   # use parallel make

This defaults to building debug. If you want to build mozilla optimized, add these lines also:

ac_add_options --disable-debug
ac_add_options --enable-optimize=-O2

6) Build Mozilla

This will take several hours.

> make -w -f client.mk

For future builds, if you want to skip the CVS update, use

> make -w -f client.mk build

7) Build embed.jar

Note that this must be done after the first build, or after any clean+rebuild. However, do not run this steps in-between update-builds. embed.jar must be built (after a clean build, as just noted) prior to building NSBrowserView, which of course must be rebuilt before building CocoaEmbed or your own project. You will get strange errors in CHDownloadFactories.mm if you do not build embed.jar.

> cd embedding/config
> make

NSBrowserView Build

NSBrowserView (CHBrowserView, actually) is the Cocoa control which you will ultimately embed in your Cocoa application. Unfortunately, the CHIMERA_M1_0_1_BRANCH contains a very sloppy and outdated NSBrowserView that “mostly works” but has a couple of crasher bugs. You will note that the files that make up NSBrowserView are duplicates of files found in the main mozilla/chimera tree. Presumably, at one point in time, the chimera files were duplicated from the chimera sources and, of course, fell out of date. There are a few vital changes in the NSBrowserView versions of the files, but careful integration can:

  1. remove a number of unused and confusing source files in the NSBrowserView directory, and
  2. resolve a number of bugs in NSBrowserView

Ultimately, a more modular approach should be taken, but initial philosophical inquiries made to the Mozilla team were not answered. Significant resources were spent to work through the issues and produce a much more solid NSBrowserView. We have been unable to submit those changes to the Mozilla team for review, and it seems as if they are moving on to Camino, anyway. Please contact us for more information on procuring this open source.

You must have ProjectBuilder 2.0 or greater. Double-click

mozilla/embedding/browser/cocoa/src/NSBrowserView.pbproj

In PB, click the build button.

Optionally, build the test app for Cocoa

This can serve as a good baseline - if the test app has problems, something other than your application code needs to be fixed to get the embedded browser to work. It is also a good example to follow to build your embedded-browser application.

a) Double-click

mozilla/embedding/tests/cocoaEmbed/CocoaEmbed.pbproj

b) In PB, click the build button.

c) Run it!

Linking HTML Content to your application

Embedding a browser in a window in your application is likely just the first step to actually embedding the content in your application. In many cases, it is necessary to hook the content in the browser to the application itself. For example, your application may browse to www.xyza.com, which contains some content which you’d like to use to modify your menu-items or post a dialog.

“Controlling” the browser from your application is straightforward. That is, programmatically entering a URL for navigation, navigating backward or forward, stopping a page load, etc. But asking the browser to “control” your application is more difficult. Typically, this is done through JavaScript in the web page which communicates to the native world using XPCOM.

XPCOM is Mozilla’s version of Microsoft’s COM - it is a distributed-object system that looks a lot like COM and is used extensively within and around Mozilla. XPConnect is a technology that hooks JavaScript to XPCOM components. XPIDL is XPCOM’s interface definition language, much like CORBA’s or MS COM’s IDLs. The details of these technologies can be found at:

http://www.mozilla.org/projects/xpcom/
http://www.mozilla.org/scriptable/

Rather than providing yet another source of detailed documentation for these technologies, I’ll step through an example and treat some of the details that are important in actually putting an XPConnect-ed XPCOM component into action.

Code

First, a makefile is necessary. Generating one is not perfectly trivial, and various online resources seem to contain mixed value, so I’ll post a template here. Create a new directory for your new XPCOM module. The following assumes this directory was created parallel to your mozilla/ tree. If that’s not the case, you’ll have to modify MOZDIR:

#!/bin/sh
MOZDIR = ../mozilla
# The remainder of this file is stolen from the nsSample sample makefile, with minor mods:
DEPTH = $(MOZDIR)
topsrcdir = $(MOZDIR)
srcdir = .
VPATH = .
include $(DEPTH)/config/autoconf.mk
MODULE = myNewFoo
XPIDL_MODULE = myNewFoo
LIBRARY_NAME = myNewFoo
MODULE_NAME = MyNewFoo
SHORT_LIBNAME = myNewFoo
IS_COMPONENT = 1
# Ensure that the xpcom classes that we build
# do not export themselves
DEFINES += -D_IMPL_NS_COM_OFF 
REQUIRES = string \
 xpcom \
 MyNewFoo \
 $(NULL)
CPPSRCS = \
 MyNewFoo.cpp \
 MyNewFooModule.cpp \
 $(NULL)
XPIDLSRCS = IMyNewFoo.idl
include $(topsrcdir)/config/config.mk
# seperate libraries linked in.
EXTRA_DSO_LDOPTS = \
 $(DIST)/lib/$(LIB_PREFIX)xpcomglue.$(LIB_SUFFIX) \
 $(XPCOM_LIBS) \
 $(NSPR_LIBS) \
 $(NULL)
LIBS = \
 $(DIST)/lib/$(LIB_PREFIX)xpcomglue.$(LIB_SUFFIX) \
 $(DIST)/lib/$(LIB_PREFIX)string_s.$(LIB_SUFFIX) \
 $(DIST)/lib/$(LIB_PREFIX)string_obsolete_s.$(LIB_SUFFIX) \
 $(XPCOM_LIBS) \
 $(NSPR_LIBS) \
 $(NULL)
# Needed to resolve __yylex (?)
ifeq ($(OS_ARCH)$(OS_RELEASE),FreeBSD2)
LIBS += -lpcap
endif
# Add Carbon framework, for AE stuff:
EXTRA_DSO_LDOPTS += -F/System/Library/Frameworks/ -framework Carbon
include $(topsrcdir)/config/rules.mk
libs:: $(TARGETS)
install:: $(TARGETS)

Note that, among other things, the Carbon framework is referenced. This is necessary if Carbon calls are going to be made from within MyNewFoo.cpp. We will discuss Apple Event Carbon calls, to interface with the embedding application, shortly.

You will also need a Makefile.in file, which can be blank. It will be filled in when you conduct a make, but it must exist beforehand. Finally, you’ll need source files.

We’ll start with an IDL file. This file (IMyNewFoo.idl, for example) will be used to generate an IDL header (IMyNewFoo.h, for example) automatically when the module is built. To generate this header explicitly, you may do the following:

XPIDL = $(MOZDIR)/xpcom/typelib/xpidl/xpidl
$(XPIDL) -m header -w -v -I $(MOZDIR)/xpcom/base -o IMyNewFoo IMyNewFoo.idl

Or you may simply do a make once you’ve filled out IMyNewFoo.idl; this will result in a successful IDL build but a failed component build (since the .cpp files aren’t completed).

The generated IMyNewFoo.h contains commented-out template code for filling in the source files. Use this to modify your source files after making interface (IMyNewFoo.idl file) changes. This is very important; mismatches in interface and implementation can lead to hideous and hard-to-find bugs. Do not manually change IMyNewFoo.h in ways that it will no longer match IMyNewFoo.idl!

Here are sample .idl, .h, and .cpp files. These four files represent the bare minimum source code necessary to create a module that will do these simple tasks. We’ve chosen to demonstrate string and integer “in” parameters only, for simplicity. Plenty of documentation and examples can be found elsewhere for XPCOM “out” parameters, return values, string and array memory management, etc.

IMyNewFoo.idl

#include "nsISupports.idl";
[scriptable, uuid(8FBF5212-33F2-11D7-AC6E-003065799276)]
interface IWebCom : nsISupports
{
   void Bar(in PRUint16 number, in string note);
};

MyNewFoo.h

#ifndef MYNEWFOO_H
#define MYNEWFOO_H
#include <Carbon/Carbon.h>
#include "IMyNewFoo.h" // which was generated by IMyNewFoo.idl & XPIDL compiler
// E7B5BF2B-3419-11D7-AF14-003065799276
#define kMyNewFooCid \
 { 0xE7B5BF2B, 0x3419, 0x11D7, { 0xAF, 0x14, 0x00, 0x30, 0x65, 0x79, 0x92, 0x76 } }
#define kMyNewFooContractId "@foo.com/MyNewFoo;1"
class AMyNewFoo : public IMyNewFoo
{
public:
   NS_DECL_ISUPPORTS
   NS_DECL_IWEBCOM
   AMyNewFoo();
   virtual ~AMyNewFoo();
private:
   long SendAppleEvent(AEEventID eventId, PRUint16 number, const char* note);
   static const AEEventClass kEventClass;
   static const OSType kAppSignature;
   static const char* kParameterFormat;
};
#endif

MyNewFoo.cpp

#include "plstr.h"
#include "stdio.h"
#include "nsCOMPtr.h"
#include "nsMemory.h"
#include "MyNewFoo.h"
// XPCOM initialization:
NS_IMPL_ISUPPORTS1(AMyNewFoo, IMyNewFoo)
// Static member initialization:
const AEEventClass AMyNewFoo::kEventClass = 'MyEv';
                                // corresponds to some event in embedding-app
const OSType AWebCom::kAppSignature = 'MyAp'; // corresponds to embedding-app
const char* AWebCom::kParameterFormat = "'----':[short(@),TEXT(@)]";
//
// AMyNewFoo Class:
//
AMyNewFoo::AMyNewFoo()
{
  NS_INIT_ISUPPORTS();
  // Constructor code here, _after_ above line!
}
AMyNewFoo::~AMyNewFoo()
{
}
/* void Bar (in PRUint16 number, in string note); */
NS_IMETHODIMP AMyNewFoo::Bar(PRUint16 number, const char* note)
{
   return this->SendAppleEvent('Bar!', type, message);
}
long AMyNewFoo::SendAppleEvent(AEEventID eventId,
 PRUint16 number, const char* note)
{
   printf("SendAppleEvent('%s', %d, \"%s\")... ", (char*)&eventId, number, note);
   // Assert preconditions:
   NS_PRECONDITION(message != nsnull, "null ptr");
   if (nsnull == message)
      return NS_ERROR_NULL_POINTER;
   // Build AppleEvent:
   AppleEvent event;
   OSStatus err;
   err = AEBuildAppleEvent(kEventClass, eventId, typeApplSignature,
    &kAppSignature, sizeof(kAppSignature), kAutoGenerateReturnID,
    kAnyTransactionID, &event, NULL, kParameterFormat, number, note);
   // Send:
   AppleEvent reply;
   if (noErr == err)
   {
      err = AESend(&event, &reply, kAENoReply, kAENormalPriority,
       kAEDefaultTimeout, NULL, NULL);
   } // if
   // Clean up:
   if (noErr == err)
      err = AEDisposeDesc(&reply); 
   // Return:
   if (noErr != err)
      return NS_ERROR_FAILURE;
   // else:
   return NS_OK;
}

IMyNewFoo.cpp

#include "nsIGenericFactory.h"
#include "MyNewFoo.h"
// Factory constructor (this defines nsMyNewFooConstructor,
// to use in struct below):
NS_GENERIC_FACTORY_CONSTRUCTOR(AMyNewFoo)
// Registration functions:
static NS_METHOD MyNewFooRegistrationProc(nsIComponentManager *aCompMgr,
 nsIFile *aPath, const char *registryLocation, const char *componentType,
 const nsModuleComponentInfo *info)
{
   return NS_OK;
}
static NS_METHOD MyNewFooUnregistrationProc(nsIComponentManager *aCompMgr,
 nsIFile *aPath, const char *registryLocation, const nsModuleComponentInfo *info)
{
   return NS_OK;
}
// nsIClassInfo support:
//NS_DECL_CLASSINFO(AMyNewFoo)
// The MyNewFoo module struct def:
static nsModuleComponentInfo components[] =
{
   { "MyNewFoo component",
      kMyNewFooCid,
      kMyNewFooContractId,
      AMyNewFooConstructor,
      MyNewFooRegistrationProc, // consider replacing with NULL!!!
      MyNewFooUnregistrationProc, // consider replacing with NULL!!!
      NULL, // no factory destructor
      NULL,//NS_CI_INTERFACE_GETTER_NAME(AMyNewFoo),
      NULL, // no language helper
      NULL,//&NS_CLASSINFO_NAME(AMyNewFoo)
   }
};
// Note, for version w/ user-def module cleanup, use NS_IMPL_NSGETMODULE_WITH_DTOR()
NS_IMPL_NSGETMODULE(AMyNewFooModule, components)

Note the Apple-Event setup. This is a convenient mechanism for completing the line of communication between the JavaScript code and the embedding app. The embedding (Cocoa) application would, in this case, be identified by the 4-character-code (“4cc”): 'MyAp'. (Setting up a Cocoa application in this way, and using 4-character-codes in the context of Apple Events is out of the scope of this document. In general, 4cc identifiers like this link applications for IPC purposes, where the Apple Event is Apple’s IPC technology. Please consult Apple Event and Cocoa documentation for more details.) The event to look for from within the application would be identified by the 4cc class 'MyEv' and ID 'Bar!', for this example. There are ample resources on Apple Event usage, so we won’t belabor the point, but there are a couple of issues we must treat. We will treat them after completing the example started above.

To complete the example, here is some test .html. This contains the JavaScript code necessary to instantiate our new XPCOM object, as well as the JavaScript code that calls on that object.

<html>
<script language="JavaScript">
   var foo = null;
   function OnLoad()
   {
      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
      foo = Components.classes["@foo.com/MyNewFoo;1"].createInstance();
      foo = foo.QueryInterface(Components.interfaces.IMyNewFoo);
   }
   function Test()
   {
      netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
      foo.Bar(234,"This is a test!");
   }
</script>

<body onload="javascript:OnLoad()">
<a href="javascript:Test();">TEST</a>
</body>

</html>

Once you’ve completed the source code (not including the .html, which needn’t be compiled), you must "make" the module. From within the MyNewFoo directory, simply type:

> make

You will note a number of warnings - these are safe to ignore, though it may make finding real errors rather difficult. Always do a 'make clean', followed by a 'make' after changing the interface, just to make sure all things are synchronized.

Setup

You’ve now got the source code in place for the following line of communication:

JavaScript in HTML -> (XPConnect) -> XPCOM MyNewFoo -> (AppleEvent) -> Cocoa app

For this to work, everything must be hooked up. When you build the MyNewFoo component, it generates two important files:

libMyNewFoo.dylib
_xpidlgen/MyNewFoo.xpt

These two files make up your new “component”, with which Mozilla can interact. If you followed the example found in mozilla/embedding/tests/cocoaEmbed/ when you embedded Mozilla in your app, then you should have references like this in your project:

Gecko Chrome/
Gecko Components/
Gecko Defaults/
Gecko Res/

libMyNewFoo.dylib and MyNewFoo.xpt must now be added to “Gecko Components/”. It is best to establish a symlink to these new files from your project’s directories, rather than copying them over and having two copies that constantly need synchronization. You then must add the two files to the project itself. Care must be taken in doing so: when adding the files, ProjectBuilder will ask if you wish to add them to the application’s target(s). Uncheck the box(es) next to the targets - you do not want to add them to the target at this point. Instead, once they’re added to the project, but not to a target, find them in the project tree browser (they will be unchecked there, as well), and drag them into the “Gecko Components” “Copy Files” build phase for the project (target), next to all the other .xpt and .dylib files found there. This will cause two checkmarks to be placed next to the files in the project tree browser, but the build will not try to actually compile these files. This is what we want.

Now find all.js in “Gecko Defaults/”. To tell the Mozilla Security Manager to enable XPConnect, at least locally, you must add these lines to this file:

pref("capability.principal.codebase.p0.granted", "UniversalXPConnect");
pref("capability.principal.codebase.p0.id", "file://");

You’re finished. Build your project and run your application. In your application’s embedded browser, surf to the example .html above via file://, on your local system. Click the “Test” link. Your application should receive the Apple Event and process it accordingly, perhaps posting a “Hello World” Cocoa dialog box. At this point, you’re finished with the basic application, and have the ground laid for further extending your application interface API. Experiment with adding more functions to your .idl file, building it, adding C++ handlers, Apple Events, etc.

Problems

Security Manager

Fortunately or unfortunately, simply adding another entry to “Gecko Defaults/all.js” for http:// does not enable UniversalXPConnect activity for files found on the Internet. This means that if the example .html above is navigated-to by file://, on your local system, everything will work, but if the .html is somewhere on the Internet, the Security Manager will disapprove. There are two ways to get around this.

  1. You can wrap your .html/JavaScript in a signed-script JAR, according to http://www.mozilla.org/projects/security/components/signed-scripts.html. This is the “correct” way to resolve the problem.
  2. If your application does not provide a facility for end-users to specify URLs (i.e., the application knows every URL to which it will surf and/or knows it can trust them all), you can disable the Security manager in the Mozilla code itself. We will not expound on the mechanism for doing so here.

Round-trip communication

Round-trip communication, wherein the return value in the calling JavaScript comes from the embedding application, cannot easily be achieved. Significant resources were spent on the problem, and the results were disappointing. The root of the problem is Apple Event management. An Apple Event message can be sent with the intention of waiting for a result Event. However, this “waiting” requires a runloop independent of the application’s runloop, since the application’s runloop must continue spinning in order for the application to even receive the original Apple Event. Attempting to put an idle-proc in the XPCOM object is destined to fail, so the only option is placing the embedded browser, itself, on it’s own thread. This attempt is also destined to fail, in the current manifestation of Mozilla and Apple GUI. Mozilla is not threadsafe, so all calls into Mozilla must come from the same thread - in this case, this “separate” thread that instantiated the embedded browser. All GUI events must then be punted from the main thread to this thread, which opens a large can of worms and eventually leads to occasional application crashes and crazy screen painting. It’s almost acceptable, by some standards, but the models would definitely need to be reworked to make this solution functional.

Round-trip communication should therefore be accomplished by re-sending any data to the server via new HTTP GET/POST operation from the application. That is, after processing the data received via AppleScript, the only known stable way of getting that data back into the page in the browser is to send it, for instance, in a URL foo.com/pageAgain?newData="yadda".

Licensing and Distribution

Section three of the Mozilla Public License (MPL) specifies the requirements for distributing code and executable binaries. Pay special attention to sections 3.2 and 3.6 especially if you have to make modifications to the codebase as suggested above.

Conclusion

“Embedding Mozilla” can mean many things and can be accomplished at different levels. In this article, we’ve discussed it’s meaning with respect to a Cocoa application in OS X 10.2. We’ve disclosed the two major levels of embedding:

  1. Simply putting a browser window in an application
  2. Establishing a line of communication between web content in that browser and the embedding (host) application

We’ve discovered that this requires a number of steps and careful attention to XPCOM, AppleEvents, and the Mozilla Security Manager, among other things.

If and when you are successful at building your killer Mozilla-embedded application, you may consider being added to the Mozilla-embedding “hall of fame”:

http://www.mozilla.org/university/HOF.html

Back to Art & Logic Resources