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,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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user