зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1795221: Implement LINKS_TO relation as a tree traversal r=Jamie
Differential Revision: https://phabricator.services.mozilla.com/D159451
This commit is contained in:
Родитель
dbeec6c2f2
Коммит
771a9f1a94
|
@ -81,7 +81,6 @@ static constexpr RelationData kRelationTypeAtoms[] = {
|
|||
Some(RelationType::DESCRIPTION_FOR)},
|
||||
{nsGkAtoms::aria_flowto, nullptr, RelationType::FLOWS_TO,
|
||||
Some(RelationType::FLOWS_FROM)},
|
||||
{nsGkAtoms::link, nullptr, RelationType::LINKS_TO, Nothing()},
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
|
|
|
@ -652,7 +652,7 @@ uint16_t PivotRadioNameRule::Match(Accessible* aAcc) {
|
|||
}
|
||||
|
||||
if (remote->IsHTMLRadioButton()) {
|
||||
nsString currName = remote->GetCachedHTMLRadioNameAttribute();
|
||||
nsString currName = remote->GetCachedHTMLNameAttribute();
|
||||
if (!currName.IsEmpty() && mName.Equals(currName)) {
|
||||
result |= nsIAccessibleTraversalRule::FILTER_MATCH;
|
||||
}
|
||||
|
@ -660,3 +660,18 @@ uint16_t PivotRadioNameRule::Match(Accessible* aAcc) {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
// MustPruneSameDocRule
|
||||
|
||||
uint16_t MustPruneSameDocRule::Match(Accessible* aAcc) {
|
||||
if (!aAcc) {
|
||||
return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
|
||||
}
|
||||
|
||||
if (nsAccUtils::MustPrune(aAcc) || aAcc->IsOuterDoc()) {
|
||||
return nsIAccessibleTraversalRule::FILTER_MATCH |
|
||||
nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
|
||||
}
|
||||
|
||||
return nsIAccessibleTraversalRule::FILTER_MATCH;
|
||||
}
|
||||
|
|
|
@ -136,6 +136,16 @@ class PivotRadioNameRule : public PivotRule {
|
|||
const nsString& mName;
|
||||
};
|
||||
|
||||
/**
|
||||
* This rule doesn't search iframes. Subtrees that should be
|
||||
* pruned by way of nsAccUtils::MustPrune are also not searched.
|
||||
*/
|
||||
|
||||
class MustPruneSameDocRule : public PivotRule {
|
||||
public:
|
||||
virtual uint16_t Match(Accessible* aAcc) override;
|
||||
};
|
||||
|
||||
} // namespace a11y
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
|
@ -1369,7 +1369,6 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
|
|||
|
||||
if (aAttribute == nsGkAtoms::href) {
|
||||
mDoc->QueueCacheUpdate(this, CacheDomain::Value);
|
||||
mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
|
||||
}
|
||||
|
||||
if (aAttribute == nsGkAtoms::aria_controls ||
|
||||
|
@ -1432,6 +1431,13 @@ void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
|
|||
if (aAttribute == nsGkAtoms::accesskey) {
|
||||
mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
|
||||
}
|
||||
|
||||
if (aAttribute == nsGkAtoms::name &&
|
||||
(mContent && mContent->IsHTMLElement(nsGkAtoms::a))) {
|
||||
// If an anchor's name changed, it's possible a LINKS_TO relation
|
||||
// also changed. Push a cache update for Relations.
|
||||
mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
|
||||
}
|
||||
}
|
||||
|
||||
void LocalAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
|
||||
|
@ -3618,18 +3624,21 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
|
|||
}
|
||||
|
||||
if (aCacheDomain & CacheDomain::Relations && mContent) {
|
||||
if (IsHTMLRadioButton()) {
|
||||
if (IsHTMLRadioButton() ||
|
||||
(mContent->IsElement() &&
|
||||
mContent->AsElement()->IsHTMLElement(nsGkAtoms::a))) {
|
||||
// HTML radio buttons with the same name should be grouped
|
||||
// and returned together when their MEMBER_OF relation is
|
||||
// requested. We cache the name attribute, if it exists, here.
|
||||
// requested. Computing LINKS_TO also requires we cache `name` on
|
||||
// anchor elements.
|
||||
nsString name;
|
||||
mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
|
||||
if (!name.IsEmpty()) {
|
||||
fields->SetAttribute(nsGkAtoms::radioLabel, std::move(name));
|
||||
fields->SetAttribute(nsGkAtoms::attributeName, std::move(name));
|
||||
} else if (aUpdateType != CacheUpdateType::Initial) {
|
||||
// It's possible we used to have a name and it's since been
|
||||
// removed. Send a delete entry.
|
||||
fields->SetAttribute(nsGkAtoms::radioLabel, DeleteEntry());
|
||||
fields->SetAttribute(nsGkAtoms::attributeName, DeleteEntry());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3646,10 +3655,6 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
|
|||
dom::HTMLLabelElement::FromNode(mContent)) {
|
||||
rel.AppendTarget(mDoc, labelEl->GetControl());
|
||||
}
|
||||
} else if (data.mType == RelationType::LINKS_TO) {
|
||||
// This has no implicit relation, so it's safe to call RelationByType
|
||||
// directly.
|
||||
rel = RelationByType(RelationType::LINKS_TO);
|
||||
} else {
|
||||
// We use an IDRefsIterator here instead of calling RelationByType
|
||||
// directly because we only want to cache explicit relations. Implicit
|
||||
|
|
|
@ -693,6 +693,43 @@ Relation RemoteAccessibleBase<Derived>::RelationByType(
|
|||
return Relation();
|
||||
}
|
||||
|
||||
if (aType == RelationType::LINKS_TO && Role() == roles::LINK) {
|
||||
Pivot p = Pivot(mDoc);
|
||||
nsString href;
|
||||
Value(href);
|
||||
if (!href.IsEmpty()) {
|
||||
// `Value` will give us the entire URL, we're only interested in the ID
|
||||
// after the hash. Split that part out.
|
||||
for (auto s : href.Split('#')) {
|
||||
href = s;
|
||||
}
|
||||
MustPruneSameDocRule rule;
|
||||
Accessible* nameMatch = nullptr;
|
||||
for (Accessible* match = p.Next(mDoc, rule); match;
|
||||
match = p.Next(match, rule)) {
|
||||
nsString currID;
|
||||
match->DOMNodeID(currID);
|
||||
MOZ_ASSERT(match->IsRemote());
|
||||
if (href.Equals(currID)) {
|
||||
return Relation(match->AsRemote());
|
||||
}
|
||||
if (!nameMatch) {
|
||||
nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute();
|
||||
if (match->TagName() == nsGkAtoms::a && href.Equals(currName)) {
|
||||
// If we find an element with a matching ID, we should return
|
||||
// that, but if we don't we should return the first anchor with
|
||||
// a matching name. To avoid doing two traversals, store the first
|
||||
// name match here.
|
||||
nameMatch = match;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nameMatch ? Relation(nameMatch->AsRemote()) : Relation();
|
||||
}
|
||||
|
||||
return Relation();
|
||||
}
|
||||
|
||||
// Handle ARIA tree, treegrid parent/child relations. Each of these cases
|
||||
// relies on cached group info. To find the parent of an accessible, use the
|
||||
// unified conceptual parent.
|
||||
|
@ -1007,15 +1044,13 @@ RemoteAccessibleBase<Derived>::GetCachedARIAAttributes() const {
|
|||
}
|
||||
|
||||
template <class Derived>
|
||||
nsString RemoteAccessibleBase<Derived>::GetCachedHTMLRadioNameAttribute()
|
||||
const {
|
||||
nsString RemoteAccessibleBase<Derived>::GetCachedHTMLNameAttribute() const {
|
||||
if (mCachedFields) {
|
||||
if (auto maybeName =
|
||||
mCachedFields->GetAttribute<nsString>(nsGkAtoms::radioLabel)) {
|
||||
mCachedFields->GetAttribute<nsString>(nsGkAtoms::attributeName)) {
|
||||
return *maybeName;
|
||||
}
|
||||
}
|
||||
|
||||
return nsString();
|
||||
}
|
||||
|
||||
|
|
|
@ -348,7 +348,7 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
|
|||
RefPtr<const AccAttributes> GetCachedTextAttributes();
|
||||
RefPtr<const AccAttributes> GetCachedARIAAttributes() const;
|
||||
|
||||
nsString GetCachedHTMLRadioNameAttribute() const;
|
||||
nsString GetCachedHTMLNameAttribute() const;
|
||||
|
||||
virtual HyperTextAccessibleBase* AsHyperTextBase() override {
|
||||
return IsHyperText() ? static_cast<HyperTextAccessibleBase*>(this)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
requestLongerTimeout(2);
|
||||
|
||||
/* import-globals-from ../../mochitest/relations.js */
|
||||
loadScripts({ name: "relations.js", dir: MOCHITESTS_DIR });
|
||||
|
@ -358,21 +359,28 @@ addAccessibleTask(
|
|||
*/
|
||||
addAccessibleTask(
|
||||
`
|
||||
<a id="link" href="#item">
|
||||
<a id="link" href="#item">a</a>
|
||||
<div id="item">hello</div>
|
||||
<div id="item2">world</div>`,
|
||||
<div id="item2">world</div>
|
||||
<a id="link2" href="#anchor">b</a>
|
||||
<a id="namedLink" name="anchor">c</a>`,
|
||||
async function(browser, accDoc) {
|
||||
const link = findAccessibleChildByID(accDoc, "link");
|
||||
const link2 = findAccessibleChildByID(accDoc, "link2");
|
||||
const namedLink = findAccessibleChildByID(accDoc, "namedLink");
|
||||
const item = findAccessibleChildByID(accDoc, "item");
|
||||
const item2 = findAccessibleChildByID(accDoc, "item2");
|
||||
|
||||
await testCachedRelation(link, RELATION_LINKS_TO, item);
|
||||
await testCachedRelation(link2, RELATION_LINKS_TO, namedLink);
|
||||
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.getElementById("link").href = "";
|
||||
content.document.getElementById("namedLink").name = "newName";
|
||||
});
|
||||
|
||||
await testCachedRelation(link, RELATION_LINKS_TO, null);
|
||||
await testCachedRelation(link2, RELATION_LINKS_TO, null);
|
||||
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.getElementById("link").href = "#item2";
|
||||
|
|
Загрузка…
Ссылка в новой задаче