Implement prototype setup infrastructure.
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 26 Jan 2012 17:23:31 +0100
changeset 85149 4119ff5bd1104150455174319f6f1edba3d41c8f
parent 85148 fdee219a75502a61e28f8d2990ec4d91385e4dbd
child 85150 cc2e300dab5b4a3beea5b95d2e9b32337cb032a0
push id51
push userbzbarsky@mozilla.com
push dateThu, 26 Jan 2012 16:23:55 +0000
milestone12.0a1
Implement prototype setup infrastructure.
dom/bindings/BindingGen.py
dom/bindings/Codegen.py
dom/bindings/GlobalGen.py
dom/bindings/Utils.h
dom/workers/Makefile.in
dom/workers/WorkerScope.cpp
js/src/jsapi.h
js/xpconnect/src/XPCWrappedNativeJSOps.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcpublic.h
--- a/dom/bindings/BindingGen.py
+++ b/dom/bindings/BindingGen.py
@@ -19,44 +19,66 @@ def generate_binding_header(config, outp
 
     prologue = """
 /* THIS FILE IS AUTO-GENERATED - DO NOT EDIT */
 
 #ifndef %s_h
 #define %s_h
 
 #include "mozilla/dom/bindings/DOMJSClass.h"
+#include "mozilla/dom/bindings/Utils.h"
 
 namespace mozilla {
 namespace dom {
 namespace bindings {
 namespace prototypes {
 """ % (outputprefix, outputprefix)
 
     chunk = """
 namespace %s {
   extern DOMJSClass Class;
-  bool Install(JSContext* aCx, JSObject* aGlobal);
+
+  JSObject* CreateProtoObject(JSContext* aCx, JSObject* aGlobal);
+
+  /* Get the prototype object for this class.  This will create the prototype
+     as needed. */
+  JSObject* GetProtoObject(JSContext* aCx, JSObject* aGlobal) {
+    /* Make sure our global is sane.  Hopefully we can remove this sometime */
+    if (!(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL)) {
+      return NULL;
+    }
+    /* Check to see whether the prototype is already installed */
+    JSObject **protoArray =
+      static_cast<JSObject**>(js::GetReservedSlot(aGlobal, DOM_PROTOTYPE_SLOT).toPrivate());
+    JSObject *ourProto = protoArray[id::%s];
+    if (!ourProto) {
+      ourProto = protoArray[id::%s] = CreateProtoObject(aCx, aGlobal);
+    }
+
+    /* ourProto might _still_ be null, but that's OK */
+    return ourProto;
+  }
 }
 """
 
     epilogue = """
 } // namespace prototypes
 } // namespace bindings
 } // namespace dom
 } // namespace mozilla
 
 #endif // %s_h
 """ % (outputprefix)
 
     # Write from templates.
     f.write(prologue)
     for domClass in config.dom_classes.values():
         for implementation in domClass.implementations:
-            f.write(chunk % implementation.name)
+            f.write(chunk % (implementation.name, implementation.name,
+                             implementation.name))
     f.write(epilogue)
     f.close()
 
 def generate_binding_cpp(config, outputprefix):
     """
     protoStructure is a list containing information about the prototypes
     this binding file defines.  Each entry is a dict with the following entries:
 
@@ -67,64 +89,99 @@ def generate_binding_cpp(config, outputp
       nativeIsISupports: a boolean indicating whether the native this class
                          wraps is a subclass of nsISupports.
     """
 
     filename = outputprefix + ".cpp"
     print "Generating binding implementation: %s" % (filename)
     f = open(filename, 'w')
 
-    prologue = """
-/* THIS FILE IS AUTO-GENERATED - DO NOT EDIT */
+    parentIncludeTemplate = """#include "%sBinding.h"
+"""
 
+    includes = ""
+    for domClass in config.dom_classes.values():
+        parentInterface = domClass.interface.parent
+        if parentInterface:
+            parentFileName = os.path.split(parentInterface.filename())[1].replace(".webidl", "");
+            includes += parentIncludeTemplate % parentFileName
+
+    prologue = """/* THIS FILE IS AUTO-GENERATED - DO NOT EDIT */
+
+%s
 #include "%s.h"
 
 namespace mozilla {
 namespace dom {
 namespace bindings {
 namespace prototypes {
-""" % (outputprefix)
+""" % (includes, outputprefix)
 
     chunk = """
 namespace %s {
 
 DOMJSClass Class = {
   { "%s",
     JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(1),
     JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
     JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
     NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
   },
     {%s}, -1, %s
 };
 
-bool
-Install(JSContext* aCx, JSObject* aGlobal)
+JSObject*
+CreateProtoObject(JSContext* aCx, JSObject* aGlobal)
 {
-  return false;
+%s
 }
 
 } // namespace %s
 """
 
+    createProtoTemplate = """  JSObject* parentProto = %s;
+  if (!parentProto) {
+    return NULL;
+  }
+
+  JSObject* ourProto = JS_NewObject(aCx, NULL, parentProto, aGlobal);
+  if (!ourProto) {
+    return NULL;
+  }
+  // XXXbz actually set up methods/properties here
+  return ourProto;
+"""
 
     epilogue = """
 } // namespace prototypes
 } // namespace bindings
 } // namespace dom
 } // namespace mozilla
 """
 
     # Write from templates.
     f.write(prologue)
     for domClass in config.dom_classes.values():
         for implementation in domClass.implementations:
-            interfaceChainString = ', '.join(['id::' + iface for iface in domClass.interfaceChain])
+            interfaceChain = domClass.interfaceChain
+            interfaceChainString = ', '.join(['id::' + iface for iface in interfaceChain])
+            if (len(interfaceChain) == 1):
+                getParentProto = "GetCanonicalObjectProto(aCx, aGlobal)"
+            else:
+                # The last entry of interfaceChain is ourselves; we want
+                # the entry before that one.
+                parentProtoName = interfaceChain[-2]
+                getParentProto = ("%s::GetProtoObject(aCx, aGlobal)" %
+                                  parentProtoName)
+
+            createProto = createProtoTemplate % (getParentProto)
+                
             f.write(chunk % (implementation.name, domClass.name, interfaceChainString,
-                             str(implementation.nativeIsISupports).lower(), implementation.name))
+                             str(implementation.nativeIsISupports).lower(),
+                             createProto, implementation.name))
     f.write(epilogue)
     f.close()
 
 def main():
 
     # Parse arguments.
     from optparse import OptionParser
     usagestring = "usage: %prog configFile outputPrefix webIDLFile"
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -65,16 +65,17 @@ class DOMClass():
         self.implementations = [DOMClassImplementation(self, implConf) for implConf in classConf['implementations']]
 
         # Build the interface chain.
         self.interfaceChain = [self.name]
         parent = interface.parent
         while parent:
             self.interfaceChain.insert(0, parent.identifier.name)
             parent = parent.parent
+        self.interface = interface
 
 class Configuration:
     def __init__(self, filename, parseData):
         self.configFile = {}
         execfile(filename, self.configFile)
 
         # We need dom_classes.
         if 'dom_classes' not in self.configFile:
--- a/dom/bindings/GlobalGen.py
+++ b/dom/bindings/GlobalGen.py
@@ -62,18 +62,18 @@ typedef id::ID ID;
             protoList.append(implementation.name)
 
     # Append the enum count.
     protoList.append('Count')
 
     # Add appropriate indentation before the prototype strings.
     protoList = ["  " + p for p in protoList]
 
-    # Start the enum at 1, because 0 is special.
-    protoList[0] = protoList[0] + " = 1"
+    # Start the enum at 0
+    protoList[0] = protoList[0] + " = 0"
 
     newFileContents = prologue + ',\n'.join(protoList) + epilogue
 
     if newFileContents == oldFileContents:
         print "Prototype list hasn't changed - not touching %s" % (filename)
         return
 
     print "Generating prototype list: %s" % (filename)
--- a/dom/bindings/Utils.h
+++ b/dom/bindings/Utils.h
@@ -5,16 +5,20 @@
 
 #ifndef mozilla_dom_bindings_Utils_h__
 #define mozilla_dom_bindings_Utils_h__
 
 #include "jsapi.h"
 #include "DOMJSClass.h"
 #include "XPCQuickStubs.h"
 #include "XPCWrapper.h"
+#include "PrototypeList.h"
+
+/* All DOM globals must have a slot at DOM_PROTOTYPE_SLOT */
+#define DOM_PROTOTYPE_SLOT (JSCLASS_GLOBAL_SLOT_COUNT + 1)
 
 namespace mozilla {
 namespace dom {
 namespace bindings {
 
 inline bool
 IsDOMClass(JSClass *clasp)
 {
@@ -133,14 +137,39 @@ UnwrapInterfaceArg(JSContext *cx,
     }
   }
 
   // Fall back on unwrapping old-style arguments (possibly including
   // nodelist bindings).
   return xpc_qsUnwrapArg(cx, v, value, argRef, vp);
 }
 
+void
+AllocateProtoCache(JSObject *obj)
+{
+  // Important: The () at the end ensure zero-initialization
+  JSObject** protoArray = new JSObject*[prototypes::id::Count]();
+  js::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT, js::PrivateValue(protoArray));
+}
+
+void
+DestroyProtoCache(JSObject *obj)
+{
+  JSObject **protoArray = static_cast<JSObject**>(
+    js::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).toPrivate());
+  delete [] protoArray;
+}
+
+JSObject*
+GetCanonicalObjectProto(JSContext *cx, JSObject *global)
+{
+  JSObject* proto;
+  if (!js_GetClassPrototype(cx, global, JSProto_Object, &proto)) {
+    return NULL;
+  }
+  return proto;
+}
 
 } // namespace bindings
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_bindings_Utils_h__ */
--- a/dom/workers/Makefile.in
+++ b/dom/workers/Makefile.in
@@ -73,15 +73,16 @@ CPPSRCS = \
   XMLHttpRequestPrivate.cpp \
   $(NULL)
 
 LOCAL_INCLUDES = \
   -I$(topsrcdir)/content/base/src \
   -I$(topsrcdir)/content/events/src \
   -I$(topsrcdir)/dom/base \
   -I$(topsrcdir)/xpcom/build \
+  -I$(topsrcdir)/js/xpconnect/src \
   $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -61,16 +61,17 @@
 #include "Worker.h"
 #include "WorkerPrivate.h"
 #include "XMLHttpRequest.h"
 #ifdef ANDROID
 #include <android/log.h>
 #endif
 
 #include "WorkerInlines.h"
+#include "mozilla/dom/bindings/Utils.h"
 
 #define PROPERTY_FLAGS \
   JSPROP_ENUMERATE | JSPROP_SHARED
 
 #define FUNCTION_FLAGS \
   JSPROP_ENUMERATE
 
 using namespace mozilla;
@@ -664,16 +665,17 @@ public:
 
     DedicatedWorkerGlobalScope* priv =
       new DedicatedWorkerGlobalScope(aWorkerPrivate);
     if (!SetJSPrivateSafeish(aCx, aObj, priv)) {
       delete priv;
       return false;
     }
 
+    mozilla::dom::bindings::AllocateProtoCache(aObj);
     return true;
   }
 
 protected:
   DedicatedWorkerGlobalScope(WorkerPrivate* aWorker)
   : WorkerGlobalScope(aWorker)
   {
     MOZ_COUNT_CTOR(mozilla::dom::workers::DedicatedWorkerGlobalScope);
@@ -803,17 +805,18 @@ private:
     }
 
     return scope->mWorker->PostMessageToParent(aCx, message);
   }
 };
 
 JSClass DedicatedWorkerGlobalScope::sClass = {
   "DedicatedWorkerGlobalScope",
-  JSCLASS_GLOBAL_FLAGS | JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,
+  JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(2) |
+  JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,
   JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
   JS_EnumerateStub, reinterpret_cast<JSResolveOp>(Resolve), JS_ConvertStub,
   Finalize, NULL, NULL, NULL, NULL, NULL, NULL, Trace, NULL
 };
 
 JSPropertySpec DedicatedWorkerGlobalScope::sProperties[] = {
   { sEventStrings[STRING_onmessage], STRING_onmessage, PROPERTY_FLAGS,
     GetEventListener, SetEventListener },
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3523,16 +3523,17 @@ struct JSClass {
 #define JSCLASS_PRIVATE_IS_NSISUPPORTS  (1<<3)  /* private is (nsISupports *) */
 #define JSCLASS_NEW_RESOLVE_GETS_START  (1<<4)  /* JSNewResolveOp gets starting
                                                    object in prototype chain
                                                    passed in via *objp in/out
                                                    parameter */
 #define JSCLASS_IS_DOMJSCLASS           (1<<5)  /* corresponds to a DOMJSClass
                                                    subclass instance. */
 #define JSCLASS_DOCUMENT_OBSERVER       (1<<6)  /* DOM document observer */
+#define JSCLASS_DOM_GLOBAL              (1<<7)  /* A new-style DOM global */
 
 /*
  * To reserve slots fetched and stored via JS_Get/SetReservedSlot, bitwise-or
  * JSCLASS_HAS_RESERVED_SLOTS(n) into the initializer for JSClass.flags, where
  * n is a constant in [1, 255].  Reserved slots are indexed from 0 to n-1.
  */
 #define JSCLASS_RESERVED_SLOTS_SHIFT    8       /* room for 8 flags below */
 #define JSCLASS_RESERVED_SLOTS_WIDTH    8       /* and 16 above this field */
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -39,16 +39,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 /* JavaScript JSClasses and JSOps for our Wrapped Native JS Objects. */
 
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
 #include "nsWrapperCacheInlines.h"
+#include "mozilla/dom/bindings/Utils.h"
 
 /***************************************************************************/
 
 // All of the exceptions thrown into JS from this file go through here.
 // That makes this a nice place to set a breakpoint.
 
 static JSBool Throw(uintN errNum, JSContext* cx)
 {
@@ -1037,16 +1038,20 @@ XPC_WN_Helper_HasInstance(JSContext *cx,
     HasInstance(wrapper, cx, obj, *valp, &retval2, &retval);
     *bp = retval2;
     POST_HELPER_STUB
 }
 
 static void
 XPC_WN_Helper_Finalize(JSContext *cx, JSObject *obj)
 {
+    js::Class* clazz = js::GetObjectClass(obj);
+    if (clazz->flags & JSCLASS_DOM_GLOBAL) {
+        mozilla::dom::bindings::DestroyProtoCache(obj);
+    }
     nsISupports* p = static_cast<nsISupports*>(xpc_GetJSPrivate(obj));
     if (IS_SLIM_WRAPPER(obj)) {
         SLIM_LOG(("----- %i finalized slim wrapper (%p, %p)\n",
                   ++sFinalizedSlimWrappers, obj, p));
 
         nsWrapperCache* cache;
         CallQueryInterface(p, &cache);
         cache->ClearWrapper();
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1215,16 +1215,20 @@ xpc_CreateGlobalObject(JSContext *cx, JS
         VerifyTraceXPCGlobalCalledTracer trc;
         JS_TracerInit(&trc.base, cx, VerifyTraceXPCGlobalCalled);
         trc.ok = false;
         JS_TraceChildren(&trc.base, *global, JSTRACE_OBJECT);
         NS_ABORT_IF_FALSE(trc.ok, "Trace hook needs to call TraceXPCGlobal if JSCLASS_XPCONNECT_GLOBAL is set.");
     }
 #endif
 
+    if (clasp->flags & JSCLASS_DOM_GLOBAL) {
+        mozilla::dom::bindings::AllocateProtoCache(*global);
+    }
+
     return NS_OK;
 }
 
 nsresult
 xpc_CreateMTGlobalObject(JSContext *cx, JSClass *clasp,
                          nsISupports *ptr, JSObject **global,
                          JSCompartment **compartment)
 {
@@ -1248,16 +1252,20 @@ xpc_CreateMTGlobalObject(JSContext *cx, 
         js::AutoSwitchCompartment sc(cx, *compartment);
 
         JSObject *tempGlobal = JS_NewGlobalObject(cx, clasp);
         if (!tempGlobal)
             return UnexpectedFailure(NS_ERROR_FAILURE);
         *global = tempGlobal;
     }
 
+    if (clasp->flags & JSCLASS_DOM_GLOBAL) {
+        mozilla::dom::bindings::AllocateProtoCache(*global);
+    }
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPConnect::InitClassesWithNewWrappedGlobal(JSContext * aJSContext,
                                              nsISupports *aCOMObj,
                                              const nsIID & aIID,
                                              nsIPrincipal * aPrincipal,
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -68,18 +68,18 @@ xpc_CreateGlobalObject(JSContext *cx, JS
                        JSCompartment **compartment);
 
 nsresult
 xpc_CreateMTGlobalObject(JSContext *cx, JSClass *clasp,
                          nsISupports *ptr, JSObject **global,
                          JSCompartment **compartment);
 
 #define XPCONNECT_GLOBAL_FLAGS                                                \
-    JSCLASS_XPCONNECT_GLOBAL | JSCLASS_HAS_PRIVATE |                          \
-    JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(1)
+    JSCLASS_DOM_GLOBAL | JSCLASS_XPCONNECT_GLOBAL | JSCLASS_HAS_PRIVATE |     \
+    JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(2)
 
 void
 TraceXPCGlobal(JSTracer *trc, JSObject *obj);
 
 // XXX where should this live?
 NS_EXPORT_(void)
 xpc_LocalizeContext(JSContext *cx);