New nsIScriptable interface: Difference between revisions
No edit summary |
No edit summary |
||
Line 114: | Line 114: | ||
=== Use Cases === | === Use Cases === | ||
==== existing dialog widget ==== | |||
Consider the dialog widget, which allows you to have an "ondialogaccept" event handler. This widget is implemented in JS. | Consider the dialog widget, which allows you to have an "ondialogaccept" event handler. This widget is implemented in JS. | ||
See dialog.xul, http://lxr.mozilla.org/seamonkey/source/xpfe/global/resources/content/bindings/dialog.xml#297 | See dialog.xul, http://lxr.mozilla.org/seamonkey/source/xpfe/global/resources/content/bindings/dialog.xml#297 | ||
Line 133: | Line 134: | ||
// Compile a function using the specified language. Returns | // Compile a function using the specified language. Returns | ||
// an nsIScriptable that can be directly called (ie, by passing | // an nsIScriptable that can be directly called (ie, by passing | ||
// DISPID_DEFAULT) | // DISPID_DEFAULT). |glob| must be the global window in | ||
nsIScriptable CompileFunction(PRUint32 langID, | // which the returned function will execute when called. | ||
// Generally you pass |this|. | |||
nsIScriptable CompileFunction(in nsISupports glob, | |||
in PRUint32 langID, | |||
in DOMString aCode, | in DOMString aCode, | ||
[array] in DOMString argNames); | [array] in DOMString argNames); | ||
Line 141: | Line 145: | ||
void Exec(PRUint32 langID, in DOMString aCode); | void Exec(PRUint32 langID, in DOMString aCode); | ||
// Takes a script-type ID, and nsISupports |glob| (which must be the | // Takes a script-type ID (which is presumably the ID for | ||
// | // a language other than the one being executed), and nsISupports | ||
// |glob| (which must be the global object for the currently | |||
// executing language) and returns an nsIScriptable, which is | |||
// the namespace for the requested language. | |||
nsIScriptable GetLanguageGlobal(in PRUint32 aRequestedLanguage, in nsISupports glob); | nsIScriptable GetLanguageGlobal(in PRUint32 aRequestedLanguage, in nsISupports glob); | ||
}; | }; | ||
Line 152: | Line 158: | ||
var s = components.classes["@mozilla.org/scriptobjectfactory"].getService(); | var s = components.classes["@mozilla.org/scriptobjectfactory"].getService(); | ||
var func = s.CompileFunction(lang_id, handler, "event"); | var func = s.CompileFunction(this, lang_id, handler, "event"); | ||
// |func| is an nsIScriptable - call it. | // |func| is an nsIScriptable - call it. | ||
var returned = func(event) | var returned = func(event) | ||
==== Contrived dialog widget ==== | |||
Below is a contrived example that demonstrates GetLanguageGlobal. Let's assume the design of the dialog widget was such that it simply assumed a global function called "ondialogaccept" in the window namespace. | Below is a contrived example that demonstrates GetLanguageGlobal. Let's assume the design of the dialog widget was such that it simply assumed a global function called "ondialogaccept" in the window namespace. | ||
Line 165: | Line 173: | ||
<dialog> | <dialog> | ||
This could be implemented in a language agnostic fashion: | This could be implemented now in a JavaScript specific way simply by calling the function 'ondialogaccept' by name, in the assumption a <script> block created | ||
such a function. | |||
Assuming nsIScriptable, this could be implemented in a language agnostic fashion as follows: | |||
var namespace = s.GetLanguageGlobal(lang_id, window) | var namespace = s.GetLanguageGlobal(lang_id, window) | ||
Line 171: | Line 182: | ||
var ret = func(event) | var ret = func(event) | ||
The first line returns an nsIScriptable object. The JS support for this | |||
interface would then allow 'namespace.ondialogaccept' to invoke the | |||
necessary nsIScriptable methods to lookup the attribute. The attribute | |||
would be looked up as a simple property reference, and as a function | |||
object would be found, |func| would itself be an nsIScriptable. | |||
|func(event)| would then automatically call nsIScriptable::Invoke with a | |||
DISPID of DISPID_DEFAULT. | |||
or the sake of completeness, the above could also obviously be written as: | or the sake of completeness, the above could also obviously be written as: | ||
Line 184: | Line 195: | ||
var namespace.ondialogaccept(event) | var namespace.ondialogaccept(event) | ||
In this case the use of nsIScriptable may be slightly different - the | |||
initial Invoke for 'ondialogaccept' may be done as a method call, assuming | |||
the language is capable of detecting that situation. |
Revision as of 05:39, 5 January 2006
nsIScriptable
A need has been identified for an XPCOM interface to support general purpose "scriptable" objects. "scriptable" objects are objects with methods and properties that change at runtime. For this reason, the use of normal XPCOM interfaces is not suitable.
nsIScriptable is designed to solve this problem. It is an interface that allows arbitrary property references to be made on an object. The operations allowed are:
- Call a method on an object (including calling the object itself - ie, an nsIScriptable is itself 'callable')
- Set and get the value of a property on the object.
- Create or delete properties on the object.
- Enumerate the properties of the object.
Not all implementations of the interface will support all operations, but the interface itself must be able to describe the above semantics.
Script languages may provide special support for this interface. For example, given an nsIScriptable object, script languages should be able to dynamically translate "x = object.foo" into the necessary IScriptable calls to lookup and fetch the attribute 'foo' on the interface.
nsIScriptable design
We will shamelessly borrow the MS COM IDispatchEx interface as a model. IDispatchEx is designed for this purpose and provides a number of enhancements to the older IDispatch interfaces. We will *not*:
- Implement "Visual Basic-isms" in IDispatch - specifically, no distinction between a "property put by value" and "property put by reference" (ie, VBs "let" vs "set"), and combining "property get" and "method call" in the flags will be illegal (ie, VB allows you to omit the parens when calling subroutines, making such calls indisinguishable from a property reference)
DispID mapping
Like MSCOM, integer IDs are used to identify attributes on an object. Functions are provided to convert from names to IDs, or from IDs to names.
The rules for IDs are:
- Once an ID has been assigned to a name, that ID must constant for that name for the life of the object.
- An ID can not be reused unless an attribute of the same name is recreated (ie, if an item is deleted from an object, the ID can not be reused for a different name)
MSCOM uses DISPIDs to refer to named params. The sementics for DISPIDs for names that appear only as params is unclear (the relevant MS docs need to be found)
nsIScriptable interface
Something like:
typedef nsDISPID PRUint32; [scriptable, uuid(...)] interface nsIScriptable : nsISupports { // Deletes a member by DISPID. DeleteMemberByDispID(in nsDISPID dispID); // Deletes a member by name. DeleteMemberByName(in DOMString &name); // Maps a single member name to its corresponding DISPID, which can then // be used on subsequent calls to Invoke() nsDISPID GetDispID(in DOMString &name); // Retrieves the name of a member. DOMString GetMemberName(in nsDISPID dispID); // Retrieves a member's properties. PRUint32 GetMemberProperties(in nsDISPID dispID, in PRUint32 propsToFetch); // Retrieves the interface for the namespace parent of an object. nsIScriptable GetNameSpaceParent(); // Enumerates the members of the object. nsDISPID GetNextDispID(in PRUint32 flags, in nsDISPID dispid); // Provides access to properties and methods exposed by an IScriptable object. nsIVariant Invoke(in nsDISPID dispID, in nsLOCALE????? locale, // flags for the call in PRUint32 flags, // Like the MSCOM DISPARGS struct. [array, sizeis(aNumArgs)] in nsIVariant aArgs, in PRUint32 aNumArgs, // Array of DISPIDs of named args. // XXX - semantics of DISPID and params is not clear. // presumably all possible param names must also provide // a dispid when asked. [array, sizeof(aNumNamedArgs)] in PRUint32 aNamedArgs, in PRUint32 aNumNamedArgs, // End of DISPARGS struct out nsIVariant *result, // MSCOM EXCEPTINFO param not used - existing // nsIExceptionService semantics should be used instead. // Service provider - may be NULL. in nsISupports aServiceProvider); }; // Flags for Invoke const PRUint32 SCRIPTABLE_METHOD = 0 //The member is invoked as a method. const PRUint32 SCRIPTABLE_PROPERTYGET = 1; // The member is retrieved as a property or data member const PRUint32 SCRIPTABLE_PROPERTYPUT = 2; // The member is changed as a property or data member const PRUint32 SCRIPTABLE_CONSTRUCT = 3; // The member is being used as a constructor. // Predefined DISPID values ... // a subset of property flags for GetMemberProperties // The distinction between 'Can' and 'Cannot' escapes me... const PRUint32 ScriptablePropCanGet // The member can be obtained using SCRIPTABLE_PROPERTYGET. const PRUint32 ScriptablePropCannotGet // The member cannot be obtained using SCRIPTABLE_PROPERTYGET. const PRUint32 ScriptablePropCanPut // The member can be set using SCRIPTABLE_PROPERTYPUT. const PRUint32 ScriptablePropCannotPut // The member cannot be set using SCRIPTABLE_PROPERTYPUT. const PRUint32 ScriptablePropCanCall // The member can be called as a method using SCRIPTABLE_METHOD. const PRUint32 ScriptablePropCannotCall // The member cannot be called as a method using SCRIPTABLE_METHOD. const PRUint32 ScriptablePropCanConstruct // The member can be called as a constructor using SCRIPTABLE_CONSTRUCT. const PRUint32 ScriptablePropCannotConstruct // The member cannot be called as a constructor using SCRIPTABLE_CONSTRUCT. // MSCOM flags *not* supported. // fdexScriptablePropCanPutRef The member can be set using DISPATCH_PROPERTYPUTREF. // fdexScriptablePropCannotPutRef The member cannot be set using DISPATCH_PROPERTYPUTREF. // fdexPropCanSourceEvents The member can fire events. // fdexPropCannotSourceEvents The member cannot fire events // fdexScriptablePropNoSideEffects The member does not have any side effects. For example, a debugger could safely get/set/call this member without changing the state of the script being debugged. // fdexScriptablePropDynamicType The member is dynamic and can change during the lifetime of the object.
Language Helpers
Use Cases
existing dialog widget
Consider the dialog widget, which allows you to have an "ondialogaccept" event handler. This widget is implemented in JS. See dialog.xul, http://lxr.mozilla.org/seamonkey/source/xpfe/global/resources/content/bindings/dialog.xml#297
In summary, a dialog widget's "ondialogaccept" handler is called using the following paraphrased code:
var handler = this.getAttribute("ondialog"+aDlgType); var fn = new Function("event", handler); var returned = fn(event);
The challenge is to allow this JS implemented widget to work in a language agnostic fashion - so that if the dialog node has a different script-type set, the JS implementation can call the other language.
This would be achieved by:
// A new helper xpcom service for cross-language work. [scriptable, uuid(...)] interface nsIDOMScriptObjectHelper: nsISupports { // Compile a function using the specified language. Returns // an nsIScriptable that can be directly called (ie, by passing // DISPID_DEFAULT). |glob| must be the global window in // which the returned function will execute when called. // Generally you pass |this|. nsIScriptable CompileFunction(in nsISupports glob, in PRUint32 langID, in DOMString aCode, [array] in DOMString argNames); // Execute arbitrary code. void Exec(PRUint32 langID, in DOMString aCode); // Takes a script-type ID (which is presumably the ID for // a language other than the one being executed), and nsISupports // |glob| (which must be the global object for the currently // executing language) and returns an nsIScriptable, which is // the namespace for the requested language. nsIScriptable GetLanguageGlobal(in PRUint32 aRequestedLanguage, in nsISupports glob); };
And the JS code implementing dialog.xml would change to:
var lang_id = node.scriptTypeID; // new attribute on nsIDOMNSEventTarget. var s = components.classes["@mozilla.org/scriptobjectfactory"].getService(); var func = s.CompileFunction(this, lang_id, handler, "event"); // |func| is an nsIScriptable - call it. var returned = func(event)
Contrived dialog widget
Below is a contrived example that demonstrates GetLanguageGlobal. Let's assume the design of the dialog widget was such that it simply assumed a global function called "ondialogaccept" in the window namespace.
For example, let's assume a dialog widget insisted you structure your dialog like:
<script> function ondialogaccept(event) { ... do something .." } </script> <dialog>
This could be implemented now in a JavaScript specific way simply by calling the function 'ondialogaccept' by name, in the assumption a <script> block created such a function.
Assuming nsIScriptable, this could be implemented in a language agnostic fashion as follows:
var namespace = s.GetLanguageGlobal(lang_id, window) var func = namespace.ondialogaccept var ret = func(event)
The first line returns an nsIScriptable object. The JS support for this interface would then allow 'namespace.ondialogaccept' to invoke the necessary nsIScriptable methods to lookup the attribute. The attribute would be looked up as a simple property reference, and as a function object would be found, |func| would itself be an nsIScriptable. |func(event)| would then automatically call nsIScriptable::Invoke with a DISPID of DISPID_DEFAULT.
or the sake of completeness, the above could also obviously be written as:
var namespace = s.GetLanguageGlobal(lang_id, window) var namespace.ondialogaccept(event)
In this case the use of nsIScriptable may be slightly different - the initial Invoke for 'ondialogaccept' may be done as a method call, assuming the language is capable of detecting that situation.