зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
61e5b963e5
Коммит
107633cba4
|
@ -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>
|
||||
|
|
Загрузка…
Ссылка в новой задаче