Bug 1277201: Fire a STATE_CHANGE event when a details element is opened or closed. r=eeejay

Differential Revision: https://phabricator.services.mozilla.com/D44872

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Morgan Reschenberg 2019-09-10 16:16:53 +00:00
Родитель 61e5b963e5
Коммит 107633cba4
4 изменённых файлов: 114 добавлений и 10 удалений

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

@ -48,6 +48,7 @@
#include "mozilla/dom/DocumentType.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "HTMLElementAccessibles.h"
using namespace mozilla;
using namespace mozilla::a11y;
@ -130,7 +131,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DocAccessible, Accessible)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchorJumpElm)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInvalidationList)
for (auto it = tmp->mARIAOwnsHash.ConstIter(); !it.Done(); it.Next()) {
nsTArray<RefPtr<Accessible> >* ar = it.UserData();
nsTArray<RefPtr<Accessible>>* ar = it.UserData();
for (uint32_t i = 0; i < ar->Length(); i++) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mARIAOwnsHash entry item");
cb.NoteXPCOMChild(ar->ElementAt(i));
@ -783,6 +784,22 @@ void DocAccessible::AttributeChangedImpl(Accessible* aAccessible,
return;
}
// When a details object has its open attribute changed
// we should fire a state-change event on the accessible of
// its main summary
if (aAttribute == nsGkAtoms::open) {
// FromDetails checks if the given accessible belongs to
// a details frame and also locates the accessible of its
// main summary.
if (HTMLSummaryAccessible* summaryAccessible =
HTMLSummaryAccessible::FromDetails(aAccessible)) {
RefPtr<AccEvent> expandedChangeEvent =
new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
FireDelayedEvent(expandedChangeEvent);
return;
}
}
// Check for namespaced ARIA attribute
if (aNameSpaceID == kNameSpaceID_None) {
// Check for hyphenated aria-foo property?
@ -1764,7 +1781,7 @@ void DocAccessible::UpdateRootElIfNeeded() {
class InsertIterator final {
public:
InsertIterator(Accessible* aContext,
const nsTArray<nsCOMPtr<nsIContent> >* aNodes)
const nsTArray<nsCOMPtr<nsIContent>>* aNodes)
: mChild(nullptr),
mChildBefore(nullptr),
mWalker(aContext),
@ -1796,7 +1813,7 @@ class InsertIterator final {
Accessible* mChildBefore;
TreeWalker mWalker;
const nsTArray<nsCOMPtr<nsIContent> >* mNodes;
const nsTArray<nsCOMPtr<nsIContent>>* mNodes;
nsTHashtable<nsPtrHashKey<const nsIContent>> mProcessedNodes;
uint32_t mNodesIdx;
};
@ -1876,7 +1893,7 @@ bool InsertIterator::Next() {
}
void DocAccessible::ProcessContentInserted(
Accessible* aContainer, const nsTArray<nsCOMPtr<nsIContent> >* aNodes) {
Accessible* aContainer, const nsTArray<nsCOMPtr<nsIContent>>* aNodes) {
// Process insertions if the container accessible is still in tree.
if (!aContainer->IsInDocument()) {
return;
@ -2016,7 +2033,7 @@ void DocAccessible::ContentRemoved(Accessible* aChild) {
MOZ_DIAGNOSTIC_ASSERT(aChild->Parent(), "Alive but unparented #1");
if (aChild->IsRelocated()) {
nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(parent);
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.Get(parent);
MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
owned->RemoveElement(aChild);
if (owned->Length() == 0) {
@ -2086,7 +2103,7 @@ void DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) {
logging::TreeInfo("aria owns relocation", logging::eVerbose, aOwner);
#endif
nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.LookupOrAdd(aOwner);
IDRefsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
uint32_t idx = 0;
@ -2196,7 +2213,7 @@ void DocAccessible::DoARIAOwnsRelocation(Accessible* aOwner) {
}
}
void DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
void DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible>>* aChildren,
uint32_t aStartIdx) {
MOZ_ASSERT(aStartIdx <= aChildren->Length(), "Wrong removal index");
@ -2287,7 +2304,7 @@ bool DocAccessible::MoveChild(Accessible* aChild, Accessible* aNewParent,
// to update it if needed.
if (aChild->IsRelocated()) {
aChild->SetRelocated(false);
nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(curParent);
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.Get(curParent);
MOZ_ASSERT(owned, "IsRelocated flag is out of sync with mARIAOwnsHash");
owned->RemoveElement(aChild);
if (owned->Length() == 0) {
@ -2383,7 +2400,7 @@ void DocAccessible::UncacheChildrenInSubtree(Accessible* aRoot) {
aRoot->mStateFlags |= eIsNotInDocument;
RemoveDependentIDsFor(aRoot);
nsTArray<RefPtr<Accessible> >* owned = mARIAOwnsHash.Get(aRoot);
nsTArray<RefPtr<Accessible>>* owned = mARIAOwnsHash.Get(aRoot);
uint32_t count = aRoot->ContentChildCount();
for (uint32_t idx = 0; idx < count; idx++) {
Accessible* child = aRoot->ContentChildAt(idx);

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

@ -151,6 +151,30 @@ uint64_t HTMLSummaryAccessible::NativeState() const {
return state;
}
HTMLSummaryAccessible* HTMLSummaryAccessible::FromDetails(Accessible* details) {
if (!dom::HTMLDetailsElement::FromNodeOrNull(details->GetContent())) {
return nullptr;
}
HTMLSummaryAccessible* summaryAccessible = nullptr;
for (uint32_t i = 0; i < details->ChildCount(); i++) {
// Iterate through the children of our details accessible to locate main
// summary. This iteration includes the anonymous summary if the details
// element was not explicitly created with one.
Accessible* child = details->GetChildAt(i);
auto* summary =
mozilla::dom::HTMLSummaryElement::FromNodeOrNull(child->GetContent());
if (summary && summary->IsMainSummary()) {
summaryAccessible = static_cast<HTMLSummaryAccessible*>(child);
break;
}
}
MOZ_ASSERT(summaryAccessible,
"Details objects should have at least one summary");
return summaryAccessible;
}
////////////////////////////////////////////////////////////////////////////////
// HTMLSummaryAccessible: Widgets

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

@ -94,6 +94,11 @@ class HTMLSummaryAccessible : public HyperTextAccessibleWrap {
HTMLSummaryAccessible(nsIContent* aContent, DocAccessible* aDoc);
// Check that the given Accessible belongs to a details frame.
// If so, find and return the accessible for the detail frame's
// main summary.
static HTMLSummaryAccessible* FromDetails(Accessible* aDetails);
// Accessible
virtual uint64_t NativeState() const override;

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

@ -21,6 +21,27 @@
// //////////////////////////////////////////////////////////////////////////
// Invokers
function openNode(aIDDetails, aIDSummary, aIsOpen) {
this.DOMNode = getNode(aIDDetails);
this.eventSeq = [
new expandedStateChecker(aIsOpen, getNode(aIDSummary)),
];
this.invoke = function expandNode_invoke() {
if (aIsOpen) {
this.DOMNode.setAttribute("open", "");
} else {
this.DOMNode.removeAttribute("open");
}
};
this.getID = function expandNode_getID() {
return prettyName(aIDDetails) + " Open changed to '" + aIsOpen + "'";
};
}
function makeEditableDoc(aDocNode, aIsEnabled) {
this.DOMNode = aDocNode;
@ -174,6 +195,16 @@
function doTests() {
gQueue = new eventQueue(nsIAccessibleEvent.EVENT_STATE_CHANGE);
// Test opening details objects
gQueue.push(new openNode("detailsOpen", "summaryOpen", true));
gQueue.push(new openNode("detailsOpen", "summaryOpen", false));
gQueue.push(new openNode("detailsOpen1", "summaryOpen1", true));
gQueue.push(new openNode("detailsOpen2", "summaryOpen2", true));
gQueue.push(new openNode("detailsOpen3", "summaryOpen3", true));
gQueue.push(new openNode("detailsOpen4", "summaryOpen4", true));
gQueue.push(new openNode("detailsOpen5", "summaryOpen5", true));
gQueue.push(new openNode("detailsOpen6", "summaryOpen6", true));
// Test delayed editable state change
var doc = document.getElementById("iframe").contentDocument;
gQueue.push(new makeEditableDoc(doc));
@ -212,7 +243,24 @@
addA11yLoadEvent(doTests);
</script>
</head>
<style>
details.openBefore::before{
content: "before detail content: ";
background: blue;
}
summary.openBefore::before{
content: "before summary content: ";
background: green;
}
details.openAfter::after{
content: " :after detail content";
background: blue;
}
summary.openAfter::after{
content: " :after summary content";
background: green;
}
</style>
<body>
<a target="_blank"
@ -246,6 +294,16 @@
<pre id="test">
</pre>
<!-- open -->
<details id="detailsOpen"><summary id="summaryOpen">open</summary>details can be opened</details>
<details id="detailsOpen1">order doesn't matter<summary id="summaryOpen1">open</summary></details>
<details id="detailsOpen2"><div>additional elements don't matter</div><summary id="summaryOpen2">open</summary></details>
<details id="detailsOpen3" class="openBefore"><summary id="summaryOpen3">summary</summary>content</details>
<details id="detailsOpen4" class="openAfter"><summary id="summaryOpen4">summary</summary>content</details>
<details id="detailsOpen5"><summary id="summaryOpen5" class="openBefore">summary</summary>content</details>
<details id="detailsOpen6"><summary id="summaryOpen6" class="openAfter">summary</summary>content</details>
<div id="testContainer">
<iframe id="iframe"></iframe>
</div>