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:
@@ -1835,25 +1835,40 @@ already_AddRefed<nsIHTMLCollection> Element::GetElementsByClassName(
|
||||
return nsContentUtils::GetElementsByClassName(this, aClassNames);
|
||||
}
|
||||
|
||||
bool Element::HasSharedRoot(const Element* aElement) const {
|
||||
nsINode* root = SubtreeRoot();
|
||||
nsINode* attrSubtreeRoot = aElement->SubtreeRoot();
|
||||
do {
|
||||
if (root == attrSubtreeRoot) {
|
||||
return true;
|
||||
}
|
||||
auto* shadow = ShadowRoot::FromNode(root);
|
||||
if (!shadow || !shadow->GetHost()) {
|
||||
break;
|
||||
}
|
||||
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->mExplicitlySetAttrElements.Get(aAttr);
|
||||
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|.
|
||||
nsINode* root = SubtreeRoot();
|
||||
nsINode* attrSubtreeRoot = attrEl->SubtreeRoot();
|
||||
do {
|
||||
if (root == attrSubtreeRoot) {
|
||||
return attrEl;
|
||||
}
|
||||
auto* shadow = ShadowRoot::FromNode(root);
|
||||
if (!shadow || !shadow->GetHost()) {
|
||||
break;
|
||||
}
|
||||
root = shadow->GetHost()->SubtreeRoot();
|
||||
} while (true);
|
||||
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());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
nsINode* root = SubtreeRoot();
|
||||
for (auto* node = root; node; node = node->GetNextNode(root)) {
|
||||
if (node->HasID() && node->AsContent()->GetID() == valueAtom) {
|
||||
return node->AsElement();
|
||||
}
|
||||
// 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());
|
||||
}
|
||||
return nullptr;
|
||||
|
||||
// 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,
|
||||
|
||||
Reference in New Issue
Block a user