Bug 1891784 - Support HTML reflected attributes returning FrozenArray. r=edgar

Differential Revision: https://phabricator.services.mozilla.com/D207754
This commit is contained in:
Peter Van der Beken 2024-07-10 10:09:11 +00:00
parent 2a61918bb1
commit a91991024d
16 changed files with 658 additions and 74 deletions

View File

@ -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<JS::Value> 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

View File

@ -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",
]

View File

@ -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)],
)

View File

@ -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<long> 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<Document> 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<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, Cached, Pure]
attribute sequence<Element>? 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<Element>? reflectedHTMLAttribute;
};
"""
)
parser.finish()
except WebIDL.WebIDLError:
threw = True
harness.ok(
threw,
"Should have thrown because [ReflectedHTMLAttributeReturningFrozenArray] "
"should not be used together with [Cached].",
)

View File

@ -1152,6 +1152,11 @@ class TestInterface : public nsISupports, public nsWrapperCache {
void PassUnionAllowSharedArrayBuffer(
const StringOrMaybeSharedArrayBuffer& foo);
void GetReflectedHTMLAttributeReturningFrozenArray(
bool*, Nullable<nsTArray<RefPtr<Element>>>&) const;
void SetReflectedHTMLAttributeReturningFrozenArray(
const Nullable<Sequence<OwningNonNull<Element>>>&);
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

View File

@ -1083,6 +1083,9 @@ interface TestInterface {
undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo);
undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo);
[Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? reflectedHTMLAttributeReturningFrozenArray;
// If you add things here, add them to TestExampleGen as well
};

View File

@ -870,6 +870,9 @@ interface TestExampleInterface {
undefined passUnionArrayBuffer((DOMString or ArrayBuffer) foo);
undefined passUnionAllowSharedArrayBuffer((DOMString or [AllowShared] ArrayBuffer) foo);
[Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? 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

View File

@ -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>
TestReflectedHTMLAttribute::Constructor(GlobalObject& aGlobal) {
return MakeAndAddRef<TestReflectedHTMLAttribute>();
}
template <typename ArrayLike>
static void AssignElements(const ArrayLike& aFrom,
Nullable<nsTArray<RefPtr<Element>>>& aTo) {
if (aTo.IsNull()) {
aTo.SetValue();
} else {
aTo.Value().Clear();
}
aTo.Value().AppendElements(aFrom);
}
void TestReflectedHTMLAttribute::GetReflectedHTMLAttribute(
bool* aUseCachedValue, Nullable<nsTArray<RefPtr<Element>>>& 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<Sequence<OwningNonNull<Element>>>& 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<OwningNonNull<Element>>& aElements) {
AssignElements(aElements, mNewElements);
}
JSObject* TestReflectedHTMLAttribute::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return TestReflectedHTMLAttribute_Binding::Wrap(aCx, this, aGivenProto);
}
} // namespace mozilla::dom

View File

@ -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<TestReflectedHTMLAttribute> Constructor(
GlobalObject& aGlobal);
void GetReflectedHTMLAttribute(bool* aUseCachedValue,
Nullable<nsTArray<RefPtr<Element>>>& aResult);
void SetReflectedHTMLAttribute(
const Nullable<Sequence<OwningNonNull<Element>>>& aValue);
void SetReflectedHTMLAttributeValue(
const Sequence<OwningNonNull<Element>>& aElements);
nsISupports* GetParentObject() const { return nullptr; }
JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;
private:
~TestReflectedHTMLAttribute() = default;
Nullable<nsTArray<RefPtr<Element>>> mCachedElements;
Nullable<nsTArray<RefPtr<Element>>> mNewElements;
};
} // namespace mozilla::dom
#endif // mozilla_dom_TestReflectedHTMLAttribute_h

View File

@ -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"]

View File

@ -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];
}

View File

@ -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
]

View File

@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=787070
<meta charset="utf-8">
<title>Test for Bug 787070</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="file_reflected_attribute_frozenarray.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
</head>
<body>
@ -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();
}

View File

@ -0,0 +1,24 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<title>Test for bug 1773732</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="file_reflected_attribute_frozenarray.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<script>
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({ set: [["dom.expose_test_interfaces", true]] }, () => {
testReflectedAttributeWithFrozenArray(window)
SimpleTest.finish();
});
</script>
</body>
</html>

View File

@ -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<T>?` 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<Element>? reflectedHTMLAttribute;
};
```
will require the following declarations in `Element`:
``` cpp
class Element {
// …
void GetReflectedHTMLAttribute(
bool* aUseCachedValue, Nullable<nsTArray<RefPtr<Element>>>& aResult);
void SetReflectedHTMLAttribute(
const Nullable<Sequence<OwningNonNull<Element>>>& aValue);
};
```
## Helper objects
The C++ side of the bindings uses a number of helper objects.

View File

@ -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<Element>? reflectedHTMLAttribute;
undefined setReflectedHTMLAttributeValue(sequence<Element> seq);
};
dictionary DictWithAllowSharedBufferSource {
ArrayBuffer arrayBuffer;
ArrayBufferView arrayBufferView;