Bug 1487311 - accessibility doesn't assosiate ids in shadow DOM, r=jamie, sr=smaug

This commit is contained in:
Alexander Surkov 2018-10-30 08:17:04 +08:00
Родитель 6d3b4e7492
Коммит 08eb9dff6a
8 изменённых файлов: 197 добавлений и 52 удалений

Просмотреть файл

@ -80,14 +80,16 @@ RelatedAccIterator::
mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr),
mBindingParent(nullptr), mIndex(0)
{
mBindingParent = aDependentContent->GetBindingParent();
mBindingParent = aDependentContent->IsInAnonymousSubtree() ?
aDependentContent->GetBindingParent() : nullptr;
nsAtom* IDAttr = mBindingParent ?
nsGkAtoms::anonid : nsGkAtoms::id;
nsAutoString id;
if (aDependentContent->IsElement() &&
aDependentContent->AsElement()->GetAttr(kNameSpaceID_None, IDAttr, id))
mProviders = mDocument->mDependentIDsHash.Get(id);
aDependentContent->AsElement()->GetAttr(kNameSpaceID_None, IDAttr, id)) {
mProviders = mDocument->GetRelProviders(aDependentContent->AsElement(), id);
}
}
Accessible*
@ -102,7 +104,8 @@ RelatedAccIterator::Next()
// Return related accessible for the given attribute and if the provider
// content is in the same binding in the case of XBL usage.
if (provider->mRelAttr == mRelAttr) {
nsIContent* bindingParent = provider->mContent->GetBindingParent();
nsIContent* bindingParent = provider->mContent->IsInAnonymousSubtree() ?
provider->mContent->GetBindingParent() : nullptr;
bool inScope = mBindingParent == bindingParent ||
mBindingParent == provider->mContent;
@ -258,8 +261,9 @@ IDRefsIterator::
nsAtom* aIDRefsAttr) :
mContent(aContent), mDoc(aDoc), mCurrIdx(0)
{
if (mContent->IsInUncomposedDoc() && mContent->IsElement())
if (mContent->IsElement()) {
mContent->AsElement()->GetAttr(kNameSpaceID_None, aIDRefsAttr, mIDs);
}
}
const nsDependentSubstring
@ -304,9 +308,13 @@ IDRefsIterator::GetElem(const nsDependentSubstring& aID)
// Get elements in DOM tree by ID attribute if this is an explicit content.
// In case of bound element check its anonymous subtree.
if (!mContent->IsInAnonymousSubtree()) {
dom::Element* refElm = mContent->OwnerDoc()->GetElementById(aID);
if (refElm || !mContent->GetXBLBinding())
return refElm;
dom::DocumentOrShadowRoot* docOrShadowRoot =
mContent->GetUncomposedDocOrConnectedShadowRoot();
if (docOrShadowRoot) {
dom::Element* refElm = docOrShadowRoot->GetElementById(aID);
if (refElm || !mContent->GetXBLBinding())
return refElm;
}
}
// If content is in anonymous subtree or an element having anonymous subtree

Просмотреть файл

@ -100,7 +100,7 @@ private:
DocAccessible* mDocument;
nsAtom* mRelAttr;
DocAccessible::AttrRelProviderArray* mProviders;
DocAccessible::AttrRelProviders* mProviders;
nsIContent* mBindingParent;
uint32_t mIndex;
};

Просмотреть файл

@ -132,13 +132,14 @@ MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument)
}
}
}
}
// If the given ID is referred by relation attribute then create an accessible
// for it.
nsAutoString id;
if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty())
return aDocument->IsDependentID(id);
// If the given ID is referred by relation attribute then create an accessible
// for it.
nsAutoString id;
if (nsCoreUtils::GetID(aContent, id) && !id.IsEmpty()) {
return aDocument->IsDependentID(aContent->AsElement(), id);
}
}
return false;
}

Просмотреть файл

@ -182,6 +182,56 @@ DocAccessible::CreateSubtree(Accessible* aChild)
}
}
inline DocAccessible::AttrRelProviders*
DocAccessible::GetRelProviders(dom::Element* aElement,
const nsAString& aID) const
{
DependentIDsHashtable* hash =
mDependentIDsHashes.Get(aElement->GetUncomposedDocOrConnectedShadowRoot());
if (hash) {
return hash->Get(aID);
}
return nullptr;
}
inline DocAccessible::AttrRelProviders*
DocAccessible::GetOrCreateRelProviders(dom::Element* aElement,
const nsAString& aID)
{
dom::DocumentOrShadowRoot* docOrShadowRoot =
aElement->GetUncomposedDocOrConnectedShadowRoot();
DependentIDsHashtable* hash = mDependentIDsHashes.Get(docOrShadowRoot);
if (!hash) {
hash = new DependentIDsHashtable();
mDependentIDsHashes.Put(docOrShadowRoot, hash);
}
AttrRelProviders* providers = hash->Get(aID);
if (!providers) {
providers = new AttrRelProviders();
hash->Put(aID, providers);
}
return providers;
}
inline void
DocAccessible::RemoveRelProvidersIfEmpty(dom::Element* aElement,
const nsAString& aID)
{
dom::DocumentOrShadowRoot* docOrShadowRoot =
aElement->GetUncomposedDocOrConnectedShadowRoot();
DependentIDsHashtable* hash = mDependentIDsHashes.Get(docOrShadowRoot);
if (hash) {
AttrRelProviders* providers = hash->Get(aID);
if (providers && providers->Length() == 0) {
hash->Remove(aID);
if (mDependentIDsHashes.IsEmpty()) {
mDependentIDsHashes.Remove(docOrShadowRoot);
}
}
}
}
} // namespace a11y
} // namespace mozilla

Просмотреть файл

@ -117,18 +117,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotificationController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVirtualCursor)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildDocuments)
for (auto iter = tmp->mDependentIDsHash.Iter(); !iter.Done(); iter.Next()) {
AttrRelProviderArray* providers = iter.UserData();
for (auto hashesIter = tmp->mDependentIDsHashes.Iter(); !hashesIter.Done();
hashesIter.Next()) {
auto dependentIDsHash = hashesIter.UserData();
for (auto providersIter = dependentIDsHash->Iter(); !providersIter.Done();
providersIter.Next()) {
AttrRelProviders* providers = providersIter.UserData();
for (int32_t provIdx = providers->Length() - 1; provIdx >= 0; provIdx--) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
cb, "content of dependent ids hash entry of document accessible");
for (int32_t jdx = providers->Length() - 1; jdx >= 0; jdx--) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
cb, "content of dependent ids hash entry of document accessible");
AttrRelProvider* provider = (*providers)[jdx];
cb.NoteXPCOMChild(provider->mContent);
NS_ASSERTION(provider->mContent->IsInUncomposedDoc(),
"Referred content is not in document!");
AttrRelProvider* provider = (*providers)[provIdx];
cb.NoteXPCOMChild(provider->mContent);
}
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessibleCache)
@ -148,7 +149,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DocAccessible, Accessible)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNotificationController)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVirtualCursor)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildDocuments)
tmp->mDependentIDsHash.Clear();
tmp->mDependentIDsHashes.Clear();
tmp->mNodeToAccessibleMap.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessibleCache)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnchorJumpElm)
@ -475,7 +476,7 @@ DocAccessible::Shutdown()
mPresShell->SetDocAccessible(nullptr);
mPresShell = nullptr; // Avoid reentrancy
mDependentIDsHash.Clear();
mDependentIDsHashes.Clear();
mNodeToAccessibleMap.Clear();
for (auto iter = mAccessibleCache.Iter(); !iter.Done(); iter.Next()) {
@ -1396,8 +1397,9 @@ DocAccessible::ProcessInvalidationList()
if (container) {
// Check if the node is a target of aria-owns, and if so, don't process
// it here and let DoARIAOwnsRelocation process it.
AttrRelProviderArray* list =
mDependentIDsHash.Get(nsDependentAtomString(content->GetID()));
AttrRelProviders* list =
GetRelProviders(content->AsElement(),
nsDependentAtomString(content->GetID()));
bool shouldProcess = !!list;
if (shouldProcess) {
for (uint32_t idx = 0; idx < list->Length(); idx++) {
@ -1594,21 +1596,15 @@ DocAccessible::AddDependentIDsFor(Accessible* aRelProvider, nsAtom* aRelAttr)
break;
nsIContent* dependentContent = iter.GetElem(id);
if (relAttr == nsGkAtoms::aria_owns && dependentContent &&
!aRelProvider->IsAcceptableChild(dependentContent))
if (!dependentContent ||
(relAttr == nsGkAtoms::aria_owns &&
!aRelProvider->IsAcceptableChild(dependentContent)))
continue;
AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
if (!providers) {
providers = new AttrRelProviderArray();
if (providers) {
mDependentIDsHash.Put(id, providers);
}
}
AttrRelProviders* providers =
GetOrCreateRelProviders(dependentContent->AsElement(), id);
if (providers) {
AttrRelProvider* provider =
new AttrRelProvider(relAttr, relProviderEl);
AttrRelProvider* provider = new AttrRelProvider(relAttr, relProviderEl);
if (provider) {
providers->AppendElement(provider);
@ -1654,7 +1650,7 @@ DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
if (id.IsEmpty())
break;
AttrRelProviderArray* providers = mDependentIDsHash.Get(id);
AttrRelProviders* providers = GetRelProviders(relProviderElm, id);
if (providers) {
for (uint32_t jdx = 0; jdx < providers->Length(); ) {
AttrRelProvider* provider = (*providers)[jdx];
@ -1664,8 +1660,7 @@ DocAccessible::RemoveDependentIDsFor(Accessible* aRelProvider,
else
jdx++;
}
if (providers->Length() == 0)
mDependentIDsHash.Remove(id);
RemoveRelProvidersIfEmpty(relProviderElm, id);
}
}
@ -2042,8 +2037,9 @@ DocAccessible::RelocateARIAOwnedIfNeeded(nsIContent* aElement)
if (!aElement->HasID())
return false;
AttrRelProviderArray* list =
mDependentIDsHash.Get(nsDependentAtomString(aElement->GetID()));
AttrRelProviders* list =
GetRelProviders(aElement->AsElement(),
nsDependentAtomString(aElement->GetID()));
if (list) {
for (uint32_t idx = 0; idx < list->Length(); idx++) {
if (list->ElementAt(idx)->mRelAttr == nsGkAtoms::aria_owns) {

Просмотреть файл

@ -329,8 +329,8 @@ public:
* XBL bindings. Be careful the result of this method may be senseless
* while it's called for XUL elements (where XBL is used widely).
*/
bool IsDependentID(const nsAString& aID) const
{ return mDependentIDsHash.Get(aID, nullptr); }
bool IsDependentID(dom::Element* aElement, const nsAString& aID) const
{ return GetRelProviders(aElement, aID); }
/**
* Initialize the newly created accessible and put it into document caches.
@ -674,12 +674,26 @@ protected:
AttrRelProvider& operator =(const AttrRelProvider&);
};
typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviders;
typedef nsClassHashtable<nsStringHashKey, AttrRelProviders> DependentIDsHashtable;
/**
* Returns/creates/removes attribute relation providers associated with
* a DOM document if the element is in uncomposed document or associated
* with shadow DOM the element is in.
*/
AttrRelProviders* GetRelProviders(dom::Element* aElement,
const nsAString& aID) const;
AttrRelProviders* GetOrCreateRelProviders(dom::Element* aElement,
const nsAString& aID);
void RemoveRelProvidersIfEmpty(dom::Element* aElement,
const nsAString& aID);
/**
* The cache of IDs pointed by relation attributes.
*/
typedef nsTArray<nsAutoPtr<AttrRelProvider> > AttrRelProviderArray;
nsClassHashtable<nsStringHashKey, AttrRelProviderArray>
mDependentIDsHash;
nsClassHashtable<nsPtrHashKey<dom::DocumentOrShadowRoot>, DependentIDsHashtable>
mDependentIDsHashes;
friend class RelatedAccIterator;

Просмотреть файл

@ -10,4 +10,5 @@ skip-if = os == 'linux' && !debug # bug 1411145
[test_tabbrowser.xul]
[test_tree.xul]
[test_ui_modalprompt.html]
[test_shadowdom.html]
[test_update.html]

Просмотреть файл

@ -0,0 +1,75 @@
<html>
<head>
<title>Explicit content and shadow DOM content relations tests</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../relations.js"></script>
<script type="application/javascript"
src="../role.js"></script>
<script type="application/javascript">
function doTest() {
let iframeDoc = document.getElementById("iframe").contentDocument;
// explicit content
let label = iframeDoc.getElementById("label");
let element = iframeDoc.getElementById("element");
testRelation(label, RELATION_LABEL_FOR, element);
testRelation(element, RELATION_LABELLED_BY, label);
// shadow DOM content
let shadowRoot = iframeDoc.getElementById("shadowcontainer").shadowRoot;
let shadowLabel = shadowRoot.getElementById("label");
let shadowElement = shadowRoot.getElementById("element");
testRelation(shadowLabel, RELATION_LABEL_FOR, shadowElement);
testRelation(shadowElement, RELATION_LABELLED_BY, shadowLabel);
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({
set: [
[ "dom.webcomponents.shadowdom.enabled", true ],
],
}, function() {
// This test loads in an iframe, to ensure that the element instance is
// loaded with the correct value of the preference.
let sc = "script";
let iframe = document.createElement("iframe");
iframe.id = "iframe";
iframe.src = `data:text/html,<html>
<body>
<div id='label'></div><div id='element' aria-labelledby='label'></div>
<div id='shadowcontainer'></div>
<${sc}>
let shadowRoot = document.getElementById('shadowcontainer').
attachShadow({mode: 'open'});
shadowRoot.innerHTML =
"<div id='label'></div><div id='element' aria-labelledby='label'></div>";
</${sc}>
</body>
</html>`;
addA11yLoadEvent(doTest, iframe.contentWindow);
document.body.appendChild(iframe);
});
</script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
</html>