diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 9723a1546e52..659d7e4b2001 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -9644,8 +9644,10 @@ class CGPerSignatureCall(CGThing): # already-preserved wrapper. if ( self.idlNode.getExtendedAttribute("Cached") - and self.descriptor.wrapperCache - ): + or self.idlNode.getExtendedAttribute( + "ReflectedHTMLAttributeReturningFrozenArray" + ) + ) and self.descriptor.wrapperCache: preserveWrapper = dedent( """ PreserveWrapper(self); @@ -10254,7 +10256,9 @@ class CGGetterCall(CGPerSignatureCall): argsPre=[], dontSetSlot=False, extendedAttributes=None, + preConversionCode=None, ): + self.preConversionCode = preConversionCode if attr.getExtendedAttribute("UseCounter"): useCounterName = "%s_%s_getter" % ( descriptor.interface.identifier.name, @@ -10280,6 +10284,12 @@ class CGGetterCall(CGPerSignatureCall): additionalArgsPre=argsPre, ) + def wrap_return_value(self): + wrap = CGPerSignatureCall.wrap_return_value(self) + if self.preConversionCode is not None: + wrap = self.preConversionCode + wrap + return wrap + class FakeIdentifier: def __init__(self, name): @@ -11134,6 +11144,8 @@ class CGSpecializedGetterCommon(CGAbstractStaticMethod): + prefix ) + argsPre = [a.name for a in self.additionalArgs] + maybeReturnCachedVal = None if self.attr.slotIndices is not None: # We're going to store this return value in a slot on some object, # to cache it. The question is, which object? For dictionary and @@ -11178,23 +11190,52 @@ class CGSpecializedGetterCommon(CGAbstractStaticMethod): slotIndex=memberReservedSlot(self.attr, self.descriptor), ) - prefix += fill( - """ - MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(JS::GetClass(slotStorage)) > slotIndex); - { - // Scope for cachedVal - JS::Value cachedVal = JS::GetReservedSlot(slotStorage, slotIndex); - if (!cachedVal.isUndefined()) { - args.rval().set(cachedVal); - // The cached value is in the compartment of slotStorage, - // so wrap into the caller compartment as needed. - return ${maybeWrap}(cx, args.rval()); - } - } + if self.attr.getExtendedAttribute( + "ReflectedHTMLAttributeReturningFrozenArray" + ): + argsPre.append("hasCachedValue ? &useCachedValue : nullptr") + prefix += dedent( + """ + MOZ_ASSERT(slotIndex < JSCLASS_RESERVED_SLOTS(JS::GetClass(slotStorage))); + JS::Rooted cachedVal(cx, JS::GetReservedSlot(slotStorage, slotIndex)); + bool hasCachedValue = !cachedVal.isUndefined(); + bool useCachedValue = false; + """ + ) + maybeReturnCachedVal = fill( + """ + MOZ_ASSERT_IF(useCachedValue, hasCachedValue); + if (hasCachedValue && useCachedValue) { + args.rval().set(cachedVal); + // The cached value is in the compartment of slotStorage, + // so wrap into the caller compartment as needed. + return ${maybeWrap}(cx, args.rval()); + } - """, - maybeWrap=getMaybeWrapValueFuncForType(self.attr.type), - ) + ${clearCachedValue}(self); + + """, + maybeWrap=getMaybeWrapValueFuncForType(self.attr.type), + clearCachedValue=MakeClearCachedValueNativeName(self.attr), + ) + else: + prefix += fill( + """ + MOZ_ASSERT(slotIndex < JSCLASS_RESERVED_SLOTS(JS::GetClass(slotStorage))); + { + // Scope for cachedVal + JS::Value cachedVal = JS::GetReservedSlot(slotStorage, slotIndex); + if (!cachedVal.isUndefined()) { + args.rval().set(cachedVal); + // The cached value is in the compartment of slotStorage, + // so wrap into the caller compartment as needed. + return ${maybeWrap}(cx, args.rval()); + } + } + + """, + maybeWrap=getMaybeWrapValueFuncForType(self.attr.type), + ) return ( prefix @@ -11204,7 +11245,8 @@ class CGSpecializedGetterCommon(CGAbstractStaticMethod): self.descriptor, self.attr, self.errorReportingLabel, - argsPre=[a.name for a in self.additionalArgs], + argsPre=argsPre, + preConversionCode=maybeReturnCachedVal, ).define() ) @@ -11855,11 +11897,11 @@ class CGMemberJITInfo(CGThing): isAlwaysInSlot=toStringBool(alwaysInSlot), isLazilyCachedInSlot=toStringBool(lazilyInSlot), isTypedMethod=toStringBool(isTypedMethod), - slotIndex=slotIndex, + slotIndex="0" if slotIndex is None else slotIndex, ) return initializer.rstrip() - if alwaysInSlot or lazilyInSlot: + if slotIndex is not None: slotAssert = fill( """ static_assert(${slotIndex} <= JSJitInfo::maxSlotIndex, "We won't fit"); @@ -11950,16 +11992,24 @@ class CGMemberJITInfo(CGThing): assert ( isAlwaysInSlot or self.member.getExtendedAttribute("Cached") + or self.member.getExtendedAttribute( + "ReflectedHTMLAttributeReturningFrozenArray" + ) or self.member.type.isObservableArray() ) - isLazilyCachedInSlot = not isAlwaysInSlot + isLazilyCachedInSlot = ( + not isAlwaysInSlot + and not self.member.getExtendedAttribute( + "ReflectedHTMLAttributeReturningFrozenArray" + ) + ) slotIndex = memberReservedSlot(self.member, self.descriptor) # We'll statically assert that this is not too big in # CGUpdateMemberSlotsMethod, in the case when # isAlwaysInSlot is true. else: isLazilyCachedInSlot = False - slotIndex = "0" + slotIndex = None result = self.defineJitInfo( getterinfo, @@ -12000,7 +12050,7 @@ class CGMemberJITInfo(CGThing): "AliasEverything", False, False, - "0", + None, [BuiltinTypes[IDLBuiltinType.Types.undefined]], None, ) @@ -12071,7 +12121,7 @@ class CGMemberJITInfo(CGThing): aliasSet, False, False, - "0", + None, [s[0] for s in sigs], args, ) @@ -19830,6 +19880,14 @@ class CGExampleGetter(CGNativeMember): descriptor.getExtendedAttributes(attr, getter=True), ) + def getArgs(self, returnType, argList): + args = CGNativeMember.getArgs(self, returnType, argList) + if self.member.getExtendedAttribute( + "ReflectedHTMLAttributeReturningFrozenArray" + ): + args.insert(0, Argument("bool*", "aUseCachedValue")) + return args + def declare(self, cgClass): assert self.member.isAttr() # We skip declaring ourselves if this is a maplike/setlike attr (in diff --git a/dom/bindings/moz.build b/dom/bindings/moz.build index 03d0821ceadf..515351c669ae 100644 --- a/dom/bindings/moz.build +++ b/dom/bindings/moz.build @@ -145,6 +145,7 @@ if CONFIG["MOZ_DEBUG"] and CONFIG["ENABLE_TESTS"]: "test/TestInterfaceObservableArray.h", "test/TestInterfaceSetlike.h", "test/TestInterfaceSetlikeNode.h", + "test/TestReflectedHTMLAttribute.h", "test/TestTrialInterface.h", "test/WrapperCachedNonISupportsTestInterface.h", ] @@ -164,6 +165,7 @@ if CONFIG["MOZ_DEBUG"] and CONFIG["ENABLE_TESTS"]: "test/TestInterfaceObservableArray.cpp", "test/TestInterfaceSetlike.cpp", "test/TestInterfaceSetlikeNode.cpp", + "test/TestReflectedHTMLAttribute.cpp", "test/TestTrialInterface.cpp", "test/WrapperCachedNonISupportsTestInterface.cpp", ] diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index f9ac2cfc8a57..6f20086a4366 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -911,7 +911,7 @@ class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMix def setNonPartial(self, location, members): if self._isKnownNonPartial: raise WebIDLError( - "Two non-partial definitions for the " "same %s" % self.typeName(), + "Two non-partial definitions for the same %s" % self.typeName(), [location, self.location], ) self._isKnownNonPartial = True @@ -1019,14 +1019,14 @@ class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace): ) if member.isStatic(): raise WebIDLError( - "Interface mixin member cannot include " "a static member", + "Interface mixin member cannot include a static member", [member.location, self.location], ) if member.isMethod(): if member.isStatic(): raise WebIDLError( - "Interface mixin member cannot include " "a static operation", + "Interface mixin member cannot include a static operation", [member.location, self.location], ) if ( @@ -1036,7 +1036,7 @@ class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace): or member.isLegacycaller() ): raise WebIDLError( - "Interface mixin member cannot include a " "special operation", + "Interface mixin member cannot include a special operation", [member.location, self.location], ) @@ -1220,7 +1220,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): if m.isAttr() or m.isMethod(): if m.isStatic(): raise WebIDLError( - "Don't mark things explicitly static " "in namespaces", + "Don't mark things explicitly static in namespaces", [self.location, m.location], ) # Just mark all our methods/attributes as static. The other @@ -1241,7 +1241,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): # because ancestors of a [Global] interface can have other # descendants. raise WebIDLError( - "[Global] interface has another interface " "inheriting from it", + "[Global] interface has another interface inheriting from it", [self.location, self.parent.location], ) @@ -1446,6 +1446,9 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): member.getExtendedAttribute("StoreInSlot") or member.getExtendedAttribute("Cached") or member.type.isObservableArray() + or member.getExtendedAttribute( + "ReflectedHTMLAttributeReturningFrozenArray" + ) ) ) or member.isMaplikeOrSetlike(): if self.isJSImplemented() and not member.isMaplikeOrSetlike(): @@ -1587,7 +1590,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): # Make sure we're not [LegacyOverrideBuiltIns] if self.getExtendedAttribute("LegacyOverrideBuiltIns"): raise WebIDLError( - "Interface with [Global] also has " "[LegacyOverrideBuiltIns]", + "Interface with [Global] also has [LegacyOverrideBuiltIns]", [self.location], ) # Mark all of our ancestors as being on the global's proto chain too @@ -1735,7 +1738,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): ) if member.isStatic(): raise WebIDLError( - "[Alias] must not be used on a " "static operation", + "[Alias] must not be used on a static operation", [member.location], ) if member.isIdentifierLess(): @@ -1768,7 +1771,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): and not self.hasInterfaceObject() ): raise WebIDLError( - "Interface with no interface object is " "exposed conditionally", + "Interface with no interface object is exposed conditionally", [self.location], ) @@ -1787,7 +1790,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): if iterableDecl.valueType != indexedGetter.signatures()[0][0]: raise WebIDLError( - "Iterable type does not match indexed " "getter type", + "Iterable type does not match indexed getter type", [iterableDecl.location, indexedGetter.location], ) @@ -1801,7 +1804,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): assert iterableDecl.isPairIterator() if indexedGetter: raise WebIDLError( - "Interface with pair iterator supports " "indexed properties", + "Interface with pair iterator supports indexed properties", [self.location, iterableDecl.location, indexedGetter.location], ) @@ -2482,7 +2485,7 @@ class IDLEnum(IDLObjectWithIdentifier): def addExtendedAttributes(self, attrs): if len(attrs) != 0: raise WebIDLError( - "There are no extended attributes that are " "allowed on enums", + "There are no extended attributes that are allowed on enums", [attrs[0].location, self.location], ) @@ -3477,7 +3480,7 @@ class IDLTypedef(IDLObjectWithIdentifier): def addExtendedAttributes(self, attrs): if len(attrs) != 0: raise WebIDLError( - "There are no extended attributes that are " "allowed on typedefs", + "There are no extended attributes that are allowed on typedefs", [attrs[0].location, self.location], ) @@ -5464,14 +5467,17 @@ class IDLAttribute(IDLInterfaceMember): raise WebIDLError( "An attribute cannot be of a dictionary type", [self.location] ) - if self.type.isSequence() and not self.getExtendedAttribute("Cached"): + if self.type.isSequence() and not ( + self.getExtendedAttribute("Cached") + or self.getExtendedAttribute("ReflectedHTMLAttributeReturningFrozenArray") + ): raise WebIDLError( - "A non-cached attribute cannot be of a sequence " "type", + "A non-cached attribute cannot be of a sequence type", [self.location], ) if self.type.isRecord() and not self.getExtendedAttribute("Cached"): raise WebIDLError( - "A non-cached attribute cannot be of a record " "type", [self.location] + "A non-cached attribute cannot be of a record type", [self.location] ) if self.type.isUnion(): for f in self.type.unroll().flatMemberTypes: @@ -5605,6 +5611,39 @@ class IDLAttribute(IDLInterfaceMember): "record-valued attributes", [self.location], ) + if self.getExtendedAttribute("ReflectedHTMLAttributeReturningFrozenArray"): + if self.getExtendedAttribute("Cached") or self.getExtendedAttribute( + "StoreInSlot" + ): + raise WebIDLError( + "[ReflectedHTMLAttributeReturningFrozenArray] can't be combined " + "with [Cached] or [StoreInSlot]", + [self.location], + ) + if not self.type.isSequence(): + raise WebIDLError( + "[ReflectedHTMLAttributeReturningFrozenArray] is only allowed on " + "sequence-valued attributes", + [self.location], + ) + + def interfaceTypeIsOrInheritsFromElement(type): + return type.identifier.name == "Element" or ( + type.parent is not None + and interfaceTypeIsOrInheritsFromElement(type.parent) + ) + + sequenceMemberType = self.type.unroll() + if ( + not sequenceMemberType.isInterface() + or not interfaceTypeIsOrInheritsFromElement(sequenceMemberType.inner) + ): + raise WebIDLError( + "[ReflectedHTMLAttributeReturningFrozenArray] is only allowed on " + "sequence-valued attributes containing interface values of type " + "Element or an interface inheriting from Element", + [self.location], + ) if not self.type.unroll().isExposedInAllOf(self.exposureSet): raise WebIDLError( "Attribute returns a type that is not exposed " @@ -5614,7 +5653,7 @@ class IDLAttribute(IDLInterfaceMember): if self.getExtendedAttribute("CEReactions"): if self.readonly: raise WebIDLError( - "[CEReactions] is not allowed on " "readonly attributes", + "[CEReactions] is not allowed on readonly attributes", [self.location], ) @@ -5626,7 +5665,7 @@ class IDLAttribute(IDLInterfaceMember): or identifier == "SetterNeedsSubjectPrincipal" ) and self.readonly: raise WebIDLError( - "Readonly attributes must not be flagged as " "[%s]" % identifier, + "Readonly attributes must not be flagged as [%s]" % identifier, [self.location], ) elif identifier == "BindingAlias": @@ -5660,7 +5699,7 @@ class IDLAttribute(IDLInterfaceMember): ) if self.isStatic(): raise WebIDLError( - "[LegacyLenientThis] is only allowed on non-static " "attributes", + "[LegacyLenientThis] is only allowed on non-static attributes", [attr.location, self.location], ) if self.getExtendedAttribute("CrossOriginReadable"): @@ -5679,7 +5718,7 @@ class IDLAttribute(IDLInterfaceMember): elif identifier == "LegacyUnforgeable": if self.isStatic(): raise WebIDLError( - "[LegacyUnforgeable] is only allowed on non-static " "attributes", + "[LegacyUnforgeable] is only allowed on non-static attributes", [attr.location, self.location], ) self._legacyUnforgeable = True @@ -5696,17 +5735,17 @@ class IDLAttribute(IDLInterfaceMember): elif identifier == "PutForwards": if not self.readonly: raise WebIDLError( - "[PutForwards] is only allowed on readonly " "attributes", + "[PutForwards] is only allowed on readonly attributes", [attr.location, self.location], ) if self.type.isPromise(): raise WebIDLError( - "[PutForwards] is not allowed on " "Promise-typed attributes", + "[PutForwards] is not allowed on Promise-typed attributes", [attr.location, self.location], ) if self.isStatic(): raise WebIDLError( - "[PutForwards] is only allowed on non-static " "attributes", + "[PutForwards] is only allowed on non-static attributes", [attr.location, self.location], ) if self.getExtendedAttribute("Replaceable") is not None: @@ -5726,17 +5765,17 @@ class IDLAttribute(IDLInterfaceMember): ) if not self.readonly: raise WebIDLError( - "[Replaceable] is only allowed on readonly " "attributes", + "[Replaceable] is only allowed on readonly attributes", [attr.location, self.location], ) if self.type.isPromise(): raise WebIDLError( - "[Replaceable] is not allowed on " "Promise-typed attributes", + "[Replaceable] is not allowed on Promise-typed attributes", [attr.location, self.location], ) if self.isStatic(): raise WebIDLError( - "[Replaceable] is only allowed on non-static " "attributes", + "[Replaceable] is only allowed on non-static attributes", [attr.location, self.location], ) if self.getExtendedAttribute("PutForwards") is not None: @@ -5752,7 +5791,7 @@ class IDLAttribute(IDLInterfaceMember): ) if not self.readonly: raise WebIDLError( - "[LegacyLenientSetter] is only allowed on readonly " "attributes", + "[LegacyLenientSetter] is only allowed on readonly attributes", [attr.location, self.location], ) if self.type.isPromise(): @@ -5763,7 +5802,7 @@ class IDLAttribute(IDLInterfaceMember): ) if self.isStatic(): raise WebIDLError( - "[LegacyLenientSetter] is only allowed on non-static " "attributes", + "[LegacyLenientSetter] is only allowed on non-static attributes", [attr.location, self.location], ) if self.getExtendedAttribute("PutForwards") is not None: @@ -5811,7 +5850,7 @@ class IDLAttribute(IDLInterfaceMember): ) if self.isStatic(): raise WebIDLError( - "[%s] is only allowed on non-static " "attributes" % identifier, + "[%s] is only allowed on non-static attributes" % identifier, [attr.location, self.location], ) if self.getExtendedAttribute("LegacyLenientThis"): @@ -5855,7 +5894,7 @@ class IDLAttribute(IDLInterfaceMember): elif identifier == "UseCounter": if self.stringifier: raise WebIDLError( - "[UseCounter] must not be used on a " "stringifier attribute", + "[UseCounter] must not be used on a stringifier attribute", [attr.location, self.location], ) elif identifier == "Unscopable": @@ -5896,6 +5935,7 @@ class IDLAttribute(IDLInterfaceMember): or identifier == "BinaryName" or identifier == "NonEnumerable" or identifier == "BindingTemplate" + or identifier == "ReflectedHTMLAttributeReturningFrozenArray" ): # Known attributes that we don't need to do anything with here pass @@ -6732,7 +6772,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): # Make sure either all our overloads return Promises or none do if overloadWithPromiseReturnType and overloadWithoutPromiseReturnType: raise WebIDLError( - "We have overloads with both Promise and " "non-Promise return types", + "We have overloads with both Promise and non-Promise return types", [ overloadWithPromiseReturnType.location, overloadWithoutPromiseReturnType.location, @@ -6741,7 +6781,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): if overloadWithPromiseReturnType and self._legacycaller: raise WebIDLError( - "May not have a Promise return type for a " "legacycaller.", + "May not have a Promise return type for a legacycaller.", [overloadWithPromiseReturnType.location], ) @@ -6834,13 +6874,13 @@ class IDLMethod(IDLInterfaceMember, IDLScope): or identifier == "GetterNeedsSubjectPrincipal" ): raise WebIDLError( - "Methods must not be flagged as " "[%s]" % identifier, + "Methods must not be flagged as [%s]" % identifier, [attr.location, self.location], ) elif identifier == "LegacyUnforgeable": if self.isStatic(): raise WebIDLError( - "[LegacyUnforgeable] is only allowed on non-static " "methods", + "[LegacyUnforgeable] is only allowed on non-static methods", [attr.location, self.location], ) self._legacyUnforgeable = True @@ -6891,7 +6931,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): ) if identifier == "CrossOriginCallable" and self.isStatic(): raise WebIDLError( - "[CrossOriginCallable] is only allowed on non-static " "attributes", + "[CrossOriginCallable] is only allowed on non-static attributes", [attr.location, self.location], ) elif identifier == "Pure": @@ -6916,7 +6956,7 @@ class IDLMethod(IDLInterfaceMember, IDLScope): elif identifier == "UseCounter": if self.isSpecial(): raise WebIDLError( - "[UseCounter] must not be used on a special " "operation", + "[UseCounter] must not be used on a special operation", [attr.location, self.location], ) elif identifier == "Unscopable": @@ -7096,17 +7136,17 @@ class IDLIncludesStatement(IDLObject): # locations. if not isinstance(interface, IDLInterface): raise WebIDLError( - "Left-hand side of 'includes' is not an " "interface", + "Left-hand side of 'includes' is not an interface", [self.interface.location, interface.location], ) if interface.isCallback(): raise WebIDLError( - "Left-hand side of 'includes' is a callback " "interface", + "Left-hand side of 'includes' is a callback interface", [self.interface.location, interface.location], ) if not isinstance(mixin, IDLInterfaceMixin): raise WebIDLError( - "Right-hand side of 'includes' is not an " "interface mixin", + "Right-hand side of 'includes' is not an interface mixin", [self.mixin.location, mixin.location], ) @@ -8864,7 +8904,7 @@ class Parser(Tokenizer): if p[1].name == "Promise": raise WebIDLError( - "Promise used without saying what it's " "parametrized over", + "Promise used without saying what it's parametrized over", [self.getLocation(p, 1)], ) diff --git a/dom/bindings/parser/tests/test_reflected_attribute.py b/dom/bindings/parser/tests/test_reflected_attribute.py new file mode 100644 index 000000000000..9b4f1d3bd985 --- /dev/null +++ b/dom/bindings/parser/tests/test_reflected_attribute.py @@ -0,0 +1,173 @@ +import WebIDL + + +def WebIDLTest(parser, harness): + def parseWithNode(test): + parser.parse( + """ + interface Node {}; + interface Document : Node {}; + interface Element : Node {}; + interface HTMLElement : Element {}; + """ + + test + ) + + def parseFrozenArrayAttribute(innerType): + parseWithNode( + """ + interface ReflectedAttribute { + [Frozen, ReflectedHTMLAttributeReturningFrozenArray] + attribute sequence<%s>? reflectedHTMLAttribute; + }; + """ + % innerType + ) + + results = parser.finish() + + harness.check(len(results), 5, "Should know about one thing") + harness.ok( + isinstance(results[4], WebIDL.IDLInterface), "Should have an interface here" + ) + members = results[4].members + harness.check(len(members), 1, "Should have one member") + harness.ok(members[0].isAttr(), "Should have attribute") + harness.ok( + members[0].getExtendedAttribute( + "ReflectedHTMLAttributeReturningFrozenArray" + ) + is not None, + "Should have extended attribute", + ) + + parseFrozenArrayAttribute("Element") + + parser = parser.reset() + parseFrozenArrayAttribute("HTMLElement") + + parser = parser.reset() + threw = False + try: + parseWithNode( + """ + interface ReflectedAttribute { + [ReflectedHTMLAttributeReturningFrozenArray] + attribute Element? reflectedHTMLAttribute; + }; + """ + ) + + parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should have thrown because [ReflectedHTMLAttributeReturningFrozenArray] " + "should only be used on attributes with a sequence<*Element> type.", + ) + + parser = parser.reset() + threw = False + try: + parseWithNode( + """ + interface ReflectedAttribute { + [Frozen, ReflectedHTMLAttributeReturningFrozenArray] + attribute sequence reflectedHTMLAttribute; + }; + """ + ) + + parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should have thrown because [ReflectedHTMLAttributeReturningFrozenArray] " + "should only be used on attributes with a sequence<*Element> type.", + ) + + parser = parser.reset() + threw = False + try: + parseWithNode( + """ + interface ReflectedAttribute { + [Frozen, ReflectedHTMLAttributeReturningFrozenArray] + attribute sequence reflectedHTMLAttribute; + }; + """ + ) + + parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should have thrown because [ReflectedHTMLAttributeReturningFrozenArray] " + "should only be used on attributes with a sequence<*Element> type.", + ) + + parser = parser.reset() + threw = False + try: + parseWithNode( + """ + interface ReflectedAttribute { + [ReflectedHTMLAttributeReturningFrozenArray] + sequence? reflectedHTMLAttribute(); + }; + """ + ) + + parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should have thrown because [ReflectedHTMLAttributeReturningFrozenArray] " + "should only be used on attributes with a sequence<*Element> type.", + ) + + parser = parser.reset() + threw = False + try: + parseWithNode( + """ + interface ReflectedAttribute { + [Frozen, ReflectedHTMLAttributeReturningFrozenArray, Cached, Pure] + attribute sequence? reflectedHTMLAttribute; + }; + """ + ) + + parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should have thrown because [ReflectedHTMLAttributeReturningFrozenArray] " + "should not be used together with [Cached].", + ) + + parser = parser.reset() + threw = False + try: + parseWithNode( + """ + interface ReflectedAttribute { + [Frozen, ReflectedHTMLAttributeReturningFrozenArray, StoreInSlot, Pure] + attribute sequence? reflectedHTMLAttribute; + }; + """ + ) + + parser.finish() + except WebIDL.WebIDLError: + threw = True + harness.ok( + threw, + "Should have thrown because [ReflectedHTMLAttributeReturningFrozenArray] " + "should not be used together with [Cached].", + ) diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 2e8c4967581f..26ff212b8091 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -1152,6 +1152,11 @@ class TestInterface : public nsISupports, public nsWrapperCache { void PassUnionAllowSharedArrayBuffer( const StringOrMaybeSharedArrayBuffer& foo); + void GetReflectedHTMLAttributeReturningFrozenArray( + bool*, Nullable>>&) const; + void SetReflectedHTMLAttributeReturningFrozenArray( + const Nullable>>&); + private: // We add signatures here that _could_ start matching if the codegen // got data types wrong. That way if it ever does we'll have a call diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index e7e66c4a3cdf..a364584523c4 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -1083,6 +1083,9 @@ interface TestInterface { undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo); undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo); + [Frozen, ReflectedHTMLAttributeReturningFrozenArray] + attribute sequence? reflectedHTMLAttributeReturningFrozenArray; + // If you add things here, add them to TestExampleGen as well }; diff --git a/dom/bindings/test/TestExampleGen.webidl b/dom/bindings/test/TestExampleGen.webidl index 65e9840cec72..85e3d026c3ac 100644 --- a/dom/bindings/test/TestExampleGen.webidl +++ b/dom/bindings/test/TestExampleGen.webidl @@ -870,6 +870,9 @@ interface TestExampleInterface { undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo); undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo); + [Frozen, ReflectedHTMLAttributeReturningFrozenArray] + attribute sequence? reflectedHTMLAttributeReturningFrozenArray; + // If you add things here, add them to TestExampleGen. If they need to be // supported in JS-implemented WebIDL then you need to add them to // TestJSImplGen as well, if they are not supported in JS-implemented WebIDL diff --git a/dom/bindings/test/TestReflectedHTMLAttribute.cpp b/dom/bindings/test/TestReflectedHTMLAttribute.cpp new file mode 100644 index 000000000000..b2a73788cceb --- /dev/null +++ b/dom/bindings/test/TestReflectedHTMLAttribute.cpp @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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/TestReflectedHTMLAttribute.h" +#include "mozilla/dom/TestFunctionsBinding.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TestReflectedHTMLAttribute, + mCachedElements, mNewElements) + +/* static */ +already_AddRefed +TestReflectedHTMLAttribute::Constructor(GlobalObject& aGlobal) { + return MakeAndAddRef(); +} + +template +static void AssignElements(const ArrayLike& aFrom, + Nullable>>& aTo) { + if (aTo.IsNull()) { + aTo.SetValue(); + } else { + aTo.Value().Clear(); + } + aTo.Value().AppendElements(aFrom); +} + +void TestReflectedHTMLAttribute::GetReflectedHTMLAttribute( + bool* aUseCachedValue, Nullable>>& aResult) { + if (aUseCachedValue) { + if (mCachedElements == mNewElements) { + *aUseCachedValue = true; + return; + } + + *aUseCachedValue = false; + } + + if (mNewElements.IsNull()) { + mCachedElements.SetNull(); + aResult.SetNull(); + } else { + AssignElements(mNewElements.Value(), mCachedElements); + aResult.SetValue(mCachedElements.Value().Clone()); + } +} + +void TestReflectedHTMLAttribute::SetReflectedHTMLAttribute( + const Nullable>>& aValue) { + // We're just testing getters, so do nothing. But this would either clear or + // set the "explicitly set attr-elements". +} + +void TestReflectedHTMLAttribute::SetReflectedHTMLAttributeValue( + const Sequence>& aElements) { + AssignElements(aElements, mNewElements); +} + +JSObject* TestReflectedHTMLAttribute::WrapObject( + JSContext* aCx, JS::Handle aGivenProto) { + return TestReflectedHTMLAttribute_Binding::Wrap(aCx, this, aGivenProto); +} + +} // namespace mozilla::dom diff --git a/dom/bindings/test/TestReflectedHTMLAttribute.h b/dom/bindings/test/TestReflectedHTMLAttribute.h new file mode 100644 index 000000000000..cd5274f864f6 --- /dev/null +++ b/dom/bindings/test/TestReflectedHTMLAttribute.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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_TestReflectedHTMLAttribute_h +#define mozilla_dom_TestReflectedHTMLAttribute_h + +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Nullable.h" +#include "nsWrapperCache.h" + +namespace mozilla::dom { + +class TestReflectedHTMLAttribute final : public nsWrapperCache { + public: + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(TestReflectedHTMLAttribute) + NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(TestReflectedHTMLAttribute) + + static already_AddRefed Constructor( + GlobalObject& aGlobal); + + void GetReflectedHTMLAttribute(bool* aUseCachedValue, + Nullable>>& aResult); + void SetReflectedHTMLAttribute( + const Nullable>>& aValue); + void SetReflectedHTMLAttributeValue( + const Sequence>& aElements); + + nsISupports* GetParentObject() const { return nullptr; } + JSObject* WrapObject(JSContext* aCx, + JS::Handle aGivenProto) override; + + private: + ~TestReflectedHTMLAttribute() = default; + + Nullable>> mCachedElements; + Nullable>> mNewElements; +}; + +} // namespace mozilla::dom + +#endif // mozilla_dom_TestReflectedHTMLAttribute_h diff --git a/dom/bindings/test/chrome.toml b/dom/bindings/test/chrome.toml index 0986974ece6b..c149fbe89e19 100644 --- a/dom/bindings/test/chrome.toml +++ b/dom/bindings/test/chrome.toml @@ -9,7 +9,7 @@ support-files = [ ["test_bug775543.html"] ["test_bug1123516_maplikesetlikechrome.xhtml"] -skip-if = ["!debug"] # TestFunctions is only available in debug builds +skip-if = ["!debug"] # Test WebIDL interfaces are only available in debug builds ["test_bug1287912.html"] @@ -20,9 +20,12 @@ skip-if = ["!debug"] # TestFunctions is only available in debug builds ["test_document_location_via_xray_cached.html"] ["test_dom_xrays.html"] -skip-if = ["debug == false"] # TestFunctions is only available in debug builds +support-files = [ + "file_reflected_attribute_frozenarray.js", +] +skip-if = ["!debug"] # Test WebIDL interfaces are only available in debug builds ["test_interfaceLength_chrome.html"] -skip-if = ["debug == false"] # TestFunctions is only available in debug builds +skip-if = ["!debug"] # Test WebIDL interfaces are only available in debug builds ["test_proxies_via_xray.html"] diff --git a/dom/bindings/test/file_reflected_attribute_frozenarray.js b/dom/bindings/test/file_reflected_attribute_frozenarray.js new file mode 100644 index 000000000000..03513aed2bdb --- /dev/null +++ b/dom/bindings/test/file_reflected_attribute_frozenarray.js @@ -0,0 +1,90 @@ +function checkEquals(value, expected, valueCheckFn = (a, b) => a == b) { + if (!valueCheckFn(value, expected)) { + return `, got ${value}, expected ${expected}`; + } + return undefined; +} + +function checkReflectedAttributeWithFrozenArrayValues(obj, values, valueCheck) { + if (!SimpleTest.isa(obj.reflectedHTMLAttribute, "Array")) { + return `, expected array`; + } + let failure = checkEquals(obj.reflectedHTMLAttribute.length, values.length); + if (!failure) { + for (let [i, v] of obj.reflectedHTMLAttribute.entries()) { + failure = checkEquals(values[i], v, valueCheck); + if (failure) { + break; + } + } + } + return failure; +} + +function checkReflectedAttributeWithFrozenArray( + obj, + values, + suffix, + valueCheck +) { + let failure = checkReflectedAttributeWithFrozenArrayValues( + obj, + values, + valueCheck + ); + ok( + !failure, + `Cached value on object for HTML reflected FrozenArray attribute should contain the right values ${suffix}${ + failure || "" + }` + ); +} + +function testReflectedAttributeWithFrozenArray(win) { + let testObject = new win.TestReflectedHTMLAttribute(); + ok( + testObject instanceof win.TestReflectedHTMLAttribute, + "Got a TestReflectedHTMLAttribute object" + ); + + is( + testObject.reflectedHTMLAttribute, + null, + "Initial value for HTML reflected FrozenArray attribute should be null" + ); + + let values = [win.document.head]; + testObject.setReflectedHTMLAttributeValue(values); + checkReflectedAttributeWithFrozenArray(testObject, values, "after setting"); + + values = [win.document.body, win.document.body.firstElementChild]; + testObject.setReflectedHTMLAttributeValue(values); + checkReflectedAttributeWithFrozenArray(testObject, values, "after resetting"); + + // Use a loop to ensure the JITs optimize the getter access. + let failure; + for (let i = 0; i < 10_000; i++) { + failure = checkReflectedAttributeWithFrozenArrayValues(testObject, values); + if (!failure) { + break; + } + if (i == 9_990) { + values = [win.document.head]; + testObject.setReflectedHTMLAttributeValue(values); + } + } + ok( + !failure, + `Shouldn't use the cached value for HTML reflected FrozenArray attribute directly from JITted code${ + failure || "" + }` + ); + + is( + testObject.reflectedHTMLAttribute, + testObject.reflectedHTMLAttribute, + "Getter for HTML reflected FrozenArray attribute should return the cached value" + ); + + return [testObject, values]; +} diff --git a/dom/bindings/test/mochitest.toml b/dom/bindings/test/mochitest.toml index bf3df97da81a..d5840cb79f84 100644 --- a/dom/bindings/test/mochitest.toml +++ b/dom/bindings/test/mochitest.toml @@ -168,8 +168,20 @@ skip-if = ["!debug"] ["test_proxy_missing_prop.html"] +["test_reflected_attribute_frozenarray.html"] +support-files = [ + "file_reflected_attribute_frozenarray.js", +] +skip-if = ["!debug"] # Test WebIDL interfaces are only available in debug builds + ["test_remoteProxyAsPrototype.html"] +["test_resizable_arraybufferview.html"] +skip-if = [ + "!debug", + "!nightly_build", # Bug 1670026 +] + ["test_returnUnion.html"] skip-if = ["!debug"] @@ -199,9 +211,3 @@ skip-if = ["!debug"] skip-if = ["!debug"] ["test_worker_UnwrapArg.html"] - -["test_resizable_arraybufferview.html"] -skip-if = [ - "!debug", - "!nightly_build", # Bug 1670026 -] diff --git a/dom/bindings/test/test_dom_xrays.html b/dom/bindings/test/test_dom_xrays.html index eafa0c632c57..023cf28724ee 100644 --- a/dom/bindings/test/test_dom_xrays.html +++ b/dom/bindings/test/test_dom_xrays.html @@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=787070 Test for Bug 787070 + @@ -390,6 +391,11 @@ function test() { "Calling an instance object for an interface marked with legacycaller shouldn't throw"); checkXrayProperty(doc.all, 0, [ element ]); + let [ testObject, expectedValues ] = testReflectedAttributeWithFrozenArray(win); + checkReflectedAttributeWithFrozenArray(testObject.wrappedJSObject, expectedValues, + "on Xray and object", + (a, b) => a.wrappedJSObject == b); + SimpleTest.finish(); } diff --git a/dom/bindings/test/test_reflected_attribute_frozenarray.html b/dom/bindings/test/test_reflected_attribute_frozenarray.html new file mode 100644 index 000000000000..52c256138ee4 --- /dev/null +++ b/dom/bindings/test/test_reflected_attribute_frozenarray.html @@ -0,0 +1,24 @@ + + + + +Test for bug 1773732 + + + + + + + + diff --git a/dom/docs/webIdlBindings/index.md b/dom/docs/webIdlBindings/index.md index 5bce1bc7ba18..53ac07d863f5 100644 --- a/dom/docs/webIdlBindings/index.md +++ b/dom/docs/webIdlBindings/index.md @@ -1987,6 +1987,54 @@ lexicographic order (which is specified by WebIDL). This should only ever be used on internal APIs that are not exposed to the Web! +### `[ReflectedHTMLAttributeReturningFrozenArray]` + +Used to flag a HTML reflected IDL attribute as having a `FrozenArray?` type, +where `T` is either `Element` or an interface that inherits from `Element`. This +should only be used to implement the algorithms for that kind of reflected IDL +attributes. + +When this attribute's getter is called, it will cache the JS reflection of the +returned value on the JS object. The C++ getter will be passed an additional +`bool*` argument before the result argument. If this argument is not `null` then +the implementation is supposed to set the `bool` that this argument is pointing +to to whether the +[attr-associated elements](https://html.spec.whatwg.org/#attr-associated-elements) +and the +[cached attr-associated elements](https://html.spec.whatwg.org/#cached-attr-associated-elements) +are equal. If it is not `null`, and the getter sets the pointee to `true` then +the cached JS value will be returned, and the result value from the C++ getter +will be ignored (so there is no need to set the result's value). If it is +`null`, or the getter sets the pointee to `false`, then the cached value will be +set to the JS reflection of the result value from the C++ getter, and that JS +value will then be returned. + +Note that this will not cause the JIT to directly get the cached value from the +slot (as `[StoreInSlot]` or `[Cached]` would). The setter will also not clear +the cached value from the slot. + +For example, this IDL: + +``` webidl +interface Element { + [Frozen, ReflectedHTMLAttributeReturningFrozenArray] + attribute sequence? reflectedHTMLAttribute; +}; +``` + +will require the following declarations in `Element`: + +``` cpp +class Element { + // … + void GetReflectedHTMLAttribute( + bool* aUseCachedValue, Nullable>>& aResult); + void SetReflectedHTMLAttribute( + const Nullable>>& aValue); +}; +``` + + ## Helper objects The C++ side of the bindings uses a number of helper objects. diff --git a/dom/webidl/TestFunctions.webidl b/dom/webidl/TestFunctions.webidl index fcc52016a154..c5de19bf33c5 100644 --- a/dom/webidl/TestFunctions.webidl +++ b/dom/webidl/TestFunctions.webidl @@ -146,6 +146,16 @@ interface TestFunctions { static boolean staticAndNonStaticOverload(optional unsigned long foo); }; +[Pref="dom.expose_test_interfaces", + Exposed=Window] +interface TestReflectedHTMLAttribute { + constructor(); + + [Frozen, ReflectedHTMLAttributeReturningFrozenArray] + attribute sequence? reflectedHTMLAttribute; + undefined setReflectedHTMLAttributeValue(sequence seq); +}; + dictionary DictWithAllowSharedBufferSource { ArrayBuffer arrayBuffer; ArrayBufferView arrayBufferView;