Bug 820957 - Support object members in WebIDL dictionary. r=bz
authorWilliam Chen <wchen@mozilla.com>
Mon, 17 Dec 2012 13:44:13 -0800
changeset 121902 f774c609480827729a5d199504763775cdd2ac40
parent 121901 bffe151ab8acc96e3fa148540e3a44d2b176bb5d
child 121903 9a99043b80e7a1901776e663187b7e8bccd06e48
push idunknown
push userunknown
push dateunknown
reviewersbz
bugs820957
milestone20.0a1
Bug 820957 - Support object members in WebIDL dictionary. r=bz
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/test/TestCodeGen.webidl
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1199,16 +1199,33 @@ protected:
   }
 
   nsRefPtr<T> ptr;
 #ifdef DEBUG
   bool inited;
 #endif
 };
 
+class NonNullLazyRootedObject : public Maybe<js::RootedObject>
+{
+public:
+  operator JSObject&() const {
+    MOZ_ASSERT(!empty() && ref(), "Can not alias null.");
+    return *ref();
+  }
+};
+
+class LazyRootedObject : public Maybe<js::RootedObject>
+{
+public:
+  operator JSObject*() const {
+    return empty() ? (JSObject*) nullptr : ref();
+  }
+};
+
 // A struct that has the same layout as an nsDependentString but much
 // faster constructor and destructor behavior
 struct FakeDependentString {
   FakeDependentString() :
     mFlags(nsDependentString::F_TERMINATED)
   {
   }
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2132,17 +2132,17 @@ def getJSToNativeConversionTemplate(type
             # Handle the non-object cases by wrapping up the whole
             # thing in an if cascade.
             templateBody = (
                 "if (${val}.isObject()) {\n" +
                 CGIndenter(CGGeneric(templateBody)).define() + "\n")
             if type.nullable():
                 templateBody += (
                     "} else if (${val}.isNullOrUndefined()) {\n"
-                    "  %s;\n" % codeToSetNull)
+                    "%s;\n" % CGIndenter(CGGeneric(codeToSetNull)).define())
             templateBody += (
                 "} else {\n" +
                 CGIndenter(onFailureNotAnObject(failureCode)).define() +
                 "}")
             if type.nullable():
                 templateBody = handleDefaultNull(templateBody, codeToSetNull)
             else:
                 assert(defaultValue is None)
@@ -2748,21 +2748,21 @@ for (uint32_t i = 0; i < length; ++i) {
                                       (enum,
                                        getEnumValueName(defaultValue.value))))
         return (template, CGGeneric(enum), None, isOptional)
 
     if type.isCallback():
         assert not isEnforceRange and not isClamp
         assert not type.treatNonCallableAsNull() or type.nullable()
 
-        if isMember:
-            raise TypeError("Can't handle member callbacks; need to sort out "
-                            "rooting issues")
-
         if descriptorProvider.workers:
+            if isMember:
+                raise NoSuchDescriptorError("Can't handle member callbacks in "
+                                            "workers; need to sort out rooting"
+                                            "issues")
             if type.nullable():
                 declType = CGGeneric("JSObject*")
             else:
                 declType = CGGeneric("NonNull<JSObject>")
             conversion = "  ${declName} = &${val}.toObject();\n"
         else:
             name = type.unroll().identifier.name
             if type.nullable():
@@ -2810,27 +2810,48 @@ for (uint32_t i = 0; i < length; ++i) {
         templateBody = "${declName} = ${val};"
         templateBody = handleDefaultNull(templateBody,
                                          "${declName} = JS::NullValue()")
         return (templateBody, CGGeneric("JS::Value"), None, isOptional)
 
     if type.isObject():
         assert not isEnforceRange and not isClamp
 
-        if isMember:
-            raise TypeError("Can't handle member 'object'; need to sort out "
-                            "rooting issues")
-        template = wrapObjectTemplate("${declName} = &${val}.toObject();",
-                                      type,
-                                      "${declName} = NULL",
+        if isMember and isMember != "Dictionary":
+            raise TypeError("Can't handle member 'object' not in a dictionary;"
+                            "need to sort out rooting issues")
+
+        if isMember == "Dictionary":
+            if type.nullable():
+                declType = CGGeneric("LazyRootedObject")
+            else:
+                declType = CGGeneric("NonNullLazyRootedObject")
+
+            # If cx is null then LazyRootedObject will not be
+            # constructed and behave as nullptr.
+            templateBody = ("if (cx) {\n"
+                            "  ${declName}.construct(cx, &${val}.toObject());\n"
+                            "}")
+
+            # Cast of nullptr to (JSObject*) is required for template deduction.
+            setToNullCode = ("if (cx) {\n"
+                             "  ${declName}.construct(cx, (JSObject*) nullptr);\n"
+                             "}")
+        else:
+            if type.nullable():
+                declType = CGGeneric("JSObject*")
+            else:
+                declType = CGGeneric("NonNull<JSObject>")
+
+            templateBody = "${declName} = &${val}.toObject();"
+            setToNullCode = "${declName} = NULL"
+
+        template = wrapObjectTemplate(templateBody, type, setToNullCode,
                                       failureCode)
-        if type.nullable():
-            declType = CGGeneric("JSObject*")
-        else:
-            declType = CGGeneric("NonNull<JSObject>")
+
         return (template, declType, None, isOptional)
 
     if type.isDictionary():
         if failureCode is not None:
             raise TypeError("Can't handle dictionaries when failureCode is not None")
         # There are no nullable dictionaries
         assert not type.nullable()
         # All optional dictionaries always have default values, so we
@@ -3143,17 +3164,17 @@ if (${argc} > ${index}) {
                     )), 4).define()
 
         variadicConversion += ("\n"
                                "  }\n"
                                "}")
         return variadicConversion
 
 def getWrapTemplateForType(type, descriptorProvider, result, successCode,
-                           isCreator, exceptionCode):
+                           isCreator, exceptionCode, isMember=False):
     """
     Reflect a C++ value stored in "result", of IDL type "type" into JS.  The
     "successCode" is the code to run once we have successfully done the
     conversion.  The resulting string should be used with string.Template, it
     needs the following keys when substituting: jsvalPtr/jsvalRef/obj.
 
     Returns (templateString, infallibility of conversion template)
     """
@@ -3343,17 +3364,20 @@ if (!%(resultStr)s) {
         return (setValue(result, True), False)
 
     if type.isObject() or type.isSpiderMonkeyInterface():
         # See comments in WrapNewBindingObject explaining why we need
         # to wrap here.
         if type.nullable():
             toValue = "JS::ObjectOrNullValue(%s)"
         else:
-            toValue = "JS::ObjectValue(*%s)"
+            if isMember:
+                toValue = "JS::ObjectValue(%s)"
+            else:
+                toValue = "JS::ObjectValue(*%s)"
         # NB: setValue(..., True) calls JS_WrapValue(), so is fallible
         return (setValue(toValue % result, True), False)
 
     if type.isUnion():
         if type.nullable():
             prefix = "%s->"
         else:
             prefix = "%s."
@@ -3393,19 +3417,20 @@ if (!%(resultStr)s) {
         return (setValue("UINT_TO_JSVAL(%s)" % result), True)
 
     elif tag == IDLType.Tags.bool:
         return (setValue("BOOLEAN_TO_JSVAL(%s)" % result), True)
 
     else:
         raise TypeError("Need to learn to wrap primitive: %s" % type)
 
-def wrapForType(type, descriptorProvider, templateValues):
-    """
-    Reflect a C++ value of IDL type "type" into JS.  TemplateValues is a dict
+def wrapForType(type, descriptorProvider, templateValues, isMember=False):
+    """
+    Reflect a C++ value of IDL type "type" into JS.  isMember should
+    be true if the type is of a dictionary member. TemplateValues is a dict
     that should contain:
 
       * 'jsvalRef': a C++ reference to the jsval in which to store the result of
                     the conversion
       * 'jsvalPtr': a C++ pointer to the jsval in which to store the result of
                     the conversion
       * 'obj' (optional): the name of the variable that contains the JSObject to
                           use as a scope when wrapping, if not supplied 'obj'
@@ -3422,17 +3447,18 @@ def wrapForType(type, descriptorProvider
                                     The default is "return false;".  The code
                                     passed here must return.
     """
     wrap = getWrapTemplateForType(type, descriptorProvider,
                                   templateValues.get('result', 'result'),
                                   templateValues.get('successCode', None),
                                   templateValues.get('isCreator', False),
                                   templateValues.get('exceptionCode',
-                                                     "return false;"))[0]
+                                                     "return false;"),
+                                  isMember)[0]
 
     defaultValues = {'obj': 'obj'}
     return string.Template(wrap).substitute(defaultValues, **templateValues)
 
 def infallibleForMember(member, type, descriptorProvider):
     """
     Determine the fallibility of changing a C++ value of IDL type "type" into
     JS for the given attribute. Apart from isCreator, all the defaults are used,
@@ -6375,17 +6401,17 @@ class CGDictionary(CGThing):
         # if we don't have a relevant descriptor when self.workers is True.
         # If that happens, just mark ourselves as not being
         # generatable and move on.
         try:
             self.memberInfo = [
                 (member,
                  getJSToNativeConversionTemplate(member.type,
                                                  descriptorProvider,
-                                                 isMember=True,
+                                                 isMember="Dictionary",
                                                  isOptional=(not member.defaultValue),
                                                  defaultValue=member.defaultValue))
                 for member in dictionary.members ]
         except NoSuchDescriptorError, err:
             if not self.workers:
                 raise err
             self.generatable = False
 
@@ -6651,18 +6677,17 @@ class CGDictionary(CGThing):
                 'result' : "currentValue",
                 'successCode' : ("if (!%s) {\n"
                                  "  return false;\n"
                                  "}" % propDef),
                 'jsvalRef': "temp",
                 'jsvalPtr': "&temp",
                 'isCreator': False,
                 'obj': "parentObject"
-                }
-            )
+            }, isMember=True)
         conversion = CGGeneric(innerTemplate)
         conversion = CGWrapper(conversion,
                                pre=("JS::Value temp;\n"
                                     "%s& currentValue = %s;\n" %
                                     (declType.define(), memberData)
                                     ))
 
         if not member.defaultValue:
@@ -6774,21 +6799,29 @@ class CGBindingRoot(CGThing):
                                                       declarePost='\n'),
                                             declareOnly=True)
             return CGWrapper(declare, declarePost='\n')
 
         for x in descriptorsForForwardDeclaration:
             forwardDeclares.append(declareNativeType(x.nativeType))
 
         # Now add the forward declarations we need for our union types
+        # and callback functions.
         for callback in callbacks:
             forwardDeclares.extend(
                 declareNativeType("mozilla::dom::" + str(t.unroll()))
                 for t in getTypesFromCallback(callback)
-                if t.unroll().isUnion())
+                if t.unroll().isUnion() or t.unroll().isCallback())
+
+        # Forward declarations for callback functions used in dictionaries.
+        for dictionary in dictionaries:
+            forwardDeclares.extend(
+                declareNativeType("mozilla::dom::" + str(t.unroll()))
+                for t in getTypesFromDictionary(dictionary)
+                if t.unroll().isCallback())
 
         forwardDeclares = CGList(forwardDeclares)
 
         descriptorsWithPrototype = filter(lambda d: d.interface.hasInterfacePrototypeObject(),
                                           descriptors)
         traitsClasses = [CGPrototypeTraitsClass(d) for d in descriptorsWithPrototype]
 
         # We must have a 1:1 mapping here, skip for prototypes that have more
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -506,16 +506,19 @@ dictionary Dict : ParentDict {
   long b = 8;
   long z = 9;
   DOMString str;
   DOMString empty = "";
   TestEnum otherEnum = "b";
   DOMString otherStr = "def";
   DOMString? yetAnotherStr = null;
   DOMString template;
+  object someObj;
+  object? anotherObj = null;
+  TestCallback? someCallback = null;
 };
 
 dictionary ParentDict : GrandparentDict {
   long c = 5;
   TestInterface someInterface;
   TestExternalInterface someExternalInterface;
 };