diff options
Diffstat (limited to 'accessible/generic/DocAccessible.cpp')
-rw-r--r-- | accessible/generic/DocAccessible.cpp | 176 |
1 files changed, 141 insertions, 35 deletions
diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index 28ed8bcbb4..0d84e8be9c 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -67,6 +67,9 @@ static nsStaticAtom* const kRelationAttrs[] = { static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs); +static nsStaticAtom* const kSingleElementRelationIdlAttrs[] = { + nsGkAtoms::popovertarget}; + //////////////////////////////////////////////////////////////////////////////// // Constructor/desctructor @@ -383,25 +386,25 @@ void DocAccessible::QueueCacheUpdate(LocalAccessible* aAcc, void DocAccessible::QueueCacheUpdateForDependentRelations( LocalAccessible* aAcc) { - if (!mIPCDoc || !aAcc || !aAcc->Elm() || !aAcc->IsInDocument() || - aAcc->IsDefunct()) { + if (!mIPCDoc || !aAcc || !aAcc->IsInDocument() || aAcc->IsDefunct()) { return; } - nsAutoString ID; - aAcc->DOMNodeID(ID); - if (AttrRelProviders* list = GetRelProviders(aAcc->Elm(), ID)) { - // We call this function when we've noticed an ID change, or when an acc - // is getting bound to its document. We need to ensure any existing accs - // that depend on this acc's ID have their rel cache entries updated. - for (const auto& provider : *list) { - LocalAccessible* relatedAcc = GetAccessible(provider->mContent); - if (!relatedAcc || relatedAcc->IsDefunct() || - !relatedAcc->IsInDocument() || - mInsertedAccessibles.Contains(relatedAcc)) { - continue; - } - QueueCacheUpdate(relatedAcc, CacheDomain::Relations); + dom::Element* el = aAcc->Elm(); + if (!el) { + return; + } + + // We call this function when we've noticed an ID change, or when an acc + // is getting bound to its document. We need to ensure any existing accs + // that depend on this acc's ID or Element have their relation cache entries + // updated. + RelatedAccIterator iter(this, el, nullptr); + while (LocalAccessible* relatedAcc = iter.Next()) { + if (relatedAcc->IsDefunct() || !relatedAcc->IsInDocument() || + mInsertedAccessibles.Contains(relatedAcc)) { + continue; } + QueueCacheUpdate(relatedAcc, CacheDomain::Relations); } } @@ -507,6 +510,7 @@ void DocAccessible::Shutdown() { } mDependentIDsHashes.Clear(); + mDependentElementsMap.Clear(); mNodeToAccessibleMap.Clear(); mAnchorJumpElm = nullptr; @@ -729,9 +733,26 @@ std::pair<nsPoint, nsRect> DocAccessible::ComputeScrollData( NS_IMPL_NSIDOCUMENTOBSERVER_CORE_STUB(DocAccessible) NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(DocAccessible) +// When a reflected element IDL attribute changes, we might get the following +// synchronous calls: +// 1. AttributeWillChange for the element. +// 2. AttributeWillChange for the content attribute. +// 3. AttributeChanged for the content attribute. +// 4. AttributeChanged for the element. +// Since the content attribute value is "" for any element, we won't always get +// 2 or 3. Even if we do, they might occur after the element has already +// changed, which means we can't detect any relevant state changes there; e.g. +// mPrevStateBits. Thus, we need 1 and 4, and we must ignore 2 and 3. To +// facilitate this, sIsAttrElementChanging will be set to true for 2 and 3. +static bool sIsAttrElementChanging = false; + void DocAccessible::AttributeWillChange(dom::Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { + if (sIsAttrElementChanging) { + // See the comment above the definition of sIsAttrElementChanging. + return; + } LocalAccessible* accessible = GetAccessible(aElement); if (!accessible) { if (aElement != mContent) return; @@ -744,10 +765,11 @@ void DocAccessible::AttributeWillChange(dom::Element* aElement, // elements. if (aModType != dom::MutationEvent_Binding::ADDITION) { RemoveDependentIDsFor(accessible, aAttribute); + RemoveDependentElementsFor(accessible, aAttribute); } if (aAttribute == nsGkAtoms::id) { - if (accessible->IsActiveDescendant()) { + if (accessible->IsActiveDescendantId()) { RefPtr<AccEvent> event = new AccStateChangeEvent(accessible, states::ACTIVE, false); FireDelayedEvent(event); @@ -778,6 +800,10 @@ void DocAccessible::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { + if (sIsAttrElementChanging) { + // See the comment above the definition of sIsAttrElementChanging. + return; + } NS_ASSERTION(!IsDefunct(), "Attribute changed called on defunct document accessible!"); @@ -851,6 +877,7 @@ void DocAccessible::AttributeChanged(dom::Element* aElement, if (aModType == dom::MutationEvent_Binding::MODIFICATION || aModType == dom::MutationEvent_Binding::ADDITION) { AddDependentIDsFor(accessible, aAttribute); + AddDependentElementsFor(accessible, aAttribute); } } @@ -883,25 +910,23 @@ void DocAccessible::ARIAAttributeDefaultChanged(dom::Element* aElement, void DocAccessible::ARIAActiveDescendantChanged(LocalAccessible* aAccessible) { if (dom::Element* elm = aAccessible->Elm()) { nsAutoString id; - if (elm->GetAttr(nsGkAtoms::aria_activedescendant, id)) { - dom::Element* activeDescendantElm = IDRefsIterator::GetElem(elm, id); - if (activeDescendantElm) { - LocalAccessible* activeDescendant = GetAccessible(activeDescendantElm); - if (activeDescendant) { - RefPtr<AccEvent> event = - new AccStateChangeEvent(activeDescendant, states::ACTIVE, true); - FireDelayedEvent(event); - if (aAccessible->IsActiveWidget()) { - FocusMgr()->ActiveItemChanged(activeDescendant, false); + if (dom::Element* activeDescendantElm = + nsCoreUtils::GetAriaActiveDescendantElement(elm)) { + LocalAccessible* activeDescendant = GetAccessible(activeDescendantElm); + if (activeDescendant) { + RefPtr<AccEvent> event = + new AccStateChangeEvent(activeDescendant, states::ACTIVE, true); + FireDelayedEvent(event); + if (aAccessible->IsActiveWidget()) { + FocusMgr()->ActiveItemChanged(activeDescendant, false); #ifdef A11Y_LOG - if (logging::IsEnabled(logging::eFocus)) { - logging::ActiveItemChangeCausedBy("ARIA activedescedant changed", - activeDescendant); - } -#endif + if (logging::IsEnabled(logging::eFocus)) { + logging::ActiveItemChangeCausedBy("ARIA activedescedant changed", + activeDescendant); } - return; +#endif } + return; } } @@ -991,6 +1016,11 @@ void DocAccessible::ElementStateChanged(dom::Document* aDocument, new AccStateChangeEvent(accessible, states::DEFAULT); FireDelayedEvent(event); } + + if (aStateMask.HasState(dom::ElementState::INDETERMINATE)) { + RefPtr<AccEvent> event = new AccStateChangeEvent(accessible, states::MIXED); + FireDelayedEvent(event); + } } void DocAccessible::CharacterDataWillChange(nsIContent* aContent, @@ -1150,6 +1180,7 @@ void DocAccessible::BindToDocument(LocalAccessible* aAccessible, if (aAccessible->HasOwnContent()) { AddDependentIDsFor(aAccessible); + AddDependentElementsFor(aAccessible); nsIContent* content = aAccessible->GetContent(); if (content->IsElement() && @@ -1769,6 +1800,61 @@ void DocAccessible::RemoveDependentIDsFor(LocalAccessible* aRelProvider, } } +void DocAccessible::AddDependentElementsFor(LocalAccessible* aRelProvider, + nsAtom* aRelAttr) { + dom::Element* providerEl = aRelProvider->Elm(); + if (!providerEl) { + return; + } + for (nsStaticAtom* attr : kSingleElementRelationIdlAttrs) { + if (aRelAttr && aRelAttr != attr) { + continue; + } + if (dom::Element* targetEl = + providerEl->GetExplicitlySetAttrElement(attr)) { + AttrRelProviders& providers = + mDependentElementsMap.LookupOrInsert(targetEl); + AttrRelProvider* provider = new AttrRelProvider(attr, providerEl); + providers.AppendElement(provider); + } + // If the relation attribute was given, we've already handled it. We don't + // have anything else to check. + if (aRelAttr) { + break; + } + } +} + +void DocAccessible::RemoveDependentElementsFor(LocalAccessible* aRelProvider, + nsAtom* aRelAttr) { + dom::Element* providerEl = aRelProvider->Elm(); + if (!providerEl) { + return; + } + for (nsStaticAtom* attr : kSingleElementRelationIdlAttrs) { + if (aRelAttr && aRelAttr != attr) { + continue; + } + if (dom::Element* targetEl = + providerEl->GetExplicitlySetAttrElement(attr)) { + if (auto providers = mDependentElementsMap.Lookup(targetEl)) { + providers.Data().RemoveElementsBy([attr, + providerEl](const auto& provider) { + return provider->mRelAttr == attr && provider->mContent == providerEl; + }); + if (providers.Data().IsEmpty()) { + providers.Remove(); + } + } + } + // If the relation attribute was given, we've already handled it. We don't + // have anything else to check. + if (aRelAttr) { + break; + } + } +} + bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement, nsAtom* aAttribute) { if (aAttribute == nsGkAtoms::role) { @@ -2003,7 +2089,7 @@ bool InsertIterator::Next() { return false; } -void DocAccessible::MaybeFireEventsForChangedPopover(LocalAccessible *aAcc) { +void DocAccessible::MaybeFireEventsForChangedPopover(LocalAccessible* aAcc) { dom::Element* el = aAcc->Elm(); if (!el || !el->IsHTMLElement() || !el->HasAttr(nsGkAtoms::popover)) { return; // Not a popover. @@ -2620,6 +2706,7 @@ void DocAccessible::UncacheChildrenInSubtree(LocalAccessible* aRoot) { MaybeFireEventsForChangedPopover(aRoot); aRoot->mStateFlags |= eIsNotInDocument; RemoveDependentIDsFor(aRoot); + RemoveDependentElementsFor(aRoot); // The parent of the removed subtree is about to be cleared, so we must do // this here rather than in LocalAccessible::UnbindFromParent because we need @@ -2757,7 +2844,7 @@ void DocAccessible::DispatchScrollingEvent(nsINode* aTarget, void DocAccessible::ARIAActiveDescendantIDMaybeMoved( LocalAccessible* aAccessible) { LocalAccessible* widget = nullptr; - if (aAccessible->IsActiveDescendant(&widget) && widget) { + if (aAccessible->IsActiveDescendantId(&widget) && widget) { // The active descendant might have just been inserted and may not be in the // tree yet. Therefore, schedule this async to ensure the tree is up to // date. @@ -2845,3 +2932,22 @@ void DocAccessible::MaybeHandleChangeToHiddenNameOrDescription( } } } + +void DocAccessible::AttrElementWillChange(dom::Element* aElement, + nsAtom* aAttr) { + MOZ_ASSERT(!sIsAttrElementChanging); + AttributeWillChange(aElement, kNameSpaceID_None, aAttr, + dom::MutationEvent_Binding::MODIFICATION); + // We might get notified about a related content attribute change. Ignore + // it. + sIsAttrElementChanging = true; +} + +void DocAccessible::AttrElementChanged(dom::Element* aElement, nsAtom* aAttr) { + MOZ_ASSERT(sIsAttrElementChanging); + // The element has changed and the content attribute change notifications + // (if any) have been sent. + sIsAttrElementChanging = false; + AttributeChanged(aElement, kNameSpaceID_None, aAttr, + dom::MutationEvent_Binding::MODIFICATION, nullptr); +} |