Monday, April 03, 2006

After a couple of weeks of laboring with issues related to the JavaScript engine, I have implemented support for "document.openDialog" and "document.close" functions. The document.openDialog() function takes a single argument, a URL to a XUL document. The document.close() function takes no arguments, and closes the current dialog. Both are implemented as functions callable from the document object in JavaScript.

The screen shot shows a dialog (with the title "Test dialog") that is displayed when the user clicks the "World!" button in the main window (with the title "Hello World App"). The onclick handler for the "World!" button simply makes the following call to display the dialog:


The source for dialog.xul is simple:

<?xml version="1.0"?>

<!DOCTYPE window SYSTEM "dialog.dtd">

<window name="main" title="&dialog.title;" main="false" width="250" height="100" x="500" y="100">
<script type="text/javascript" src="resources/content/dialog.js"/>
<box orient="vertical">
<box orient="horizontal">
<button onclick="return Button1Click();" label="&button1.label;"/>

The above layout displays a 250x100 pixel dialog and centers the button in the middle of the dialog. I could have used any arbitrary layout or content, this is just one example.

To close the window, I associate an onclick handler with the "Click me to dismiss" button, and call document.close() from it. This code is in dialog.js (see the script tag in the above code for how dialog.js is associated with the dialog). Here is the onclick handler:

function Button1Click()
dump("Inside of dialog::Button1Click\n");

So, what was all the trouble I had with JavaScript in implementing the above? Well, it turns out that both dialogs implemented a function named Button1Click() in their respective JavaScript. Whenever the engine creates a document (both dialogs in the above example have a document object to represent them in the layout engine), I parse their scripts as a part of creating the layout document object, and prior to displaying the dialog. The problem I ran into was that the Button1Click() function associated with the "Hello World App" JavaScript was getting overridden by the Button1Click() function associated with the "Test Dialog" JavaScript. In the end, the scripts were being compiled and evaluated into a common JavaScript engine global object. The only way I found to overcome this problem was to spend numerous hours reading the netscape.public.mozilla.jseng newsgroup for clues. I didn't get a direct answer from doing this, but in the end, I did get some clues, and the trick I came up with involved the following:

-- creating a superglobal JavaScript object
-- for each document, creating a private JS object that I compiled and executed JavaScript into when processing scripts in response to an openDialog() call on the document
-- adding listeners in the layout Document class for window raise/focus events. When the window is raised, switching the global document from the previously raised window's JavaScript object to the one of the newly raised window by making a call to JavaScript API function JS_SetGlobalObject().
-- at the same time, recompiling the scripts associated with the newly focused document. This last step seemed unsavory at best, but it was necessary.

Turns out, recompiling the JavaScript scripts at document raise time does not seem to affect global variables in the JavaScript. I'm guessing this is because they are rooted in the superglobal object.

Someone needs to encourage Brendan Eich to write a book on JavaScript embedding. So many questions on the newsgroup were asked over and over again...