forked from mirrors/gecko-dev
Bug 1879255 part 2: Add a dependent elements map to DocAccessible and use it for popoverTargetElement. r=morgan
As well as getting an invoker's popover target, we need to be able to do the reverse: get a popover's invokers. We can already do this when the popovertarget content attribute is set to a string id using the dependent ids map. However, the popover target can also be explicitly set to a DOM element using the .popoverTargetElement WebIDL attribute. For this, we need a new map which maps from target elements instead of target ids. RelatedAccIterator has also been updated to use this map. DocAccessible::QueueCacheUpdateForDependentRelations had to be updated as well. Rather than duplicating logic, RelatedAccIterator has been taught how to optionally return all relations and QueueCacheUpdateForDependentRelations now uses RelatedAccIterator. Differential Revision: https://phabricator.services.mozilla.com/D201661
This commit is contained in:
parent
33de5c2029
commit
df9bf2b1df
8 changed files with 376 additions and 37 deletions
|
|
@ -71,7 +71,12 @@ AccIterator::IteratorState::IteratorState(const LocalAccessible* aParent,
|
||||||
RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument,
|
RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument,
|
||||||
nsIContent* aDependentContent,
|
nsIContent* aDependentContent,
|
||||||
nsAtom* aRelAttr)
|
nsAtom* aRelAttr)
|
||||||
: mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr), mIndex(0) {
|
: mDocument(aDocument),
|
||||||
|
mDependentContent(aDependentContent),
|
||||||
|
mRelAttr(aRelAttr),
|
||||||
|
mProviders(nullptr),
|
||||||
|
mIndex(0),
|
||||||
|
mIsWalkingDependentElements(false) {
|
||||||
nsAutoString id;
|
nsAutoString id;
|
||||||
if (aDependentContent->IsElement() &&
|
if (aDependentContent->IsElement() &&
|
||||||
aDependentContent->AsElement()->GetAttr(nsGkAtoms::id, id)) {
|
aDependentContent->AsElement()->GetAttr(nsGkAtoms::id, id)) {
|
||||||
|
|
@ -80,26 +85,57 @@ RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument,
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalAccessible* RelatedAccIterator::Next() {
|
LocalAccessible* RelatedAccIterator::Next() {
|
||||||
if (!mProviders) return nullptr;
|
if (!mProviders || mIndex == mProviders->Length()) {
|
||||||
|
if (mIsWalkingDependentElements) {
|
||||||
|
// We've walked both dependent ids and dependent elements, so there are
|
||||||
|
// no more targets.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// We've returned all dependent ids, but there might be dependent elements
|
||||||
|
// too. Walk those next.
|
||||||
|
mIsWalkingDependentElements = true;
|
||||||
|
mIndex = 0;
|
||||||
|
if (auto providers =
|
||||||
|
mDocument->mDependentElementsMap.Lookup(mDependentContent)) {
|
||||||
|
mProviders = &providers.Data();
|
||||||
|
} else {
|
||||||
|
mProviders = nullptr;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (mIndex < mProviders->Length()) {
|
while (mIndex < mProviders->Length()) {
|
||||||
const auto& provider = (*mProviders)[mIndex++];
|
const auto& provider = (*mProviders)[mIndex++];
|
||||||
|
|
||||||
// Return related accessible for the given attribute.
|
// Return related accessible for the given attribute.
|
||||||
if (provider->mRelAttr == mRelAttr) {
|
if (mRelAttr && provider->mRelAttr != mRelAttr) {
|
||||||
LocalAccessible* related = mDocument->GetAccessible(provider->mContent);
|
continue;
|
||||||
if (related) {
|
}
|
||||||
return related;
|
// 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(
|
||||||
|
mDependentContent, provider->mContent)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LocalAccessible* related = mDocument->GetAccessible(provider->mContent);
|
||||||
|
if (related) {
|
||||||
|
return related;
|
||||||
|
}
|
||||||
|
|
||||||
// If the document content is pointed by relation then return the
|
// If the document content is pointed by relation then return the
|
||||||
// document itself.
|
// document itself.
|
||||||
if (provider->mContent == mDocument->GetContent()) {
|
if (provider->mContent == mDocument->GetContent()) {
|
||||||
return mDocument;
|
return mDocument;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We exhausted mProviders without returning anything.
|
||||||
|
if (!mIsWalkingDependentElements) {
|
||||||
|
// Call this function again to start walking the dependent elements.
|
||||||
|
return Next();
|
||||||
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,9 @@ class AccIterator : public AccIterable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to traverse through related accessibles that are pointing to the given
|
* Allows to traverse through related accessibles that are pointing to the given
|
||||||
* dependent accessible by relation attribute.
|
* dependent accessible by relation attribute. This is typically used to query
|
||||||
|
* implicit reverse relations; e.g. calculating the LABEL_FOR relation for a
|
||||||
|
* label where that label was referenced using aria-labelledby.
|
||||||
*/
|
*/
|
||||||
class RelatedAccIterator : public AccIterable {
|
class RelatedAccIterator : public AccIterable {
|
||||||
public:
|
public:
|
||||||
|
|
@ -79,7 +81,7 @@ class RelatedAccIterator : public AccIterable {
|
||||||
* @param aDependentContent [in] the content of dependent accessible that
|
* @param aDependentContent [in] the content of dependent accessible that
|
||||||
* relations were requested for
|
* relations were requested for
|
||||||
* @param aRelAttr [in] relation attribute that relations are
|
* @param aRelAttr [in] relation attribute that relations are
|
||||||
* pointed by
|
* pointed by, null for all relations
|
||||||
*/
|
*/
|
||||||
RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
|
RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
|
||||||
nsAtom* aRelAttr);
|
nsAtom* aRelAttr);
|
||||||
|
|
@ -97,9 +99,11 @@ class RelatedAccIterator : public AccIterable {
|
||||||
RelatedAccIterator& operator=(const RelatedAccIterator&);
|
RelatedAccIterator& operator=(const RelatedAccIterator&);
|
||||||
|
|
||||||
DocAccessible* mDocument;
|
DocAccessible* mDocument;
|
||||||
|
nsIContent* mDependentContent;
|
||||||
nsAtom* mRelAttr;
|
nsAtom* mRelAttr;
|
||||||
DocAccessible::AttrRelProviders* mProviders;
|
DocAccessible::AttrRelProviders* mProviders;
|
||||||
uint32_t mIndex;
|
uint32_t mIndex;
|
||||||
|
bool mIsWalkingDependentElements;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -620,3 +620,20 @@ bool nsCoreUtils::IsDocumentVisibleConsideringInProcessAncestors(
|
||||||
} while ((parent = parent->GetInProcessParentDocument()));
|
} while ((parent = parent->GetInProcessParentDocument()));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool nsCoreUtils::IsDescendantOfAnyShadowIncludingAncestor(
|
||||||
|
nsINode* aDescendant, nsINode* aStartAncestor) {
|
||||||
|
const nsINode* descRoot = aDescendant->SubtreeRoot();
|
||||||
|
nsINode* ancRoot = aStartAncestor->SubtreeRoot();
|
||||||
|
for (;;) {
|
||||||
|
if (ancRoot == descRoot) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
auto* shadow = mozilla::dom::ShadowRoot::FromNode(ancRoot);
|
||||||
|
if (!shadow || !shadow->GetHost()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ancRoot = shadow->GetHost()->SubtreeRoot();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,6 +324,13 @@ class nsCoreUtils {
|
||||||
*/
|
*/
|
||||||
static bool IsDocumentVisibleConsideringInProcessAncestors(
|
static bool IsDocumentVisibleConsideringInProcessAncestors(
|
||||||
const Document* aDocument);
|
const Document* aDocument);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if `aDescendant` is a descendant of any of `aStartAncestor`'s
|
||||||
|
* shadow-including ancestors.
|
||||||
|
*/
|
||||||
|
static bool IsDescendantOfAnyShadowIncludingAncestor(nsINode* aDescendant,
|
||||||
|
nsINode* aStartAncestor);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,9 @@ static nsStaticAtom* const kRelationAttrs[] = {
|
||||||
|
|
||||||
static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
|
static const uint32_t kRelationAttrsLen = ArrayLength(kRelationAttrs);
|
||||||
|
|
||||||
|
static nsStaticAtom* const kSingleElementRelationIdlAttrs[] = {
|
||||||
|
nsGkAtoms::popovertarget};
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Constructor/desctructor
|
// Constructor/desctructor
|
||||||
|
|
||||||
|
|
@ -383,25 +386,25 @@ void DocAccessible::QueueCacheUpdate(LocalAccessible* aAcc,
|
||||||
|
|
||||||
void DocAccessible::QueueCacheUpdateForDependentRelations(
|
void DocAccessible::QueueCacheUpdateForDependentRelations(
|
||||||
LocalAccessible* aAcc) {
|
LocalAccessible* aAcc) {
|
||||||
if (!mIPCDoc || !aAcc || !aAcc->Elm() || !aAcc->IsInDocument() ||
|
if (!mIPCDoc || !aAcc || !aAcc->IsInDocument() || aAcc->IsDefunct()) {
|
||||||
aAcc->IsDefunct()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
nsAutoString ID;
|
dom::Element* el = aAcc->Elm();
|
||||||
aAcc->DOMNodeID(ID);
|
if (!el) {
|
||||||
if (AttrRelProviders* list = GetRelProviders(aAcc->Elm(), ID)) {
|
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 have their rel cache entries updated.
|
// We call this function when we've noticed an ID change, or when an acc
|
||||||
for (const auto& provider : *list) {
|
// is getting bound to its document. We need to ensure any existing accs
|
||||||
LocalAccessible* relatedAcc = GetAccessible(provider->mContent);
|
// that depend on this acc's ID or Element have their relation cache entries
|
||||||
if (!relatedAcc || relatedAcc->IsDefunct() ||
|
// updated.
|
||||||
!relatedAcc->IsInDocument() ||
|
RelatedAccIterator iter(this, el, nullptr);
|
||||||
mInsertedAccessibles.Contains(relatedAcc)) {
|
while (LocalAccessible* relatedAcc = iter.Next()) {
|
||||||
continue;
|
if (relatedAcc->IsDefunct() || !relatedAcc->IsInDocument() ||
|
||||||
}
|
mInsertedAccessibles.Contains(relatedAcc)) {
|
||||||
QueueCacheUpdate(relatedAcc, CacheDomain::Relations);
|
continue;
|
||||||
}
|
}
|
||||||
|
QueueCacheUpdate(relatedAcc, CacheDomain::Relations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -507,6 +510,7 @@ void DocAccessible::Shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mDependentIDsHashes.Clear();
|
mDependentIDsHashes.Clear();
|
||||||
|
mDependentElementsMap.Clear();
|
||||||
mNodeToAccessibleMap.Clear();
|
mNodeToAccessibleMap.Clear();
|
||||||
|
|
||||||
mAnchorJumpElm = nullptr;
|
mAnchorJumpElm = nullptr;
|
||||||
|
|
@ -1150,6 +1154,7 @@ void DocAccessible::BindToDocument(LocalAccessible* aAccessible,
|
||||||
|
|
||||||
if (aAccessible->HasOwnContent()) {
|
if (aAccessible->HasOwnContent()) {
|
||||||
AddDependentIDsFor(aAccessible);
|
AddDependentIDsFor(aAccessible);
|
||||||
|
AddDependentElementsFor(aAccessible);
|
||||||
|
|
||||||
nsIContent* content = aAccessible->GetContent();
|
nsIContent* content = aAccessible->GetContent();
|
||||||
if (content->IsElement() &&
|
if (content->IsElement() &&
|
||||||
|
|
@ -1769,6 +1774,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,
|
bool DocAccessible::UpdateAccessibleOnAttrChange(dom::Element* aElement,
|
||||||
nsAtom* aAttribute) {
|
nsAtom* aAttribute) {
|
||||||
if (aAttribute == nsGkAtoms::role) {
|
if (aAttribute == nsGkAtoms::role) {
|
||||||
|
|
@ -2003,7 +2063,7 @@ bool InsertIterator::Next() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DocAccessible::MaybeFireEventsForChangedPopover(LocalAccessible *aAcc) {
|
void DocAccessible::MaybeFireEventsForChangedPopover(LocalAccessible* aAcc) {
|
||||||
dom::Element* el = aAcc->Elm();
|
dom::Element* el = aAcc->Elm();
|
||||||
if (!el || !el->IsHTMLElement() || !el->HasAttr(nsGkAtoms::popover)) {
|
if (!el || !el->IsHTMLElement() || !el->HasAttr(nsGkAtoms::popover)) {
|
||||||
return; // Not a popover.
|
return; // Not a popover.
|
||||||
|
|
@ -2620,6 +2680,7 @@ void DocAccessible::UncacheChildrenInSubtree(LocalAccessible* aRoot) {
|
||||||
MaybeFireEventsForChangedPopover(aRoot);
|
MaybeFireEventsForChangedPopover(aRoot);
|
||||||
aRoot->mStateFlags |= eIsNotInDocument;
|
aRoot->mStateFlags |= eIsNotInDocument;
|
||||||
RemoveDependentIDsFor(aRoot);
|
RemoveDependentIDsFor(aRoot);
|
||||||
|
RemoveDependentElementsFor(aRoot);
|
||||||
|
|
||||||
// The parent of the removed subtree is about to be cleared, so we must do
|
// 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
|
// this here rather than in LocalAccessible::UnbindFromParent because we need
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ class DocAccessible : public HyperTextAccessible,
|
||||||
void QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain);
|
void QueueCacheUpdate(LocalAccessible* aAcc, uint64_t aNewDomain);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Walks the mDependentIDsHashes list for the given accessible and
|
* Walks the dependent ids and elements maps for the given accessible and
|
||||||
* queues a CacheDomain::Relations cache update fore each related acc.
|
* queues a CacheDomain::Relations cache update fore each related acc.
|
||||||
* We call this when we observe an ID mutation or when an acc is bound
|
* We call this when we observe an ID mutation or when an acc is bound
|
||||||
* to its document.
|
* to its document.
|
||||||
|
|
@ -485,6 +485,35 @@ class DocAccessible : public HyperTextAccessible,
|
||||||
void RemoveDependentIDsFor(LocalAccessible* aRelProvider,
|
void RemoveDependentIDsFor(LocalAccessible* aRelProvider,
|
||||||
nsAtom* aRelAttr = nullptr);
|
nsAtom* aRelAttr = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add dependent elements targeted by a relation attribute on an accessible
|
||||||
|
* element to the dependent elements cache. This is used for reflected IDL
|
||||||
|
* attributes which return DOM elements and reflect a content attribute, where
|
||||||
|
* the IDL attribute has been set to an element. For example, if the
|
||||||
|
* .popoverTargetElement IDL attribute is set to an element using JS, the
|
||||||
|
* target element will be added to the dependent elements cache. If the
|
||||||
|
* relation attribute is not specified, then all relation attributes are
|
||||||
|
* checked.
|
||||||
|
*
|
||||||
|
* @param aRelProvider [in] the accessible with the relation IDL attribute.
|
||||||
|
* @param aRelAttr [in, optional] the name of the reflected content attribute.
|
||||||
|
* For example, for the popoverTargetElement IDL attribute, this would be
|
||||||
|
* "popovertarget".
|
||||||
|
*/
|
||||||
|
void AddDependentElementsFor(LocalAccessible* aRelProvider,
|
||||||
|
nsAtom* aRelAttr = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove dependent elements targeted by a relation attribute on an accessible
|
||||||
|
* element from the dependent elements cache. If the relation attribute is
|
||||||
|
* not specified, then all relation attributes are checked.
|
||||||
|
*
|
||||||
|
* @param aRelProvider [in] the accessible with the relation IDL attribute.
|
||||||
|
* @param aRelAttr [in, optional] the name of the reflected content attribute.
|
||||||
|
*/
|
||||||
|
void RemoveDependentElementsFor(LocalAccessible* aRelProvider,
|
||||||
|
nsAtom* aRelAttr = nullptr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update or recreate an accessible depending on a changed attribute.
|
* Update or recreate an accessible depending on a changed attribute.
|
||||||
*
|
*
|
||||||
|
|
@ -727,12 +756,35 @@ class DocAccessible : public HyperTextAccessible,
|
||||||
void RemoveRelProvidersIfEmpty(dom::Element* aElement, const nsAString& aID);
|
void RemoveRelProvidersIfEmpty(dom::Element* aElement, const nsAString& aID);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cache of IDs pointed by relation attributes.
|
* A map used to look up the target node for an implicit reverse relation
|
||||||
|
* where the target of the explicit relation is specified as an id.
|
||||||
|
* For example:
|
||||||
|
* <div id="label">Name:</div><input aria-labelledby="label">
|
||||||
|
* The div should get a LABEL_FOR relation targeting the input. To facilitate
|
||||||
|
* that, mDependentIDsHashes maps from "label" to an AttrRelProvider
|
||||||
|
* specifying aria-labelledby and the input. Because ids are scoped to the
|
||||||
|
* nearest ancestor document or shadow root, mDependentIDsHashes maps from the
|
||||||
|
* DocumentOrShadowRoot first.
|
||||||
*/
|
*/
|
||||||
nsClassHashtable<nsPtrHashKey<dom::DocumentOrShadowRoot>,
|
nsClassHashtable<nsPtrHashKey<dom::DocumentOrShadowRoot>,
|
||||||
DependentIDsHashtable>
|
DependentIDsHashtable>
|
||||||
mDependentIDsHashes;
|
mDependentIDsHashes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map used to look up the target element for an implicit reverse relation
|
||||||
|
* where the target of the explicit relation is also specified as an element.
|
||||||
|
* This is similar to mDependentIDsHashes, except that this is used when a
|
||||||
|
* DOM property is used to set the relation target element directly, rather
|
||||||
|
* than using an id. For example:
|
||||||
|
* <button>More info</button><div popover>Some info</div>
|
||||||
|
* The button's .popoverTargetElement property is set to the div so that the
|
||||||
|
* button invokes the popover.
|
||||||
|
* To facilitate finding the invoker given the popover, mDependentElementsMap
|
||||||
|
* maps from the div to an AttrRelProvider specifying popovertarget and the
|
||||||
|
* button.
|
||||||
|
*/
|
||||||
|
nsTHashMap<nsIContent*, AttrRelProviders> mDependentElementsMap;
|
||||||
|
|
||||||
friend class RelatedAccIterator;
|
friend class RelatedAccIterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -293,7 +293,7 @@ addAccessibleTask(
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test details relations on popovers and their invokers.
|
* Test details relations for the popovertarget content attribute.
|
||||||
*/
|
*/
|
||||||
addAccessibleTask(
|
addAccessibleTask(
|
||||||
`
|
`
|
||||||
|
|
@ -304,7 +304,7 @@ addAccessibleTask(
|
||||||
<div id="popover" popover>popover</div>
|
<div id="popover" popover>popover</div>
|
||||||
<div id="details">details</div>
|
<div id="details">details</div>
|
||||||
`,
|
`,
|
||||||
async function testPopover(browser, docAcc) {
|
async function testPopoverContent(browser, docAcc) {
|
||||||
// The popover is hidden, so nothing should be referring to it.
|
// The popover is hidden, so nothing should be referring to it.
|
||||||
const hide = findAccessibleChildByID(docAcc, "hide");
|
const hide = findAccessibleChildByID(docAcc, "hide");
|
||||||
await testCachedRelation(hide, RELATION_DETAILS, []);
|
await testCachedRelation(hide, RELATION_DETAILS, []);
|
||||||
|
|
@ -330,7 +330,7 @@ addAccessibleTask(
|
||||||
await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
|
await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
|
||||||
await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
|
await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
|
||||||
|
|
||||||
info("Setting toggle2 popovertargetaction");
|
info("Setting toggle2 popovertarget");
|
||||||
await invokeSetAttribute(browser, "toggle2", "popovertarget", "popover");
|
await invokeSetAttribute(browser, "toggle2", "popovertarget", "popover");
|
||||||
await testCachedRelation(toggle2, RELATION_DETAILS, popover);
|
await testCachedRelation(toggle2, RELATION_DETAILS, popover);
|
||||||
await testCachedRelation(popover, RELATION_DETAILS_FOR, [toggle1, toggle2]);
|
await testCachedRelation(popover, RELATION_DETAILS_FOR, [toggle1, toggle2]);
|
||||||
|
|
@ -364,3 +364,88 @@ addAccessibleTask(
|
||||||
},
|
},
|
||||||
{ chrome: false, topLevel: true }
|
{ chrome: false, topLevel: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test details relations for the popoverTargetElement WebIDL attribute.
|
||||||
|
*/
|
||||||
|
addAccessibleTask(
|
||||||
|
`
|
||||||
|
<button id="toggle1">toggle1</button>
|
||||||
|
<button id="toggle2">toggle2</button>
|
||||||
|
between
|
||||||
|
<div id="popover1" popover>popover1</div>
|
||||||
|
<button id="toggle3">toggle3</button>
|
||||||
|
<div id="shadowHost"><template shadowrootmode="open">
|
||||||
|
<button id="toggle4">toggle4</button>
|
||||||
|
between
|
||||||
|
<div id="popover2" popover>popover2</div>
|
||||||
|
<button id="toggle5">toggle5</button>
|
||||||
|
</template></div>
|
||||||
|
<script>
|
||||||
|
const toggle1 = document.getElementById("toggle1");
|
||||||
|
const toggle2 = document.getElementById("toggle2");
|
||||||
|
const popover1 = document.getElementById("popover1");
|
||||||
|
toggle1.popoverTargetElement = popover1;
|
||||||
|
toggle2.popoverTargetElement = popover1;
|
||||||
|
const toggle3 = document.getElementById("toggle3");
|
||||||
|
const shadow = document.getElementById("shadowHost").shadowRoot;
|
||||||
|
const toggle4 = shadow.getElementById("toggle4");
|
||||||
|
const popover2 = shadow.getElementById("popover2");
|
||||||
|
toggle3.popoverTargetElement = popover2;
|
||||||
|
toggle4.popoverTargetElement = popover2;
|
||||||
|
const toggle5 = shadow.getElementById("toggle5");
|
||||||
|
toggle5.popoverTargetElement = popover1;
|
||||||
|
</script>
|
||||||
|
`,
|
||||||
|
async function testPopoverIdl(browser, docAcc) {
|
||||||
|
// No popover is showing, so there shouldn't be any details relations.
|
||||||
|
const toggle1 = findAccessibleChildByID(docAcc, "toggle1");
|
||||||
|
await testCachedRelation(toggle1, RELATION_DETAILS, []);
|
||||||
|
const toggle2 = findAccessibleChildByID(docAcc, "toggle2");
|
||||||
|
await testCachedRelation(toggle2, RELATION_DETAILS, []);
|
||||||
|
const toggle3 = findAccessibleChildByID(docAcc, "toggle3");
|
||||||
|
await testCachedRelation(toggle3, RELATION_DETAILS, []);
|
||||||
|
const toggle4 = findAccessibleChildByID(docAcc, "toggle4");
|
||||||
|
await testCachedRelation(toggle4, RELATION_DETAILS, []);
|
||||||
|
const toggle5 = findAccessibleChildByID(docAcc, "toggle5");
|
||||||
|
await testCachedRelation(toggle5, RELATION_DETAILS, []);
|
||||||
|
|
||||||
|
info("Showing popover1");
|
||||||
|
let shown = waitForEvent(EVENT_SHOW, "popover1");
|
||||||
|
toggle1.doAction(0);
|
||||||
|
const popover1 = (await shown).accessible;
|
||||||
|
await testCachedRelation(toggle1, RELATION_DETAILS, popover1);
|
||||||
|
await testCachedRelation(toggle2, RELATION_DETAILS, popover1);
|
||||||
|
// toggle5 is inside the shadow DOM and popover1 is outside, so the target
|
||||||
|
// is valid.
|
||||||
|
await testCachedRelation(toggle5, RELATION_DETAILS, popover1);
|
||||||
|
await testCachedRelation(popover1, RELATION_DETAILS_FOR, [
|
||||||
|
toggle1,
|
||||||
|
toggle2,
|
||||||
|
toggle5,
|
||||||
|
]);
|
||||||
|
info("Hiding popover1");
|
||||||
|
let hidden = waitForEvent(EVENT_HIDE, popover1);
|
||||||
|
toggle1.doAction(0);
|
||||||
|
await hidden;
|
||||||
|
await testCachedRelation(toggle1, RELATION_DETAILS, []);
|
||||||
|
await testCachedRelation(toggle2, RELATION_DETAILS, []);
|
||||||
|
await testCachedRelation(toggle5, RELATION_DETAILS, []);
|
||||||
|
|
||||||
|
info("Showing popover2");
|
||||||
|
shown = waitForEvent(EVENT_SHOW, "popover2");
|
||||||
|
toggle4.doAction(0);
|
||||||
|
const popover2 = (await shown).accessible;
|
||||||
|
// toggle4 is in the same shadow DOM as popover2.
|
||||||
|
await testCachedRelation(toggle4, RELATION_DETAILS, popover2);
|
||||||
|
// toggle3 is outside popover2's shadow DOM, so the target isn't valid.
|
||||||
|
await testCachedRelation(toggle3, RELATION_DETAILS, []);
|
||||||
|
await testCachedRelation(popover2, RELATION_DETAILS_FOR, [toggle4]);
|
||||||
|
info("Hiding popover2");
|
||||||
|
hidden = waitForEvent(EVENT_HIDE, popover2);
|
||||||
|
toggle4.doAction(0);
|
||||||
|
await hidden;
|
||||||
|
await testCachedRelation(toggle4, RELATION_DETAILS, []);
|
||||||
|
},
|
||||||
|
{ chrome: true, topLevel: true }
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
requestLongerTimeout(2);
|
||||||
|
|
||||||
/* import-globals-from ../../mochitest/role.js */
|
/* import-globals-from ../../mochitest/role.js */
|
||||||
/* import-globals-from ../../mochitest/states.js */
|
/* import-globals-from ../../mochitest/states.js */
|
||||||
|
|
@ -484,7 +485,7 @@ addAccessibleTask(
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test caching of the expanded state for popover target element.
|
* Test caching of the expanded state for the popovertarget content attribute.
|
||||||
*/
|
*/
|
||||||
addAccessibleTask(
|
addAccessibleTask(
|
||||||
`
|
`
|
||||||
|
|
@ -550,3 +551,79 @@ addAccessibleTask(
|
||||||
},
|
},
|
||||||
{ chrome: true, topLevel: true, remoteIframe: true }
|
{ chrome: true, topLevel: true, remoteIframe: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test caching of the expanded state for the popoverTargetElement WebIDL
|
||||||
|
* attribute.
|
||||||
|
*/
|
||||||
|
addAccessibleTask(
|
||||||
|
`
|
||||||
|
<button id="toggle1">toggle</button>
|
||||||
|
<div id="popover1" popover>popover1</div>
|
||||||
|
<button id="toggle2">toggle2</button>
|
||||||
|
<button id="toggle3">toggle3</button>
|
||||||
|
<div id="shadowHost"><template shadowrootmode="open">
|
||||||
|
<button id="toggle4">toggle4</button>
|
||||||
|
<div id="popover2" popover>popover2</div>
|
||||||
|
<button id="toggle5">toggle5</button>
|
||||||
|
</template></div>
|
||||||
|
<script>
|
||||||
|
const toggle1 = document.getElementById("toggle1");
|
||||||
|
const popover1 = document.getElementById("popover1");
|
||||||
|
toggle1.popoverTargetElement = popover1;
|
||||||
|
toggle2.popoverTargetElement = popover1;
|
||||||
|
const toggle3 = document.getElementById("toggle3");
|
||||||
|
const shadow = document.getElementById("shadowHost").shadowRoot;
|
||||||
|
const toggle4 = shadow.getElementById("toggle4");
|
||||||
|
const popover2 = shadow.getElementById("popover2");
|
||||||
|
toggle3.popoverTargetElement = popover2;
|
||||||
|
toggle4.popoverTargetElement = popover2;
|
||||||
|
const toggle5 = shadow.getElementById("toggle5");
|
||||||
|
toggle5.popoverTargetElement = popover1;
|
||||||
|
</script>
|
||||||
|
`,
|
||||||
|
async function (browser, docAcc) {
|
||||||
|
const toggle1 = findAccessibleChildByID(docAcc, "toggle1");
|
||||||
|
testStates(toggle1, STATE_COLLAPSED);
|
||||||
|
const toggle2 = findAccessibleChildByID(docAcc, "toggle2");
|
||||||
|
testStates(toggle2, STATE_COLLAPSED);
|
||||||
|
const toggle5 = findAccessibleChildByID(docAcc, "toggle5");
|
||||||
|
// toggle5 is inside the shadow DOM and popover1 is outside, so the target
|
||||||
|
// is valid.
|
||||||
|
testStates(toggle5, STATE_COLLAPSED);
|
||||||
|
|
||||||
|
// Changes to the popover should fire events on all invokers.
|
||||||
|
const changeEvents = [
|
||||||
|
[EVENT_STATE_CHANGE, toggle1],
|
||||||
|
[EVENT_STATE_CHANGE, toggle2],
|
||||||
|
[EVENT_STATE_CHANGE, toggle5],
|
||||||
|
];
|
||||||
|
info("Showing popover1");
|
||||||
|
let changed = waitForEvents(changeEvents);
|
||||||
|
toggle1.doAction(0);
|
||||||
|
await changed;
|
||||||
|
testStates(toggle1, STATE_EXPANDED);
|
||||||
|
testStates(toggle2, STATE_EXPANDED);
|
||||||
|
|
||||||
|
info("Hiding popover1");
|
||||||
|
changed = waitForEvents(changeEvents);
|
||||||
|
toggle1.doAction(0);
|
||||||
|
await changed;
|
||||||
|
testStates(toggle1, STATE_COLLAPSED);
|
||||||
|
testStates(toggle2, STATE_COLLAPSED);
|
||||||
|
|
||||||
|
const toggle3 = findAccessibleChildByID(docAcc, "toggle3");
|
||||||
|
// toggle3 is outside popover2's shadow DOM, so the target isn't valid.
|
||||||
|
testStates(
|
||||||
|
toggle3,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
STATE_EXPANDED | STATE_COLLAPSED,
|
||||||
|
EXT_STATE_EXPANDABLE
|
||||||
|
);
|
||||||
|
const toggle4 = findAccessibleChildByID(docAcc, "toggle4");
|
||||||
|
// toggle4 is in the same shadow DOM as popover2.
|
||||||
|
testStates(toggle4, STATE_COLLAPSED);
|
||||||
|
},
|
||||||
|
{ chrome: true, topLevel: true }
|
||||||
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue