New nsIScriptable interface: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
m (fix some typos)
m (Reverted edit of Vbdy, changed back to last version by RyanJones)
 
(7 intermediate revisions by 6 users not shown)
Line 1: Line 1:
= nsIScriptable =
This page has the technical details on the [[ScriptableObject|nsIScriptableObject]] interface.


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.
= Differences to IDispatch =


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:
We will *not* implement a number of "Visual Basic-isms" in IDispatch - specifically:
* 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.
* 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 indistinguishable from a property reference)


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.
* names will all be case-sensitive.


== nsIScriptable design ==
= Identifier mapping =
 
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)
 
=== Identifier 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.
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.
Line 29: Line 19:
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)
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 ===
= nsIScriptableObject interface =


Using XPCOM vocabulary (attribute for property, e.g.), the interface should look something like this (recall that xpidl capitalizes names for C++):
Using XPCOM vocabulary (attribute for property, e.g.), the interface should look something like this (recall that xpidl capitalizes names for C++):
Line 36: Line 26:
   
   
  [scriptable, uuid(...)]
  [scriptable, uuid(...)]
  interface nsIScriptable : nsISupports
  interface nsIScriptableObject : nsISupports
  {
  {
     // Maps a single member name to its corresponding DISPID, which can then
     // Maps a single member name to its corresponding DISPID, which can then
Line 44: Line 34:
     // Deletes a member by DISPID.
     // Deletes a member by DISPID.
     void deleteMemberByDispID(in nsDISPID aDispID);
     void deleteMemberByDispID(in nsDISPID aDispID);
    // Adds a member and returns the dispid for the new item.
    nsDISPID addMemberByName(in AString aName);
   
   
     // Deletes a member by name.
     // Deletes a member by name.
Line 61: Line 54:
   
   
     // Retrieves the interface for the scope parent of an object.
     // Retrieves the interface for the scope parent of an object.
     nsIScriptable getParentObject();
     nsIScriptableObject getParentObject();
   
   
     // Enumerates the members of the object.
     // Enumerates the members of the object.
Line 93: Line 86:
     /* DISPID reserved for the standard "NewEnum" method */
     /* DISPID reserved for the standard "NewEnum" method */
     const PRUint32 DISPID_NEWENUM = -4;
     const PRUint32 DISPID_NEWENUM = -4;
 
     const PRUint32 DISPID_CONSTRUCTOR = -6;
     const PRUint32 DISPID_CONSTRUCTOR = -6;
  };
  };
=== 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 execute(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 global scope object 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/scriptobjecthelper"].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 global = s.getLanguageGlobal(lang_id, window)
  var func = global.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.
   
For the sake of completeness, the above could also obviously be written as:
  var global = s.getLanguageGlobal(lang_id, window)
  var ret = global.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.
== Mozilla specific considerations ==
=== nsIClassInfo ===
A new bit will be added to nsIClassInfo to indicate that the object is likely to work with nsIScriptable.  This is a (possibly premature) optimization that predits the implementation will prefer to check this bit than perform a QI.)
=== Naming ===
[http://lxr.mozilla.org/seamonkey/source/js/src/xpconnect/idl/nsIXPCScriptable.idl nsIXPCScriptable ] already exists but has quite different semantics.  Is this a problem?

Latest revision as of 10:46, 25 November 2006

This page has the technical details on the nsIScriptableObject interface.

Differences to IDispatch

We will *not* implement a number of "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 indistinguishable from a property reference)
  • names will all be case-sensitive.

Identifier 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)

nsIScriptableObject interface

Using XPCOM vocabulary (attribute for property, e.g.), the interface should look something like this (recall that xpidl capitalizes names for C++):

typedef PRUint32 nsDISPID;

[scriptable, uuid(...)]
interface nsIScriptableObject : nsISupports
{
    // Maps a single member name to its corresponding DISPID, which can then
    // be used on subsequent calls to invoke()
    nsDISPID getDispID(in AString aName);

    // Deletes a member by DISPID.
    void deleteMemberByDispID(in nsDISPID aDispID);

    // Adds a member and returns the dispid for the new item.
    nsDISPID addMemberByName(in AString aName);

    // Deletes a member by name.
    void deleteMemberByName(in AString aName);

    // Retrieves the name of a member.
    AString getMemberName(in nsDISPID aDispID);

    // Retrieves a member's properties.
    PRUint32 getMemberFlags(in nsDISPID aDispID);

    // Flags describing members.
    const PRUint32 MEMBER_METHOD      = 1; // member is a method
    const PRUint32 MEMBER_CONSTRUCTOR = 2; // member is a constructor
    const PRUint32 MEMBER_ATTRIBUTE   = 4; // member is an attribute
    const PRUint32 MEMBER_READONLY    = 8; // member is readonly

    // Retrieves the interface for the scope parent of an object.
    nsIScriptableObject getParentObject();

    // Enumerates the members of the object.
    nsDISPID getNextDispID(in PRUint32 aFlags, in nsDISPID aDispID);

    // Invoke a method or access an attribute exposed by an IScriptable.

    // XXX - semantics of DISPID and params is not clear.
    // presumably all possible param names must also provide
    // a dispid when asked.

    // MSCOM EXCEPTINFO param not used - existing
    // nsIExceptionService semantics should be used instead.

    nsIVariant invoke(in nsDISPID aDispID,
                      in PRUint32 aInvokeHow,
                      [array, size_is(aNumArgs)] in nsIVariant aArgs,
                      in PRUint32 aNumArgs,
                      [array, size_is(aNumNamedArgs)] in nsDISPID aNamedArgs,
                      in PRUint32 aNumNamedArgs);

    // Values for the aInvokeHow argument to invoke.
    const PRUint32 INVOKE_CALL      = 0; // invoke as a method
    const PRUint32 INVOKE_CONSTRUCT = 1; // invoke as a constructor
    const PRUint32 INVOKE_GETATTR   = 2; // get an attribute value
    const PRUint32 INVOKE_SETATTR   = 3; // set an attribute value

    // Predefined nsDISPID values
   /* DISPID reserved for the "value" property (ie, the 'default' value )*/
   const PRUint32 DISPID_VALUE = 0;
   /* DISPID reserved for the standard "NewEnum" method */
   const PRUint32 DISPID_NEWENUM = -4;

   const PRUint32 DISPID_CONSTRUCTOR = -6;
};