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,
|
||||
nsIContent* aDependentContent,
|
||||
nsAtom* aRelAttr)
|
||||
: mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr), mIndex(0) {
|
||||
: mDocument(aDocument),
|
||||
mDependentContent(aDependentContent),
|
||||
mRelAttr(aRelAttr),
|
||||
mProviders(nullptr),
|
||||
mIndex(0),
|
||||
mIsWalkingDependentElements(false) {
|
||||
nsAutoString id;
|
||||
if (aDependentContent->IsElement() &&
|
||||
aDependentContent->AsElement()->GetAttr(nsGkAtoms::id, id)) {
|
||||
|
|
@ -80,26 +85,57 @@ RelatedAccIterator::RelatedAccIterator(DocAccessible* aDocument,
|
|||
}
|
||||
|
||||
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()) {
|
||||
const auto& provider = (*mProviders)[mIndex++];
|
||||
|
||||
// Return related accessible for the given attribute.
|
||||
if (provider->mRelAttr == mRelAttr) {
|
||||
LocalAccessible* related = mDocument->GetAccessible(provider->mContent);
|
||||
if (related) {
|
||||
return related;
|
||||
}
|
||||
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(
|
||||
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
|
||||
// document itself.
|
||||
if (provider->mContent == mDocument->GetContent()) {
|
||||
return mDocument;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ class AccIterator : public AccIterable {
|
|||
|
||||
/**
|
||||
* 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 {
|
||||
public:
|
||||
|
|
@ -79,7 +81,7 @@ class RelatedAccIterator : public AccIterable {
|
|||
* @param aDependentContent [in] the content of dependent accessible that
|
||||
* relations were requested for
|
||||
* @param aRelAttr [in] relation attribute that relations are
|
||||
* pointed by
|
||||
* pointed by, null for all relations
|
||||
*/
|
||||
RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
|
||||
nsAtom* aRelAttr);
|
||||
|
|
@ -97,9 +99,11 @@ class RelatedAccIterator : public AccIterable {
|
|||
RelatedAccIterator& operator=(const RelatedAccIterator&);
|
||||
|
||||
DocAccessible* mDocument;
|
||||
nsIContent* mDependentContent;
|
||||
nsAtom* mRelAttr;
|
||||
DocAccessible::AttrRelProviders* mProviders;
|
||||
uint32_t mIndex;
|
||||
bool mIsWalkingDependentElements;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -620,3 +620,20 @@ bool nsCoreUtils::IsDocumentVisibleConsideringInProcessAncestors(
|
|||
} while ((parent = parent->GetInProcessParentDocument()));
|
||||
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(
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1150,6 +1154,7 @@ void DocAccessible::BindToDocument(LocalAccessible* aAccessible,
|
|||
|
||||
if (aAccessible->HasOwnContent()) {
|
||||
AddDependentIDsFor(aAccessible);
|
||||
AddDependentElementsFor(aAccessible);
|
||||
|
||||
nsIContent* content = aAccessible->GetContent();
|
||||
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,
|
||||
nsAtom* aAttribute) {
|
||||
if (aAttribute == nsGkAtoms::role) {
|
||||
|
|
@ -2003,7 +2063,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 +2680,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
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ class DocAccessible : public HyperTextAccessible,
|
|||
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.
|
||||
* We call this when we observe an ID mutation or when an acc is bound
|
||||
* to its document.
|
||||
|
|
@ -485,6 +485,35 @@ class DocAccessible : public HyperTextAccessible,
|
|||
void RemoveDependentIDsFor(LocalAccessible* aRelProvider,
|
||||
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.
|
||||
*
|
||||
|
|
@ -727,12 +756,35 @@ class DocAccessible : public HyperTextAccessible,
|
|||
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>,
|
||||
DependentIDsHashtable>
|
||||
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;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ addAccessibleTask(
|
|||
);
|
||||
|
||||
/**
|
||||
* Test details relations on popovers and their invokers.
|
||||
* Test details relations for the popovertarget content attribute.
|
||||
*/
|
||||
addAccessibleTask(
|
||||
`
|
||||
|
|
@ -304,7 +304,7 @@ addAccessibleTask(
|
|||
<div id="popover" popover>popover</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.
|
||||
const hide = findAccessibleChildByID(docAcc, "hide");
|
||||
await testCachedRelation(hide, RELATION_DETAILS, []);
|
||||
|
|
@ -330,7 +330,7 @@ addAccessibleTask(
|
|||
await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
|
||||
await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
|
||||
|
||||
info("Setting toggle2 popovertargetaction");
|
||||
info("Setting toggle2 popovertarget");
|
||||
await invokeSetAttribute(browser, "toggle2", "popovertarget", "popover");
|
||||
await testCachedRelation(toggle2, RELATION_DETAILS, popover);
|
||||
await testCachedRelation(popover, RELATION_DETAILS_FOR, [toggle1, toggle2]);
|
||||
|
|
@ -364,3 +364,88 @@ addAccessibleTask(
|
|||
},
|
||||
{ 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/. */
|
||||
|
||||
"use strict";
|
||||
requestLongerTimeout(2);
|
||||
|
||||
/* import-globals-from ../../mochitest/role.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(
|
||||
`
|
||||
|
|
@ -550,3 +551,79 @@ addAccessibleTask(
|
|||
},
|
||||
{ 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