Bug 1769586 - P1: Implement ARIA element reflection in Element and ElementInternals. r=peterv

Differential Revision: https://phabricator.services.mozilla.com/D209767
This commit is contained in:
Eitan Isaacson
2024-11-29 03:26:34 +00:00
parent 05aa173dd2
commit fe237f4955
13 changed files with 424 additions and 143 deletions

View File

@@ -1835,18 +1835,12 @@ already_AddRefed<nsIHTMLCollection> Element::GetElementsByClassName(
return nsContentUtils::GetElementsByClassName(this, aClassNames);
}
Element* Element::GetAttrAssociatedElement(nsAtom* aAttr) const {
if (const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElements.Get(aAttr);
if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl)) {
// If reflectedTarget's explicitly set attr-element |attrEl| is
// a descendant of any of element's shadow-including ancestors, then
// return |atrEl|.
bool Element::HasSharedRoot(const Element* aElement) const {
nsINode* root = SubtreeRoot();
nsINode* attrSubtreeRoot = attrEl->SubtreeRoot();
nsINode* attrSubtreeRoot = aElement->SubtreeRoot();
do {
if (root == attrSubtreeRoot) {
return attrEl;
return true;
}
auto* shadow = ShadowRoot::FromNode(root);
if (!shadow || !shadow->GetHost()) {
@@ -1854,6 +1848,27 @@ Element* Element::GetAttrAssociatedElement(nsAtom* aAttr) const {
}
root = shadow->GetHost()->SubtreeRoot();
} while (true);
return false;
}
Element* Element::GetElementByIdInDocOrSubtree(nsAtom* aID) const {
if (auto* docOrShadowRoot = GetContainingDocumentOrShadowRoot()) {
return docOrShadowRoot->GetElementById(aID);
}
return nsContentUtils::MatchElementId(SubtreeRoot()->AsContent(), aID);
}
Element* Element::GetAttrAssociatedElement(nsAtom* aAttr) const {
if (const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElementMap.Get(aAttr);
if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl)) {
// If reflectedTarget's explicitly set attr-element |attrEl| is
// a descendant of any of element's shadow-including ancestors, then
// return |atrEl|.
if (HasSharedRoot(attrEl)) {
return attrEl;
}
return nullptr;
}
}
@@ -1866,23 +1881,102 @@ Element* Element::GetAttrAssociatedElement(nsAtom* aAttr) const {
MOZ_ASSERT(value->Type() == nsAttrValue::eAtom,
"Attribute used for attr associated element must be parsed");
nsAtom* valueAtom = value->GetAtomValue();
if (auto* docOrShadowRoot = GetContainingDocumentOrShadowRoot()) {
return docOrShadowRoot->GetElementById(valueAtom);
return GetElementByIdInDocOrSubtree(value->GetAtomValue());
}
nsINode* root = SubtreeRoot();
for (auto* node = root; node; node = node->GetNextNode(root)) {
if (node->HasID() && node->AsContent()->GetID() == valueAtom) {
return node->AsElement();
void Element::GetAttrAssociatedElements(
nsAtom* aAttr, bool* aUseCachedValue,
Nullable<nsTArray<RefPtr<Element>>>& aElements) {
MOZ_ASSERT(aElements.IsNull());
auto& [explicitlySetAttrElements, cachedAttrElements] =
ExtendedDOMSlots()->mAttrElementsMap.LookupOrInsert(aAttr);
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#attr-associated-elements
auto getAttrAssociatedElements =
[&, &explicitlySetAttrElements =
explicitlySetAttrElements]() -> Maybe<nsTArray<RefPtr<Element>>> {
nsTArray<RefPtr<Element>> elements;
if (explicitlySetAttrElements) {
// 3. If reflectedTarget's explicitly set attr-elements is not null
for (const nsWeakPtr& weakEl : *explicitlySetAttrElements) {
// For each attrElement in reflectedTarget's explicitly set
// attr-elements:
if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakEl)) {
// If attrElement is not a descendant of any of element's
// shadow-including ancestors, then continue.
if (!HasSharedRoot(attrEl)) {
continue;
}
// Append attrElement to elements.
elements.AppendElement(attrEl);
}
}
return nullptr;
} else {
// 4. Otherwise
// 1. Let contentAttributeValue be the result of running
// reflectedTarget's get the content attribute.
const nsAttrValue* value = GetParsedAttr(aAttr);
// 2. If contentAttributeValue is null, then return null.
if (!value) {
return Nothing();
}
// 3. Let tokens be contentAttributeValue, split on ASCII whitespace.
MOZ_ASSERT(value->Type() == nsAttrValue::eAtomArray ||
value->Type() == nsAttrValue::eAtom,
"Attribute used for attr associated elements must be parsed");
for (uint32_t i = 0; i < value->GetAtomCount(); i++) {
// For each id of tokens:
if (auto* candidate = GetElementByIdInDocOrSubtree(
value->AtomAt(static_cast<int32_t>(i)))) {
// Append candidate to elements.
elements.AppendElement(candidate);
}
}
}
return Some(std::move(elements));
};
// getter steps:
// 1. Let elements be the result of running this's get the attr-associated
// elements.
auto elements = getAttrAssociatedElements();
if (elements && elements == cachedAttrElements) {
// 2. If the contents of elements is equal to the contents of this's cached
// attr-associated elements, then return this's cached attr-associated
// elements object.
MOZ_ASSERT(!*aUseCachedValue);
*aUseCachedValue = true;
return;
}
// 3. Let elementsAsFrozenArray be elements, converted to a FrozenArray<T>?.
// (the binding code takes aElements and returns it as a FrozenArray)
// 5. Set this's cached attr-associated elements object to
// elementsAsFrozenArray.
// (the binding code stores the attr-associated elements object in a slot)
// 6. Return elementsAsFrozenArray.
if (elements) {
aElements.SetValue(elements->Clone());
}
// 4. Set this's cached attr-associated elements to elements.
cachedAttrElements = std::move(elements);
}
void Element::ClearExplicitlySetAttrElement(nsAtom* aAttr) {
if (auto* slots = GetExistingExtendedDOMSlots()) {
slots->mExplicitlySetAttrElements.Remove(aAttr);
slots->mExplicitlySetAttrElementMap.Remove(aAttr);
}
}
void Element::ClearExplicitlySetAttrElements(nsAtom* aAttr) {
if (auto* slots = GetExistingExtendedDOMSlots()) {
slots->mAttrElementsMap.Remove(aAttr);
}
}
@@ -1905,7 +1999,7 @@ void Element::ExplicitlySetAttrElement(nsAtom* aAttr, Element* aElement) {
#endif
SetAttr(aAttr, EmptyString(), IgnoreErrors());
nsExtendedDOMSlots* slots = ExtendedDOMSlots();
slots->mExplicitlySetAttrElements.InsertOrUpdate(
slots->mExplicitlySetAttrElementMap.InsertOrUpdate(
aAttr, do_GetWeakReference(aElement));
#ifdef ACCESSIBILITY
if (accService) {
@@ -1929,9 +2023,48 @@ void Element::ExplicitlySetAttrElement(nsAtom* aAttr, Element* aElement) {
#endif
}
void Element::ExplicitlySetAttrElements(
nsAtom* aAttr,
const Nullable<Sequence<OwningNonNull<Element>>>& aElements) {
#ifdef ACCESSIBILITY
nsAccessibilityService* accService = GetAccService();
#endif
// Accessibility requires that no other attribute changes occur between
// AttrElementWillChange and AttrElementChanged. Scripts could cause
// this, so don't let them run here. We do this even if accessibility isn't
// running so that the JS behavior is consistent regardless of accessibility.
// Otherwise, JS might be able to use this difference to determine whether
// accessibility is running, which would be a privacy concern.
nsAutoScriptBlocker scriptBlocker;
#ifdef ACCESSIBILITY
if (accService) {
accService->NotifyAttrElementWillChange(this, aAttr);
}
#endif
if (aElements.IsNull()) {
ClearExplicitlySetAttrElements(aAttr);
UnsetAttr(aAttr, IgnoreErrors());
} else {
SetAttr(aAttr, EmptyString(), IgnoreErrors());
auto& entry = ExtendedDOMSlots()->mAttrElementsMap.LookupOrInsert(aAttr);
entry.first.emplace(nsTArray<nsWeakPtr>());
for (Element* el : aElements.Value()) {
entry.first->AppendElement(do_GetWeakReference(el));
}
}
#ifdef ACCESSIBILITY
if (accService) {
accService->NotifyAttrElementChanged(this, aAttr);
}
#endif
}
Element* Element::GetExplicitlySetAttrElement(nsAtom* aAttr) const {
if (const nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots()) {
nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElements.Get(aAttr);
nsWeakPtr weakAttrEl = slots->mExplicitlySetAttrElementMap.Get(aAttr);
if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl)) {
return attrEl;
}
@@ -2897,7 +3030,14 @@ bool Element::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
}
if (aNamespaceID == kNameSpaceID_None) {
if (aAttribute == nsGkAtoms::_class || aAttribute == nsGkAtoms::part) {
if (aAttribute == nsGkAtoms::_class || aAttribute == nsGkAtoms::part ||
aAttribute == nsGkAtoms::aria_controls ||
aAttribute == nsGkAtoms::aria_describedby ||
aAttribute == nsGkAtoms::aria_details ||
aAttribute == nsGkAtoms::aria_errormessage ||
aAttribute == nsGkAtoms::aria_flowto ||
aAttribute == nsGkAtoms::aria_labelledby ||
aAttribute == nsGkAtoms::aria_owns) {
aResult.ParseAtomArray(aValue);
return true;
}
@@ -2969,6 +3109,14 @@ void Element::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
}
} else if (aName == nsGkAtoms::aria_activedescendant) {
ClearExplicitlySetAttrElement(aName);
} else if (aName == nsGkAtoms::aria_controls ||
aName == nsGkAtoms::aria_describedby ||
aName == nsGkAtoms::aria_details ||
aName == nsGkAtoms::aria_errormessage ||
aName == nsGkAtoms::aria_flowto ||
aName == nsGkAtoms::aria_labelledby ||
aName == nsGkAtoms::aria_owns) {
ClearExplicitlySetAttrElements(aName);
}
}
}
@@ -3025,6 +3173,16 @@ void Element::OnAttrSetButNotChanged(int32_t aNamespaceID, nsAtom* aName,
aName == nsGkAtoms::aria_activedescendant) {
ClearExplicitlySetAttrElement(aName);
}
if (aNamespaceID == kNameSpaceID_None &&
(aName == nsGkAtoms::aria_controls ||
aName == nsGkAtoms::aria_describedby ||
aName == nsGkAtoms::aria_details ||
aName == nsGkAtoms::aria_errormessage ||
aName == nsGkAtoms::aria_flowto || aName == nsGkAtoms::aria_labelledby ||
aName == nsGkAtoms::aria_owns)) {
ClearExplicitlySetAttrElements(aName);
}
}
EventListenerManager* Element::GetEventListenerManagerForAttr(nsAtom* aAttrName,

View File

@@ -267,6 +267,17 @@ class TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString;
ExplicitlySetAttrElement(nsGkAtoms::attr, aElement); \
}
#define REFLECT_NULLABLE_ELEMENTS_ATTR(method, attr) \
void Get##method(bool* aUseCachedValue, \
Nullable<nsTArray<RefPtr<Element>>>& aElements) { \
GetAttrAssociatedElements(nsGkAtoms::attr, aUseCachedValue, aElements); \
} \
\
void Set##method( \
const Nullable<Sequence<OwningNonNull<Element>>>& aElements) { \
ExplicitlySetAttrElements(nsGkAtoms::attr, aElements); \
}
// TODO(keithamus): Reference the spec link once merged.
// https://github.com/whatwg/html/pull/9841/files#diff-41cf6794ba4200b839c53531555f0f3998df4cbb01a4d5cb0b94e3ca5e23947dR86024
enum class InvokeAction : uint8_t {
@@ -706,21 +717,28 @@ class Element : public FragmentOrElement {
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaColIndex, aria_colindex)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaColIndexText, aria_colindextext)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaColSpan, aria_colspan)
REFLECT_NULLABLE_ELEMENTS_ATTR(AriaControlsElements, aria_controls)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaCurrent, aria_current)
REFLECT_NULLABLE_ELEMENTS_ATTR(AriaDescribedByElements, aria_describedby)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaDescription, aria_description)
REFLECT_NULLABLE_ELEMENTS_ATTR(AriaDetailsElements, aria_details)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaDisabled, aria_disabled)
REFLECT_NULLABLE_ELEMENTS_ATTR(AriaErrorMessageElements, aria_errormessage)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaExpanded, aria_expanded)
REFLECT_NULLABLE_ELEMENTS_ATTR(AriaFlowToElements, aria_flowto)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaHasPopup, aria_haspopup)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaHidden, aria_hidden)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaInvalid, aria_invalid)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaKeyShortcuts, aria_keyshortcuts)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaLabel, aria_label)
REFLECT_NULLABLE_ELEMENTS_ATTR(AriaLabelledByElements, aria_labelledby)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaLevel, aria_level)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaLive, aria_live)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaModal, aria_modal)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaMultiLine, aria_multiline)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaMultiSelectable, aria_multiselectable)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaOrientation, aria_orientation)
REFLECT_NULLABLE_ELEMENTS_ATTR(AriaOwnsElements, aria_owns)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaPlaceholder, aria_placeholder)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaPosInSet, aria_posinset)
REFLECT_NULLABLE_DOMSTRING_ATTR(AriaPressed, aria_pressed)
@@ -1172,6 +1190,10 @@ class Element : public FragmentOrElement {
const MappedAttributeEntry* const aMaps[],
uint32_t aMapCount);
bool HasSharedRoot(const Element* aElement) const;
Element* GetElementByIdInDocOrSubtree(nsAtom* aID) const;
protected:
inline bool GetAttr(const nsAtom* aName, DOMString& aResult) const {
MOZ_ASSERT(aResult.IsEmpty(), "Should have empty string coming in");
@@ -1308,14 +1330,21 @@ class Element : public FragmentOrElement {
* https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#attr-associated-element
*/
Element* GetAttrAssociatedElement(nsAtom* aAttr) const;
void GetAttrAssociatedElements(
nsAtom* aAttr, bool* aUseCachedValue,
Nullable<nsTArray<RefPtr<Element>>>& aElements);
/**
* Sets an attribute element for the given attribute.
* https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element
*/
void ExplicitlySetAttrElement(nsAtom* aAttr, Element* aElement);
void ExplicitlySetAttrElements(
nsAtom* aAttr,
const Nullable<Sequence<OwningNonNull<Element>>>& aElements);
void ClearExplicitlySetAttrElement(nsAtom*);
void ClearExplicitlySetAttrElements(nsAtom*);
/**
* Gets the attribute element for the given attribute.

View File

@@ -637,7 +637,8 @@ void FragmentOrElement::nsExtendedDOMSlots::UnlinkExtendedSlots(
mAnimations = nullptr;
aContent.ClearMayHaveAnimations();
}
mExplicitlySetAttrElements.Clear();
mExplicitlySetAttrElementMap.Clear();
mAttrElementsMap.Clear();
mRadioGroupContainer = nullptr;
mPart = nullptr;
}
@@ -661,6 +662,15 @@ void FragmentOrElement::nsExtendedDOMSlots::TraverseExtendedSlots(
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mSlots->mPart");
aCb.NoteXPCOMChild(mPart.get());
for (auto& tableEntry : mAttrElementsMap) {
auto& [explicitlySetElements, cachedAttrElements] =
*tableEntry.GetModifiableData();
if (cachedAttrElements) {
ImplCycleCollectionTraverse(aCb, *cachedAttrElements,
"cached attribute elements entry", 0);
}
}
if (mCustomElementData) {
mCustomElementData->Traverse(aCb);
}

View File

@@ -268,10 +268,21 @@ class FragmentOrElement : public nsIContent {
RefPtr<nsDOMTokenList> mPart;
/**
* Explicitly set attr-elements, see
* Explicitly set attr-element, see
* https://html.spec.whatwg.org/#explicitly-set-attr-element
*/
nsTHashMap<RefPtr<nsAtom>, nsWeakPtr> mExplicitlySetAttrElements;
nsTHashMap<RefPtr<nsAtom>, nsWeakPtr> mExplicitlySetAttrElementMap;
/**
* Explicitly set attr-elements, see
* https://html.spec.whatwg.org/#explicitly-set-attr-elements
*
* The first member of the pair are the explicitly set attr-elements. The
* second member is the cached attr-associated elements.
*/
nsTHashMap<RefPtr<nsAtom>, std::pair<Maybe<nsTArray<nsWeakPtr>>,
Maybe<nsTArray<RefPtr<Element>>>>>
mAttrElementsMap;
};
class nsDOMSlots : public nsIContent::nsContentSlots {

View File

@@ -41,6 +41,13 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ElementInternals)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTarget, mSubmissionValue, mState,
mValidity, mValidationAnchor,
mCustomStateSet);
for (auto& tableEntry : tmp->mAttrElementsMap) {
auto& [explicitlySetElements, cachedAttrElements] =
*tableEntry.GetModifiableData();
ImplCycleCollectionTraverse(cb, cachedAttrElements,
"cached attribute elements entry", 0);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ElementInternals)
@@ -428,6 +435,7 @@ void ElementInternals::Unlink() {
mFieldSet->RemoveElement(mTarget);
mFieldSet = nullptr;
}
mAttrElementsMap.Clear();
}
void ElementInternals::GetAttr(const nsAtom* aName, nsAString& aResult) const {
@@ -469,6 +477,23 @@ nsresult ElementInternals::SetAttr(nsAtom* aName, const nsAString& aValue) {
return rs;
}
nsresult ElementInternals::SetAttrInternal(nsAtom* aName,
const nsAString& aValue) {
bool attrHadValue;
nsAttrValue attrValue(aValue);
return mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue);
}
nsresult ElementInternals::UnsetAttrInternal(nsAtom* aName) {
nsAttrValue attrValue;
auto attrPos = mAttrs.IndexOfAttr(aName);
if (attrPos >= 0) {
return mAttrs.RemoveAttrAt(attrPos, attrValue);
}
return NS_OK;
}
DocGroup* ElementInternals::GetDocGroup() {
return mTarget->OwnerDoc()->GetDocGroup();
}
@@ -516,9 +541,11 @@ void ElementInternals::SetAttrElement(nsAtom* aAttr, Element* aElement) {
#endif
if (aElement) {
mAttrElements.InsertOrUpdate(aAttr, do_GetWeakReference(aElement));
mAttrElementMap.InsertOrUpdate(aAttr, do_GetWeakReference(aElement));
SetAttrInternal(aAttr, EmptyString());
} else {
mAttrElements.Remove(aAttr);
mAttrElementMap.Remove(aAttr);
UnsetAttrInternal(aAttr);
}
#ifdef ACCESSIBILITY
@@ -529,9 +556,103 @@ void ElementInternals::SetAttrElement(nsAtom* aAttr, Element* aElement) {
}
Element* ElementInternals::GetAttrElement(nsAtom* aAttr) const {
nsWeakPtr weakAttrEl = mAttrElements.Get(aAttr);
nsWeakPtr weakAttrEl = mAttrElementMap.Get(aAttr);
nsCOMPtr<Element> attrEl = do_QueryReferent(weakAttrEl);
return attrEl;
}
void ElementInternals::SetAttrElements(
nsAtom* aAttr,
const Nullable<Sequence<OwningNonNull<Element>>>& aElements) {
#ifdef ACCESSIBILITY
nsAccessibilityService* accService = GetAccService();
#endif
// Accessibility requires that no other attribute changes occur between
// AttrElementWillChange and AttrElementChanged. Scripts could cause
// this, so don't let them run here. We do this even if accessibility isn't
// running so that the JS behavior is consistent regardless of accessibility.
// Otherwise, JS might be able to use this difference to determine whether
// accessibility is running, which would be a privacy concern.
nsAutoScriptBlocker scriptBlocker;
#ifdef ACCESSIBILITY
if (accService) {
accService->NotifyAttrElementWillChange(mTarget, aAttr);
}
#endif
nsAttrValue emptyAttr;
if (aElements.IsNull()) {
mAttrElementsMap.Remove(aAttr);
UnsetAttrInternal(aAttr);
} else {
auto& [attrElements, cachedAttrElements] =
mAttrElementsMap.LookupOrInsert(aAttr);
attrElements.Clear();
for (Element* el : aElements.Value()) {
attrElements.AppendElement(do_GetWeakReference(el));
}
SetAttrInternal(aAttr, EmptyString());
}
#ifdef ACCESSIBILITY
if (accService) {
accService->NotifyAttrElementChanged(mTarget, aAttr);
}
#endif
}
void ElementInternals::GetAttrElements(
nsAtom* aAttr, bool* aUseCachedValue,
Nullable<nsTArray<RefPtr<Element>>>& aElements) {
MOZ_ASSERT(aElements.IsNull());
auto attrElementsMaybeEntry = mAttrElementsMap.Lookup(aAttr);
if (!attrElementsMaybeEntry) {
return;
}
aElements.SetValue(nsTArray<RefPtr<Element>>());
auto& [attrElements, cachedAttrElements] = attrElementsMaybeEntry.Data();
auto getAttrAssociatedElements = [&, &attrElements = attrElements]() {
CopyableTArray<RefPtr<Element>> elements;
for (const nsWeakPtr& weakEl : attrElements) {
// For each attrElement in reflectedTarget's explicitly set attr-elements:
if (nsCOMPtr<Element> attrEl = do_QueryReferent(weakEl)) {
// Append attrElement to elements.
elements.AppendElement(attrEl);
}
}
return elements;
};
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#attr-associated-elements
// Getter steps:
// 1. Let elements be the result of running this's get the attr-associated
// elements.
auto elements = getAttrAssociatedElements();
if (elements == cachedAttrElements) {
// 2. If the contents of elements is equal to the contents of this's cached
// attr-associated elements, then return this's cached attr-associated
// elements object.
MOZ_ASSERT(!*aUseCachedValue);
*aUseCachedValue = true;
return;
}
// 3. Let elementsAsFrozenArray be elements, converted to a FrozenArray<T>?.
// (the binding code takes aElements and returns it as a FrozenArray)
// 5. Set this's cached attr-associated elements object to
// elementsAsFrozenArray.
// (the binding code stores the attr-associated elements object in a slot)
// 6. Return elementsAsFrozenArray.
aElements.SetValue(elements.Clone());
// 4. Set this's cached attr-associated elements to elements.
cachedAttrElements = std::move(elements);
}
} // namespace mozilla::dom

View File

@@ -34,6 +34,17 @@
SetAttrElement(nsGkAtoms::attr, aElement); \
}
#define ARIA_REFLECT_ATTR_ELEMENTS(method, attr) \
void Get##method(bool* aUseCachedValue, \
Nullable<nsTArray<RefPtr<Element>>>& aElements) { \
GetAttrElements(nsGkAtoms::attr, aUseCachedValue, aElements); \
} \
\
void Set##method( \
const Nullable<Sequence<OwningNonNull<Element>>>& aElements) { \
SetAttrElements(nsGkAtoms::attr, aElements); \
}
class nsINodeList;
class nsGenericHTMLElement;
@@ -137,21 +148,28 @@ class ElementInternals final : public nsIFormControl,
ARIA_REFLECT_ATTR(AriaColIndex, aria_colindex)
ARIA_REFLECT_ATTR(AriaColIndexText, aria_colindextext)
ARIA_REFLECT_ATTR(AriaColSpan, aria_colspan)
ARIA_REFLECT_ATTR_ELEMENTS(AriaControlsElements, aria_controls)
ARIA_REFLECT_ATTR(AriaCurrent, aria_current)
ARIA_REFLECT_ATTR_ELEMENTS(AriaDescribedByElements, aria_describedby)
ARIA_REFLECT_ATTR(AriaDescription, aria_description)
ARIA_REFLECT_ATTR_ELEMENTS(AriaDetailsElements, aria_details)
ARIA_REFLECT_ATTR(AriaDisabled, aria_disabled)
ARIA_REFLECT_ATTR_ELEMENTS(AriaErrorMessageElements, aria_errormessage)
ARIA_REFLECT_ATTR(AriaExpanded, aria_expanded)
ARIA_REFLECT_ATTR_ELEMENTS(AriaFlowToElements, aria_flowto)
ARIA_REFLECT_ATTR(AriaHasPopup, aria_haspopup)
ARIA_REFLECT_ATTR(AriaHidden, aria_hidden)
ARIA_REFLECT_ATTR(AriaInvalid, aria_invalid)
ARIA_REFLECT_ATTR(AriaKeyShortcuts, aria_keyshortcuts)
ARIA_REFLECT_ATTR(AriaLabel, aria_label)
ARIA_REFLECT_ATTR_ELEMENTS(AriaLabelledByElements, aria_labelledby)
ARIA_REFLECT_ATTR(AriaLevel, aria_level)
ARIA_REFLECT_ATTR(AriaLive, aria_live)
ARIA_REFLECT_ATTR(AriaModal, aria_modal)
ARIA_REFLECT_ATTR(AriaMultiLine, aria_multiline)
ARIA_REFLECT_ATTR(AriaMultiSelectable, aria_multiselectable)
ARIA_REFLECT_ATTR(AriaOrientation, aria_orientation)
ARIA_REFLECT_ATTR_ELEMENTS(AriaOwnsElements, aria_owns)
ARIA_REFLECT_ATTR(AriaPlaceholder, aria_placeholder)
ARIA_REFLECT_ATTR(AriaPosInSet, aria_posinset)
ARIA_REFLECT_ATTR(AriaPressed, aria_pressed)
@@ -194,6 +212,17 @@ class ElementInternals final : public nsIFormControl,
*/
void SetAttrElement(nsAtom* aAttr, Element* aElement);
void SetAttrElements(
nsAtom* aAttr,
const Nullable<Sequence<OwningNonNull<Element>>>& aElements);
void GetAttrElements(nsAtom* aAttr, bool* aUseCachedValue,
Nullable<nsTArray<RefPtr<Element>>>& aElements);
nsresult SetAttrInternal(nsAtom* aName, const nsAString& aValue);
nsresult UnsetAttrInternal(nsAtom* aName);
// It's a target element which is a custom element.
RefPtr<HTMLElement> mTarget;
@@ -238,7 +267,11 @@ class ElementInternals final : public nsIFormControl,
* Explicitly set attr-elements, see
* https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#explicitly-set-attr-element
*/
nsTHashMap<RefPtr<nsAtom>, nsWeakPtr> mAttrElements;
nsTHashMap<RefPtr<nsAtom>, nsWeakPtr> mAttrElementMap;
nsTHashMap<RefPtr<nsAtom>,
std::pair<nsTArray<nsWeakPtr>, nsTArray<RefPtr<Element>>>>
mAttrElementsMap;
};
} // namespace mozilla::dom

View File

@@ -47,18 +47,38 @@ interface mixin ARIAMixin {
[CEReactions, SetterThrows]
attribute DOMString? ariaColSpan;
// TODO: Use FrozenArray once available. (Bug 1236777)
[Pref="accessibility.ARIAElementReflection.enabled", Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? ariaControlsElements;
[CEReactions, SetterThrows]
attribute DOMString? ariaCurrent;
// TODO: Use FrozenArray once available. (Bug 1236777)
[Pref="accessibility.ARIAElementReflection.enabled", Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? ariaDescribedByElements;
[CEReactions, SetterThrows]
attribute DOMString? ariaDescription;
// TODO: Use FrozenArray once available. (Bug 1236777)
[Pref="accessibility.ARIAElementReflection.enabled", Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? ariaDetailsElements;
[CEReactions, SetterThrows]
attribute DOMString? ariaDisabled;
// TODO: Use FrozenArray once available. (Bug 1236777)
[Pref="accessibility.ARIAElementReflection.enabled", Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? ariaErrorMessageElements;
[CEReactions, SetterThrows]
attribute DOMString? ariaExpanded;
// TODO: Use FrozenArray once available. (Bug 1236777)
[Pref="accessibility.ARIAElementReflection.enabled", Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? ariaFlowToElements;
[CEReactions, SetterThrows]
attribute DOMString? ariaHasPopup;
@@ -74,6 +94,10 @@ interface mixin ARIAMixin {
[CEReactions, SetterThrows]
attribute DOMString? ariaLabel;
// TODO: Use FrozenArray once available. (Bug 1236777)
[Pref="accessibility.ARIAElementReflection.enabled", Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? ariaLabelledByElements;
[CEReactions, SetterThrows]
attribute DOMString? ariaLevel;
@@ -92,6 +116,10 @@ interface mixin ARIAMixin {
[CEReactions, SetterThrows]
attribute DOMString? ariaOrientation;
// TODO: Use FrozenArray once available. (Bug 1236777)
[Pref="accessibility.ARIAElementReflection.enabled", Frozen, ReflectedHTMLAttributeReturningFrozenArray]
attribute sequence<Element>? ariaOwnsElements;
[CEReactions, SetterThrows]
attribute DOMString? ariaPlaceholder;

View File

@@ -1,23 +1,2 @@
[ElementInternals-accessibility.html]
prefs: [accessibility.ARIAElementReflection.enabled:true]
[ariaControlsElements is defined in ElementInternals]
expected: FAIL
[ariaDescribedByElements is defined in ElementInternals]
expected: FAIL
[ariaDetailsElements is defined in ElementInternals]
expected: FAIL
[ariaFlowToElements is defined in ElementInternals]
expected: FAIL
[ariaLabelledByElements is defined in ElementInternals]
expected: FAIL
[ariaOwnsElements is defined in ElementInternals]
expected: FAIL
[ariaErrorMessageElements is defined in ElementInternals]
expected: FAIL

View File

@@ -1,6 +1,5 @@
[element-internals-aria-element-reflection.html]
[Getting previously-unset ARIA element reflection properties on ElementInternals should return null.]
expected: FAIL
prefs: [accessibility.ARIAElementReflection.enabled:true]
[Setting ariaLabelledByElements should change the accessible name of the custom element]
expected: FAIL

View File

@@ -1,4 +1,2 @@
[aria-element-reflection-disconnected.html]
prefs: [accessibility.ARIAElementReflection.enabled:true]
[Element references should stay valid when content is disconnected (element array)]
expected: FAIL

View File

@@ -1,12 +0,0 @@
[aria-element-reflection-labelledby.html]
[Setting ariaLabelledByElements should determine the computed label for the labelled element]
expected: FAIL
[Setting ariaLabelledByElements before inserting the elements referred to in the document should cause the label to be updated once elements are inserted]
expected: FAIL
[Setting ariaLabelledByElements on an element before inserting it in the document should cause the label to be updated once the element is inserted]
expected: FAIL
[Moving the label from shadow DOM to light DOM causes the reference to become valid]
expected: FAIL

View File

@@ -1,34 +1,2 @@
[aria-element-reflection.html]
prefs: [accessibility.ARIAElementReflection.enabled:true]
[aria-errormessage]
expected: FAIL
[aria-details]
expected: FAIL
[aria-labelledby.]
expected: FAIL
[aria-controls.]
expected: FAIL
[aria-describedby.]
expected: FAIL
[aria-flowto.]
expected: FAIL
[aria-owns.]
expected: FAIL
[shadow DOM behaviour for FrozenArray element reflection.]
expected: FAIL
[Moving explicitly set elements across shadow DOM boundaries.]
expected: FAIL
[Moving explicitly set elements around within the same scope, and removing from the DOM.]
expected: FAIL
[Passing values of the wrong type should throw a TypeError]
expected: FAIL

View File

@@ -1,43 +1,2 @@
[idlharness.window.html]
prefs: [accessibility.ARIAElementReflection.enabled:true]
[Element interface: attribute ariaControlsElements]
expected: FAIL
[Element interface: attribute ariaDescribedByElements]
expected: FAIL
[Element interface: attribute ariaDetailsElements]
expected: FAIL
[Element interface: attribute ariaFlowToElements]
expected: FAIL
[Element interface: attribute ariaLabelledByElements]
expected: FAIL
[Element interface: attribute ariaOwnsElements]
expected: FAIL
[Element interface: element must inherit property "ariaControlsElements" with the proper type]
expected: FAIL
[Element interface: element must inherit property "ariaDescribedByElements" with the proper type]
expected: FAIL
[Element interface: element must inherit property "ariaDetailsElements" with the proper type]
expected: FAIL
[Element interface: element must inherit property "ariaFlowToElements" with the proper type]
expected: FAIL
[Element interface: element must inherit property "ariaLabelledByElements" with the proper type]
expected: FAIL
[Element interface: element must inherit property "ariaOwnsElements" with the proper type]
expected: FAIL
[Element interface: attribute ariaErrorMessageElements]
expected: FAIL
[Element interface: element must inherit property "ariaErrorMessageElements" with the proper type]
expected: FAIL