Bug 742153 part 3. Implement codegen for dictionary arguments. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 12 Jun 2012 10:22:05 -0400
changeset 96481 84536fdda9b7574c16d8c61d3693a19f87612761
parent 96480 d82140dbb2a5cc2447547f8145f2fb0679120229
child 96482 16f1a804057cb1fc8f46cb260dd025e40460a4c4
push id22910
push usermbrubeck@mozilla.com
push dateWed, 13 Jun 2012 01:26:32 +0000
treeherdermozilla-central@964b11fea7f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs742153
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 742153 part 3. Implement codegen for dictionary arguments. r=peterv Another implementation option would be to put all the dictionaries in a single file and have a static global set of ids which works across all dictionaries and is initialized once at startup or so. That would also handle cross-file dictionary inheritance better. One problem that remains is the fake descriptor business. At the moment this does not allow interface types inside dictionaries. We could probably work around this by either refactoring code to make it possible to get the declType independently of the actual conversion template (whether because it lives in a separate function or because the conversion template generator knows to just return an empty string when the fake descriptor provirder is passed) or by figuring out a way to pass an actual descriptor provider to dictionary codegen.
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/Configuration.py
dom/bindings/Makefile.in
dom/bindings/parser/WebIDL.py
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
dom/bindings/test/TestDictionary.webidl
dom/webidl/WebIDL.mk
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -566,34 +566,41 @@ WrapNativeParent(JSContext* cx, JSObject
 
   qsObjectHelper helper(GetParentPointer(p), cache);
   JS::Value v;
   return XPCOMObjectToJsval(cx, scope, helper, NULL, false, &v) ?
          JSVAL_TO_OBJECT(v) :
          NULL;
 }
 
+static inline bool
+InternJSString(JSContext* cx, jsid& id, const char* chars)
+{
+  if (JSString *str = ::JS_InternString(cx, chars)) {
+    id = INTERNED_STRING_TO_JSID(cx, str);
+    return true;
+  }
+  return false;
+}
+
 // Spec needs a name property
 template <typename Spec>
 static bool
 InitIds(JSContext* cx, Prefable<Spec>* prefableSpecs, jsid* ids)
 {
   MOZ_ASSERT(prefableSpecs);
   MOZ_ASSERT(prefableSpecs->specs);
   do {
     // We ignore whether the set of ids is enabled and just intern all the IDs,
     // because this is only done once per application runtime.
     Spec* spec = prefableSpecs->specs;
     do {
-      JSString *str = ::JS_InternString(cx, spec->name);
-      if (!str) {
+      if (!InternJSString(cx, *ids, spec->name)) {
         return false;
       }
-
-      *ids = INTERNED_STRING_TO_JSID(cx, str);
     } while (++ids, (++spec)->name);
 
     // We ran out of ids for that pref.  Put a JSID_VOID in on the id
     // corresponding to the list terminator for the pref.
     *ids = JSID_VOID;
     ++ids;
   } while ((++prefableSpecs)->specs);
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -308,33 +308,34 @@ class CGIncludeGuard(CGWrapper):
         CGWrapper.__init__(self, child,
                            declarePre='#ifndef %s\n#define %s\n\n' % (define, define),
                            declarePost='\n#endif // %s\n' % define)
 
 class CGHeaders(CGWrapper):
     """
     Generates the appropriate include statements.
     """
-    def __init__(self, descriptors, declareIncludes, defineIncludes, child):
+    def __init__(self, descriptors, dictionaries, declareIncludes,
+                 defineIncludes, child):
         """
         Builds a set of includes to cover |descriptors|.
 
         Also includes the files in |declareIncludes| in the header
         file and the files in |defineIncludes| in the .cpp.
         """
 
         # Determine the filenames for which we need headers.
         interfaceDeps = [d.interface for d in descriptors]
         ancestors = []
         for iface in interfaceDeps:
             while iface.parent:
                 ancestors.append(iface.parent)
                 iface = iface.parent
         interfaceDeps.extend(ancestors)
-        bindingIncludes = set(self.getInterfaceFilename(d) for d in interfaceDeps)
+        bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
 
         # Grab all the implementation declaration files we need.
         implementationIncludes = set(d.headerFile for d in descriptors)
 
         # Now find all the things we'll need as arguments because we
         # need to wrap or unwrap them.
         bindingHeaders = set()
         for d in descriptors:
@@ -354,32 +355,40 @@ class CGHeaders(CGWrapper):
                 if t.unroll().isInterface():
                     if t.unroll().isSpiderMonkeyInterface():
                         bindingHeaders.add("jsfriendapi.h")
                         bindingHeaders.add("mozilla/dom/TypedArray.h")
                     else:
                         typeDesc = d.getDescriptor(t.unroll().inner.identifier.name)
                         if typeDesc is not None:
                             implementationIncludes.add(typeDesc.headerFile)
-                            bindingHeaders.add(self.getInterfaceFilename(typeDesc.interface))
+                            bindingHeaders.add(self.getDeclarationFilename(typeDesc.interface))
+                elif t.unroll().isDictionary():
+                    bindingHeaders.add(self.getDeclarationFilename(t.unroll().inner))
+
+        declareIncludes = set(declareIncludes)
+        for d in dictionaries:
+            if d.parent:
+                declareIncludes.add(self.getDeclarationFilename(d.parent))
+            bindingHeaders.add(self.getDeclarationFilename(d))
 
         # Let the machinery do its thing.
         def _includeString(includes):
             return ''.join(['#include "%s"\n' % i for i in includes]) + '\n'
         CGWrapper.__init__(self, child,
-                           declarePre=_includeString(declareIncludes),
+                           declarePre=_includeString(sorted(declareIncludes)),
                            definePre=_includeString(sorted(set(defineIncludes) |
                                                            bindingIncludes |
                                                            bindingHeaders |
                                                            implementationIncludes)))
     @staticmethod
-    def getInterfaceFilename(interface):
+    def getDeclarationFilename(decl):
         # Use our local version of the header, not the exported one, so that
         # test bindings, which don't export, will work correctly.
-        basename = os.path.basename(interface.filename())
+        basename = os.path.basename(decl.filename())
         return basename.replace('.webidl', 'Binding.h')
 
 class Argument():
     """
     A class for outputting the type and name of an argument
     """
     def __init__(self, argType, name):
         self.argType = argType
@@ -1693,16 +1702,45 @@ for (uint32_t i = 0; i < length; ++i) {
                                       "${declName} = NULL",
                                       descriptorProvider.workers, 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")
+
+        if type.nullable():
+            typeName = type.inner.inner.identifier.name
+            declType = CGGeneric("Nullable<%s>" % typeName)
+            selfRef = "${declName}.Value()"
+        else:
+            typeName = type.inner.identifier.name
+            declType = CGGeneric(typeName)
+            selfRef = "${declName}"
+        # If we're optional or a member of something else, the const
+        # will come from the Optional or our container.
+        mutableTypeName = declType
+        if not isOptional and not isMember:
+            declType = CGWrapper(declType, pre="const ")
+            selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef)
+
+        template = wrapObjectTemplate("if (!%s.Init(cx, &${val}.toObject())) {\n"
+                                      "  return false;\n"
+                                      "}" % selfRef,
+                                      isDefinitelyObject, type,
+                                      ("const_cast<%s&>(${declName}).SetNull()" %
+                                       mutableTypeName.define()),
+                                      descriptorProvider.workers, None)
+
+        return (template, declType, None, isOptional)
+
     if not type.isPrimitive():
         raise TypeError("Need conversion for argument type '%s'" % type)
 
     # XXXbz need to add support for [EnforceRange] and [Clamp]
     typeName = builtinNames[type.tag()]
     if type.nullable():
         return ("if (${val}.isNullOrUndefined()) {\n"
                 "  ${declName}.SetNull();\n"
@@ -3374,16 +3412,189 @@ class CGNamespacedEnum(CGThing):
         # Save the result.
         self.node = curr
 
     def declare(self):
         return self.node.declare()
     def define(self):
         assert False # Only for headers.
 
+class CGDictionary(CGThing):
+    def __init__(self, dictionary, workers):
+        self.dictionary = dictionary;
+        self.workers = workers
+        # Fake a descriptorProvider
+        # XXXbz this will fail for interface types!
+        for member in dictionary.members:
+            if member.type.unroll().isInterface():
+                raise TypeError("No support for interface members of dictionaries: %s.%s" %
+                                (dictionary.identifier.name, member.identifier.name))
+        self.memberInfo = [
+            (member,
+             getJSToNativeConversionTemplate(member.type,
+                                             { "workers": workers },
+                                             isMember=True,
+                                             isOptional=(not member.defaultValue)))
+            for member in dictionary.members ]
+
+    def declare(self):
+        d = self.dictionary
+        if d.parent:
+            inheritance = ": public %s " % self.makeClassName(d.parent)
+        else:
+            inheritance = ""
+        memberDecls = ["  %s %s;" %
+                       (self.getMemberType(m), m[0].identifier.name)
+                       for m in self.memberInfo]
+
+        return (string.Template(
+                "struct ${selfName} ${inheritance}{\n"
+                "  ${selfName}() {}\n"
+                "  bool Init(JSContext* cx, JSObject* obj);\n"
+                "\n" +
+                "\n".join(memberDecls) + "\n"
+                "private:\n"
+                "  // Disallow copy-construction\n"
+                "  ${selfName}(const ${selfName}&) MOZ_DELETE;\n"
+                "  static bool InitIds(JSContext* cx);\n"
+                "  static bool initedIds;\n" +
+                "\n".join("  static jsid " +
+                          self.makeIdName(m.identifier.name) + ";" for
+                          m in d.members) + "\n"
+                "};").substitute( { "selfName": self.makeClassName(d),
+                                    "inheritance": inheritance }))
+
+    def define(self):
+        d = self.dictionary
+        if d.parent:
+            initParent = ("// Per spec, we init the parent's members first\n"
+                          "if (!%s::Init(cx, obj)) {\n"
+                          "  return false;\n"
+                          "}\n" % self.makeClassName(d.parent))
+        else:
+            initParent = ""
+
+        memberInits = [CGIndenter(self.getMemberConversion(m)).define()
+                       for m in self.memberInfo]
+        idinit = [CGGeneric('!InternJSString(cx, %s, "%s")' %
+                            (m.identifier.name + "_id", m.identifier.name))
+                  for m in d.members]
+        idinit = CGList(idinit, " ||\n")
+        idinit = CGWrapper(idinit, pre="if (",
+                           post=(") {\n"
+                                 "  return false;\n"
+                                 "}"),
+                           reindent=True)
+
+        return string.Template(
+            "bool ${selfName}::initedIds = false;\n" +
+            "\n".join("jsid ${selfName}::%s = JSID_VOID;" %
+                      self.makeIdName(m.identifier.name)
+                      for m in d.members) + "\n"
+            "\n"
+            "bool\n"
+            "${selfName}::InitIds(JSContext* cx)\n"
+            "{\n"
+            "  MOZ_ASSERT(!initedIds);\n"
+            "${idInit}\n"
+            "  initedIds = true;\n"
+            "  return true;\n"
+            "}\n"
+            "\n"
+            "bool\n"
+            "${selfName}::Init(JSContext* cx, JSObject* obj)\n"
+            "{\n"
+            "  if (!initedIds && !InitIds(cx)) {\n"
+            "    return false;\n"
+            "  }\n"
+            "${initParent}"
+            "  JSBool found;\n"
+            "  JS::Value temp;\n"
+            "\n"
+            "${initMembers}\n"
+            "  return true;\n"
+            "}").substitute({
+                "selfName": self.makeClassName(d),
+                "initParent": CGIndenter(CGGeneric(initParent)).define(),
+                "initMembers": "\n\n".join(memberInits),
+                "idInit": CGIndenter(idinit).define()
+                })
+
+    def makeClassName(self, dictionary):
+        suffix = "Workers" if self.workers else ""
+        return dictionary.identifier.name + suffix
+
+    def getMemberType(self, memberInfo):
+        (member, (templateBody, declType,
+                  holderType, dealWithOptional)) = memberInfo
+        # We can't handle having a holderType here
+        assert holderType is None
+        if dealWithOptional:
+            declType = CGWrapper(declType, pre="Optional< ", post=" >")
+        return declType.define()
+
+    def getMemberConversion(self, memberInfo):
+        # Fake a descriptorProvider
+        (member, (templateBody, declType,
+                  holderType, dealWithOptional)) = memberInfo
+        replacements = { "val": "temp",
+                         "valPtr": "&temp",
+                         # Use this->%s to refer to members, because we don't
+                         # control the member names and want to make sure we're
+                         # talking about the member, not some local that
+                         # shadows the member.  Another option would be to move
+                         # the guts of init to a static method which is passed
+                         # an explicit reference to our dictionary object, so
+                         # we couldn't screw this up even if we wanted to....
+                         "declName": ("(this->%s)" % member.identifier.name) }
+        # We can't handle having a holderType here
+        assert holderType is None
+        if dealWithOptional:
+            replacements["declName"] = "(" + replacements["declName"] + ".Value())"
+
+        conversionReplacements = {
+            "propId" : self.makeIdName(member.identifier.name),
+            "prop": "(this->%s)" % member.identifier.name,
+            "convert": string.Template(templateBody).substitute(replacements)
+            }
+        conversion = ("if (!JS_HasPropertyById(cx, obj, ${propId}, &found)) {\n"
+                      "  return false;\n"
+                      "}\n")
+        if member.defaultValue:
+            conversion += (
+                "if (found) {\n"
+                "  if (!JS_GetPropertyById(cx, obj, ${propId}, &temp)) {\n"
+                "    return false;\n"
+                "  }\n"
+                "} else {\n"
+                "  temp = ${defaultVal};\n"
+                "}\n"
+                "${convert}")
+            conversionReplacements["defaultVal"] = (
+                convertIDLDefaultValueToJSVal(member.defaultValue))
+        else:
+            conversion += (
+                "if (found) {\n"
+                "  ${prop}.Construct();\n"
+                "  if (!JS_GetPropertyById(cx, obj, ${propId}, &temp)) {\n"
+                "    return false;\n"
+                "  }\n"
+                "${convert}\n"
+                "}")
+            conversionReplacements["convert"] = CGIndenter(
+                CGGeneric(conversionReplacements["convert"])).define()
+        
+        return CGGeneric(
+            string.Template(conversion).substitute(conversionReplacements)
+            )
+
+    @staticmethod
+    def makeIdName(name):
+        return name + "_id"
+
 class CGRegisterProtos(CGAbstractMethod):
     def __init__(self, config):
         CGAbstractMethod.__init__(self, None, 'Register', 'void',
                                   [Argument('nsScriptNameSpaceManager*', 'aNameSpaceManager')])
         self.config = config
 
     def _defineMacro(self):
        return """
@@ -3404,16 +3615,17 @@ class CGRegisterProtos(CGAbstractMethod)
 class CGBindingRoot(CGThing):
     """
     Root codegen class for binding generation. Instantiate the class, and call
     declare or define to generate header or cpp code (respectively).
     """
     def __init__(self, config, prefix, webIDLFile):
         descriptors = config.getDescriptors(webIDLFile=webIDLFile,
                                             hasInterfaceOrInterfacePrototypeObject=True)
+        dictionaries = config.getDictionaries(webIDLFile)
 
         forwardDeclares = [CGClassForwardDeclare('XPCWrappedNativeScope')]
 
         for x in descriptors:
             nativeType = x.nativeType
             components = x.nativeType.split('::')
             className = components[-1]
             # JSObject is a struct, not a class
@@ -3441,40 +3653,63 @@ class CGBindingRoot(CGThing):
             traitsClasses = CGNamespace.build(['mozilla', 'dom'],
                                      CGWrapper(CGList(traitsClasses),
                                                declarePre='\n'),
                                                declareOnly=True)
             traitsClasses = CGWrapper(traitsClasses, declarePost='\n')
         else:
             traitsClasses = None
 
-        # Do codegen for all the descriptors and enums.
+        # Do codegen for all the enums
         def makeEnum(e):
             return CGNamespace.build([e.identifier.name + "Values"],
                                      CGEnum(e))
         def makeEnumTypedef(e):
             return CGGeneric(declare=("typedef %sValues::valuelist %s;\n" %
                                       (e.identifier.name, e.identifier.name)))
         cgthings = [ fun(e) for e in config.getEnums(webIDLFile)
                      for fun in [makeEnum, makeEnumTypedef] ]
+
+        # Do codegen for all the dictionaries.  We have to be a bit careful
+        # here, because we have to generate these in order from least derived to
+        # most derived so that class inheritance works out.
+        #
+        # XXXbz this will fail if we have two webidl files A and B such that A
+        # declares a dictionary which inherits from a dictionary in B and B
+        # declares a dictionary (possibly a different one!) that inherits from a
+        # dictionary in A.  The good news is that I expect this to never happen.
+        reSortedDictionaries = []
+        while len(dictionaries) != 0:
+            toMove = [d for d in dictionaries if d.parent not in dictionaries]
+            dictionaries = [d for d in dictionaries if d.parent in dictionaries]
+            reSortedDictionaries.extend(toMove)
+
+        dictionaries = reSortedDictionaries
+        cgthings.extend([CGDictionary(d, workers=True) for d in dictionaries])
+        cgthings.extend([CGDictionary(d, workers=False) for d in dictionaries])
+
+        # Do codegen for all the descriptors
         cgthings.extend([CGDescriptor(x) for x in descriptors])
-        curr = CGList(cgthings, "\n")
+
+        # And make sure we have the right number of newlines at the end
+        curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'],
                                  CGWrapper(curr, pre="\n"))
 
         curr = CGList([forwardDeclares,
                        CGWrapper(CGGeneric("using namespace mozilla::dom;"),
                                  defineOnly=True),
                        traitsClasses, curr],
                       "\n")
 
         # Add header includes.
         curr = CGHeaders(descriptors,
+                         dictionaries,
                          ['mozilla/dom/BindingUtils.h',
                           'mozilla/dom/DOMJSClass.h'],
                          ['mozilla/dom/Nullable.h',
                           'PrimitiveConversions.h',
                           'XPCQuickStubs.h',
                           'nsDOMQS.h',
                           'AccessCheck.h',
                           'WorkerPrivate.h',
@@ -3567,20 +3802,20 @@ struct PrototypeIDMap;
         curr = CGRegisterProtos(config)
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'],
                                  CGWrapper(curr, post='\n'))
         curr = CGWrapper(curr, post='\n')
 
         # Add the includes
-        defineIncludes = [CGHeaders.getInterfaceFilename(desc.interface)
+        defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
                           for desc in config.getDescriptors(hasInterfaceObject=True,
                                                             workers=False,
                                                             register=True)]
         defineIncludes.append('nsScriptNameSpaceManager.h')
-        curr = CGHeaders([], [], defineIncludes, curr)
+        curr = CGHeaders([], [], [], defineIncludes, curr)
 
         # Add include guards.
         curr = CGIncludeGuard('RegisterBindings', curr)
 
         # Done.
         return curr
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -37,16 +37,17 @@ class Configuration:
         # an interface.
         for descriptor in self.descriptors:
             intefaceName = descriptor.interface.identifier.name
             otherDescriptors = [d for d in self.descriptors
                                 if d.interface.identifier.name == intefaceName]
             descriptor.uniqueImplementation = len(otherDescriptors) == 1
 
         self.enums = [e for e in parseData if e.isEnum()]
+        self.dictionaries = [d for d in parseData if d.isDictionary()]
 
         # Keep the descriptor list sorted for determinism.
         self.descriptors.sort(lambda x,y: cmp(x.name, y.name))
 
     def getInterface(self, ifname):
         return self.interfaces[ifname]
     def getDescriptors(self, **filters):
         """Gets the descriptors that match the given filters."""
@@ -67,16 +68,18 @@ class Configuration:
             elif key == 'isExternal':
                 getter = lambda x: x.interface.isExternal()
             else:
                 getter = lambda x: getattr(x, key)
             curr = filter(lambda x: getter(x) == val, curr)
         return curr
     def getEnums(self, webIDLFile):
         return filter(lambda e: e.filename() == webIDLFile, self.enums)
+    def getDictionaries(self, webIDLFile):
+        return filter(lambda d: d.filename() == webIDLFile, self.dictionaries)
 
 class Descriptor:
     """
     Represents a single descriptor for an interface. See Bindings.conf.
     """
     def __init__(self, config, interface, desc):
         self.config = config
         self.interface = interface
--- a/dom/bindings/Makefile.in
+++ b/dom/bindings/Makefile.in
@@ -141,8 +141,14 @@ GARBAGE += \
   $(binding_header_files) \
   $(binding_cpp_files) \
   $(all_webidl_files) \
   $(globalgen_targets) \
   ParserResults.pkl \
   webidlyacc.py \
   parser.out \
   $(NULL)
+
+# Make sure all binding header files are created during the export stage, so we
+# don't have issues with .cpp files being compiled before we've generated the
+# headers they depend on.  This is really only needed for the test files, since
+# the non-test headers are all exported above anyway.
+export:: $(binding_header_files)
\ No newline at end of file
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -145,16 +145,19 @@ class IDLObject(object):
         return False
 
     def isCallback(self):
         return False
 
     def isType(self):
         return False
 
+    def isDictionary(self):
+        return False;
+
     def getUserData(self, key, default):
         return self.userData.get(key, default)
 
     def setUserData(self, key, value):
         self.userData[key] = value
 
     def addExtendedAttributes(self, attrs):
         assert False # Override me!
@@ -598,16 +601,19 @@ class IDLDictionary(IDLObjectWithScope):
         self._finished = False
         self.members = list(members)
 
         IDLObjectWithScope.__init__(self, location, parentScope, name)
 
     def __str__(self):
         return "Dictionary '%s'" % self.identifier.name
 
+    def isDictionary(self):
+        return True;
+
     def finish(self, scope):
         if self._finished:
             return
 
         self._finished = True
 
         if self.parent:
             assert isinstance(self.parent, IDLIdentifierPlaceholder)
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -289,16 +289,24 @@ public:
 
   // binaryNames tests
   void MethodRenamedTo(ErrorResult&);
   void MethodRenamedTo(int8_t, ErrorResult&);
   int8_t GetAttributeGetterRenamedTo(ErrorResult&);
   int8_t GetAttributeRenamedTo(ErrorResult&);
   void SetAttributeRenamedTo(int8_t, ErrorResult&);
 
+  // Dictionary tests
+  void PassDictionary(const Dict&, ErrorResult&);
+  void PassOptionalDictionary(const Optional<Dict>&, ErrorResult&);
+  void PassNullableDictionary(const Nullable<Dict>&, ErrorResult&);
+  void PassOptionalNullableDictionary(const Optional<Nullable<Dict> >&, ErrorResult&);
+  void PassOtherDictionary(const GrandparentDict&, ErrorResult&);
+  void PassSequenceOfDictionaries(const Sequence<Dict>&, ErrorResult&);
+
   // Methods and properties imported via "implements"
   bool GetImplementedProperty(ErrorResult&);
   void SetImplementedProperty(bool, ErrorResult&);
   void ImplementedMethod(ErrorResult&);
   bool GetImplementedParentProperty(ErrorResult&);
   void SetImplementedParentProperty(bool, ErrorResult&);
   void ImplementedParentMethod(ErrorResult&);
   bool GetIndirectlyImplementedProperty(ErrorResult&);
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -219,16 +219,23 @@ interface TestInterface {
   object receiveObject();
   object? receiveNullableObject();
 
   // binaryNames tests
   void methodRenamedFrom();
   void methodRenamedFrom(byte argument);
   readonly attribute byte attributeGetterRenamedFrom;
   attribute byte attributeRenamedFrom;
+
+  void passDictionary(Dict x);
+  void passOptionalDictionary(optional Dict x);
+  void passNullableDictionary(Dict? x);
+  void passOptionalNullableDictionary(optional Dict? x);
+  void passOtherDictionary(GrandparentDict x);
+  void passSequenceOfDictionaries(sequence<Dict> x);
 };
 
 interface ImplementedInterfaceParent {
   void implementedParentMethod();
   attribute boolean implementedParentProperty;
 
   const long implementedParentConstant = 8;
 };
@@ -261,8 +268,19 @@ interface DiamondBranch2A : DiamondImple
 interface DiamondBranch2B : DiamondImplements {
 };
 TestInterface implements DiamondBranch1A;
 TestInterface implements DiamondBranch1B;
 TestInterface implements DiamondBranch2A;
 TestInterface implements DiamondBranch2B;
 DiamondBranch1A implements DiamondImplements;
 DiamondBranch1B implements DiamondImplements;
+
+dictionary Dict : ParentDict {
+  long x;
+  long a;
+  long b = 8;
+  long z = 9;
+};
+
+dictionary ParentDict : GrandparentDict {
+  long c = 5;
+};
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/TestDictionary.webidl
@@ -0,0 +1,9 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+dictionary GrandparentDict {
+  double someNum;
+};
\ No newline at end of file
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -10,13 +10,16 @@ webidl_files = \
   EventTarget.webidl \
   XMLHttpRequest.webidl \
   XMLHttpRequestEventTarget.webidl \
   XMLHttpRequestUpload.webidl \
   WebGLRenderingContext.webidl \
   $(NULL)
 
 ifdef ENABLE_TESTS
-test_webidl_files := TestCodeGen.webidl
+test_webidl_files := \
+  TestCodeGen.webidl \
+  TestDictionary.webidl \
+  $(NULL)
 else
 test_webidl_files := $(NULL)
 endif