Breaking the grip JS has on the DOM: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(We don't want to do this anymore.)
 
(28 intermediate revisions by 14 users not shown)
Line 1: Line 1:
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:
We want to change the grip JS has on the DOM and on XUL.  We will do this in 2 steps:


Line 4: Line 8:
* Create a Python implementation.
* Create a Python implementation.


Ideally, the first step could be done without consideration for the second, in the assumption at the implementation should be truly language neutralHowever, this first real implementation is likely to impact the design decisions made, so there will be some iteration involved
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.


== Original specification - stage 1 ==
== Description of changes - both already made and yet to be made ==
This is the high-level task list as specified at the start of the project:


* Extend nsScriptLoader using the XPCOM category manager to handle arbitrary <script type="...">
=== Overview ===
* MIME types, loading extension component mapped by MIME type through a category
* Abstract and multiplex nsIScriptContext, generalizing it (changing its IID of course) away from the JS API, making a nsIJavaScriptContext for JS and an nsIPythonContext for Python, fixing various places that assume nsIScriptContext "does JS"
* Fix default script language selection and event handlers, which are script-language-typed by the selected default, so that you can write Python event handlers.


== High-level design decisions ==
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).  <tt>nsDOMScriptObjectFactory</tt> will become the factory for these <tt>nsILanguageRuntime</tt>s, with the language runtime taking responsibility for creating the <tt>nsIScriptContext</tt>.
   
The existing <tt>nsIScriptContext</tt> interface will remain tied to a specific language.  The <tt>void *</tt> params in its interface will remain.  This means that a <tt>void *</tt> and a suitable <tt>nsIScriptContext</tt> must always be treated as a pair (ie, given just a <tt>void *</tt>, there is no way to determine an appropriate <tt>nsIScriptContext</tt> suitable for it.  See [[#nsIScriptContext]]


Decisions made/ratified by Brendan etc:
<tt>nsIProgrammingLanguage</tt> constants will internally identify a language offering an efficient array-based implementation for multiple languages.  The <tt>nsIDOMScriptObjectFactory</tt> will be able to convert language names to IDs and will be responsible for instantiating new language runtimes.


* No sharing of language namespacesOnly the "global DOM namespace" (ie, the nsIScriptGlobalObject) will be shared.
The existing <tt>nsIScriptGlobalObject</tt> interface will move towards a model where there is a global <tt>nsIScriptContext</tt> per language.  <tt>GetGlobalJSObject</tt> and <tt>GetContext</tt> hav largely been replaced with <tt>GetLanguageGlobal</tt> and <tt>GetLanguageContext</tt> methods (hard-coding <tt>nsIProgrammingLanguage::JAVASCRIPT</tt>) where necessary.  Thus, <tt>nsIScriptGlobalObject</tt> may have many <tt>nsIScriptContext</tt>s 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 <tt>EnsureScriptEnvironment()</tt> for a language. See [[#nsIScriptGlobalObject and nsGlobalWindow]]


== Identified task list ==
A new context stack may need to be invented - see [[#Context Stack]]


These are the tasks identified during the analysis and implementation phases of the project.
The special casing for js in <tt>nsDOMClassInfo</tt> will need a fair bit of work for each language - see [[#nsDOMClassInfo]]


=== Overview ===
Below are specific implementation notes:
 
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 - although "assume JS" is likely to remain for existing code that does exactly this)


nsIScriptContext will grow new methods relating to:
=== Array and Variant object model changes ===
* language specific cleanup and "default language context" type code.  For example, nsGlobalWindow.cpp has a number of calls to ::JS_ClearScope/::JS_ClearWatchPointsForObject etc - these need to be hidden behind one of the interfaces.
* The "WrapNative" process (or possibly this can be hidden behind JS 'SetProperty' functions?)


The existing nsIScriptGlobalObject interface will move towards a model where there is a global nsIScriptContext per languageGetGlobalJSObject and GetContext would be deprecated, with a new method allowing the desired language to be specified.  Thus, nsIScriptGlobalObject may have many nsIScriptContexts associated with it (one per supported and initialized language)
* <tt>nsIArray</tt> to be used in place of <tt>jsval</tt> argc/argv used nowThere is a new <tt>nsJSArgArray</tt> object that "wraps" a <tt>jsval *argv/int argc</tt> pair into an <tt>nsIArray</tt> - but also support a private interface for getting the original <tt>jsval</tt> 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 <tt>nsIArray</tt> methods are directly used (eg, when another language sees the array).


A new 'context stack' will be invented:
* Each of these <tt>nsIArray</tt> elements params are expected to hold either an <tt>nsIVariant</tt> or an <tt>nsISupportsPrimitive</tt>.
* nsIScriptContexts to be used directly on the stack, rather than JSContext (hmm - is this necessary?  I guess a "void *" would still work so long as it was associated with the language)
* Contexts for *all* languages are pushed and poped as a single operation - a new context applies to all languages in the environment, not just a single one.  This is so when calls cross multiple language boundaries, they always have a correct context, not just the last one that happened to be pushed for their language.


nsDOMClassInfo will need a fair bit of work, and significant help from Mozilla resourcesIt may be able to be split into a language neutral nsDOMClassInfo, and a JS specific nsIXPCScriptable.  The JS specific code in this file will need to be re-written for Python, but this should allow all DOM knowledge to be reused by the Python implementation.
* 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 handledIf may be more efficient to convert this to use <tt>nsISupportsPrimitive</tt> (but xpconnect already provided <tt>nsIVariant</tt> converters).


Need to agree on a single "script language ID".  nsIProgrammingLanguage defines a set of integers, while nsIScriptElement uses a generic "char *". This document assumes an integer.
=== new interface nsILanguageRuntime ===


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


=== new interface nsIScriptLanguage ===
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.


* This is really only a factory for nsIScriptContext objectsThe nsIScriptContext itself becomes the factory for the language specific, void * "global object".
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 operationCurrently 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.


=== nsIScriptContext ===
=== nsIScriptContext ===


* Existing JSObject replaced with void
* The ownership model of the <tt>void *</tt> objects returned from <tt>nsIScriptContext</tt> must be clarified and made more explicit.  MarkH is working on this.


* Existing "void *aScopeObject" (ExecuteScript/EvaluateStringWithValue/CompileScript), replaced with nsIScriptGlobalObject.  The language impl can then get its "void *" via either sgo->GetLanguageContext() or provide a default via its own private means.
* Existing <tt>JSObject</tt> replaced with <tt>void</tt>. JS implementation still returns a <tt>JSObject</tt>, but that implementation detail is hidden inside the script context itself.
This may require a helper function to return a (temporary?) nsIScriptGlobalObject for a given JSObject - eg, plugin code, nsXBLProtoImplField::InstallMember.
[actually, the above may be gratuitious and cause unnecessary problems][Actually, XBL has an nsIScriptGlobalObject around when it's installing members; it'd just need to pass it around --[[User:Bzbarsky|Bzbarsky]] 18:55, 15 Aug 2005 (PDT)]


* Need a "WrapNative" type function (or maybe not - this may end up being able to be hidden behind Get/SetProperty functions?
* Existing <tt>char *version</tt> replaced with <tt>PRUint32 version</tt>.  New method on <tt>nsILanguageRuntime</tt> to convert a <tt>char * version</tt> string into the flags.


* FinalizeClasses method - JS does the ClearScope/ClearWatchpointsForObject/ClearRegExpStatistics?
* New <tt>ClearScope</tt> method - JS implementation does the <tt>ClearScope/ClearWatchpointsForObject/ClearRegExpStatistics</tt> dance.


* Some kind of "SetProperty" function - as needed by nsGlobalWindow - "arguments", "navigator" etc.  http://lxr.mozilla.org/seamonkey/source/dom/src/base/nsGlobalWindow.cpp#871 [Not sure this is needed. IMO we should make "arguments" be XPCOM objects, i.e. an nsIPropertyBag or whatnot and we'd finally be able to get to the arguments passed to window.open() from *any* language. "navigator" is just a property of a window like all others, except that there's some JS:isms around it that I think we can easily ignore for Python -- jst]
* As described above, <tt>nsIArray</tt> and <tt>nsIVariant</tt> used to make language args and result values agnostic..


* SetTerminationFunction needs thinking through - this does *not* seem to be a per-language thingMaybe could be on the nsIDOMContextStackItem proposed below?  Only few callers though.
* <tt>CompileEventHandler</tt> now takes an array of arg names - this is because the <tt>onerror</tt> event takes 3 params<tt>nsContentUtils::GetEventArgName()</tt> renamed to <tt>GetEventArgNames()</tt> with params changed accordingly, and returns 3 names for 'onerror'.


=== nsIScriptGlobalObject / nsGlobalWindow ===
* A new <tt>SetProperty</tt> method has been added, currently used only to set <tt>window.arguments</tt>.  Note that <tt>BindCompiledEventHandler()</tt> changes make these 2 functions almost identical in concept, so these could possibly be merged.  Alternatively, now we use <tt>nsIArray</tt> for window arguments, we could possibly add <tt>arguments</tt> as an XPCOM property on the DOM object itself, meaning <tt>SetProperty()</tt> could go away.


http://lxr.mozilla.org/seamonkey/source/dom/public/nsIScriptGlobalObject.h
* <tt>SetTerminationFunction</tt> 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.


nsGlobalWindow is the main implemention of nsIScriptGlobalObject, but XUL has one too.
* New method <tt>void *GetNativeGlobal()</tt> to return the "global object" used by <tt>nsIScriptGlobalWindow</tt> (previously stored in <tt>mJSObject</tt>) for this context.  <tt>nsIScriptGlobalObject</tt> calls this method during language init to setup its environment.


* Still need to understand callers of GetNativeContext - they will not be language neutral
* New <tt>Serialize</tt> and <tt>Deserialize</tt> methods added with <tt>nsXULPrototypeScript</tt> delegating to these.


* nsIScriptGlobalObject probably needs to remain a single object (ie, not per language).  It will move towards keeping a list of "IScriptContext *ctx, void *global" items, one for each language.  New method:
=== nsIScriptGlobalObject and nsGlobalWindow ===
  nsresult GetLanguageContext( [in] language_id language, [out] nsIScriptContext **retLangContext, [out]void **retLangGlobal);
The concept of a single context/global will be deprecated - callers will be encouraged to use the new method.
GetContext()/GetGlobalJSObject() remain for b/w compat.  They are equivalent to "GetLanguageContext('js', ...)"


* New method to ensure the nsIScriptGlobal is initialized for a specific language.  This may be called at any time - whenever someone needs to run a script in a language.
<tt>nsGlobalWindow</tt> is the main implemention of <tt>nsIScriptGlobalObject</tt>, but XUL and XBL have a few.


* New method to set and get "properties" - nsGlobalWindow does this for JS.  Setting a property should presumably set it in all languages (via the global attached to the context).  Getting is tricker - what if the property is in a different language? Or in multiple languages?
* <tt>nsIScriptGlobalObject</tt> remains a single object (ie, not per language).  It keeps an array of <tt>nsIScriptContext</tt> objects and <tt>void *</tt> globals for each supported language. Methods <tt>GetContext</tt> and <tt>GetGlobalJSObject</tt> have been replaced with <tt>GetLanguageContext</tt> and <tt>GetLanguageGlobal</tt>, both of which take the language ID as a paramThe places still tied to JS explicitly pass <tt>nsIProgrammingLanguage::JAVASCRIPT</tt> when necessary.
** Properties set include "arguments" and "navigator"Presumably other code also sets additional properties?
** Properties fetched seem arbitrary.


* SetNewArguments will need to be specified as an nsISupports.  This trickles down into a number of things, including the concept of "extra args" nsGlobalWindow has.  May need to supply *2* nsISupports.
* New method <tt>EnsureScriptEnvironment(PRUint32 langID)</tt> to ensure the <tt>nsIScriptGlobalObject</tt> is initialized for a specific language.  This may be called at any time - whenever someone needs to run a script in a language.


* New method to push all current contexts onto the context stack.
* nsGlobalWindow still has some complicated JS specific code in place - most notably in <tt>SetNewDocument</tt>.  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.


* Serializing scripts?
* A new interface <tt>nsIScriptTimeoutHandler</tt> 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.


* CompileScript:
=== XUL Fastload Cache ===
  nsXulElement.  Line 3666:
  // XXXbe violate nsIScriptContext layering because its version parameter
  // is mis-typed as const char *
  sets mJSObject


=== nsDOMClassInfo ===
* XUL cache now stores and returns both a <tt>void *</tt> and a <tt>PRUint32 langID</tt>As described in [[#new interface nsILanguageRuntime]], this language ID is enough to perform the necessary ownership management of objects in the cache.
Note: Work on nsDOMClassInfo could be done by a Mozilla resource in parallel with this other workWe will not need to use this enhanced class info until we already have Python code being executed and that code trying to work with a "dumb" DOM object.


One radical alternative: Invent a new "nsIDynamicClassInfo" or similar interface that allows the additional class info to be expressed.  Modify the JS XPConnect code to also use that new interface is supported regardless of implementorPython's xpcom code would do likewise.  nsIDOMClassInfo is responsible only for implementing this new interface.  It is possible this could also subsume your existing IDispatch support.
* Serializing scripts is supported by first writing the language ID to the stream, then delegating to the (new) <tt>nsIScriptContext::Serialize()</tt> methodDeserialization then reads this language ID to determine the appropriate nsIScriptContext to delegate to.


Notes assuming a less radical evolution of the existing code:
* 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.


Lots of DOM namespace magic happens here, but it is very JS specific.  We will try to split the implementation:
=== nsDOMClassInfo ===


* Create ns(I)DOMClassInfo.  It contains all the existing code that works with nsDOMClassInfoData - that includes most of the "tables" defined using macros.
nsDOMClassInfo provides 2 key functions:


* nsDOMXPC will have all the JS specific code, which is currently implementing nsIXPCScriptable.  This is the bulk of the existing nsDOMClassInfo implementation.
* standard nsIClassInfo implementations for DOM.


This language neutral interface will allow language "helpers" to be explicitly installed, and will then be able to be fetched by the existing nsIClassInfo::GetLanguageHelper() functionThus, anyone with an nsIDOMClassInfo will still be able to get the JS helper.
* Enhanced support for DOM objects which can not be expressed using nsIClassInfo.  These are the "scriptable helpers"


The Python language implementation will then need to write an equivalent to the new nsDOMXPCThis will be the magic where attributes are got/set for objects, etc.  This Python implementation will need to lean heavily on nsDOMClassInfo.
The extra nsIClassInfo implementation is suitable for all languagesHowever, the scriptable helpers have lots of DOM namespace magic, and are very JS specific.  This functionality currently needs to be re-implemented for Python.


Decide what to do with the various flags - eg, ALLOW_PROP_MODS_TO_PROTOTYPEPresumably these will have some meaning to languages other than JS, but they are defined in a JS specific interface (nsIXPCScriptable)
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 propertiesThus, 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.


The following "public" functions are problematic:
* nsWindowSH::InvalidateGlobalScopePolluter referenced by nsGlobalWindow.
* GC
* oops - lost the other one!


=== Context Stack ===
=== Context Stack ===


Existing nsIJSContextStack "@mozilla.org/js/xpc/ContextStack;1" service is replaced with language generic ContextStack.  This is almost identical to the JS version, but all operations work for the entire set of languages in use, not just the language about to be executed.
[MarkH - I've managed to completely ignore this for the time being]


Probably need a new interface for each language to do their thingThe DOM will simply ask for a new context, and the nsIScriptGlobal will push the context for each language it is initialized with.
Existing code that works directly with the nsIJSContextStack "@mozilla.org/js/xpc/ContextStack;1" service must be modified to be language agnostic.  These callers may be unaware of the language being used, or the set of language availableTherefore, 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.
 
Thus, we will have one ContextStack, with each holding an array of nsIScriptContexts - one for each language. You can not push a context for only a single language on the stack.
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)
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)
Line 131: Line 113:
As mentioned above re nsIScriptContext, we may need to handle SetTerminationFunction functionality on this stack.
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 aware, and may not be for some time.  Eg:
Tricky: We need to interoperate with code that is not multi-language nor nsIScriptGlobalObject aware, and may not be for some time.  Eg:
http://lxr.mozilla.org/seamonkey/source/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp#216
http://lxr.mozilla.org/seamonkey/source/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp#216
http://lxr.mozilla.org/seamonkey/source/extensions/webservices/security/src/nsWebScriptsAccess.cpp#767 - pushes a NULL context?
http://lxr.mozilla.org/seamonkey/source/extensions/webservices/security/src/nsWebScriptsAccess.cpp#767 - pushes a NULL context?
This probably will mean that the existing JS Context Stack will be a wrapper around the new cross-language one.


Generic "GetCallerDocShell" or similar will avoid some JS specifics - eg, http://lxr.mozilla.org/seamonkey/source/docshell/base/nsDocShell.cpp#5898
Generic "GetCallerDocShell" or similar will avoid some JS specifics - eg, http://lxr.mozilla.org/seamonkey/source/docshell/base/nsDocShell.cpp#6092


What about http://lxr.mozilla.org/seamonkey/source/dom/public/nsIScriptContext.h#356 - it
Possible implementation strategy:
checks flags.  Will there be flags for a single stack entry, or flags for each language in a single stack entry, or both?


==== New interface - nsIDOMContextStackItem ====
==== New interface - nsIDOMContextStackItem ====
Line 159: Line 139:
   void                                  push(in nsIDOMContextStackItem cx);
   void                                  push(in nsIDOMContextStackItem cx);


   /* what is a "safe context" anyway??? */
   /* what is a "safe context" anyway???  
  * - a context guaranteed to be available and usable.
  */
   nsIScriptContext getLanguageSafeContext(in PRInt32 langId);
   nsIScriptContext getLanguageSafeContext(in PRInt32 langId);
    
    
Line 166: Line 148:
   nsIDocShell GetCallerDocShell()
   nsIDocShell GetCallerDocShell()
};
};
==== Notes on callers of existing JS Context Stack ====
nsJSConfigTriggers.cpp: pushed and pops its own private JSContext.  Not nsIScript* aware at all.
nsWebScriptsAccess.cpp: pushes and pops an explicitly null context
nsXMLHttpRequest.cpp: appears to just push the context from its nsIScriptContext *mScriptContext, and fetch an nsIScriptContext from the context stack - can use the language independent function.  In NotifyEventListeners/ChangeState/OnChannelRedirect
nsScriptSecurityManager: Very JS specific already and will probably remain so.
nsDocShell: Use new GetCallerDocShell as described above.
LegacyPlugin.cpp: Does some private magic with context stacks.  Filename implies we can ignore this.
ns4xPlugin.cpp: some private js context work.
nsWindowWatcher: Generally looks at the top of the stack to try and locate a dynamic or static script global.  Some JS specifics though.
js/src/*: Lots of JS specific stuff as you expect
ProxyClassLoader.cpp: Peeks at the stack to get a context, to get a property.  Could use new property fetch functions.
nsProfile.cpp: Calls GetSafeJSContext just to force a GC.
nsContentUtils: See nsContentUtils discussion below.
nsRange.cpp::CreateContextualFragment - seems to push/pop a JS Context, and compares principals and contexts.
// If there's no JS or system JS running, push the current document's context on the JS context stack
What does that mean???
content/html - a few uses ignored.
nsLocation: Looks for existing context.  Some DynamicContext.
::JS_GetContextPrivate ???
[JS_GetContextPrivate() is how one gets to the nsIScriptContext given a JSContext. All JSContext's are obviously not nsIScriptContext's, so flags need to be checked etc, as you saw in GetScriptContextFromJSContext() -- jst]
=== nsIScriptSecurityManager ===
All JSContext* can probably be replaced with nsIScriptContext.  Use of simple JObject* may need to be replaced with nsIScriptContext* and matching void*.
nsIScriptSecurityManager is caps' interface, and caps will most likely remain JS specific. Caps is also extremely performance critical, and having to go through a virtual function to get to the JSContext may not be what we want, if it can be avoided... -- jst
=== nsScriptLoader ===
nsScriptLoader's interface is language neutral.
* nsScriptLoadRequest will have either a nsIScriptContext or language ID as a member.
=== nsScriptLoaderObserver ===
Presumably will need to become script language aware.  eg, scriptAvailable() method has text of the script passed - not much use without also knowing the language.  Possibly replace with nsIScriptElement, which does have the language ID
=== nsXULPrototypeScript ===
* Change JSObject *mJSObject -> void *mScriptObject.
* Add language_id reference - caller must provide their own nsIScriptContext.
* Deserializing etc needs to support multi-languages.  May require serialize/deserialize functions on the context?
=== nsContentUtils ===
Uses native JSContext for ReparentContentWrapper
GetDocShellFromCaller/GetDocumentFromCaller - uses JS context stack to get the nsIScriptGlobal
nsCxPusher helper class: Peeks at JSContextStack to determine if a script is running (the simple fact an item is on the stack is all it checks).  Pushes and pops a context.
nsContentUtils::NotifyXPCIfExceptionPending(JSContext* aCx) - 2 callers:
* content/xbl/src/nsXBLProtoImplMethod.cpp -  nsXBLProtoImplAnonymousMethod::Execute() - very JS specific [Do we plan to keep XBL JS-specific, then?  Or allow binding implementations done in other languages? I'd assume the "very JS specific" comment refers to the latter, right?  If so, we'll need a lot of work on this front, especially to keep our existing brutal sharing; perhaps we need some nsIScriptContext methods for that sort of thing?  I'd love for Execute() to not rely on jsapi.h --[[User:Bzbarsky|Bzbarsky]] 18:55, 15 Aug 2005 (PDT)]
* dom/src/base/nsJSEnvironment.cpp
=== nsAppShellService ===
SetXPConnectSafeContext - seems to create and set the "safe context" for the context stack.
=== Event listeners ===
http://lxr.mozilla.org/seamonkey/source/content/events/src/nsEventListenerManager.cpp#1160
Changes required:
* AddScriptEventListener() needs a "language_id" passed in
* 'context' still comes from ScriptGlobal
* Use of "@mozilla.org/js/xpc/ContextStack;1" needs to be abstracted into the nsIScript interfaces.
=== nsDOMScriptObjectFactory ===
nsDOMScriptObjectFactory:::NewScriptContext may be unnecessary - just allow the ScriptGlobalObject to auto-create its own somehow.  There are multiple implementations, so may not be reasonable.


=== Exceptions ===
=== Exceptions ===


Exceptions should be chained across languages.  This should just mean diligent use of nsIExceptionService?
Exceptions should be chained across languages.  This should just mean diligent use of nsIExceptionService?  This has not been addressed yet.
 
== Identified list of things we will *not* do ==
* We will make no attempt to have abstract the security interfaces - Python has no concept of "untrusted code". This means Python will be restricted to running from trusted XUL.
* Principals (obviously we must not change existing semantics, but Python will ignore them.)
* Allowing a language version to be specified.
 
 
== Stuff Mark still needs to grok ==
 
* Still a little gray on the relationship between a JSContext, a "scope" and a "global". [ One could say that "scope" is the 'this' property when running code, "global" is what's at the top of any object's scope chain. A JSContext always has a global object (in mozilla at least), but there's not always a 1:1 mapping. In fact now with the split window work landed, there's generally more than one global object per JSContext. -- jst]
** JSContext a DOM specific concept, so script_language -> C++ -> script_language preserves the state of the globals (correct?) [Not sure what you mean by this -- jst]
** What is in a "context" beyond the global object? [For one, the stack frame for the running JS is in the context, very important for security code -- jst]
 
* WrapNative and what it really means to this
 
* Best way to tackle nsDOMClassInfo??? [ I think the harder problem is the scriptable helpers, the nsDOMClassInfo class is fairly language neutral already -- jst]
 
* nsBindingManager - very JS specific and not using nsIScript interfaces
 
* Need to understand the desired 'undefined' semantics for the return value.  Python's builtin None is closer to a JS null, so may not be suitable for 'undefined'. However, a simple 'return' will return None.
 
See nsXBLProtoImplField.cpp
 
* Timeouts look tricky. [Yeah, somewhat. Especially the management of timeout objects is tricky due to reentrancy of all sorts being possible there. nsTimeout probably needs now to know the language, and have code to execute different languages... -- jst]
 
* Events look tricky [Shouldn't be too bad. There's some JS:isms in the event code, but mostly to support node.onclick=... style event handlers. Whether we want to support those in Python is a desision that'll make this potentially much harder to do :) -- jst]
 
== Random Notes ==
 
Some notes about existing callers of certain nsIScriptContext.
 
nsDOMParser:
 
* Gets "native call context" to end up with nsIScriptContext - apparently all just to get the script URI nsDocShell
** http://lxr.mozilla.org/seamonkey/source/extensions/xmlextras/base/src/nsDOMParser.cpp#476
** Will need to know the language_id that should process the data from the stream
* Security related functions
 
nsXMLHttpRequest.cpp
* GetCurrentContext uses the JS ContextStack.
 
mozXMLTermUtils.cpp:
  * mozXMLTermUtils::ExecuteScript called nsIScriptContext::ExecuteScript -      but with NULL "void *" pointers.  Needs to have nsIScriptContext passed?
 
nsScriptSecurityManager.cpp/nsSecurityManagerFactory.cpp:
*Almost all functions convert a JSContext to an nsIScriptContext.  Most just use to GetGlobalObject - GetPrincipalAndFrame has JS assumptions
* nsSecurityNameSet::InitializeNameSet - Lots of JS specific code.
 
docshell/base/nsDocShell.cpp:
1 use nsDocShell::CheckLoadingPermissions() - use of JF ContextStack to fetch nsIScriptContext - just to GetGlobalObject
 
embedding/components/windowwatcher/src/nsWWJSUtils.cpp
* Converts JSContext to nsIScriptContext.  Mainly for GetGlobalObject, but nsIScriptGlobalObject *nsWWJSUtils::GetStaticScriptGlobal has JS deps.
 
embedding/components/windowwatcher/src/nsWindowWatcher.cpp:
 
    JSObject * nsWindowWatcher::GetWindowScriptObject
        not used???
 
    nsWindowWatcher::AttachArguments
    AddSupportsTojsvals
        2038      rv = xpc->WrapNative(cx, ::JS_GetGlobalObject(cx), data,
        2039                            *iid, getter_AddRefs(wrapper));
 
 
content/base/src/nsContentUtils.cpp:
    Convert nsIScriptContext -> JS Context
content/base/src/nsDocument.cpp:
    Convert nsIScriptContext -> JS Context -> CanExecuteScripts
    (can CanExecuteScripts take an nsIScriptContext?)
 
content/base/src/nsScriptLoader.cpp:
 
        nsScriptLoader::ProcessScriptElement
            Does the actual loading of the stream.
        EvaluateScript calls nsIScriptContext->EvaluateScript - with most args
 
content/events/src/nsEventListenerManager.cpp:
 
    Quite JS specific
   
content/xbl/src/
nsXBLBinding
    ::JSSetPrototype for a new context?
nsXBLDocumentInfo:
  implementation of nsIScriptGlobalObject.
  JSObject *mJSObject;    // XXX JS language rabies bigotry badness
  mJSObject = ::JS_NewObject - abstraction?
nsXBLProtoImpl:
  Lots of JS Specific code.
  Fairly generic compile function: http://lxr.mozilla.org/seamonkey/source/content/xbl/src/nsXBLProtoImpl.cpp#192
 
content/xul:
nsXULElement
    event handlers remain JS specific
    serialization too
nsXULDocument:
    Calls ExecuteScript with mScriptGlobalObject->GetGlobalJSObject() as an arg
    case nsXULPrototypeNode::eType_Script: {
      else if (scriptproto->mJSObject) {
                    // An inline script
                    rv = ExecuteScript(scriptproto->mJSObject);
                    if (NS_FAILED(rv)) return rv;
                }
 
    XUL Script cache
 
dom/src/base/nsDOMClassInfo.cpp
  Lots of JS - dynamic DOM not exposed by XPCOM class info?
 
dom/src/base/nsGlobalWindow.cpp
    JS specific "scope" code
    nsIScriptContext *currentCX = nsJSUtils::GetDynamicScriptContext(cx);
    argv handling
    Creating a window - calls WindowWatcher::OpenWindowJS
   
dom/src/events/nsJSEventListener.cpp:
 
layout/generic/nsObjectFrame.cpp
 
xpfe/appshell/src/nsAppShellService.cpp
 
plugins/activex/embedding/crypto: ignoring for now.

Latest revision as of 18:08, 8 June 2010

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

Overview

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.

nsIScriptContext

  • 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

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 "@mozilla.org/js/xpc/ContextStack;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: http://lxr.mozilla.org/seamonkey/source/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp#216 http://lxr.mozilla.org/seamonkey/source/extensions/webservices/security/src/nsWebScriptsAccess.cpp#767 - pushes a NULL context?

Generic "GetCallerDocShell" or similar will avoid some JS specifics - eg, http://lxr.mozilla.org/seamonkey/source/docshell/base/nsDocShell.cpp#6092

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

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