From c0355434582acd6a63912b2f252b58e3d0d3d16e Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Tue, 5 May 2015 23:42:27 -0700 Subject: [PATCH] Bug 1123516 - Implement maplike/setlike in WebIDL Codegen; r=bz --- b2g/installer/package-manifest.in | 1 + browser/installer/package-manifest.in | 1 + dom/bindings/BindingUtils.cpp | 84 +++ dom/bindings/BindingUtils.h | 23 + dom/bindings/Codegen.py | 640 +++++++++++++++++- dom/bindings/ToJSValue.h | 9 + dom/bindings/moz.build | 19 + dom/bindings/parser/WebIDL.py | 17 +- dom/bindings/test/TestInterfaceJS.manifest | 2 + dom/bindings/test/TestInterfaceJSMaplike.js | 38 ++ dom/bindings/test/TestInterfaceMaplike.cpp | 84 +++ dom/bindings/test/TestInterfaceMaplike.h | 52 ++ .../test/TestInterfaceMaplikeObject.cpp | 88 +++ .../test/TestInterfaceMaplikeObject.h | 52 ++ dom/bindings/test/TestInterfaceSetlike.cpp | 58 ++ dom/bindings/test/TestInterfaceSetlike.h | 46 ++ .../test/TestInterfaceSetlikeNode.cpp | 58 ++ dom/bindings/test/TestInterfaceSetlikeNode.h | 46 ++ dom/bindings/test/chrome.ini | 2 + dom/bindings/test/mochitest.ini | 2 + dom/bindings/test/moz.build | 1 + .../test/test_bug1123516_maplikesetlike.html | 270 ++++++++ .../test_bug1123516_maplikesetlikechrome.xul | 68 ++ .../chrome/test_sandbox_bindings.xul | 3 + .../TestInterfaceJSMaplikeSetlike.webidl | 47 ++ dom/webidl/moz.build | 4 +- mobile/android/installer/package-manifest.in | 1 + 27 files changed, 1690 insertions(+), 26 deletions(-) create mode 100644 dom/bindings/test/TestInterfaceJSMaplike.js create mode 100644 dom/bindings/test/TestInterfaceMaplike.cpp create mode 100644 dom/bindings/test/TestInterfaceMaplike.h create mode 100644 dom/bindings/test/TestInterfaceMaplikeObject.cpp create mode 100644 dom/bindings/test/TestInterfaceMaplikeObject.h create mode 100644 dom/bindings/test/TestInterfaceSetlike.cpp create mode 100644 dom/bindings/test/TestInterfaceSetlike.h create mode 100644 dom/bindings/test/TestInterfaceSetlikeNode.cpp create mode 100644 dom/bindings/test/TestInterfaceSetlikeNode.h create mode 100644 dom/bindings/test/test_bug1123516_maplikesetlike.html create mode 100644 dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul create mode 100644 dom/webidl/TestInterfaceJSMaplikeSetlike.webidl diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 8447fdb36eee..ae9775314767 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -696,6 +696,7 @@ #ifdef MOZ_DEBUG @RESPATH@/components/TestInterfaceJS.js @RESPATH@/components/TestInterfaceJS.manifest +@RESPATH@/components/TestInterfaceJSMaplike.js #endif @RESPATH@/components/PACGenerator.js diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index b13939fbaee6..30821b133309 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -623,6 +623,7 @@ #ifdef MOZ_DEBUG @RESPATH@/components/TestInterfaceJS.js @RESPATH@/components/TestInterfaceJS.manifest +@RESPATH@/components/TestInterfaceJSMaplike.js #endif @RESPATH@/components/PACGenerator.js diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index d3297fa89759..aad5d2038cff 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2816,5 +2816,89 @@ SystemGlobalEnumerate(JSContext* cx, JS::Handle obj) ResolveSystemBinding(cx, obj, JSID_VOIDHANDLE, &ignored); } +template +bool +GetMaplikeSetlikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated) +{ + JS::Rooted reflector(aCx); + reflector = IsDOMObject(aObj) ? aObj : js::UncheckedUnwrap(aObj, + /* stopAtOuter = */ false); + + // Retrieve the backing object from the reserved slot on the maplike/setlike + // object. If it doesn't exist yet, create it. + JS::Rooted slotValue(aCx); + slotValue = js::GetReservedSlot(reflector, aSlotIndex); + if (slotValue.isUndefined()) { + // Since backing object access can happen in non-originating compartments, + // make sure to create the backing object in reflector compartment. + { + JSAutoCompartment ac(aCx, reflector); + JS::Rooted newBackingObj(aCx); + newBackingObj.set(Method(aCx)); + if (NS_WARN_IF(!newBackingObj)) { + return false; + } + js::SetReservedSlot(reflector, aSlotIndex, JS::ObjectValue(*newBackingObj)); + } + slotValue = js::GetReservedSlot(reflector, aSlotIndex); + *aBackingObjCreated = true; + } else { + *aBackingObjCreated = false; + } + if (!MaybeWrapNonDOMObjectValue(aCx, &slotValue)) { + return false; + } + aBackingObj.set(&slotValue.toObject()); + return true; +} + +bool +GetMaplikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated) +{ + return GetMaplikeSetlikeBackingObject(aCx, aObj, aSlotIndex, + aBackingObj, + aBackingObjCreated); +} + +bool +GetSetlikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated) +{ + return GetMaplikeSetlikeBackingObject(aCx, aObj, aSlotIndex, + aBackingObj, + aBackingObjCreated); +} + +bool +ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp) +{ + JS::CallArgs args = CallArgsFromVp(aArgc, aVp); + // Unpack callback and object from slots + JS::Rooted + callbackFn(aCx, js::GetFunctionNativeReserved(&args.callee(), + FOREACH_CALLBACK_SLOT)); + JS::Rooted + maplikeOrSetlikeObj(aCx, + js::GetFunctionNativeReserved(&args.callee(), + FOREACH_MAPLIKEORSETLIKEOBJ_SLOT)); + MOZ_ASSERT(aArgc == 3); + JS::AutoValueVector newArgs(aCx); + // Arguments are passed in as value, key, object. Keep value and key, replace + // object with the maplike/setlike object. + newArgs.append(args.get(0)); + newArgs.append(args.get(1)); + newArgs.append(maplikeOrSetlikeObj); + JS::Rooted rval(aCx, JS::UndefinedValue()); + // Now actually call the user specified callback + return JS::Call(aCx, args.thisv(), callbackFn, newArgs, &rval); +} } // namespace dom } // namespace mozilla diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 00af88abfeda..b66477717b17 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -3248,6 +3248,29 @@ bool SystemGlobalResolve(JSContext* cx, JS::Handle obj, // thrown. bool SystemGlobalEnumerate(JSContext* cx, JS::Handle obj); +// Slot indexes for maplike/setlike forEach functions +#define FOREACH_CALLBACK_SLOT 0 +#define FOREACH_MAPLIKEORSETLIKEOBJ_SLOT 1 + +// Backing function for running .forEach() on maplike/setlike interfaces. +// Unpacks callback and maplike/setlike object from reserved slots, then runs +// callback for each key (and value, for maplikes) +bool ForEachHandler(JSContext* aCx, unsigned aArgc, JS::Value* aVp); + +// Unpacks backing object (ES6 map/set) from the reserved slot of a reflector +// for a maplike/setlike interface. If backing object does not exist, creates +// backing object in the compartment of the reflector involved, making this safe +// to use across compartments/via xrays. Return values of these methods will +// always be in the context compartment. +bool GetMaplikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated); +bool GetSetlikeBackingObject(JSContext* aCx, JS::Handle aObj, + size_t aSlotIndex, + JS::MutableHandle aBackingObj, + bool* aBackingObjCreated); + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 4e8e09774443..4253a759c47b 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -1143,6 +1143,18 @@ class CGHeaders(CGWrapper): if funcList is not None: addHeaderForFunc(funcList[0]) + for desc in descriptors: + if desc.interface.maplikeOrSetlike: + # We need ToJSValue.h for maplike/setlike type conversions + bindingHeaders.add("mozilla/dom/ToJSValue.h") + # Add headers for the key and value types of the maplike, since + # they'll be needed for convenience functions + addHeadersForType((desc.interface.maplikeOrSetlike.keyType, + desc, None)) + if desc.interface.maplikeOrSetlike.valueType: + addHeadersForType((desc.interface.maplikeOrSetlike.valueType, + desc, None)) + for d in dictionaries: if d.parent: declareIncludes.add(self.getDeclarationFilename(d.parent)) @@ -2164,7 +2176,9 @@ class MethodDefiner(PropertyDefiner): "name": m.identifier.name, "methodInfo": not m.isStatic(), "length": methodLength(m), - "flags": "JSPROP_ENUMERATE", + # Methods generated for a maplike/setlike declaration are not + # enumerable. + "flags": "JSPROP_ENUMERATE" if not m.isMaplikeOrSetlikeMethod() else "0", "condition": PropertyDefiner.getControllingCondition(m, descriptor), "allowCrossOriginThis": m.getExtendedAttribute("CrossOriginCallable"), "returnsPromise": m.returnsPromise(), @@ -2175,9 +2189,21 @@ class MethodDefiner(PropertyDefiner): else: self.regular.append(method) - # FIXME Check for an existing iterator on the interface first. - if (any(m.isGetter() and m.isIndexed() for m in methods) and - not any("@@iterator" in m.aliases for m in methods)): + # TODO: Once iterable is implemented, use tiebreak rules instead of + # failing. Also, may be more tiebreak rules to implement once spec bug + # is resolved. + # https://www.w3.org/Bugs/Public/show_bug.cgi?id=28592 + def hasIterator(methods, regular): + return (any("@@iterator" in m.aliases for m in methods) or + any("@@iterator" == r["name"] for r in regular)) + + if (any(m.isGetter() and m.isIndexed() for m in methods)): + if hasIterator(methods, self.regular): + raise TypeError("Cannot have indexed getter/attr on " + "interface %s with other members " + "that generate @@iterator, such as " + "maplike/setlike or aliased functions." % + self.descriptor.interface.identifier.name) self.regular.append({ "name": "@@iterator", "methodInfo": False, @@ -2187,6 +2213,31 @@ class MethodDefiner(PropertyDefiner): "condition": MemberCondition(None, None) }) + # Generate the maplike/setlike iterator, if one wasn't already + # generated by a method. If we already have an @@iterator symbol, fail. + if descriptor.interface.maplikeOrSetlike: + if hasIterator(methods, self.regular): + raise TypeError("Cannot have maplike/setlike interface with " + "other members that generate @@iterator " + "on interface %s, such as indexed getters " + "or aliased functions." % + self.descriptor.interface.identifier.name) + for m in methods: + if (m.isMaplikeOrSetlikeMethod() and + ((m.maplikeOrSetlike.isMaplike() and + m.identifier.name == "entries") or + (m.maplikeOrSetlike.isSetlike() and + m.identifier.name == "values"))): + self.regular.append({ + "name": "@@iterator", + "methodName": m.identifier.name, + "length": methodLength(m), + "flags": "0", + "condition": PropertyDefiner.getControllingCondition(m, + descriptor), + }) + break + if not static: stringifier = descriptor.operations['Stringifier'] if (stringifier and @@ -2284,7 +2335,9 @@ class MethodDefiner(PropertyDefiner): jitinfo = "nullptr" else: selfHostedName = "nullptr" - accessor = m.get("nativeName", IDLToCIdentifier(m["name"])) + # When defining symbols, function name may not match symbol name + methodName = m.get("methodName", m["name"]) + accessor = m.get("nativeName", IDLToCIdentifier(methodName)) if m.get("methodInfo", True): # Cast this in case the methodInfo is a # JSTypedMethodJitInfo. @@ -2377,8 +2430,10 @@ class AttrDefiner(PropertyDefiner): def flags(attr): unforgeable = " | JSPROP_PERMANENT" if self.unforgeable else "" - return ("JSPROP_SHARED | JSPROP_ENUMERATE" + - unforgeable) + # Attributes generated as part of a maplike/setlike declaration are + # not enumerable. + enumerable = " | JSPROP_ENUMERATE" if not attr.isMaplikeOrSetlikeAttr() else "" + return ("JSPROP_SHARED" + enumerable + unforgeable) def getter(attr): if self.static: @@ -5539,7 +5594,7 @@ class CGArgumentConverter(CGThing): will be automatically uppercased. """ def __init__(self, argument, index, descriptorProvider, - argDescription, + argDescription, member, invalidEnumValueFatal=True, lenientFloatCode=None): CGThing.__init__(self) self.argument = argument @@ -5556,8 +5611,16 @@ class CGArgumentConverter(CGThing): "obj": "obj", "passedToJSImpl": toStringBool(isJSImplementedDescriptor(descriptorProvider)) } - self.replacementVariables["val"] = string.Template( - "args[${index}]").substitute(replacer) + # If we have a method generated by the maplike/setlike portion of an + # interface, arguments can possibly be undefined, but will need to be + # converted to the key/value type of the backing object. In this case, + # use .get() instead of direct access to the argument. + if member.isMethod() and member.isMaplikeOrSetlikeMethod(): + self.replacementVariables["val"] = string.Template( + "args.get(${index})").substitute(replacer) + else: + self.replacementVariables["val"] = string.Template( + "args[${index}]").substitute(replacer) haveValueCheck = string.Template( "args.hasDefined(${index})").substitute(replacer) self.replacementVariables["haveValue"] = haveValueCheck @@ -6795,7 +6858,7 @@ class CGPerSignatureCall(CGThing): cgThings.append( CGArgumentConverter(arguments[i], i, self.descriptor, argDescription % {"index": i + 1}, - invalidEnumValueFatal=not setter, + idlNode, invalidEnumValueFatal=not setter, lenientFloatCode=lenientFloatCode)) if needsUnwrap: @@ -6832,11 +6895,19 @@ class CGPerSignatureCall(CGThing): CGIfWrapper(CGList(xraySteps), "objIsXray")) - cgThings.append(CGCallGenerator( - self.getErrorReport() if self.isFallible() else None, - self.getArguments(), argsPre, returnType, - self.extendedAttributes, descriptor, nativeMethodName, - static, argsPost=argsPost, resultVar=resultVar)) + # If this is a method that was generated by a maplike/setlike + # interface, use the maplike/setlike generator to fill in the body. + # Otherwise, use CGCallGenerator to call the native method. + if idlNode.isMethod() and idlNode.isMaplikeOrSetlikeMethod(): + cgThings.append(CGMaplikeOrSetlikeMethodGenerator(descriptor, + idlNode.maplikeOrSetlike, + idlNode.identifier.name)) + else: + cgThings.append(CGCallGenerator( + self.getErrorReport() if self.isFallible() else None, + self.getArguments(), argsPre, returnType, + self.extendedAttributes, descriptor, nativeMethodName, + static, argsPost=argsPost, resultVar=resultVar)) self.cgRoot = CGList(cgThings) def getArguments(self): @@ -7051,7 +7122,10 @@ class CGMethodCall(CGThing): self.cgRoot = CGList([getPerSignatureCall(signature)]) requiredArgs = requiredArgCount(signature) - if requiredArgs > 0: + # Skip required arguments check for maplike/setlike interfaces, as + # they can have arguments which are not passed, and are treated as + # if undefined had been explicitly passed. + if requiredArgs > 0 and not method.isMaplikeOrSetlikeMethod(): code = fill( """ if (MOZ_UNLIKELY(args.length() < ${requiredArgs})) { @@ -7146,7 +7220,7 @@ class CGMethodCall(CGThing): # possibleSignatures[0] caseBody = [CGArgumentConverter(possibleSignatures[0][1][i], i, descriptor, - argDesc % (i + 1)) + argDesc % (i + 1), method) for i in range(0, distinguishingIndex)] # Select the right overload from our set. @@ -7438,7 +7512,12 @@ class FakeArgument(): self.variadic = False self.defaultValue = None self._allowTreatNonCallableAsNull = allowTreatNonCallableAsNull - self.treatNullAs = interfaceMember.treatNullAs + # For FakeArguments generated by maplike/setlike convenience functions, + # we won't have an interfaceMember to pass in. + if interfaceMember: + self.treatNullAs = interfaceMember.treatNullAs + else: + self.treatNullAs = "Default" if isinstance(interfaceMember, IDLAttribute): self.enforceRange = interfaceMember.enforceRange self.clamp = interfaceMember.clamp @@ -8008,6 +8087,12 @@ class CGSpecializedGetter(CGAbstractStaticMethod): CGAbstractStaticMethod.__init__(self, descriptor, name, "bool", args) def definition_body(self): + if self.attr.maplikeOrSetlike: + # If the interface is maplike/setlike, there will be one getter + # method for the size property of the backing object. Due to having + # to unpack the backing object from the slot, this requires its own + # generator. + return getMaplikeOrSetlikeSizeGetterBody(self.descriptor, self.attr) nativeName = CGSpecializedGetter.makeNativeName(self.descriptor, self.attr) if self.attr.slotIndex is not None: @@ -11263,6 +11348,10 @@ class CGDescriptor(CGThing): crossOriginMethods.add(m.identifier.name) if m.getExtendedAttribute("MethodIdentityTestable"): cgThings.append(CGMethodIdentityTest(descriptor, m)) + # If we've hit the maplike/setlike member itself, go ahead and + # generate its convenience functions. + elif m.isMaplikeOrSetlike(): + cgThings.append(CGMaplikeOrSetlikeHelperGenerator(descriptor, m)) elif m.isAttr(): if m.stringifier: raise TypeError("Stringifier attributes not supported yet. " @@ -12438,6 +12527,15 @@ class CGForwardDeclarations(CGWrapper): # Needed for at least Wrap. for d in descriptors: builder.add(d.nativeType) + # If we're an interface and we have a maplike/setlike declaration, + # we'll have helper functions exposed to the native side of our + # bindings, which will need to show up in the header. If either of + # our key/value types are interfaces, they'll be passed as + # arguments to helper functions, and they'll need to be forward + # declared in the header. + if d.interface.maplikeOrSetlike: + forwardDeclareForType(d.interface.maplikeOrSetlike.keyType) + forwardDeclareForType(d.interface.maplikeOrSetlike.valueType) # We just about always need NativePropertyHooks builder.addInMozillaDom("NativePropertyHooks", isStruct=True) @@ -14070,8 +14168,12 @@ class FakeMember(): class CallbackMember(CGNativeMember): + # XXXbz It's OK to use CallbackPreserveColor for wrapScope because + # CallSetup already handled the unmark-gray bits for us. we don't have + # anything better to use for 'obj', really... def __init__(self, sig, name, descriptorProvider, needThisHandling, - rethrowContentException=False, typedArraysAreStructs=False): + rethrowContentException=False, typedArraysAreStructs=False, + wrapScope='CallbackPreserveColor()'): """ needThisHandling is True if we need to be able to accept a specified thisObj, False otherwise. @@ -14095,6 +14197,8 @@ class CallbackMember(CGNativeMember): # will handle generating public versions that handle the "this" stuff. visibility = "private" if needThisHandling else "public" self.rethrowContentException = rethrowContentException + + self.wrapScope = wrapScope # We don't care, for callback codegen, whether our original member was # a method or attribute or whatnot. Just always pass FakeMember() # here. @@ -14214,10 +14318,7 @@ class CallbackMember(CGNativeMember): 'successCode': "continue;\n" if arg.variadic else "break;\n", 'jsvalRef': "argv[%s]" % jsvalIndex, 'jsvalHandle': "argv[%s]" % jsvalIndex, - # XXXbz we don't have anything better to use for 'obj', - # really... It's OK to use CallbackPreserveColor because - # CallSetup already handled the unmark-gray bits for us. - 'obj': 'CallbackPreserveColor()', + 'obj': self.wrapScope, 'returnsNewObject': False, 'exceptionCode': self.exceptionCode, 'typedArraysAreStructs': self.typedArraysAreStructs @@ -14546,6 +14647,497 @@ class CGJSImplInitOperation(CallbackOperationBase): return "__init" +def getMaplikeOrSetlikeErrorReturn(helperImpl): + """ + Generate return values based on whether a maplike or setlike generated + method is an interface method (which returns bool) or a helper function + (which uses ErrorResult). + """ + if helperImpl: + return dedent( + """ + aRv.Throw(NS_ERROR_UNEXPECTED); + return%s; + """ % helperImpl.getDefaultRetval()) + return "return false;\n" + + +def getMaplikeOrSetlikeBackingObject(descriptor, maplikeOrSetlike, helperImpl=None): + """ + Generate code to get/create a JS backing object for a maplike/setlike + declaration from the declaration slot. + """ + func_prefix = maplikeOrSetlike.maplikeOrSetlikeType.title() + ret = fill( + """ + JS::Rooted backingObj(cx); + bool created = false; + if (!Get${func_prefix}BackingObject(cx, obj, ${slot}, &backingObj, &created)) { + $*{errorReturn} + } + if (created) { + PreserveWrapper<${selfType}>(self); + } + """, + slot=memberReservedSlot(maplikeOrSetlike), + func_prefix=func_prefix, + errorReturn=getMaplikeOrSetlikeErrorReturn(helperImpl), + selfType=descriptor.nativeType) + return ret + + +def getMaplikeOrSetlikeSizeGetterBody(descriptor, attr): + """ + Creates the body for the size getter method of maplike/setlike interfaces. + """ + # We should only have one declaration attribute currently + assert attr.identifier.name == "size" + assert attr.isMaplikeOrSetlikeAttr() + return fill( + """ + $*{getBackingObj} + uint32_t result = JS::${funcPrefix}Size(cx, backingObj); + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + args.rval().setNumber(result); + return true; + """, + getBackingObj=getMaplikeOrSetlikeBackingObject(descriptor, + attr.maplikeOrSetlike), + funcPrefix=attr.maplikeOrSetlike.prefix) + + +class CGMaplikeOrSetlikeMethodGenerator(CGThing): + """ + Creates methods for maplike/setlike interfaces. It is expected that all + methods will be have a maplike/setlike object attached. Unwrapping/wrapping + will be taken care of by the usual method generation machinery in + CGMethodCall/CGPerSignatureCall. Functionality is filled in here instead of + using CGCallGenerator. + """ + def __init__(self, descriptor, maplikeOrSetlike, methodName, + helperImpl=None): + CGThing.__init__(self) + # True if this will be the body of a C++ helper function. + self.helperImpl = helperImpl + self.descriptor = descriptor + self.maplikeOrSetlike = maplikeOrSetlike + self.cgRoot = CGList([]) + impl_method_name = methodName + if impl_method_name[0] == "_": + # double underscore means this is a js-implemented chrome only rw + # function. Truncate the double underscore so calling the right + # underlying JSAPI function still works. + impl_method_name = impl_method_name[2:] + self.cgRoot.append(CGGeneric( + getMaplikeOrSetlikeBackingObject(self.descriptor, + self.maplikeOrSetlike, + self.helperImpl))) + self.returnStmt = getMaplikeOrSetlikeErrorReturn(self.helperImpl) + + # Generates required code for the method. Method descriptions included + # in definitions below. Throw if we don't have a method to fill in what + # we're looking for. + try: + methodGenerator = getattr(self, impl_method_name) + except AttributeError: + raise TypeError("Missing %s method definition '%s'" % + (self.maplikeOrSetlike.maplikeOrSetlikeType, + methodName)) + # Method generator returns tuple, containing: + # + # - a list of CGThings representing setup code for preparing to call + # the JS API function + # - a list of arguments needed for the JS API function we're calling + # - list of code CGThings needed for return value conversion. + (setupCode, arguments, setResult) = methodGenerator() + + # Create the actual method call, and then wrap it with the code to + # return the value if needed. + funcName = (self.maplikeOrSetlike.prefix + + MakeNativeName(impl_method_name)) + # Append the list of setup code CGThings + self.cgRoot.append(CGList(setupCode)) + # Create the JS API call + self.cgRoot.append(CGWrapper( + CGGeneric(fill( + """ + if (!JS::${funcName}(${args})) { + $*{errorReturn} + } + """, + funcName=funcName, + args=", ".join(["cx", "backingObj"] + arguments), + errorReturn=self.returnStmt)))) + # Append result conversion + self.cgRoot.append(CGList(setResult)) + + def mergeTuples(self, a, b): + """ + Expecting to take 2 tuples were all elements are lists, append the lists in + the second tuple to the lists in the first. + """ + return tuple([x + y for x, y in zip(a, b)]) + + def appendArgConversion(self, name): + """ + Generate code to convert arguments to JS::Values, so they can be + passed into JSAPI functions. + """ + return CGGeneric(fill( + """ + JS::Rooted ${name}Val(cx); + if (!ToJSValue(cx, ${name}, &${name}Val)) { + $*{errorReturn} + } + """, + name=name, + errorReturn=self.returnStmt)) + + def appendKeyArgConversion(self): + """ + Generates the key argument for methods. Helper functions will use + an AutoValueVector, while interface methods have seperate JS::Values. + """ + if self.helperImpl: + return ([], ["argv[0]"], []) + return ([self.appendArgConversion("arg0")], ["arg0Val"], []) + + def appendKeyAndValueArgConversion(self): + """ + Generates arguments for methods that require a key and value. Helper + functions will use an AutoValueVector, while interface methods have + seperate JS::Values. + """ + r = self.appendKeyArgConversion() + if self.helperImpl: + return self.mergeTuples(r, ([], ["argv[1]"], [])) + return self.mergeTuples(r, ([self.appendArgConversion("arg1")], + ["arg1Val"], + [])) + + def appendIteratorResult(self): + """ + Generate code to output JSObject* return values, needed for functions that + return iterators. Iterators cannot currently be wrapped via Xrays. If + something that would return an iterator is called via Xray, fail early. + """ + # TODO: Bug 1173651 - Remove check once bug 1023984 is fixed. + code = CGGeneric(dedent( + """ + // TODO (Bug 1173651): Xrays currently cannot wrap iterators. Change + // after bug 1023984 is fixed. + if (xpc::WrapperFactory::IsXrayWrapper(obj)) { + JS_ReportError(cx, "Xray wrapping of iterators not supported."); + return false; + } + JS::Rooted result(cx); + JS::Rooted v(cx); + """)) + arguments = "&v" + setResult = CGGeneric(dedent( + """ + result = &v.toObject(); + """)) + return ([code], [arguments], [setResult]) + + def appendSelfResult(self): + """ + Generate code to return the interface object itself. + """ + code = CGGeneric(dedent( + """ + JS::Rooted result(cx); + """)) + setResult = CGGeneric(dedent( + """ + result = obj; + """)) + return ([code], [], [setResult]) + + def appendBoolResult(self): + if self.helperImpl: + return ([CGGeneric()], ["&aRetVal"], []) + return ([CGGeneric("bool result;\n")], ["&result"], []) + + def forEach(self): + """ + void forEach(callback c, any thisval); + + ForEach takes a callback, and a possible value to use as 'this'. The + callback needs to take value, key, and the interface object + implementing maplike/setlike. In order to make sure that the third arg + is our interface object instead of the map/set backing object, we + create a js function with the callback and original object in its + storage slots, then use a helper function in BindingUtils to make sure + the callback is called correctly. + """ + assert(not self.helperImpl) + code = [CGGeneric(dedent( + """ + // Create a wrapper function. + JSFunction* func = js::NewFunctionWithReserved(cx, ForEachHandler, 3, 0, nullptr); + if (!func) { + return false; + } + JS::Rooted funcObj(cx, JS_GetFunctionObject(func)); + JS::Rooted funcVal(cx, JS::ObjectValue(*funcObj)); + js::SetFunctionNativeReserved(funcObj, FOREACH_CALLBACK_SLOT, + JS::ObjectValue(*arg0)); + js::SetFunctionNativeReserved(funcObj, FOREACH_MAPLIKEORSETLIKEOBJ_SLOT, + JS::ObjectValue(*obj)); + """))] + arguments = ["funcVal", "arg1"] + return (code, arguments, []) + + def set(self): + """ + object set(key, value); + + Maplike only function, takes key and sets value to it, returns + interface object unless being called from a C++ helper. + """ + assert self.maplikeOrSetlike.isMaplike() + r = self.appendKeyAndValueArgConversion() + if self.helperImpl: + return r + return self.mergeTuples(r, self.appendSelfResult()) + + def add(self): + """ + object add(value); + + Setlike only function, adds value to set, returns interface object + unless being called from a C++ helper + """ + assert self.maplikeOrSetlike.isSetlike() + r = self.appendKeyArgConversion() + if self.helperImpl: + return r + return self.mergeTuples(r, self.appendSelfResult()) + + def get(self): + """ + type? get(key); + + Retrieves a value from a backing object based on the key. Returns value + if key is in backing object, undefined otherwise. + """ + assert self.maplikeOrSetlike.isMaplike() + r = self.appendKeyArgConversion() + code = [CGGeneric(dedent( + """ + JS::Rooted result(cx); + """))] + arguments = ["&result"] + return self.mergeTuples(r, (code, arguments, [])) + + def has(self): + """ + bool has(key); + + Check if an entry exists in the backing object. Returns true if value + exists in backing object, false otherwise. + """ + return self.mergeTuples(self.appendKeyArgConversion(), + self.appendBoolResult()) + + def keys(self): + """ + object keys(); + + Returns new object iterator with all keys from backing object. + """ + return self.appendIteratorResult() + + def values(self): + """ + object values(); + + Returns new object iterator with all values from backing object. + """ + return self.appendIteratorResult() + + def entries(self): + """ + object entries(); + + Returns new object iterator with all keys and values from backing + object. Keys will be null for set. + """ + return self.appendIteratorResult() + + def clear(self): + """ + void clear(); + + Removes all entries from map/set. + """ + return ([], [], []) + + def delete(self): + """ + bool delete(key); + + Deletes an entry from the backing object. Returns true if value existed + in backing object, false otherwise. + """ + return self.mergeTuples(self.appendKeyArgConversion(), + self.appendBoolResult()) + + def define(self): + return self.cgRoot.define() + + +class CGMaplikeOrSetlikeHelperFunctionGenerator(CallbackMember): + """ + Generates code to allow C++ to perform operations on backing objects. Gets + a context from the binding wrapper, turns arguments into JS::Values (via + CallbackMember/CGNativeMember argument conversion), then uses + CGMaplikeOrSetlikeMethodGenerator to generate the body. + + """ + + class HelperFunction(CGAbstractMethod): + """ + Generates context retrieval code and rooted JSObject for interface for + CGMaplikeOrSetlikeMethodGenerator to use + """ + def __init__(self, descriptor, name, args, code, needsBoolReturn=False): + self.code = code + CGAbstractMethod.__init__(self, descriptor, name, + "bool" if needsBoolReturn else "void", + args) + + def definition_body(self): + return self.code + + def __init__(self, descriptor, maplikeOrSetlike, name, needsKeyArg=False, + needsValueArg=False, needsBoolReturn=False): + args = [] + self.maplikeOrSetlike = maplikeOrSetlike + self.needsBoolReturn = needsBoolReturn + if needsKeyArg: + args.append(FakeArgument(maplikeOrSetlike.keyType, None, 'aKey')) + if needsValueArg: + assert needsKeyArg + args.append(FakeArgument(maplikeOrSetlike.valueType, None, 'aValue')) + # Run CallbackMember init function to generate argument conversion code. + # wrapScope is set to 'obj' when generating maplike or setlike helper + # functions, as we don't have access to the CallbackPreserveColor + # method. + CallbackMember.__init__(self, + [BuiltinTypes[IDLBuiltinType.Types.void], args], + name, descriptor, False, + wrapScope='obj') + # Wrap CallbackMember body code into a CGAbstractMethod to make + # generation easier. + self.implMethod = CGMaplikeOrSetlikeHelperFunctionGenerator.HelperFunction( + descriptor, name, self.args, self.body, needsBoolReturn) + + def getCallSetup(self): + return dedent( + """ + MOZ_ASSERT(self); + AutoJSAPI jsapi; + jsapi.Init(); + jsapi.TakeOwnershipOfErrorReporting(); + JSContext* cx = jsapi.cx(); + JSAutoCompartment tempCompartment(cx, xpc::UnprivilegedJunkScope()); + JS::Rooted v(cx); + if(!ToJSValue(cx, self, &v)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return%s; + } + // This is a reflector, but due to trying to name things + // similarly across method generators, it's called obj here. + JS::Rooted obj(cx); + obj = js::UncheckedUnwrap(&v.toObject(), /* stopAtOuter = */ false); + JSAutoCompartment reflectorCompartment(cx, obj); + """ % self.getDefaultRetval()) + + def getArgs(self, returnType, argList): + # We don't need the context or the value. We'll generate those instead. + args = CGNativeMember.getArgs(self, returnType, argList) + # Prepend a pointer to the binding object onto the arguments + return [Argument(self.descriptorProvider.nativeType + "*", "self")] + args + + def getResultConversion(self): + if self.needsBoolReturn: + return "return aRetVal;\n" + return "return;\n" + + def getRvalDecl(self): + if self.needsBoolReturn: + return "bool aRetVal;\n" + return "" + + def getArgcDecl(self): + # Don't need argc for anything. + return None + + def getDefaultRetval(self): + if self.needsBoolReturn: + return " false" + return "" + + + def getCall(self): + return CGMaplikeOrSetlikeMethodGenerator(self.descriptorProvider, + self.maplikeOrSetlike, + self.name.lower(), + helperImpl=self).define() + + def getPrettyName(self): + return self.name + + def declare(self): + return self.implMethod.declare() + + def define(self): + return self.implMethod.define() + + +class CGMaplikeOrSetlikeHelperGenerator(CGNamespace): + """ + Declares and defines convenience methods for accessing backing objects on + setlike/maplike interface. Generates function signatures, un/packs + backing objects from slot, etc. + """ + def __init__(self, descriptor, maplikeOrSetlike): + self.descriptor = descriptor + self.maplikeOrSetlike = maplikeOrSetlike + self.namespace = "%sHelpers" % (self.maplikeOrSetlike.maplikeOrSetlikeType.title()) + self.helpers = [ + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Clear"), + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Delete", + needsKeyArg=True, + needsBoolReturn=True), + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Has", + needsKeyArg=True, + needsBoolReturn=True)] + if self.maplikeOrSetlike.isMaplike(): + self.helpers.append( + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Set", + needsKeyArg=True, + needsValueArg=True)) + else: + assert(self.maplikeOrSetlike.isSetlike()) + self.helpers.append( + CGMaplikeOrSetlikeHelperFunctionGenerator(descriptor, + maplikeOrSetlike, + "Add", + needsKeyArg=True)) + CGNamespace.__init__(self, self.namespace, CGList(self.helpers)) + + class GlobalGenRoots(): """ Roots for global codegen. diff --git a/dom/bindings/ToJSValue.h b/dom/bindings/ToJSValue.h index f80652e2c58f..d72fee023d22 100644 --- a/dom/bindings/ToJSValue.h +++ b/dom/bindings/ToJSValue.h @@ -209,6 +209,15 @@ ToJSValue(JSContext* aCx, return ToJSValue(aCx, *aArgument.get(), aValue); } +template +MOZ_WARN_UNUSED_RESULT bool +ToJSValue(JSContext* aCx, + const NonNull& aArgument, + JS::MutableHandle aValue) +{ + return ToJSValue(aCx, *aArgument.get(), aValue); +} + // Accept WebIDL dictionaries template MOZ_WARN_UNUSED_RESULT diff --git a/dom/bindings/moz.build b/dom/bindings/moz.build index 58a4a7e39afb..ed17373e5f48 100644 --- a/dom/bindings/moz.build +++ b/dom/bindings/moz.build @@ -84,6 +84,25 @@ SOURCES += [ 'StructuredClone.cpp', ] +# Tests for maplike and setlike require bindings to be built, which means they +# must be included in libxul. This breaks the "no test classes are exported" +# rule stated in the test/ directory, but it's the only way this will work. +# Test classes are only built in debug mode, and all tests requiring use of +# them are only run in debug mode. +if CONFIG['MOZ_DEBUG']: + EXPORTS.mozilla.dom += [ + "test/TestInterfaceMaplike.h", + "test/TestInterfaceMaplikeObject.h", + "test/TestInterfaceSetlike.h", + "test/TestInterfaceSetlikeNode.h" + ] + UNIFIED_SOURCES += [ + "test/TestInterfaceMaplike.cpp", + "test/TestInterfaceMaplikeObject.cpp", + "test/TestInterfaceSetlike.cpp", + "test/TestInterfaceSetlikeNode.cpp", + ] + include('/ipc/chromium/chromium-config.mozbuild') if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']: diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 4ff1937a5e64..0b288e8df6e6 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -3399,6 +3399,7 @@ class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): [self.location]) self.aliases.append(alias) + # MaplikeOrSetlike adds a trait to an interface, like map or iteration # functions. To handle them while still getting all of the generated binding # code taken care of, we treat them as macros that are expanded into members @@ -3425,6 +3426,12 @@ class IDLMaplikeOrSetlike(IDLInterfaceMember): self.disallowedMemberNames = [] self.disallowedNonMethodNames = [] + # When generating JSAPI access code, we need to know the backing object + # type prefix to create the correct function. Generate here for reuse. + if self.isMaplike(): + self.prefix = 'Map' + elif self.isSetlike(): + self.prefix = 'Set' def __str__(self): return "declared '%s' with key '%s'" % (self.maplikeOrSetlikeType, self.keyType) @@ -4349,7 +4356,15 @@ class IDLMethod(IDLInterfaceMember, IDLScope): return self._hasOverloads def isIdentifierLess(self): - return self.identifier.name[:2] == "__" + """ + True if the method name started with __, and if the method is not a + maplike/setlike method. Interfaces with maplike/setlike will generate + methods starting with __ for chrome only backing object access in JS + implemented interfaces, so while these functions use what is considered + an non-identifier name, they actually DO have an identifier. + """ + return (self.identifier.name[:2] == "__" and + not self.isMaplikeOrSetlikeMethod()) def resolve(self, parentScope): assert isinstance(parentScope, IDLScope) diff --git a/dom/bindings/test/TestInterfaceJS.manifest b/dom/bindings/test/TestInterfaceJS.manifest index 8aa0feaabc01..161a42156085 100644 --- a/dom/bindings/test/TestInterfaceJS.manifest +++ b/dom/bindings/test/TestInterfaceJS.manifest @@ -1,2 +1,4 @@ component {2ac4e026-cf25-47d5-b067-78d553c3cad8} TestInterfaceJS.js contract @mozilla.org/dom/test-interface-js;1 {2ac4e026-cf25-47d5-b067-78d553c3cad8} +component {4bc6f6f3-e005-4f0a-b42d-4d1663a9013a} TestInterfaceJSMaplike.js +contract @mozilla.org/dom/test-interface-js-maplike;1 {4bc6f6f3-e005-4f0a-b42d-4d1663a9013a} diff --git a/dom/bindings/test/TestInterfaceJSMaplike.js b/dom/bindings/test/TestInterfaceJSMaplike.js new file mode 100644 index 000000000000..b108ef5b6123 --- /dev/null +++ b/dom/bindings/test/TestInterfaceJSMaplike.js @@ -0,0 +1,38 @@ +/* -*- Mode: JavaScript; 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/. */ + +"use strict"; +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +function TestInterfaceJSMaplike() {} + +TestInterfaceJSMaplike.prototype = { + classID: Components.ID("{4bc6f6f3-e005-4f0a-b42d-4d1663a9013a}"), + contractID: "@mozilla.org/dom/test-interface-js-maplike;1", + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, + Ci.nsIDOMGlobalPropertyInitializer]), + + init: function(win) { this._win = win; }, + + __init: function () {}, + + setInternal: function(aKey, aValue) { + return this.__DOM_IMPL__.__set(aKey, aValue); + }, + + deleteInternal: function(aKey) { + return this.__DOM_IMPL__.__delete(aKey); + }, + + clearInternal: function() { + return this.__DOM_IMPL__.__clear(); + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TestInterfaceJSMaplike]) diff --git a/dom/bindings/test/TestInterfaceMaplike.cpp b/dom/bindings/test/TestInterfaceMaplike.cpp new file mode 100644 index 000000000000..934867c1aa91 --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplike.cpp @@ -0,0 +1,84 @@ +/* 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/. */ + +#include "mozilla/dom/TestInterfaceMaplike.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplike, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceMaplike) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceMaplike) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceMaplike) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceMaplike::TestInterfaceMaplike(nsPIDOMWindow* aParent) +: mParent(aParent) +{ +} + +//static +already_AddRefed +TestInterfaceMaplike::Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv) +{ + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr r = new TestInterfaceMaplike(window); + return r.forget(); +} + +JSObject* +TestInterfaceMaplike::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return TestInterfaceMaplikeBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindow* +TestInterfaceMaplike::GetParentObject() const +{ + return mParent; +} + +void +TestInterfaceMaplike::SetInternal(const nsAString& aKey, int32_t aValue) +{ + ErrorResult rv; + TestInterfaceMaplikeBinding::MaplikeHelpers::Set(this, aKey, aValue, rv); +} + +void +TestInterfaceMaplike::ClearInternal() +{ + ErrorResult rv; + TestInterfaceMaplikeBinding::MaplikeHelpers::Clear(this, rv); +} + +bool +TestInterfaceMaplike::DeleteInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeBinding::MaplikeHelpers::Delete(this, aKey, rv); +} + +bool +TestInterfaceMaplike::HasInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeBinding::MaplikeHelpers::Has(this, aKey, rv); +} + +} +} diff --git a/dom/bindings/test/TestInterfaceMaplike.h b/dom/bindings/test/TestInterfaceMaplike.h new file mode 100644 index 000000000000..f266e2d6bf9d --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplike.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef mozilla_dom_TestInterfaceMaplike_h +#define mozilla_dom_TestInterfaceMaplike_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class nsPIDOMWindow; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class GlobalObject; + +// Implementation of test binding for webidl maplike interfaces, using +// primitives for key and value types. +class TestInterfaceMaplike final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceMaplike) + + explicit TestInterfaceMaplike(nsPIDOMWindow* aParent); + nsPIDOMWindow* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + static already_AddRefed + Constructor(const GlobalObject& aGlobal, ErrorResult& rv); + + // External access for testing internal convenience functions. + void SetInternal(const nsAString& aKey, int32_t aValue); + void ClearInternal(); + bool DeleteInternal(const nsAString& aKey); + bool HasInternal(const nsAString& aKey); +private: + virtual ~TestInterfaceMaplike() {} + nsCOMPtr mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestInterfaceMaplike_h diff --git a/dom/bindings/test/TestInterfaceMaplikeObject.cpp b/dom/bindings/test/TestInterfaceMaplikeObject.cpp new file mode 100644 index 000000000000..564c99f9e43a --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplikeObject.cpp @@ -0,0 +1,88 @@ +/* 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/. */ + +#include "mozilla/dom/TestInterfaceMaplikeObject.h" +#include "mozilla/dom/TestInterfaceMaplike.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceMaplikeObject, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceMaplikeObject) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceMaplikeObject) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceMaplikeObject) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceMaplikeObject::TestInterfaceMaplikeObject(nsPIDOMWindow* aParent) +: mParent(aParent) +{ +} + +//static +already_AddRefed +TestInterfaceMaplikeObject::Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv) +{ + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr r = + new TestInterfaceMaplikeObject(window); + return r.forget(); +} + +JSObject* +TestInterfaceMaplikeObject::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) +{ + return TestInterfaceMaplikeObjectBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindow* +TestInterfaceMaplikeObject::GetParentObject() const +{ + return mParent; +} + +void +TestInterfaceMaplikeObject::SetInternal(const nsAString& aKey) +{ + nsRefPtr p(new TestInterfaceMaplike(mParent)); + ErrorResult rv; + TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Set(this, aKey, *p, rv); +} + +void +TestInterfaceMaplikeObject::ClearInternal() +{ + ErrorResult rv; + TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Clear(this, rv); +} + +bool +TestInterfaceMaplikeObject::DeleteInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Delete(this, aKey, rv); +} + +bool +TestInterfaceMaplikeObject::HasInternal(const nsAString& aKey) +{ + ErrorResult rv; + return TestInterfaceMaplikeObjectBinding::MaplikeHelpers::Has(this, aKey, rv); +} + +} +} diff --git a/dom/bindings/test/TestInterfaceMaplikeObject.h b/dom/bindings/test/TestInterfaceMaplikeObject.h new file mode 100644 index 000000000000..4b69ef70cba5 --- /dev/null +++ b/dom/bindings/test/TestInterfaceMaplikeObject.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef mozilla_dom_TestInterfaceMaplikeObject_h +#define mozilla_dom_TestInterfaceMaplikeObject_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class nsPIDOMWindow; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class GlobalObject; + +// Implementation of test binding for webidl maplike interfaces, using +// primitives for key types and objects for value types. +class TestInterfaceMaplikeObject final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceMaplikeObject) + + explicit TestInterfaceMaplikeObject(nsPIDOMWindow* aParent); + nsPIDOMWindow* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + static already_AddRefed + Constructor(const GlobalObject& aGlobal,ErrorResult& rv); + + // External access for testing internal convenience functions. + void SetInternal(const nsAString& aKey); + void ClearInternal(); + bool DeleteInternal(const nsAString& aKey); + bool HasInternal(const nsAString& aKey); +private: + virtual ~TestInterfaceMaplikeObject() {} + nsCOMPtr mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestInterfaceMaplikeObject_h diff --git a/dom/bindings/test/TestInterfaceSetlike.cpp b/dom/bindings/test/TestInterfaceSetlike.cpp new file mode 100644 index 000000000000..abd3ab3fa129 --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlike.cpp @@ -0,0 +1,58 @@ +/* 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/. */ + +#include "mozilla/dom/TestInterfaceSetlike.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlike, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceSetlike) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceSetlike) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceSetlike) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceSetlike::TestInterfaceSetlike(JSContext* aCx, + nsPIDOMWindow* aParent) +: mParent(aParent) +{ +} + +//static +already_AddRefed +TestInterfaceSetlike::Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv) +{ + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr r = new TestInterfaceSetlike(nullptr, window); + return r.forget(); +} + +JSObject* +TestInterfaceSetlike::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) +{ + return TestInterfaceSetlikeBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindow* +TestInterfaceSetlike::GetParentObject() const +{ + return mParent; +} + +} +} diff --git a/dom/bindings/test/TestInterfaceSetlike.h b/dom/bindings/test/TestInterfaceSetlike.h new file mode 100644 index 000000000000..291a828e4abf --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlike.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef mozilla_dom_TestInterfaceSetlike_h +#define mozilla_dom_TestInterfaceSetlike_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class nsPIDOMWindow; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class GlobalObject; + +// Implementation of test binding for webidl setlike interfaces, using +// primitives for key type. +class TestInterfaceSetlike final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceSetlike) + explicit TestInterfaceSetlike(JSContext* aCx, + nsPIDOMWindow* aParent); + nsPIDOMWindow* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + static already_AddRefed + Constructor(const GlobalObject& aGlobal, ErrorResult& rv); +private: + virtual ~TestInterfaceSetlike() {} + nsCOMPtr mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestInterfaceSetlike_h diff --git a/dom/bindings/test/TestInterfaceSetlikeNode.cpp b/dom/bindings/test/TestInterfaceSetlikeNode.cpp new file mode 100644 index 000000000000..d57a883339b3 --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlikeNode.cpp @@ -0,0 +1,58 @@ +/* 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/. */ + +#include "mozilla/dom/TestInterfaceSetlikeNode.h" +#include "mozilla/dom/TestInterfaceJSMaplikeSetlikeBinding.h" +#include "nsPIDOMWindow.h" +#include "mozilla/dom/BindingUtils.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestInterfaceSetlikeNode, mParent) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TestInterfaceSetlikeNode) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TestInterfaceSetlikeNode) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TestInterfaceSetlikeNode) +NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY +NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +TestInterfaceSetlikeNode::TestInterfaceSetlikeNode(JSContext* aCx, + nsPIDOMWindow* aParent) +: mParent(aParent) +{ +} + +//static +already_AddRefed +TestInterfaceSetlikeNode::Constructor(const GlobalObject& aGlobal, + ErrorResult& aRv) +{ + nsCOMPtr window = do_QueryInterface(aGlobal.GetAsSupports()); + if (!window) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsRefPtr r = new TestInterfaceSetlikeNode(nullptr, window); + return r.forget(); +} + +JSObject* +TestInterfaceSetlikeNode::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) +{ + return TestInterfaceSetlikeNodeBinding::Wrap(aCx, this, aGivenProto); +} + +nsPIDOMWindow* +TestInterfaceSetlikeNode::GetParentObject() const +{ + return mParent; +} + +} +} diff --git a/dom/bindings/test/TestInterfaceSetlikeNode.h b/dom/bindings/test/TestInterfaceSetlikeNode.h new file mode 100644 index 000000000000..bdf577a60a38 --- /dev/null +++ b/dom/bindings/test/TestInterfaceSetlikeNode.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef mozilla_dom_TestInterfaceSetlikeNode_h +#define mozilla_dom_TestInterfaceSetlikeNode_h + +#include "nsWrapperCache.h" +#include "nsCOMPtr.h" + +class nsPIDOMWindow; + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +class GlobalObject; + +// Implementation of test binding for webidl setlike interfaces, using +// primitives for key type. +class TestInterfaceSetlikeNode final : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TestInterfaceSetlikeNode) + explicit TestInterfaceSetlikeNode(JSContext* aCx, + nsPIDOMWindow* aParent); + nsPIDOMWindow* GetParentObject() const; + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + static already_AddRefed + Constructor(const GlobalObject& aGlobal, ErrorResult& rv); +private: + virtual ~TestInterfaceSetlikeNode() {} + nsCOMPtr mParent; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_TestInterfaceSetlikeNode_h diff --git a/dom/bindings/test/chrome.ini b/dom/bindings/test/chrome.ini index e4c8caf75e77..2cdc5bb5ee42 100644 --- a/dom/bindings/test/chrome.ini +++ b/dom/bindings/test/chrome.ini @@ -14,3 +14,5 @@ support-files = skip-if = e10s # prerendering doesn't work in e10s yet [test_kill_longrunning_prerendered_content.xul] skip-if = e10s # prerendering doesn't work in e10s yet +[test_bug1123516_maplikesetlikechrome.xul] +skip-if = debug == false diff --git a/dom/bindings/test/mochitest.ini b/dom/bindings/test/mochitest.ini index f9b64bede895..749a62581774 100644 --- a/dom/bindings/test/mochitest.ini +++ b/dom/bindings/test/mochitest.ini @@ -61,3 +61,5 @@ skip-if = debug == false [test_worker_UnwrapArg.html] [test_unforgeablesonexpando.html] [test_crossOriginWindowSymbolAccess.html] +[test_bug1123516_maplikesetlike.html] +skip-if = debug == false \ No newline at end of file diff --git a/dom/bindings/test/moz.build b/dom/bindings/test/moz.build index afa87cc7e4ee..1b81ad3e6e35 100644 --- a/dom/bindings/test/moz.build +++ b/dom/bindings/test/moz.build @@ -17,6 +17,7 @@ Library('dombindings_test_s') EXTRA_COMPONENTS += [ 'TestInterfaceJS.js', 'TestInterfaceJS.manifest', + 'TestInterfaceJSMaplike.js' ] MOCHITEST_MANIFESTS += ['mochitest.ini'] diff --git a/dom/bindings/test/test_bug1123516_maplikesetlike.html b/dom/bindings/test/test_bug1123516_maplikesetlike.html new file mode 100644 index 000000000000..b5a74c0d4df9 --- /dev/null +++ b/dom/bindings/test/test_bug1123516_maplikesetlike.html @@ -0,0 +1,270 @@ + + + + + Test Maplike Interface + + + + + + + diff --git a/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul new file mode 100644 index 000000000000..4bc45cdddcde --- /dev/null +++ b/dom/bindings/test/test_bug1123516_maplikesetlikechrome.xul @@ -0,0 +1,68 @@ + + + + + + + + + + + Mozilla Bug 1123516 + + + + + diff --git a/dom/tests/mochitest/chrome/test_sandbox_bindings.xul b/dom/tests/mochitest/chrome/test_sandbox_bindings.xul index 4d472a31bf04..f58e1717b2c1 100644 --- a/dom/tests/mochitest/chrome/test_sandbox_bindings.xul +++ b/dom/tests/mochitest/chrome/test_sandbox_bindings.xul @@ -7,7 +7,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=741267 diff --git a/dom/webidl/TestInterfaceJSMaplikeSetlike.webidl b/dom/webidl/TestInterfaceJSMaplikeSetlike.webidl new file mode 100644 index 000000000000..22966a3f1b75 --- /dev/null +++ b/dom/webidl/TestInterfaceJSMaplikeSetlike.webidl @@ -0,0 +1,47 @@ +/* -*- 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/. + */ + +[Constructor(), + Pref="dom.expose_test_interfaces"] +interface TestInterfaceMaplike { + maplike; + void setInternal(DOMString aKey, long aValue); + void clearInternal(); + boolean deleteInternal(DOMString aKey); + boolean hasInternal(DOMString aKey); +}; + +[Constructor(), + Pref="dom.expose_test_interfaces"] +interface TestInterfaceMaplikeObject { + readonly maplike; + void setInternal(DOMString aKey); + void clearInternal(); + boolean deleteInternal(DOMString aKey); + boolean hasInternal(DOMString aKey); +}; + +[Pref="dom.expose_test_interfaces", + JSImplementation="@mozilla.org/dom/test-interface-js-maplike;1", + Constructor()] +interface TestInterfaceJSMaplike { + readonly maplike; + void setInternal(DOMString aKey, long aValue); + void clearInternal(); + boolean deleteInternal(DOMString aKey); +}; + +[Constructor(), + Pref="dom.expose_test_interfaces"] +interface TestInterfaceSetlike { + setlike; +}; + +[Constructor(), + Pref="dom.expose_test_interfaces"] +interface TestInterfaceSetlikeNode { + setlike; +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index 6acc41aead29..058a08693570 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -643,7 +643,9 @@ WEBIDL_FILES += [ # We only expose our prefable test interfaces in debug builds, just to be on # the safe side. if CONFIG['MOZ_DEBUG']: - WEBIDL_FILES += ['TestInterfaceJS.webidl', 'TestInterfaceJSDictionaries.webidl'] + WEBIDL_FILES += ['TestInterfaceJS.webidl', + 'TestInterfaceJSDictionaries.webidl', + 'TestInterfaceJSMaplikeSetlike.webidl'] if CONFIG['MOZ_B2G_BT']: if CONFIG['MOZ_B2G_BT_API_V1']: diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index ae186b4285ff..1530c1ef7e57 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -449,6 +449,7 @@ #ifdef MOZ_DEBUG @BINPATH@/components/TestInterfaceJS.js @BINPATH@/components/TestInterfaceJS.manifest +@BINPATH@/components/TestInterfaceJSMaplike.js #endif @BINPATH@/components/nsAsyncShutdown.manifest