Bug 827158 part 8. Implement legacycaller support in WebIDL. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 04 Mar 2013 14:08:24 -0500
changeset 123717 b158b0b9d6c582f0a3fdcc6f7027a433472208ad
parent 123716 f108b865d7856bd7df9402baeccf8bafa4c85b64
child 123718 194ff1cc47b1119ded34e025115aec5aa9743809
push id24008
push userbzbarsky@mozilla.com
push dateMon, 04 Mar 2013 19:09:00 +0000
treeherdermozilla-inbound@b30d6c68b8a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs827158
milestone22.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 827158 part 8. Implement legacycaller support in WebIDL. r=peterv
dom/bindings/Bindings.conf
dom/bindings/Codegen.py
dom/bindings/Configuration.py
dom/bindings/parser/WebIDL.py
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
dom/bindings/test/TestExampleGen.webidl
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -218,30 +218,24 @@ DOMInterfaces = {
 },
 
 'DocumentFragment': {
     'resultNotAddRefed': [ 'querySelector' ]
 },
 
 'DOMSettableTokenList': {
     'nativeType': 'nsDOMSettableTokenList',
-    'binaryNames': {
-        '__stringifier': 'Stringify'
-    }
 },
 
 'DOMStringMap': {
     'nativeType': 'nsDOMStringMap'
 },
 
 'DOMTokenList': {
     'nativeType': 'nsDOMTokenList',
-    'binaryNames': {
-        '__stringifier': 'Stringify'
-    }
 },
 
 'DummyInterface': {
     'skipGen': True,
     'register': False,
 },
 
 'DummyInterfaceWorkers': {
@@ -320,22 +314,16 @@ DOMInterfaces = {
     'nativeType': 'JSObject'
 }],
 
 'GainNode': {
     'resultNotAddRefed': [ 'gain' ],
     'wrapperCache': False
 },
 
-'HTMLAnchorElement': {
-    'binaryNames': {
-        '__stringifier': 'Stringify'
-    },
-},
-
 'HTMLBaseElement': {
     'nativeType': 'mozilla::dom::HTMLSharedElement'
 },
 
 'HTMLCollection': {
     'nativeType': 'nsIHTMLCollection',
     'resultNotAddRefed': [ 'item' ]
 },
@@ -1038,18 +1026,22 @@ DOMInterfaces = {
                                'ReceiveWeakCallbackInterface',
                                'ReceiveWeakNullableCallbackInterface',
                                'receiveWeakCastableObjectSequence',
                                'receiveWeakNullableCastableObjectSequence',
                                'receiveWeakCastableObjectNullableSequence',
                                'receiveWeakNullableCastableObjectNullableSequence' ],
         'binaryNames': { 'methodRenamedFrom': 'methodRenamedTo',
                          'attributeGetterRenamedFrom': 'attributeGetterRenamedTo',
-                         'attributeRenamedFrom': 'attributeRenamedTo',
-                         '__stringifier' : 'Stringify' }
+                         'attributeRenamedFrom': 'attributeRenamedTo' }
+        },
+
+'TestParentInterface' : {
+        'headerFile': 'TestBindingHeader.h',
+        'register': False,
         },
 
 'TestChildInterface' : {
         'headerFile': 'TestBindingHeader.h',
         'register': False,
         },
 
 'TestExternalInterface' : {
@@ -1110,17 +1102,16 @@ DOMInterfaces = {
 'TestIndexedAndNamedSetterInterface' : {
         'headerFile': 'TestBindingHeader.h',
         'register': False
         },
 
 'TestIndexedAndNamedGetterAndSetterInterface' : {
         'headerFile': 'TestBindingHeader.h',
         'register': False,
-        'binaryNames': { '__stringifier': 'Stringify' }
         },
 
 'TestRenamedInterface' : {
         'headerFile': 'TestBindingHeader.h',
         'register': False,
         'nativeType': 'nsRenamedInterface'
         },
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -13,16 +13,17 @@ from WebIDL import BuiltinTypes, IDLBuil
 from Configuration import NoSuchDescriptorError, getTypesFromDescriptor, getTypesFromDictionary, getTypesFromCallback
 
 AUTOGENERATED_WARNING_COMMENT = \
     "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
 ADDPROPERTY_HOOK_NAME = '_addProperty'
 FINALIZE_HOOK_NAME = '_finalize'
 TRACE_HOOK_NAME = '_trace'
 CONSTRUCT_HOOK_NAME = '_constructor'
+LEGACYCALLER_HOOK_NAME = '_legacycaller'
 HASINSTANCE_HOOK_NAME = '_hasInstance'
 
 def replaceFileIfChanged(filename, newContents):
     """
     Read a copy of the old file, so that we don't touch it if it hasn't changed.
     Returns True if the file was updated, false otherwise.
     """
     oldFileContents = ""
@@ -154,41 +155,42 @@ class CGDOMJSClass(CGThing):
     def __init__(self, descriptor):
         CGThing.__init__(self)
         self.descriptor = descriptor
         # Our current reserved slot situation is unsafe for globals. Fix bug 760095!
         assert "Window" not in descriptor.interface.identifier.name
     def declare(self):
         return "extern DOMJSClass Class;\n"
     def define(self):
-        traceHook = TRACE_HOOK_NAME if self.descriptor.customTrace else 'NULL'
+        traceHook = TRACE_HOOK_NAME if self.descriptor.customTrace else 'nullptr'
+        callHook = LEGACYCALLER_HOOK_NAME if self.descriptor.operations["LegacyCaller"] else 'nullptr'
         return """
 DOMJSClass Class = {
   { "%s",
     JSCLASS_IS_DOMJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(3),
     %s, /* addProperty */
     JS_PropertyStub,       /* delProperty */
     JS_PropertyStub,       /* getProperty */
     JS_StrictPropertyStub, /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     %s, /* finalize */
     NULL,                  /* checkAccess */
-    NULL,                  /* call */
+    %s, /* call */
     NULL,                  /* hasInstance */
     NULL,                  /* construct */
     %s, /* trace */
     JSCLASS_NO_INTERNAL_MEMBERS
   },
 %s
 };
 """ % (self.descriptor.interface.identifier.name,
        ADDPROPERTY_HOOK_NAME if self.descriptor.concrete and not self.descriptor.workers and self.descriptor.wrapperCache else 'JS_PropertyStub',
-       FINALIZE_HOOK_NAME, traceHook,
+       FINALIZE_HOOK_NAME, callHook, traceHook,
        CGIndenter(CGGeneric(DOMClass(self.descriptor))).define())
 
 def PrototypeIDAndDepth(descriptor):
     prototypeID = "prototypes::id::"
     if descriptor.interface.hasInterfacePrototypeObject():
         prototypeID += descriptor.interface.identifier.name
         if descriptor.workers:
             prototypeID += "_workers"
@@ -2126,19 +2128,21 @@ def getJSToNativeConversionTemplate(type
     failureCode is.  However what actually happens when throwing an exception
     can be controlled by exceptionCode.  The only requirement on that is that
     exceptionCode must end up doing a return, and every return from this
     function must happen via exceptionCode if exceptionCode is not None.
 
     If isDefinitelyObject is True, that means we know the value
     isObject() and we have no need to recheck that.
 
-    if isMember is True, we're being converted from a property of some
-    JS object, not from an actual method argument, so we can't rely on
-    our jsval being rooted or outliving us in any way.
+    if isMember is not False, we're being converted from a property of some JS
+    object, not from an actual method argument, so we can't rely on our jsval
+    being rooted or outliving us in any way.  Callers can pass "Dictionary" or
+    "Variadic" to indicate that the conversion is for something that is a
+    dictionary member or a variadic argument respectively.
 
     If isOptional is true, then we are doing conversion of an optional
     argument with no default value.
 
     invalidEnumValueFatal controls whether an invalid enum value conversion
     attempt will throw (if true) or simply return without doing anything (if
     false).
 
@@ -2956,17 +2960,18 @@ for (uint32_t i = 0; i < length; ++i) {
         assert not isEnforceRange and not isClamp
 
         if isMember == "Dictionary":
             declType = "RootedJSValue"
             templateBody = ("if (!${declName}.SetValue(cx, ${val})) {\n"
                             "  return false;\n"
                             "}")
             nullHandling = "${declName}.SetValue(nullptr, JS::NullValue())"
-        elif isMember:
+        elif isMember and isMember != "Variadic":
+            # Variadic arguments are rooted by being in argv
             raise TypeError("Can't handle sequence member 'any'; need to sort "
                             "out rooting issues")
         else:
             declType = "JS::Value"
             templateBody = "${declName} = ${val};"
             nullHandling = "${declName} = JS::NullValue()"
         templateBody = handleDefaultNull(templateBody, nullHandling)
         return (templateBody, CGGeneric(declType), None, isOptional)
@@ -3288,17 +3293,17 @@ class CGArgumentConverter(CGThing):
                         not self.argument.variadic),
             invalidEnumValueFatal=self.invalidEnumValueFatal,
             defaultValue=self.argument.defaultValue,
             treatNullAs=self.argument.treatNullAs,
             treatUndefinedAs=self.argument.treatUndefinedAs,
             isEnforceRange=self.argument.enforceRange,
             isClamp=self.argument.clamp,
             lenientFloatCode=self.lenientFloatCode,
-            isMember=self.argument.variadic,
+            isMember="Variadic" if self.argument.variadic else False,
             allowTreatNonCallableAsNull=self.allowTreatNonCallableAsNull)
 
         if not self.argument.variadic:
             return instantiateJSToNativeConversionTemplate(
                 typeConversion,
                 self.replacementVariables,
                 self.argcAndIndex).define()
 
@@ -3881,16 +3886,24 @@ if (global.Failed()) {
 """ % globalObjectType))
             argsPre.append("global")
 
         needsCx = needCx(returnType, arguments, self.extendedAttributes,
                          descriptor)
         if needsCx and not (static and descriptor.workers):
             argsPre.append("cx")
 
+        if idlNode.isMethod() and idlNode.isLegacycaller():
+            # If we can have legacycaller with identifier, we can't
+            # just use the idlNode to determine whether we're
+            # generating code for the legacycaller or not.
+            assert idlNode.isIdentifierLess()
+            # Pass in our thisVal
+            argsPre.append("JS_THIS_VALUE(cx, vp)")
+
         cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(),
                                              self.getArgc(), self.descriptor,
                                              invalidEnumValueFatal=not setter,
                                              allowTreatNonCallableAsNull=setter,
                                              lenientFloatCode=lenientFloatCode) for
                          i in range(argConversionStartsAt, self.argCount)])
 
         cgThings.append(CGCallGenerator(
@@ -4341,35 +4354,37 @@ class CGSetterCall(CGPerSignatureCall):
 class CGAbstractBindingMethod(CGAbstractStaticMethod):
     """
     Common class to generate the JSNatives for all our methods, getters, and
     setters.  This will generate the function declaration and unwrap the
     |this| object.  Subclasses are expected to override the generate_code
     function to do the rest of the work.  This function should return a
     CGThing which is already properly indented.
     """
-    def __init__(self, descriptor, name, args, unwrapFailureCode=None):
+    def __init__(self, descriptor, name, args, unwrapFailureCode=None,
+                 getThisObj="JS_THIS_OBJECT(cx, vp)"):
         CGAbstractStaticMethod.__init__(self, descriptor, name, "JSBool", args)
 
         if unwrapFailureCode is None:
             self.unwrapFailureCode = 'return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s");' % self.descriptor.name
         else:
             self.unwrapFailureCode = unwrapFailureCode
+        self.getThisObj = getThisObj
 
     def definition_body(self):
         # Our descriptor might claim that we're not castable, simply because
         # we're someone's consequential interface.  But for this-unwrapping, we
         # know that we're the real deal.  So fake a descriptor here for
         # consumption by CastableObjectUnwrapper.
-        getThis = CGGeneric("""js::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
+        getThis = CGGeneric("""js::RootedObject obj(cx, %s);
 if (!obj) {
   return false;
 }
 
-%s* self;""" % self.descriptor.nativeType)
+%s* self;""" % (self.getThisObj, self.descriptor.nativeType))
         unwrapThis = CGGeneric(
             str(CastableObjectUnwrapper(
                         self.descriptor,
                         "obj", "self", self.unwrapFailureCode)))
         return CGList([ CGIndenter(getThis), CGIndenter(unwrapThis),
                         self.generate_code() ], "\n").define()
 
     def generate_code(self):
@@ -4436,16 +4451,40 @@ class CGSpecializedMethod(CGAbstractStat
         return CGMethodCall(nativeName, self.method.isStatic(), self.descriptor,
                             self.method).define()
 
     @staticmethod
     def makeNativeName(descriptor, method):
         name = method.identifier.name
         return MakeNativeName(descriptor.binaryNames.get(name, name))
 
+class CGLegacyCallHook(CGAbstractBindingMethod):
+    """
+    Call hook for our object
+    """
+    def __init__(self, descriptor):
+        self._legacycaller = descriptor.operations["LegacyCaller"]
+        args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
+                Argument('JS::Value*', 'vp')]
+        # Our "self" is actually the callee in this case, not the thisval.
+        CGAbstractBindingMethod.__init__(
+            self, descriptor, LEGACYCALLER_HOOK_NAME,
+            args, getThisObj="&JS_CALLEE(cx, vp).toObject()")
+
+    def define(self):
+        if not self._legacycaller:
+            return ""
+        return CGAbstractBindingMethod.define(self)
+
+    def generate_code(self):
+        name = self._legacycaller.identifier.name
+        nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
+        return CGMethodCall(nativeName, False, self.descriptor,
+                            self._legacycaller)
+
 class CppKeywords():
     """
     A class for checking if method names declared in webidl
     are not in conflict with C++ keywords.
     """
     keywords = frozenset(['alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool',
     'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', 'compl', 'const', 'constexpr',
     'const_cast', 'continue', 'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum',
@@ -6539,16 +6578,18 @@ class CGDescriptor(CGThing):
                                                descriptor.interface.ctor()))
             cgThings.append(CGClassHasInstanceHook(descriptor))
             if not descriptor.interface.isCallback():
                 cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
             if descriptor.needsConstructHookHolder():
                 cgThings.append(CGClassConstructHookHolder(descriptor))
             cgThings.append(CGNamedConstructors(descriptor))
 
+        cgThings.append(CGLegacyCallHook(descriptor))
+
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGPrototypeJSClass(descriptor, properties))
 
         cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGGetProtoObjectMethod(descriptor))
         if descriptor.interface.hasInterfaceObject():
             cgThings.append(CGGetConstructorObjectMethod(descriptor))
@@ -7395,16 +7436,21 @@ class CGNativeMember(ClassMethod):
             type = CGWrapper(CGGeneric(elementDecl), pre="nsTArray< ", post=" >")
             if nullable:
                 type = CGWrapper(type, pre="Nullable< ", post=" >")
             args.append(Argument("%s&" % type.define(), "retval"))
         # And the ErrorResult
         if not 'infallible' in self.extendedAttrs:
             # Use aRv so it won't conflict with local vars named "rv"
             args.append(Argument("ErrorResult&", "aRv"))
+        # The legacycaller thisval
+        if self.member.isMethod() and self.member.isLegacycaller():
+            # If it has an identifier, we can't deal with it yet
+            assert self.member.isIdentifierLess()
+            args.insert(0, Argument("JS::Value", "aThisVal"))
         # And jscontext bits.
         if (self.passCxAsNeeded and
             needCx(returnType, argList, self.extendedAttrs,
                    self.descriptor)):
             args.insert(0, Argument("JSContext*", "cx"))
         # And if we're static, a global
         if self.member.isStatic():
             globalObjectType = "GlobalObject"
@@ -7611,18 +7657,17 @@ class CGExampleClass(CGClass):
             methodDecls.append(CGExampleMethod(descriptor, m, sigs[-1]))
 
         if iface.ctor():
             appendMethod(iface.ctor())
         for n in iface.namedConstructors:
             appendMethod(n)
         for m in iface.members:
             if m.isMethod():
-                if (m.isIdentifierLess() and
-                    m != descriptor.operations['Stringifier']):
+                if m.isIdentifierLess():
                     continue
                 appendMethod(m)
             elif m.isAttr():
                 methodDecls.append(CGExampleGetter(descriptor, m))
                 if not m.readonly:
                     methodDecls.append(CGExampleSetter(descriptor, m))
 
         # Now do the special operations
@@ -7637,19 +7682,30 @@ class CGExampleClass(CGClass):
             # Make a copy of the args, since we plan to modify them.
             args = list(args)
             if op.isGetter() or op.isDeleter():
                 # This is a total hack.  The '&' belongs with the
                 # type, not the name!  But it works, and is simpler
                 # than trying to somehow make this pretty.
                 args.append(FakeArgument(BuiltinTypes[IDLBuiltinType.Types.boolean],
                                          op, name="&found"))
-            if name == "Stringifier" and op.isIdentifierLess():
-                # XXXbz I wish we were consistent about our renaming here.
-                name = "__stringify"
+            if name == "Stringifier":
+                if op.isIdentifierLess():
+                    # XXXbz I wish we were consistent about our renaming here.
+                    name = "Stringify"
+                else:
+                    # We already added this method
+                    return
+            if name == "LegacyCaller":
+                if op.isIdentifierLess():
+                    # XXXbz I wish we were consistent about our renaming here.
+                    name = "LegacyCall"
+                else:
+                    # We already added this method
+                    return
             methodDecls.append(
                 CGNativeMember(descriptor, op,
                                name,
                                (returnType, args),
                                descriptor.getExtendedAttributes(op)))
         # Sort things by name so we get stable ordering in the output.
         ops = descriptor.operations.items()
         ops.sort(key=lambda x: x[0])
@@ -7928,16 +7984,18 @@ class CGCallbackInterface(CGCallback):
 
 class FakeMember():
     def __init__(self):
         self.treatUndefinedAs = self.treatNullAs = "Default"
     def isStatic(self):
         return False
     def isAttr(self):
         return False
+    def isMethod(self):
+        return False
     def getExtendedAttribute(self, name):
         # Claim to be a [Creator] so we can avoid the "mark this
         # resultNotAddRefed" comments CGNativeMember codegen would
         # otherwise stick in.
         if name == "Creator":
             return True
         return None
 
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -244,29 +244,40 @@ class Descriptor(DescriptorProvider):
             'IndexedGetter': None,
             'IndexedSetter': None,
             'IndexedCreator': None,
             'IndexedDeleter': None,
             'NamedGetter': None,
             'NamedSetter': None,
             'NamedCreator': None,
             'NamedDeleter': None,
-            'Stringifier': None
+            'Stringifier': None,
+            'LegacyCaller': None
             }
         if self.concrete:
             self.proxy = False
             iface = self.interface
             def addOperation(operation, m):
                 if not operations[operation]:
                     operations[operation] = m
             # Since stringifiers go on the prototype, we only need to worry
             # about our own stringifier, not those of our ancestor interfaces.
             for m in iface.members:
                 if m.isMethod() and m.isStringifier():
                     addOperation('Stringifier', m)
+                # Don't worry about inheriting legacycallers either: in
+                # practice these are on most-derived prototypes.
+                if m.isMethod() and m.isLegacycaller():
+                    if not m.isIdentifierLess():
+                        raise TypeError("We don't support legacycaller with "
+                                        "identifier.\n%s" % m.location);
+                    if len(m.signatures()) != 1:
+                        raise TypeError("We don't support overloaded "
+                                        "legacycaller.\n%s" % m.location)
+                    addOperation('LegacyCaller', m)
             while iface:
                 for m in iface.members:
                     if not m.isMethod():
                         continue
 
                     def addIndexedOrNamedOperation(operation, m):
                         self.proxy = True
                         if m.isIndexed():
@@ -279,16 +290,20 @@ class Descriptor(DescriptorProvider):
                     if m.isGetter():
                         addIndexedOrNamedOperation('Getter', m)
                     if m.isSetter():
                         addIndexedOrNamedOperation('Setter', m)
                     if m.isCreator():
                         addIndexedOrNamedOperation('Creator', m)
                     if m.isDeleter():
                         addIndexedOrNamedOperation('Deleter', m)
+                    if m.isLegacycaller() and iface != self.interface:
+                        raise TypeError("We don't support legacycaller on "
+                                        "non-leaf interface %s.\n%s" %
+                                        (iface, iface.location))
 
                 iface.setUserData('hasConcreteDescendant', True)
                 iface = iface.parent
 
             if self.proxy:
                 if (not operations['IndexedGetter'] and
                     (operations['IndexedSetter'] or
                      operations['IndexedDeleter'] or
@@ -298,16 +313,20 @@ class Descriptor(DescriptorProvider):
                                       (self.interface, self.interface.location))
                 if (not operations['NamedGetter'] and
                     (operations['NamedSetter'] or
                      operations['NamedDeleter'] or
                      operations['NamedCreator'])):
                     raise SyntaxError("%s supports named properties but does "
                                       "not have a named getter.\n%s" %
                                       (self.interface, self.interface.location))
+                if operations['LegacyCaller']:
+                    raise SyntaxError("%s has a legacy caller but is a proxy; "
+                                      "we don't support that yet.\n%s" %
+                                      (self.interface, self.interface.location))
                 iface = self.interface
                 while iface:
                     iface.setUserData('hasProxyDescendant', True)
                     iface = iface.parent
         self.operations = operations
 
         if self.interface.isExternal() and 'prefable' in desc:
             raise TypeError("%s is external but has a prefable setting" %
@@ -365,16 +384,20 @@ class Descriptor(DescriptorProvider):
                         iface = iface.parent
                 else:
                     add('all', [config], attribute)
 
         for attribute in ['implicitJSContext', 'resultNotAddRefed']:
             addExtendedAttribute(attribute, desc.get(attribute, {}))
 
         self.binaryNames = desc.get('binaryNames', {})
+        if '__legacycaller' not in self.binaryNames:
+            self.binaryNames["__legacycaller"] = "LegacyCall"
+        if '__stringifier' not in self.binaryNames:
+            self.binaryNames["__stringifier"] = "Stringify"
 
         # Build the prototype chain.
         self.prototypeChain = []
         parent = interface
         while parent:
             self.prototypeChain.insert(0, make_name(parent.identifier.name))
             parent = parent.parent
         config.maxProtoChainLength = max(config.maxProtoChainLength,
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -665,36 +665,41 @@ class IDLInterface(IDLObjectWithScope):
                 # inheriting this down the proto chain.  If we really cared we
                 # could try to do something where we set up the unforgeable
                 # attributes of ancestor interfaces, with their corresponding
                 # getters, on our interface, but that gets pretty complicated
                 # and seems unnecessary.
                 self.members.append(unforgeableAttr)
 
         # Ensure that there's at most one of each {named,indexed}
-        # {getter,setter,creator,deleter} and at most one stringifier.
+        # {getter,setter,creator,deleter}, at most one stringifier,
+        # and at most one legacycaller.  Note that this last is not
+        # quite per spec, but in practice no one overloads
+        # legacycallers.
         specialMembersSeen = {}
         for member in self.members:
             if not member.isMethod():
                 continue
 
             if member.isGetter():
                 memberType = "getters"
             elif member.isSetter():
                 memberType = "setters"
             elif member.isCreator():
                 memberType = "creators"
             elif member.isDeleter():
                 memberType = "deleters"
             elif member.isStringifier():
                 memberType = "stringifiers"
+            elif member.isLegacycaller():
+                memberType = "legacycallers"
             else:
                 continue
 
-            if memberType != "stringifiers":
+            if memberType != "stringifiers" and memberType != "legacycallers":
                 if member.isNamed():
                     memberType = "named " + memberType
                 else:
                     assert member.isIndexed()
                     memberType = "indexed " + memberType
 
             if memberType in specialMembersSeen:
                 raise WebIDLError("Multiple " + memberType + " on %s" % (self),
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -513,16 +513,17 @@ public:
   TestInterface* PutForwardsAttr3();
   void ThrowingMethod(ErrorResult& aRv);
   bool GetThrowingAttr(ErrorResult& aRv) const;
   void SetThrowingAttr(bool arg, ErrorResult& aRv);
   bool GetThrowingGetterAttr(ErrorResult& aRv) const;
   void SetThrowingGetterAttr(bool arg);
   bool ThrowingSetterAttr() const;
   void SetThrowingSetterAttr(bool arg, ErrorResult& aRv);
+  int16_t LegacyCall(JS::Value, uint32_t, TestInterface&);
 
   // Methods and properties imported via "implements"
   bool ImplementedProperty();
   void SetImplementedProperty(bool);
   void ImplementedMethod();
   bool ImplementedParentProperty();
   void SetImplementedParentProperty(bool);
   void ImplementedParentMethod();
@@ -881,18 +882,26 @@ public:
   void NamedDeleter(const nsAString&, bool&);
   void NamedDeleter(const nsAString&) MOZ_DELETE;
   long NamedGetter(const nsAString&, bool&);
   void DelNamedItem(const nsAString&);
   void DelNamedItem(const nsAString&, bool&) MOZ_DELETE;
   void GetSupportedNames(nsTArray<nsString>&);
 };
 
-class TestChildInterface : public TestInterface
+class TestParentInterface : public nsISupports,
+                            public nsWrapperCache
 {
 public:
   NS_DECL_ISUPPORTS
+
+  // We need a GetParentObject to make binding codegen happy
+  virtual nsISupports* GetParentObject();
+};
+
+class TestChildInterface : public TestParentInterface
+{
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* TestBindingHeader_h */
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -503,21 +503,25 @@ interface TestInterface {
   void passRenamedInterface(TestRenamedInterface arg);
   [PutForwards=writableByte] readonly attribute TestInterface putForwardsAttr;
   [PutForwards=writableByte, LenientThis] readonly attribute TestInterface putForwardsAttr2;
   [PutForwards=writableByte, ChromeOnly] readonly attribute TestInterface putForwardsAttr3;
   [Throws] void throwingMethod();
   [Throws] attribute boolean throwingAttr;
   [GetterThrows] attribute boolean throwingGetterAttr;
   [SetterThrows] attribute boolean throwingSetterAttr;
+  legacycaller short(unsigned long arg1, TestInterface arg2);
 
   // If you add things here, add them to TestExampleGen as well
 };
 
-interface TestChildInterface : TestInterface {
+interface TestParentInterface {
+};
+
+interface TestChildInterface : TestParentInterface {
 };
 
 interface TestNonWrapperCacheInterface {
 };
 
 interface ImplementedInterfaceParent {
   void implementedParentMethod();
   attribute boolean implementedParentProperty;
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -417,16 +417,17 @@ interface TestExampleInterface {
   void passRenamedInterface(TestRenamedInterface arg);
   [PutForwards=writableByte] readonly attribute TestExampleInterface putForwardsAttr;
   [PutForwards=writableByte, LenientThis] readonly attribute TestExampleInterface putForwardsAttr2;
   [PutForwards=writableByte, ChromeOnly] readonly attribute TestExampleInterface putForwardsAttr3;
   [Throws] void throwingMethod();
   [Throws] attribute boolean throwingAttr;
   [GetterThrows] attribute boolean throwingGetterAttr;
   [SetterThrows] attribute boolean throwingSetterAttr;
+  legacycaller short(unsigned long arg1, TestInterface arg2);
 
   // If you add things here, add them to TestCodeGen as well
 };
 
 interface TestExampleProxyInterface {
   getter long longIndexedGetter(unsigned long ix);
   deleter void (unsigned long ix);
   setter creator void longIndexedSetter(unsigned long y, long z);