/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "AccIterator.h" #include "AccGroupInfo.h" #include "ARIAMap.h" #include "DocAccessible-inl.h" #include "LocalAccessible-inl.h" #include "nsAccUtils.h" #include "XULTreeAccessible.h" #include "mozilla/a11y/DocAccessibleParent.h" #include "mozilla/dom/DocumentOrShadowRoot.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/ElementInternals.h" #include "mozilla/dom/HTMLLabelElement.h" #include "mozilla/dom/TreeOrderedArrayInlines.h" using namespace mozilla; using namespace mozilla::a11y; //////////////////////////////////////////////////////////////////////////////// // AccIterator //////////////////////////////////////////////////////////////////////////////// AccIterator::AccIterator(const LocalAccessible* aAccessible, filters::FilterFuncPtr aFilterFunc) : mFilterFunc(aFilterFunc) { mState = new IteratorState(aAccessible); } AccIterator::~AccIterator() { while (mState) { IteratorState* tmp = mState; mState = tmp->mParentState; delete tmp; } } LocalAccessible* AccIterator::Next() { while (mState) { LocalAccessible* child = mState->mParent->LocalChildAt(mState->mIndex++); if (!child) { IteratorState* tmp = mState; mState = mState->mParentState; delete tmp; continue; } uint32_t result = mFilterFunc(child); if (result & filters::eMatch) return child; if (!(result & filters::eSkipSubtree)) { IteratorState* childState = new IteratorState(child, mState); mState = childState; } } return nullptr; } //////////////////////////////////////////////////////////////////////////////// // nsAccIterator::IteratorState AccIterator::IteratorState::IteratorState(const LocalAccessible* aParent, IteratorState* mParentState) : mParent(aParent), mIndex(0), mParentState(mParentState) {} //////////////////////////////////////////////////////////////////////////////// // RelatedAccIterator //////////////////////////////////////////////////////////////////////////////// RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent, nsAtom* aRelAttr) : mDocument(aDocument), mDependentContentOrShadowHost(aDependentContent), mRelAttr(aRelAttr), mProviders(nullptr), mIndex(0), mIsWalkingDependentElements(false) { mProviders = GetIdRelProvidersFor(mDependentContentOrShadowHost); } DocAccessible::AttrRelProviders* RelatedAccIterator::GetIdRelProvidersFor( nsIContent* aContent) { if (!aContent->IsElement() || !aContent->HasID()) { return nullptr; } return mDocument->GetRelProviders(aContent->AsElement(), aContent->GetID()); } LocalAccessible* RelatedAccIterator::Next() { if (!mProviders || mIndex == mProviders->Length()) { mIndex = 0; mProviders = nullptr; if (!mIsWalkingDependentElements) { // We've returned all dependent ids, but there might be dependent elements // too. Walk those next. mIsWalkingDependentElements = true; if (auto providers = mDocument->mDependentElementsMap.Lookup( mDependentContentOrShadowHost)) { mProviders = &providers.Data(); } } if (!mProviders) { // We've walked both dependent ids and dependent elements, so there are // no more targets in this root. dom::ShadowRoot* shadow = mDependentContentOrShadowHost->GetContainingShadow(); dom::Element* element = dom::Element::FromNodeOrNull(mDependentContentOrShadowHost); if (shadow && element && element == shadow->GetReferenceTargetElement()) { // If we can walk up to the shadow host, do that. mDependentContentOrShadowHost = shadow->Host(); mProviders = GetIdRelProvidersFor(mDependentContentOrShadowHost); mIsWalkingDependentElements = false; // Call this function again to start walking at the next level up. return Next(); } else { // Otherwise, we've exhausted all the providers. return nullptr; } } } while (mIndex < mProviders->Length()) { const auto& provider = (*mProviders)[mIndex++]; // Return related accessible for the given attribute. if (mRelAttr && provider->mRelAttr != mRelAttr) { continue; } // If we're walking elements (not ids), the explicitly set attr-element // `mDependentContent` must be a descendant of any of the refering element // `mProvider->mContent`'s shadow-including ancestors. if (mIsWalkingDependentElements && !nsCoreUtils::IsDescendantOfAnyShadowIncludingAncestor( mDependentContentOrShadowHost, provider->mContent)) { continue; } LocalAccessible* related = mDocument->GetAccessible(provider->mContent); if (related) { return related; } // If the document content is pointed by relation then return the // document itself. if (provider->mContent == mDocument->GetContent()) { return mDocument; } } // We exhausted mProviders without returning anything. if (!mIsWalkingDependentElements) { // Call this function again to start walking the dependent elements. return Next(); } return nullptr; } //////////////////////////////////////////////////////////////////////////////// // HTMLLabelIterator //////////////////////////////////////////////////////////////////////////////// HTMLLabelIterator::HTMLLabelIterator(DocAccessible* aDocument, const LocalAccessible* aAccessible, LabelFilter aFilter) : mDocument(aDocument), mAcc(aAccessible), mLabelFilter(aFilter) {} bool HTMLLabelIterator::IsLabel(LocalAccessible* aLabel) { dom::HTMLLabelElement* labelEl = dom::HTMLLabelElement::FromNode(aLabel->GetContent()); return labelEl && labelEl->GetLabeledElementInternal() == mAcc->GetContent(); } void HTMLLabelIterator::Initialize() { // Since HTMLLabelIterator is used in computing the accessible name for // certain elements, the order in which related nodes are returned from the // iterator must match the DOM order. Since RelatedAccIterator isn't // guaranteed to match the DOM order, we don't use it here, but instead // eagerly populate a TreeOrderedArray (mRelatedNodes) and iterate over that // in successive calls to Next(). nsIContent* content = mAcc->GetContent(); dom::DocumentOrShadowRoot* root = content->GetUncomposedDocOrConnectedShadowRoot(); while (root) { if (nsAtom* id = content->GetID()) { MOZ_ASSERT(content->IsElement()); DocAccessible::AttrRelProviders* idProviders = mDocument->GetRelProviders(content->AsElement(), id); if (idProviders) { for (auto& provider : *idProviders) { if (provider->mRelAttr != nsGkAtoms::_for) { continue; } mRelatedNodes.Insert(*provider->mContent); } } } dom::ShadowRoot* shadow = content->GetContainingShadow(); dom::Element* element = content->IsElement() ? content->AsElement() : nullptr; if (shadow && element && element == shadow->GetReferenceTargetElement()) { content = shadow->Host(); root = content->GetUncomposedDocOrConnectedShadowRoot(); } else { root = nullptr; } } mInitialized = true; } LocalAccessible* HTMLLabelIterator::Next() { if (!mInitialized) { Initialize(); } // Get either