Bug 820665 part 1. More WebIDL spec updates to changes in how dictionaries work. r=khuey
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 03 Jan 2013 14:03:00 -0500
changeset 117476 cc603feaa2862586bfc2c5eefdfdadf70401d09b
parent 117475 150d2a82c060025ff013b7e71118f9637f624262
child 117477 e78f9e6ea4d9977c9b1e98f7e9a3fa65ee971459
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerskhuey
bugs820665
milestone20.0a1
Bug 820665 part 1. More WebIDL spec updates to changes in how dictionaries work. r=khuey Specifically, the following changes: 1) A union that includes a dictionary and is the last non-optional argument must actually be marked optional, just like a dictionary argument. 2) Disallow a union from containing both a nullable type and a dictionary. 3) Now all non-Date and non-RegExp objects can be used as dictionaries, including from overload resolution and union conversion. We don't support dictionaries inside unions yet, or unions as distinguishing args, so the spec changes to do with converting null to dictionaries inside a union or picking the union overload if there is a union containing a dictionary and null is the distinguishing arg value are not relevant to us so far.
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/Errors.msg
dom/bindings/parser/WebIDL.py
dom/bindings/parser/tests/test_dictionary.py
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
dom/bindings/test/TestExampleGen.webidl
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -188,17 +188,17 @@ UnwrapObject(JSContext* cx, JSObject* ob
     return NS_OK;
   }
 
   /* It's the wrong sort of DOM object */
   return NS_ERROR_XPC_BAD_CONVERT_JS;
 }
 
 inline bool
-IsArrayLike(JSContext* cx, JSObject* obj)
+IsNotDateOrRegExp(JSContext* cx, JSObject* obj)
 {
   MOZ_ASSERT(obj);
   // For simplicity, check for security wrappers up front.  In case we
   // have a security wrapper, don't forget to enter the compartment of
   // the underlying object after unwrapping.
   Maybe<JSAutoCompartment> ac;
   if (js::IsWrapper(obj)) {
     obj = xpc::Unwrap(cx, obj, false);
@@ -209,27 +209,44 @@ IsArrayLike(JSContext* cx, JSObject* obj
 
     ac.construct(cx, obj);
   }
 
   // Everything except dates and regexps is arraylike
   return !JS_ObjectIsDate(cx, obj) && !JS_ObjectIsRegExp(cx, obj);
 }
 
+MOZ_ALWAYS_INLINE bool
+IsArrayLike(JSContext* cx, JSObject* obj)
+{
+  return IsNotDateOrRegExp(cx, obj);
+}
+
+MOZ_ALWAYS_INLINE bool
+IsConvertibleToDictionary(JSContext* cx, JSObject* obj)
+{
+  return IsNotDateOrRegExp(cx, obj);
+}
+
+MOZ_ALWAYS_INLINE bool
+IsConvertibleToDictionary(JSContext* cx, JS::Value val)
+{
+  return val.isNullOrUndefined() ||
+    (val.isObject() && IsConvertibleToDictionary(cx, &val.toObject()));
+}
+
 inline bool
 IsPlatformObject(JSContext* cx, JSObject* obj)
 {
-  // XXXbz Should be treating list-binding objects as platform objects
-  // too?  The one consumer so far wants non-array-like platform
-  // objects, so listbindings that have an indexGetter should test
-  // false from here.  Maybe this function should have a different
-  // name?
   MOZ_ASSERT(obj);
-  // Fast-path the common case
+  // Fast-path the common cases
   JSClass* clasp = js::GetObjectJSClass(obj);
+  if (js::Valueify(clasp) == &js::ObjectClass) {
+    return false;
+  }
   if (IsDOMClass(clasp)) {
     return true;
   }
   // Now for simplicity check for security wrappers before anything else
   if (js::IsWrapper(obj)) {
     obj = xpc::Unwrap(cx, obj, false);
     if (!obj) {
       // Let's say it's not
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2343,64 +2343,63 @@ for (uint32_t i = 0; i < length; ++i) {
             assert len(callbackMemberTypes) == 1
             memberType = callbackMemberTypes[0]
             name = memberType.name
             callbackObject = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${obj}, ${val}, ${valPtr}, tryNext)) || !tryNext;" % (unionArgumentObj, name))
             names.append(name)
         else:
             callbackObject = None
 
+        if callbackObject:
+            callbackObject = CGWrapper(CGIndenter(callbackObject),
+                                       pre="if (!IsPlatformObject(cx, &argObj)) {\n",
+                                       post="\n}")
+        else:
+            callbackObject = None
+
         dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes)
         if len(dictionaryMemberTypes) > 0:
             raise TypeError("No support for unwrapping dictionaries as member "
                             "of a union")
         else:
             dictionaryObject = None
 
-        if callbackObject or dictionaryObject:
-            nonPlatformObject = CGList([callbackObject, dictionaryObject], "\n")
-            nonPlatformObject = CGWrapper(CGIndenter(nonPlatformObject),
-                                          pre="if (!IsPlatformObject(cx, &argObj)) {\n",
-                                          post="\n}")
-        else:
-            nonPlatformObject = None
-
         objectMemberTypes = filter(lambda t: t.isObject(), memberTypes)
         if len(objectMemberTypes) > 0:
             object = CGGeneric("%s.SetToObject(&argObj);\n"
                                "done = true;" % unionArgumentObj)
         else:
             object = None
 
-        hasObjectTypes = interfaceObject or arrayObject or dateObject or nonPlatformObject or object
+        hasObjectTypes = interfaceObject or arrayObject or dateObject or callbackObject or dictionaryObject or object
         if hasObjectTypes:
-            # If we try more specific object types first then we need to check
-            # whether that succeeded before converting to object.
-            if object and (interfaceObject or arrayObject or dateObject or nonPlatformObject):
-                object = CGWrapper(CGIndenter(object), pre="if (!done) {\n",
-                                   post=("\n}"))
-
-            if arrayObject or dateObject or nonPlatformObject:
-                # An object can be both an array object and not a platform
-                # object, but we shouldn't have both in the union's members
+            # "object" is not distinguishable from other types
+            assert not object or not (interfaceObject or arrayObject or dateObject or callbackObject or dictionaryObject)
+            if arrayObject or dateObject or callbackObject or dictionaryObject:
+                # An object can be both an array object and a callback or
+                # dictionary, but we shouldn't have both in the union's members
                 # because they are not distinguishable.
-                assert not (arrayObject and nonPlatformObject)
-                templateBody = CGList([arrayObject, dateObject, nonPlatformObject], " else ")
+                assert not (arrayObject and callbackObject)
+                assert not (arrayObject and dictionaryObject)
+                assert not (dictionaryObject and callbackObject)
+                templateBody = CGList([arrayObject, dateObject, callbackObject,
+                                       dictionaryObject], " else ")
             else:
                 templateBody = None
             if interfaceObject:
+                assert not object
                 if templateBody:
-                    templateBody = CGList([templateBody, object], "\n")
                     templateBody = CGWrapper(CGIndenter(templateBody),
                                              pre="if (!done) {\n", post=("\n}"))
                 templateBody = CGList([interfaceObject, templateBody], "\n")
             else:
                 templateBody = CGList([templateBody, object], "\n")
 
-            if any([arrayObject, dateObject, nonPlatformObject, object]):
+            if any([arrayObject, dateObject, callbackObject, dictionaryObject,
+                    object]):
                 templateBody.prepend(CGGeneric("JSObject& argObj = ${val}.toObject();"))
             templateBody = CGWrapper(CGIndenter(templateBody),
                                      pre="if (${val}.isObject()) {\n",
                                      post="\n}")
         else:
             templateBody = CGGeneric()
 
         otherMemberTypes = filter(lambda t: t.isString() or t.isEnum(),
@@ -2865,18 +2864,20 @@ for (uint32_t i = 0; i < length; ++i) {
             setToNullCode = "${declName} = NULL"
 
         template = wrapObjectTemplate(templateBody, type, setToNullCode,
                                       failureCode)
 
         return (template, declType, None, isOptional)
 
     if type.isDictionary():
-        if failureCode is not None:
-            raise TypeError("Can't handle dictionaries when failureCode is not None")
+        if failureCode is not None and not isDefinitelyObject:
+            raise TypeError("Can't handle dictionaries when failureCode is "
+                            "not None and we don't know we're an object")
+
         # There are no nullable dictionaries
         assert not type.nullable()
         # All optional dictionaries always have default values, so we
         # should be able to assume not isOptional here.
         assert not isOptional
 
         typeName = CGDictionary.makeDictionaryName(type.inner,
                                                    descriptorProvider.workers)
@@ -2888,25 +2889,39 @@ for (uint32_t i = 0; i < length; ++i) {
         # If we're a member of something else, the const
         # will come from the Optional or our container.
         if not isMember:
             declType = CGWrapper(declType, pre="const ")
             selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef)
 
         # We do manual default value handling here, because we
         # actually do want a jsval, and we only handle null anyway
-        if defaultValue is not None:
+        # NOTE: if isNullOrUndefined or isDefinitelyObject are true,
+        # we know we have a value, so we don't have to worry about the
+        # default value.
+        if (not isNullOrUndefined and not isDefinitelyObject and
+            defaultValue is not None):
             assert(isinstance(defaultValue, IDLNullValue))
             val = "(${haveValue}) ? ${val} : JSVAL_NULL"
         else:
             val = "${val}"
 
-        template = ("if (!%s.Init(cx, ${obj}, %s)) {\n"
-                    "%s\n"
-                    "}" % (selfRef, val, exceptionCodeIndented.define()))
+        if failureCode is not None:
+            assert isDefinitelyObject
+            # Check that the value we have can in fact be converted to
+            # a dictionary, and return failureCode if not.
+            template = CGIfWrapper(
+                CGGeneric(failureCode),
+                "!IsConvertibleToDictionary(cx, &${val}.toObject())").define() + "\n\n"
+        else:
+            template = ""
+
+        template += ("if (!%s.Init(cx, ${obj}, %s)) {\n"
+                     "%s\n"
+                     "}" % (selfRef, val, exceptionCodeIndented.define()))
 
         return (template, declType, None, False)
 
     if type.isVoid():
         assert not isOptional
         # This one only happens for return values, and its easy: Just
         # ignore the jsval.
         return ("", None, None, False)
@@ -4010,49 +4025,51 @@ class CGMethodCall(CGThing):
                                           distinguishingArg))
                 tryCall(nullOrUndefSigs[0], 2, isNullOrUndefined=True)
                 caseBody.append(CGGeneric("}"))
 
             # Now check for distinguishingArg being various kinds of objects.
             # The spec says to check for the following things in order:
             # 1)  A platform object that's not a platform array object, being
             #     passed to an interface or "object" arg.
-            # 2)  A platform array object or Array or platform object with
-            #     indexed properties being passed to an array or sequence or
-            #     "object" arg.
-            # 3)  A Date object being passed to a Date or "object" arg
-            # 4)  Some other kind of object being passed to a callback
-            #     interface, callback function, dictionary, or "object" arg.
+            # 2)  A Date object being passed to a Date or "object" arg.
+            # 3)  A RegExp object being passed to a RegExp or "object" arg.
+            # 4)  Any non-Date and non-RegExp object being passed to a
+            #     dictionary or array or sequence or "object" arg.
+            # 5)  Some other kind of object being passed to a callback
+            #     interface, callback function, or "object" arg.
             #
             # Unfortunately, we cannot push the "some other kind of object"
-            # check down into case 4, because dictionaries _can_ normally be
+            # check down into case 5, because callbacks _can_ normally be
             # initialized from platform objects. But we can coalesce the other
-            # three cases together, as long as we make sure to check whether our
+            # four cases together, as long as we make sure to check whether our
             # object works as an interface argument before checking whether it
-            # works as an arraylike.
+            # works as an arraylike or dictionary.
 
             # First grab all the overloads that have a non-callback interface
             # (which includes typed arrays and arraybuffers) at the
             # distinguishing index.  We can also include the ones that have an
             # "object" here, since if those are present no other object-typed
             # argument will be.
             objectSigs = [
                 s for s in possibleSignatures
                 if (distinguishingType(s).isObject() or
                     distinguishingType(s).isNonCallbackInterface()) ]
 
-            # Now append all the overloads that take an array or sequence:
-            objectSigs.extend(s for s in possibleSignatures
-                              if (distinguishingType(s).isArray() or
-                                  distinguishingType(s).isSequence()))
-
             # And all the overloads that take Date
             objectSigs.extend(s for s in possibleSignatures
                               if distinguishingType(s).isDate())
 
+            # Now append all the overloads that take an array or sequence or
+            # dictionary:
+            objectSigs.extend(s for s in possibleSignatures
+                              if (distinguishingType(s).isArray() or
+                                  distinguishingType(s).isSequence() or
+                                  distinguishingType(s).isDictionary()))
+
             # There might be more than one thing in objectSigs; we need to check
             # which ones we unwrap to.
             if len(objectSigs) > 0:
                 # Here it's enough to guard on our argument being an object. The
                 # code for unwrapping non-callback interfaces, typed arrays,
                 # sequences, arrays, and Dates will just bail out and move on to
                 # the next overload if the object fails to unwrap correctly,
                 # while "object" accepts any object anyway.  We could even not
@@ -4068,22 +4085,20 @@ class CGMethodCall(CGThing):
                     # Indent by 4, since we need to indent further
                     # than our "do" statement
                     tryCall(sig, 4, isDefinitelyObject=True)
                     caseBody.append(CGIndenter(CGGeneric("} while (0);")))
 
                 caseBody.append(CGGeneric("}"))
 
             # Check for vanilla JS objects
-            # XXXbz Do we need to worry about security wrappers?
             pickFirstSignature("%s.isObject() && !IsPlatformObject(cx, &%s.toObject())" %
                                (distinguishingArg, distinguishingArg),
                                lambda s: (distinguishingType(s).isCallback() or
-                                          distinguishingType(s).isCallbackInterface() or
-                                          distinguishingType(s).isDictionary()))
+                                          distinguishingType(s).isCallbackInterface()))
 
             # The remaining cases are mutually exclusive.  The
             # pickFirstSignature calls are what change caseBody
             # Check for strings or enums
             if pickFirstSignature(None,
                                   lambda s: (distinguishingType(s).isString() or
                                              distinguishingType(s).isEnum())):
                 pass
@@ -6632,18 +6647,18 @@ class CGDictionary(CGThing):
             # NOTE: jsids are per-runtime, so don't use them in workers
             ("  if (cx && !initedIds && !InitIds(cx)) {\n"
              "    return false;\n"
              "  }\n" if self.needToInitIds else "") +
             "${initParent}" +
             ("  JSBool found;\n"
              "  JS::Value temp;\n" if len(memberInits) > 0 else "") +
             "  bool isNull = val.isNullOrUndefined();\n"
-            "  if (!isNull && !val.isObject()) {\n"
-            "    return ThrowErrorMessage(cx, MSG_NOT_OBJECT);\n"
+            "  if (!IsConvertibleToDictionary(cx, val)) {\n"
+            "    return ThrowErrorMessage(cx, MSG_NOT_DICTIONARY);\n"
             "  }\n"
             "\n"
             "${initMembers}\n"
             "  return true;\n"
             "}\n"
             "\n"
             "bool\n"
             "${selfName}::ToObject(JSContext* cx, JSObject* parentObject, JS::Value *vp)\n"
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -25,13 +25,14 @@ MSG_DEF(MSG_NOT_OBJECT, 0, "Value not an
 MSG_DEF(MSG_NOT_CALLABLE, 0, "Value is not callable.")
 MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 1, "Value does not implement interface {0}.")
 MSG_DEF(MSG_NOT_IN_UNION, 1, "Value could not be converted to any of: {0}.")
 MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, "Illegal constructor.")
 MSG_DEF(MSG_NO_PROPERTY_SETTER, 1, "{0} doesn't have an indexed property setter.")
 MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, "Non-finite value is out of range for {0}.")
 MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 1, "Value is out of range for {0}.")
 MSG_DEF(MSG_NOT_SEQUENCE, 0, "object can not be converted to a sequence")
+MSG_DEF(MSG_NOT_DICTIONARY, 0, "value can not be converted to a dictionary")
 MSG_DEF(MSG_INVALID_ARG, 2, "argument {0} is not valid for any of the {1}-argument overloads")
 MSG_DEF(MSG_GLOBAL_NOT_NATIVE, 0, "global is not a native object")
 MSG_DEF(MSG_ENCODING_NOT_SUPPORTED, 1, "The given encoding '{0}' is not supported.")
 MSG_DEF(MSG_DOM_ENCODING_NOT_UTF, 0, "The encoding must be utf-8, utf-16, or utf-16be.")
 MSG_DEF(MSG_NOT_FINITE, 0, "Floating-point value is not finite.")
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -1289,16 +1289,17 @@ class IDLSequenceType(IDLType):
         return (other.isPrimitive() or other.isString() or other.isEnum() or
                 other.isDate() or other.isNonCallbackInterface())
 
 class IDLUnionType(IDLType):
     def __init__(self, location, memberTypes):
         IDLType.__init__(self, location, "")
         self.memberTypes = memberTypes
         self.hasNullableType = False
+        self.hasDictionaryType = False
         self.flatMemberTypes = None
         self.builtin = False
 
     def __eq__(self, other):
         return isinstance(other, IDLUnionType) and self.memberTypes == other.memberTypes
 
     def isVoid(self):
         return False
@@ -1339,21 +1340,34 @@ class IDLUnionType(IDLType):
         self.name = "Or".join(typeName(type) for type in self.memberTypes)
         self.flatMemberTypes = list(self.memberTypes)
         i = 0
         while i < len(self.flatMemberTypes):
             if self.flatMemberTypes[i].nullable():
                 if self.hasNullableType:
                     raise WebIDLError("Can't have more than one nullable types in a union",
                                       [nullableType.location, self.flatMemberTypes[i].location])
+                if self.hasDictionaryType:
+                    raise WebIDLError("Can't have a nullable type and a "
+                                      "dictionary type in a union",
+                                      [dictionaryType.location,
+                                       self.flatMemberTypes[i].location])
                 self.hasNullableType = True
                 nullableType = self.flatMemberTypes[i]
                 self.flatMemberTypes[i] = self.flatMemberTypes[i].inner
                 continue
-            if self.flatMemberTypes[i].isUnion():
+            if self.flatMemberTypes[i].isDictionary():
+                if self.hasNullableType:
+                    raise WebIDLError("Can't have a nullable type and a "
+                                      "dictionary type in a union",
+                                      [nullableType.location,
+                                       self.flatMemberTypes[i].location])
+                self.hasDictionaryType = True
+                dictionaryType = self.flatMemberTypes[i]
+            elif self.flatMemberTypes[i].isUnion():
                 self.flatMemberTypes[i:i + 1] = self.flatMemberTypes[i].memberTypes
                 continue
             i += 1
 
         for (i, t) in enumerate(self.flatMemberTypes[:-1]):
             for u in self.flatMemberTypes[i + 1:]:
                 if not t.isDistinguishableFrom(u):
                     raise WebIDLError("Flat member types of a union should be "
@@ -2530,38 +2544,37 @@ class IDLMethod(IDLInterfaceMember, IDLS
             arguments = overload.arguments
             for (idx, argument) in enumerate(arguments):
                 if argument.isComplete():
                     continue
 
                 argument.complete(scope)
                 assert argument.type.isComplete()
 
-                if argument.type.isDictionary():
-                    # Dictionaries at the end of the list or followed by
-                    # optional arguments must be optional.
+                if (argument.type.isDictionary() or
+                    (argument.type.isUnion() and
+                     argument.type.unroll().hasDictionaryType)):
+                    # Dictionaries and unions containing dictionaries at the
+                    # end of the list or followed by optional arguments must be
+                    # optional.
                     if (not argument.optional and
                         (idx == len(arguments) - 1 or arguments[idx+1].optional)):
-                        raise WebIDLError("Dictionary argument not followed by "
-                                          "a required argument must be "
-                                          "optional", [argument.location])
+                        raise WebIDLError("Dictionary argument or union "
+                                          "argument containing a dictionary "
+                                          "not followed by a required argument "
+                                          "must be optional",
+                                          [argument.location])
 
                     # An argument cannot be a Nullable Dictionary
                     if argument.type.nullable():
-                        raise WebIDLError("An argument cannot be a nullable dictionary",
+                        raise WebIDLError("An argument cannot be a nullable "
+                                          "dictionary or nullable union "
+                                          "containing a dictionary",
                                           [argument.location])
 
-                # An argument cannot be a nullable union containing a dictionary
-                if argument.type.isUnion() and argument.type.nullable():
-                    for memberType in argument.type.inner.flatMemberTypes:
-                        if memberType.isDictionary():
-                            raise WebIDLError("An argument cannot be a nullable union "
-                                              "containing a dictionary",
-                                              [argument.location, memberType.location])
-
                 # Only the last argument can be variadic
                 if variadicArgument:
                     raise WebIDLError("Variadic argument is not last argument",
                                       [variadicArgument.location])
                 # Once we see an optional argument, there can't be any non-optional
                 # arguments.
                 if inOptionalArguments and not argument.optional:
                     raise WebIDLError("Non-optional argument after optional "
--- a/dom/bindings/parser/tests/test_dictionary.py
+++ b/dom/bindings/parser/tests/test_dictionary.py
@@ -140,26 +140,61 @@ def WebIDLTest(parser, harness):
 
     parser = parser.reset()
     threw = False
     try:
         parser.parse("""
             dictionary A {
             };
             interface X {
+              void doFoo((A or DOMString) arg);
+            };
+        """)
+        results = parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw,
+               "Trailing union arg containing a dictionary must be optional")
+
+    parser = parser.reset()
+    threw = False
+    try:
+        parser.parse("""
+            dictionary A {
+            };
+            interface X {
               void doFoo(A arg1, optional long arg2);
             };
         """)
         results = parser.finish()
     except:
         threw = True
 
     harness.ok(threw, "Dictionary arg followed by optional arg must be optional")
 
     parser = parser.reset()
+    threw = False
+    try:
+        parser.parse("""
+            dictionary A {
+            };
+            interface X {
+              void doFoo((A or DOMString) arg1, optional long arg2);
+            };
+        """)
+        results = parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw,
+               "Union arg containing dictionary followed by optional arg must "
+               "be optional")
+
+    parser = parser.reset()
     parser.parse("""
             dictionary A {
             };
             interface X {
               void doFoo(A arg1, long arg2);
             };
         """)
     results = parser.finish()
@@ -183,29 +218,81 @@ def WebIDLTest(parser, harness):
 
     parser = parser.reset()
     threw = False
     try:
         parser.parse("""
             dictionary A {
             };
             interface X {
-              void doFoo((A or long)? arg1);
+              void doFoo(optional (A or long)? arg1);
             };
         """)
         results = parser.finish()
     except:
         threw = True
 
     harness.ok(threw, "Dictionary arg must not be in a nullable union")
 
     parser = parser.reset()
+    threw = False
+    try:
+        parser.parse("""
+            dictionary A {
+            };
+            interface X {
+              void doFoo(optional (A or long?) arg1);
+            };
+        """)
+        results = parser.finish()
+    except:
+        threw = True
+    harness.ok(threw,
+               "Dictionary must not be in a union with a nullable type")
+
+    parser = parser.reset()
+    threw = False
+    try:
+        parser.parse("""
+            dictionary A {
+            };
+            interface X {
+              void doFoo(optional (long? or A) arg1);
+            };
+        """)
+        results = parser.finish()
+    except:
+        threw = True
+    harness.ok(threw,
+               "A nullable type must not be in a union with a dictionary")
+
+    parser = parser.reset()
     parser.parse("""
         dictionary A {
         };
         interface X {
           A? doFoo();
         };
     """)
     results = parser.finish()
-
     harness.ok(True, "Dictionary return value can be nullable")
 
+    parser = parser.reset()
+    parser.parse("""
+        dictionary A {
+        };
+        interface X {
+          void doFoo(optional A arg);
+        };
+    """)
+    results = parser.finish()
+    harness.ok(True, "Dictionary arg should actually parse")
+
+    parser = parser.reset()
+    parser.parse("""
+        dictionary A {
+        };
+        interface X {
+          void doFoo(optional (A or DOMString) arg);
+        };
+    """)
+    results = parser.finish()
+    harness.ok(True, "Union arg containing a dictionary should actually parse")
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -477,16 +477,18 @@ public:
   // Static methods and attributes
   static void StaticMethod(nsISupports*, bool);
   static bool StaticAttribute(nsISupports*);
   static void SetStaticAttribute(nsISupports*, bool);
 
   // Overload resolution tests
   bool Overload1(TestInterface&);
   TestInterface* Overload1(const nsAString&, TestInterface&);
+  void Overload2(const Dict&);
+  void Overload2(const nsAString&);
 
   // Variadic handling
   void PassVariadicThirdArg(const nsAString&, int32_t,
                             const Sequence<OwningNonNull<TestInterface> >&);
 
   // Miscellania
   int32_t AttrWithLenientThis();
   void SetAttrWithLenientThis(int32_t);
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -427,16 +427,18 @@ interface TestInterface {
   // Static methods and attributes
   static attribute boolean staticAttribute;
   static void staticMethod(boolean arg);
 
   // Overload resolution tests
   //void overload1(DOMString... strs);
   boolean overload1(TestInterface arg);
   TestInterface overload1(DOMString strs, TestInterface arg);
+  void overload2(optional Dict arg);
+  void overload2(DOMString arg);
 
   // Variadic handling
   void passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3);
 
   // Miscellania
   [LenientThis] attribute long attrWithLenientThis;
   [Unforgeable] readonly attribute long unforgeableAttr;
   [Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -346,16 +346,18 @@ interface TestExampleInterface {
   // Static methods and attributes
   static attribute boolean staticAttribute;
   static void staticMethod(boolean arg);
 
   // Overload resolution tests
   //void overload1(DOMString... strs);
   boolean overload1(TestInterface arg);
   TestInterface overload1(DOMString strs, TestInterface arg);
+  void overload2(optional Dict arg);
+  void overload2(DOMString arg);
 
   // Variadic handling
   void passVariadicThirdArg(DOMString arg1, long arg2, TestInterface... arg3);
 
   // Miscellania
   [LenientThis] attribute long attrWithLenientThis;
   [Unforgeable] readonly attribute long unforgeableAttr;
   [Unforgeable, ChromeOnly] readonly attribute long unforgeableAttr2;