Breaking the grip JS has on the DOM

From MozillaWiki
Jump to: navigation, search

We care more about speed now. See bug 570738.

We want to change the grip JS has on the DOM and on XUL. We will do this in 2 steps:

This work is currently being done on a DOM_AGNOSTIC2_BRANCH cvs branch (note the 2 in the branch name!). The initial work on this, including a Python implementation is largely complete. This document attempts to capture the current state of this work, including any issues not yet addressed.

Description of changes - both already made and yet to be made


A #new interface nsILanguageRuntime will be implemented for each language. It is a singleton (JS will be manually instantiated, but all other languages will be services). nsDOMScriptObjectFactory will become the factory for these nsILanguageRuntimes, with the language runtime taking responsibility for creating the nsIScriptContext.

The existing nsIScriptContext interface will remain tied to a specific language. The void * params in its interface will remain. This means that a void * and a suitable nsIScriptContext must always be treated as a pair (ie, given just a void *, there is no way to determine an appropriate nsIScriptContext suitable for it. See #nsIScriptContext

nsIProgrammingLanguage constants will internally identify a language offering an efficient array-based implementation for multiple languages. The nsIDOMScriptObjectFactory will be able to convert language names to IDs and will be responsible for instantiating new language runtimes.

The existing nsIScriptGlobalObject interface will move towards a model where there is a global nsIScriptContext per language. GetGlobalJSObject and GetContext hav largely been replaced with GetLanguageGlobal and GetLanguageContext methods (hard-coding nsIProgrammingLanguage::JAVASCRIPT) where necessary. Thus, nsIScriptGlobalObject may have many nsIScriptContexts associated with it (one per supported and initialized language). The script global itself is responsible for preparing itself to work with a specific language, but will only do so via an explicit request to EnsureScriptEnvironment() for a language. See #nsIScriptGlobalObject and nsGlobalWindow

A new context stack may need to be invented - see #Context Stack

The special casing for js in nsDOMClassInfo will need a fair bit of work for each language - see #nsDOMClassInfo

Below are specific implementation notes:

Array and Variant object model changes

  • nsIArray to be used in place of jsval argc/argv used now. There is a new nsJSArgArray object that "wraps" a jsval *argv/int argc pair into an nsIArray - but also support a private interface for getting the original jsval based array. Thus, js->js calls do not convert elements in the array - (offering both performance and no conversion issues), but they are converted when nsIArray methods are directly used (eg, when another language sees the array).
  • Each of these nsIArray elements params are expected to hold either an nsIVariant or an nsISupportsPrimitive.
  • Result values from calling scripts are now nsIVariant. This is currently used only for the "return value" of an event handler, and only boolean and string result values are handled. If may be more efficient to convert this to use nsISupportsPrimitive (but xpconnect already provided nsIVariant converters).

new interface nsILanguageRuntime

This is a factory for nsIScriptContext objects and has methods for parsing "version strings" into a version integer specific to the language.

nsJSEnvironment implements this interface and becomes the JS factory (and renamed to nsJSRuntime). Public function for creating JSContext replaced with public function for the nsJSRuntime.

All memory management/object ownership related functions will exist on the language runtime. This is so a language object can be locked or unlocked without having an nsIScriptContent - all it needs is the integer language ID so it can fetch the nsILanguageRuntime to perform the operation. Currently these are defined in terms of the JS GC API (and hence ignored by Python), but this will change to a more language neutral technique.


  • The ownership model of the void * objects returned from nsIScriptContext must be clarified and made more explicit. MarkH is working on this.
  • Existing JSObject replaced with void. JS implementation still returns a JSObject, but that implementation detail is hidden inside the script context itself.
  • Existing char *version replaced with PRUint32 version. New method on nsILanguageRuntime to convert a char * version string into the flags.
  • New ClearScope method - JS implementation does the ClearScope/ClearWatchpointsForObject/ClearRegExpStatistics dance.
  • As described above, nsIArray and nsIVariant used to make language args and result values agnostic..
  • CompileEventHandler now takes an array of arg names - this is because the onerror event takes 3 params. nsContentUtils::GetEventArgName() renamed to GetEventArgNames() with params changed accordingly, and returns 3 names for 'onerror'.
  • A new SetProperty method has been added, currently used only to set window.arguments. Note that BindCompiledEventHandler() changes make these 2 functions almost identical in concept, so these could possibly be merged. Alternatively, now we use nsIArray for window arguments, we could possibly add arguments as an XPCOM property on the DOM object itself, meaning SetProperty() could go away.
  • SetTerminationFunction needs thinking through - this does *not* seem to be a per-language thing, but is used only for JS GC, so that the script global is notified when the JS global object is collected. This needs more thinking through, or possibly just moved privately inside the JS implementation.
  • New method void *GetNativeGlobal() to return the "global object" used by nsIScriptGlobalWindow (previously stored in mJSObject) for this context. nsIScriptGlobalObject calls this method during language init to setup its environment.
  • New Serialize and Deserialize methods added with nsXULPrototypeScript delegating to these.

nsIScriptGlobalObject and nsGlobalWindow

nsGlobalWindow is the main implemention of nsIScriptGlobalObject, but XUL and XBL have a few.

  • nsIScriptGlobalObject remains a single object (ie, not per language). It keeps an array of nsIScriptContext objects and void * globals for each supported language. Methods GetContext and GetGlobalJSObject have been replaced with GetLanguageContext and GetLanguageGlobal, both of which take the language ID as a param. The places still tied to JS explicitly pass nsIProgrammingLanguage::JAVASCRIPT when necessary.
  • New method EnsureScriptEnvironment(PRUint32 langID) to ensure the nsIScriptGlobalObject is initialized for a specific language. This may be called at any time - whenever someone needs to run a script in a language.
  • nsGlobalWindow still has some complicated JS specific code in place - most notably in SetNewDocument. Some of these subtleties must also be done for other languages - but these have not yet been done in the hope of keeping the patch as small and "obviously correct" as possible.
  • A new interface nsIScriptTimeoutHandler has been created. This abstracts most of the JS specific code related to timeouts and intervals. The core logic in nsGlobalWindow relating to timeouts has not changed at all. The JS specific code that remains in nsGlobalWindow should probably be relocated into a JS specific file to help keep the size of nsGlobalWindow.cpp down.

XUL Fastload Cache

  • XUL cache now stores and returns both a void * and a PRUint32 langID. As described in #new interface nsILanguageRuntime, this language ID is enough to perform the necessary ownership management of objects in the cache.
  • Serializing scripts is supported by first writing the language ID to the stream, then delegating to the (new) nsIScriptContext::Serialize() method. Deserialization then reads this language ID to determine the appropriate nsIScriptContext to delegate to.
  • Note that fast-load is fragile in the face of errors; more work is needed to ensure things work for script languages that do not support fast-load. This is not currently a problem as Python does implement this functionality. This fragility did previously exist - it is just more open to failing when multi-languages are considered.


nsDOMClassInfo provides 2 key functions:

  • standard nsIClassInfo implementations for DOM.
  • Enhanced support for DOM objects which can not be expressed using nsIClassInfo. These are the "scriptable helpers"

The extra nsIClassInfo implementation is suitable for all languages. However, the scriptable helpers have lots of DOM namespace magic, and are very JS specific. This functionality currently needs to be re-implemented for Python.

Widgets implemented in XBL do not have any class info available. This works in JavaScript as the widgets themselves are implemented by JS, and therefore all methods and properties exist as native JS properties. Thus, JS does not use XPConnect to talk to XBL implemented widgets. Python has currently worked around this by hardcoding mapping between a XUL tag name and a list of interfaces that should be implemented. This should be addressed in XBL 1.5/2.0.

Context Stack

[MarkH - I've managed to completely ignore this for the time being]

Existing code that works directly with the nsIJSContextStack ";1" service must be modified to be language agnostic. These callers may be unaware of the language being used, or the set of language available. Therefore, the nsIScriptGlobalObject interface will grow a way to generically push and pop contexts for *all* languages. It will probably not be possible to explicitly push a context for only a single language, as that will muddy the semantics.

Exactly what this means is still TDB, but there are a few possibilities:

  • We invent a new interface for each language to do its thing.
  • We simple push nsIScriptContext objects directly on a stack - we push a context for every language known to us.

If a language is initialized even after items are already on the context stack, only new items pushed will include a context item for that language. Once the stack pops back past where a language has no entries, the language will not be able to run (but this should be impossible - there can be no stack entries for that language higher in the context stack, and attempting to start a new script in that language will simply re-push a context entry which will again include the language)

As mentioned above re nsIScriptContext, we may need to handle SetTerminationFunction functionality on this stack.

Tricky: We need to interoperate with code that is not multi-language nor nsIScriptGlobalObject aware, and may not be for some time. Eg: - pushes a NULL context?

Generic "GetCallerDocShell" or similar will avoid some JS specifics - eg,

Possible implementation strategy:

New interface - nsIDOMContextStackItem

An item on the context stack - stores one nsIScriptContext for every language initialized for this item.

nsIDOMContextStackItem : nsISupports {

 nsIScriptContext getLanguageContext(in PRInt32 langId);
 void setLanguageContext(in PRInt32 langId, in nsIScriptContext cx);


New interface - nsIDOMContextStack

interface nsIDOMContextStack : nsISupports {

 readonly attribute PRInt32     count;
 nsIDOMContextStackItem      peek();
 nsIDOMContextStackItem      pop();
 void                                   push(in nsIDOMContextStackItem cx);
 /* what is a "safe context" anyway??? 
  * - a context guaranteed to be available and usable.
 nsIScriptContext getLanguageSafeContext(in PRInt32 langId);
 /* A helper for code that wants the most recent nsIDocShell on
 the context stack - any/all languages on the stack can provide it */
 nsIDocShell GetCallerDocShell()



Exceptions should be chained across languages. This should just mean diligent use of nsIExceptionService? This has not been addressed yet.