Skip to content

Commit 04d104d

Browse files
committed
Reflection for FrozenArray<Element> caching invariant
https://bugs.webkit.org/show_bug.cgi?id=240563 Reviewed by Ryosuke Niwa. This patch implements the caching layer described in the spec PR for reflection of FrozenArray<T> attributes: whatwg/html#3917 Which fixes the test cases that were checking for: el.ariaDescribedByElements === el.ariaDescribedByElements This patch stores a new JSObject in the JSElement using a PrivateName. Then for each attribute we store the cached JSValue in the JSObject. If the cached JSValue matches the current Vector of Elements that we're going to return, we return the cached JSValue instead. * LayoutTests/imported/w3c/web-platform-tests/dom/nodes/aria-element-reflection.tentative-expected.txt: Update expectations. * LayoutTests/imported/w3c/web-platform-tests/dom/nodes/aria-element-reflection.tentative.html: Add new test cases. * Source/WebCore/accessibility/AriaAttributes.idl: Add CustomGetter for FrozenArray<Element> reflection. * Source/WebCore/bindings/js/JSElementCustom.cpp: (WebCore::getElementsArrayAttribute): New method that implements the caching invariant. (WebCore::JSElement::ariaControlsElements const): Custom getter that calls getElementsArrayAttribute(). (WebCore::JSElement::ariaDescribedByElements const): Ditto. (WebCore::JSElement::ariaDetailsElements const): Ditto. (WebCore::JSElement::ariaFlowToElements const): Ditto. (WebCore::JSElement::ariaLabelledByElements const): Ditto. (WebCore::JSElement::ariaOwnsElements const): Ditto. * Source/WebCore/bindings/js/WebCoreBuiltinNames.h: New built-in PrivateName. Canonical link: https://commits.webkit.org/251237@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@295148 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent 279b6e7 commit 04d104d

File tree

5 files changed

+120
-7
lines changed

5 files changed

+120
-7
lines changed

LayoutTests/imported/w3c/web-platform-tests/dom/nodes/aria-element-reflection.tentative-expected.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ PASS Changing the ID of an element causes the content attribute to become out of
5050
PASS Reparenting an element into a descendant shadow scope hides the element reference.
5151
PASS Reparenting referenced element cannot cause retargeting of reference.
5252
PASS Element reference set in invalid scope remains intact throughout move to valid scope.
53-
FAIL aria-labelledby. assert_equals: check idl attribute caching after parsing expected [Element node <div id="billingElement">Billing</div>, Element node <div id="nameElement">Name</div>] but got [Element node <div id="billingElement">Billing</div>, Element node <div id="nameElement">Name</div>]
53+
PASS aria-labelledby.
5454
PASS aria-controls.
5555
PASS aria-describedby.
5656
PASS aria-flowto.
@@ -62,4 +62,6 @@ PASS Reparenting.
6262
PASS Attaching element reference before it's inserted into the DOM.
6363
PASS Cross-document references and moves.
6464
PASS Adopting element keeps references.
65+
PASS Caching invariant different attributes.
66+
PASS Caching invariant different elements.
6567

LayoutTests/imported/w3c/web-platform-tests/dom/nodes/aria-element-reflection.tentative.html

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,54 @@ <h2 id="lightDomHeading" aria-flowto="shadowChild1 shadowChild2">Light DOM Headi
730730
}, "Adopting element keeps references.");
731731
</script>
732732

733+
<div id="cachingInvariantMain"></div>
734+
<div id="cachingInvariantElement1"></div>
735+
<div id="cachingInvariantElement2"></div>
736+
<div id="cachingInvariantElement3"></div>
737+
<div id="cachingInvariantElement4"></div>
738+
<div id="cachingInvariantElement5"></div>
739+
740+
<script>
741+
test(function(t) {
742+
cachingInvariantMain.ariaControlsElements = [cachingInvariantElement1, cachingInvariantElement2];
743+
cachingInvariantMain.ariaDescribedByElements = [cachingInvariantElement3, cachingInvariantElement4];
744+
cachingInvariantMain.ariaDetailsElements = [cachingInvariantElement5];
745+
cachingInvariantMain.ariaFlowToElements = [cachingInvariantElement1, cachingInvariantElement3];
746+
cachingInvariantMain.ariaLabelledByElements = [cachingInvariantElement2, cachingInvariantElement4];
747+
cachingInvariantMain.ariaOwnsElements = [cachingInvariantElement1, cachingInvariantElement2, cachingInvariantElement3];
748+
749+
let ariaControlsElementsArray = cachingInvariantMain.ariaControlsElements;
750+
let ariaDescribedByElementsArray = cachingInvariantMain.ariaDescribedByElements;
751+
let ariaDetailsElementsArray = cachingInvariantMain.ariaDetailsElements;
752+
let ariaFlowToElementsArray = cachingInvariantMain.ariaFlowToElements;
753+
let ariaLabelledByElementsArray = cachingInvariantMain.ariaLabelledByElements;
754+
let ariaOwnsElementsArray = cachingInvariantMain.ariaOwnsElements;
755+
756+
assert_equals(ariaControlsElementsArray, cachingInvariantMain.ariaControlsElements, "Caching invariant for ariaControlsElements");
757+
assert_equals(ariaDescribedByElementsArray, cachingInvariantMain.ariaDescribedByElements, "Caching invariant for ariaDescribedByElements");
758+
assert_equals(ariaDetailsElementsArray, cachingInvariantMain.ariaDetailsElements, "Caching invariant for ariaDetailsElements");
759+
assert_equals(ariaFlowToElementsArray, cachingInvariantMain.ariaFlowToElements, "Caching invariant for ariaFlowToElements");
760+
assert_equals(ariaLabelledByElementsArray, cachingInvariantMain.ariaLabelledByElements, "Caching invariant for ariaLabelledByElements");
761+
assert_equals(ariaOwnsElementsArray, cachingInvariantMain.ariaOwnsElements, "Caching invariant for ariaOwnsElements");
762+
}, "Caching invariant different attributes.");
763+
</script>
764+
765+
<div id="cachingInvariantMain1"></div>
766+
<div id="cachingInvariantMain2"></div>
767+
768+
<script>
769+
test(function(t) {
770+
cachingInvariantMain1.ariaDescribedByElements = [cachingInvariantElement1, cachingInvariantElement2];
771+
cachingInvariantMain2.ariaDescribedByElements = [cachingInvariantElement3, cachingInvariantElement4];
772+
773+
let ariaDescribedByElementsArray1 = cachingInvariantMain1.ariaDescribedByElements;
774+
let ariaDescribedByElementsArray2 = cachingInvariantMain2.ariaDescribedByElements;
775+
776+
assert_equals(ariaDescribedByElementsArray1, cachingInvariantMain1.ariaDescribedByElements, "Caching invariant for ariaDescribedByElements in one elemnt");
777+
assert_equals(ariaDescribedByElementsArray2, cachingInvariantMain2.ariaDescribedByElements, "Caching invariant for ariaDescribedByElements in onother elemnt");
778+
}, "Caching invariant different elements.");
779+
</script>
780+
733781
<!-- TODO(chrishall): add additional GC test covering:
734782
if an element is in an invalid scope but attached to the document, it's
735783
not GC'd;

Source/WebCore/accessibility/AriaAttributes.idl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,27 +33,27 @@ interface mixin AriaAttributes {
3333
[Reflect=aria_colcount] attribute DOMString? ariaColCount;
3434
[Reflect=aria_colindex] attribute DOMString? ariaColIndex;
3535
[Reflect=aria_colspan] attribute DOMString? ariaColSpan;
36-
[Reflect=aria_controls, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaControlsElements;
36+
[CustomGetter, Reflect=aria_controls, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaControlsElements;
3737
[Reflect=aria_current] attribute DOMString? ariaCurrent;
38-
[Reflect=aria_describedby, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaDescribedByElements;
39-
[Reflect=aria_details, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaDetailsElements;
38+
[CustomGetter, Reflect=aria_describedby, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaDescribedByElements;
39+
[CustomGetter, Reflect=aria_details, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaDetailsElements;
4040
[Reflect=aria_disabled] attribute DOMString? ariaDisabled;
4141
[Reflect=aria_errormessage, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute Element? ariaErrorMessageElement;
4242
[Reflect=aria_expanded] attribute DOMString? ariaExpanded;
43-
[Reflect=aria_flowto, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaFlowToElements;
43+
[CustomGetter, Reflect=aria_flowto, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaFlowToElements;
4444
[Reflect=aria_haspopup] attribute DOMString? ariaHasPopup;
4545
[Reflect=aria_hidden] attribute DOMString? ariaHidden;
4646
[Reflect=aria_invalid] attribute DOMString? ariaInvalid;
4747
[Reflect=aria_keyshortcuts] attribute DOMString? ariaKeyShortcuts;
4848
[Reflect=aria_label] attribute DOMString? ariaLabel;
49-
[Reflect=aria_labelledby, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaLabelledByElements;
49+
[CustomGetter, Reflect=aria_labelledby, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaLabelledByElements;
5050
[Reflect=aria_level] attribute DOMString? ariaLevel;
5151
[Reflect=aria_live] attribute DOMString? ariaLive;
5252
[Reflect=aria_modal] attribute DOMString? ariaModal;
5353
[Reflect=aria_multiline] attribute DOMString? ariaMultiLine;
5454
[Reflect=aria_multiselectable] attribute DOMString? ariaMultiSelectable;
5555
[Reflect=aria_orientation] attribute DOMString? ariaOrientation;
56-
[Reflect=aria_owns, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaOwnsElements;
56+
[CustomGetter, Reflect=aria_owns, EnabledBySetting=AriaReflectionForElementReferencesEnabled] attribute FrozenArray<Element>? ariaOwnsElements;
5757
[Reflect=aria_placeholder] attribute DOMString? ariaPlaceholder;
5858
[Reflect=aria_posinset] attribute DOMString? ariaPosInSet;
5959
[Reflect=aria_pressed] attribute DOMString? ariaPressed;

Source/WebCore/bindings/js/JSElementCustom.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@
3535
#include "HTMLNames.h"
3636
#include "JSAttr.h"
3737
#include "JSDOMBinding.h"
38+
#include "JSDOMConvertInterface.h"
39+
#include "JSDOMConvertNullable.h"
40+
#include "JSDOMConvertSequences.h"
3841
#include "JSHTMLElementWrapperFactory.h"
3942
#include "JSMathMLElementWrapperFactory.h"
4043
#include "JSNodeList.h"
4144
#include "JSSVGElementWrapperFactory.h"
4245
#include "MathMLElement.h"
4346
#include "NodeList.h"
4447
#include "SVGElement.h"
48+
#include "WebCoreJSClientData.h"
4549

4650

4751
namespace WebCore {
@@ -81,4 +85,62 @@ JSValue toJSNewlyCreated(JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<E
8185
return createNewElementWrapper(globalObject, WTFMove(element));
8286
}
8387

88+
static JSValue getElementsArrayAttribute(JSGlobalObject& lexicalGlobalObject, const JSElement& thisObject, const QualifiedName& attributeName)
89+
{
90+
auto& vm = JSC::getVM(&lexicalGlobalObject);
91+
auto throwScope = DECLARE_THROW_SCOPE(vm);
92+
93+
JSObject* cachedObject = nullptr;
94+
JSValue cachedObjectValue = thisObject.getDirect(vm, builtinNames(vm).cachedAttrAssociatedElementsPrivateName());
95+
if (cachedObjectValue)
96+
cachedObject = asObject(cachedObjectValue);
97+
else {
98+
cachedObject = constructEmptyObject(vm, thisObject.globalObject()->nullPrototypeObjectStructure());
99+
const_cast<JSElement&>(thisObject).putDirect(vm, builtinNames(vm).cachedAttrAssociatedElementsPrivateName(), cachedObject);
100+
}
101+
102+
std::optional<Vector<RefPtr<Element>>> elements = thisObject.wrapped().getElementsArrayAttribute(attributeName);
103+
auto propertyName = PropertyName(Identifier::fromString(vm, attributeName.toString()));
104+
JSValue cachedValue = cachedObject->getDirect(vm, propertyName);
105+
if (!cachedValue.isEmpty()) {
106+
std::optional<Vector<RefPtr<Element>>> cachedElements = convert<IDLNullable<IDLFrozenArray<IDLInterface<Element>>>>(lexicalGlobalObject, cachedValue);
107+
if (elements == cachedElements)
108+
return cachedValue;
109+
}
110+
111+
JSValue elementsValue = toJS<IDLNullable<IDLFrozenArray<IDLInterface<Element>>>>(lexicalGlobalObject, *thisObject.globalObject(), throwScope, elements);
112+
cachedObject->putDirect(vm, propertyName, elementsValue);
113+
return elementsValue;
114+
}
115+
116+
JSValue JSElement::ariaControlsElements(JSGlobalObject& lexicalGlobalObject) const
117+
{
118+
return getElementsArrayAttribute(lexicalGlobalObject, *this, WebCore::HTMLNames::aria_controlsAttr);
119+
}
120+
121+
JSValue JSElement::ariaDescribedByElements(JSGlobalObject& lexicalGlobalObject) const
122+
{
123+
return getElementsArrayAttribute(lexicalGlobalObject, *this, WebCore::HTMLNames::aria_describedbyAttr);
124+
}
125+
126+
JSValue JSElement::ariaDetailsElements(JSGlobalObject& lexicalGlobalObject) const
127+
{
128+
return getElementsArrayAttribute(lexicalGlobalObject, *this, WebCore::HTMLNames::aria_detailsAttr);
129+
}
130+
131+
JSValue JSElement::ariaFlowToElements(JSGlobalObject& lexicalGlobalObject) const
132+
{
133+
return getElementsArrayAttribute(lexicalGlobalObject, *this, WebCore::HTMLNames::aria_flowtoAttr);
134+
}
135+
136+
JSValue JSElement::ariaLabelledByElements(JSGlobalObject& lexicalGlobalObject) const
137+
{
138+
return getElementsArrayAttribute(lexicalGlobalObject, *this, WebCore::HTMLNames::aria_labelledbyAttr);
139+
}
140+
141+
JSValue JSElement::ariaOwnsElements(JSGlobalObject& lexicalGlobalObject) const
142+
{
143+
return getElementsArrayAttribute(lexicalGlobalObject, *this, WebCore::HTMLNames::aria_ownsAttr);
144+
}
145+
84146
} // namespace WebCore

Source/WebCore/bindings/js/WebCoreBuiltinNames.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ namespace WebCore {
432432
macro(blur) \
433433
macro(body) \
434434
macro(byobRequest) \
435+
macro(cachedAttrAssociatedElements) \
435436
macro(caches) \
436437
macro(cancel) \
437438
macro(cancelAlgorithm) \

0 commit comments

Comments
 (0)