зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1487311 - accessibility doesn't assosiate ids in shadow DOM, r=jamie, sr=smaug
This commit is contained in:
Родитель
6d3b4e7492
Коммит
08eb9dff6a
|
@ -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>
|
Загрузка…
Ссылка в новой задаче