Bug 1280188 - Don't relocate children in or out of editable content. r=nlapre
Depends on D234768 Differential Revision: https://phabricator.services.mozilla.com/D234769
This commit is contained in:
@@ -275,16 +275,16 @@ const nsDependentSubstring AssociatedElementsIterator::NextID() {
|
||||
return Substring(mIDs, idStartIdx, mCurrIdx++ - idStartIdx);
|
||||
}
|
||||
|
||||
nsIContent* AssociatedElementsIterator::NextElem() {
|
||||
dom::Element* AssociatedElementsIterator::NextElem() {
|
||||
while (true) {
|
||||
const nsDependentSubstring id = NextID();
|
||||
if (id.IsEmpty()) break;
|
||||
|
||||
nsIContent* refContent = GetElem(id);
|
||||
dom::Element* refContent = GetElem(id);
|
||||
if (refContent) return refContent;
|
||||
}
|
||||
|
||||
while (nsIContent* element = mElements.SafeElementAt(mElemIdx++)) {
|
||||
while (dom::Element* element = mElements.SafeElementAt(mElemIdx++)) {
|
||||
if (nsCoreUtils::IsDescendantOfAnyShadowIncludingAncestor(element,
|
||||
mContent)) {
|
||||
return element;
|
||||
@@ -317,7 +317,7 @@ dom::Element* AssociatedElementsIterator::GetElem(
|
||||
}
|
||||
|
||||
LocalAccessible* AssociatedElementsIterator::Next() {
|
||||
nsIContent* nextEl = nullptr;
|
||||
dom::Element* nextEl = nullptr;
|
||||
while ((nextEl = NextElem())) {
|
||||
LocalAccessible* acc = mDoc->GetAccessible(nextEl);
|
||||
if (acc) {
|
||||
|
||||
@@ -225,7 +225,7 @@ class AssociatedElementsIterator : public AccIterable {
|
||||
/**
|
||||
* Return next element.
|
||||
*/
|
||||
nsIContent* NextElem();
|
||||
dom::Element* NextElem();
|
||||
|
||||
/**
|
||||
* Return the element with the given ID.
|
||||
|
||||
@@ -1018,6 +1018,18 @@ void DocAccessible::ElementStateChanged(dom::Document* aDocument,
|
||||
new AccStateChangeEvent(accessible, states::READONLY, !isEditable);
|
||||
FireDelayedEvent(event);
|
||||
}
|
||||
|
||||
if (aElement->HasAttr(nsGkAtoms::aria_owns)) {
|
||||
// If this has aria-owns, update children that are relocated into here.
|
||||
// If we are becoming editable, put them back into their original
|
||||
// containers, if we are becoming readonly, acquire them.
|
||||
mNotificationController->ScheduleRelocation(accessible);
|
||||
}
|
||||
|
||||
// If this is a node inside of a newly editable subtree, it needs to be
|
||||
// un-aria-owned. And inversely, if the node becomes uneditable, allow the
|
||||
// node to be aria-owned.
|
||||
RelocateARIAOwnedIfNeeded(aElement);
|
||||
}
|
||||
|
||||
if (aStateMask.HasState(dom::ElementState::CHECKED)) {
|
||||
@@ -2460,9 +2472,24 @@ void DocAccessible::DoARIAOwnsRelocation(LocalAccessible* aOwner) {
|
||||
nsTArray<RefPtr<LocalAccessible>>* owned =
|
||||
mARIAOwnsHash.GetOrInsertNew(aOwner);
|
||||
|
||||
if (aOwner->Elm()->State().HasState(dom::ElementState::READWRITE)) {
|
||||
// The container is editable.
|
||||
PutChildrenBack(owned, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
AssociatedElementsIterator iter(this, aOwner->Elm(), nsGkAtoms::aria_owns);
|
||||
uint32_t idx = 0;
|
||||
while (nsIContent* childEl = iter.NextElem()) {
|
||||
while (dom::Element* childEl = iter.NextElem()) {
|
||||
if (childEl->State().HasState(dom::ElementState::READWRITE)) {
|
||||
nsINode* parentEl = childEl->GetFlattenedTreeParentNode();
|
||||
if (parentEl->IsElement() && parentEl->AsElement()->State().HasState(
|
||||
dom::ElementState::READWRITE)) {
|
||||
// The child is inside of an editable subtree, don't relocate it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
LocalAccessible* child = GetAccessible(childEl);
|
||||
auto insertIdx = aOwner->ChildCount() - owned->Length() + idx;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
/* import-globals-from ../../mochitest/role.js */
|
||||
loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
|
||||
/* import-globals-from ../../mochitest/states.js */
|
||||
loadScripts({ name: "states.js", dir: MOCHITESTS_DIR });
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
@@ -606,3 +608,95 @@ addAccessibleTask(`<div id='a'></div>`, async function (browser, accDoc) {
|
||||
});
|
||||
is(getAccessibleDOMNodeID(a.firstChild), "b", "'a' owns relocated child");
|
||||
});
|
||||
|
||||
/*
|
||||
* Test to assure that aria-owned elements are not relocated into an editable subtree.
|
||||
*/
|
||||
addAccessibleTask(
|
||||
`
|
||||
<button id="btn">World</button>
|
||||
<div contentEditable="true" id="textbox" role="textbox">
|
||||
<p id="p" aria-owns="btn">Hello</p>
|
||||
</div>
|
||||
`,
|
||||
async function (browser, accDoc) {
|
||||
const p = findAccessibleChildByID(accDoc, "p");
|
||||
const textbox = findAccessibleChildByID(accDoc, "textbox");
|
||||
testStates(textbox, 0, EXT_STATE_EDITABLE, 0, 0);
|
||||
|
||||
isnot(getAccessibleDOMNodeID(p.lastChild), "btn", "'p' owns relocated btn");
|
||||
is(textbox.value, "Hello");
|
||||
|
||||
let expectedEvents = Promise.all([
|
||||
waitForStateChange(textbox, EXT_STATE_EDITABLE, false, true),
|
||||
waitForEvent(EVENT_INNER_REORDER, p),
|
||||
]);
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.getElementById("textbox").contentEditable = false;
|
||||
});
|
||||
await expectedEvents;
|
||||
is(getAccessibleDOMNodeID(p.lastChild), "btn", "'p' owns relocated btn");
|
||||
is(textbox.value, "Hello World");
|
||||
|
||||
expectedEvents = Promise.all([
|
||||
waitForStateChange(textbox, EXT_STATE_EDITABLE, true, true),
|
||||
waitForEvent(EVENT_INNER_REORDER, p),
|
||||
]);
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.getElementById("textbox").contentEditable = true;
|
||||
});
|
||||
await expectedEvents;
|
||||
isnot(getAccessibleDOMNodeID(p.lastChild), "btn", "'p' owns relocated btn");
|
||||
is(textbox.value, "Hello");
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Test to ensure that aria-owned elements are not relocated out of editable subtree.
|
||||
*/
|
||||
addAccessibleTask(
|
||||
`
|
||||
<div contentEditable="true" id="textbox" role="textbox">
|
||||
<button id="btn">World</button>
|
||||
</div>
|
||||
<p id="p" aria-owns="btn">Hello</p>
|
||||
<p id="p2" aria-owns="textbox"></p>
|
||||
`,
|
||||
async function (browser, accDoc) {
|
||||
const p = findAccessibleChildByID(accDoc, "p");
|
||||
const textbox = findAccessibleChildByID(accDoc, "textbox");
|
||||
testStates(textbox, 0, EXT_STATE_EDITABLE, 0, 0);
|
||||
|
||||
is(
|
||||
getAccessibleDOMNodeID(textbox.parent),
|
||||
"p2",
|
||||
"editable root can be relocated"
|
||||
);
|
||||
isnot(
|
||||
getAccessibleDOMNodeID(p.lastChild),
|
||||
"btn",
|
||||
"editable element cannot be relocated"
|
||||
);
|
||||
is(textbox.value, "World");
|
||||
|
||||
let expectedEvents = Promise.all([
|
||||
waitForStateChange(textbox, EXT_STATE_EDITABLE, false, true),
|
||||
waitForEvent(EVENT_REORDER, p),
|
||||
]);
|
||||
await invokeContentTask(browser, [], () => {
|
||||
content.document.getElementById("textbox").contentEditable = false;
|
||||
});
|
||||
await expectedEvents;
|
||||
is(
|
||||
getAccessibleDOMNodeID(p.lastChild),
|
||||
"btn",
|
||||
"'p' owns readonly relocated btn"
|
||||
);
|
||||
is(textbox.value, "");
|
||||
is(
|
||||
getAccessibleDOMNodeID(textbox.parent),
|
||||
"p2",
|
||||
"textbox is still relocated"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
async function testComboBox(browser, accDoc, suppressPopupInValueTodo = false) {
|
||||
async function testComboBox(browser, accDoc) {
|
||||
const box = getNativeInterface(accDoc, "box");
|
||||
is(box.getAttributeValue("AXRole"), "AXComboBox");
|
||||
is(box.getAttributeValue("AXValue"), "peach", "Initial value correct");
|
||||
@@ -20,20 +20,10 @@ async function testComboBox(browser, accDoc, suppressPopupInValueTodo = false) {
|
||||
});
|
||||
|
||||
await expandedChanged;
|
||||
if (suppressPopupInValueTodo) {
|
||||
todo(
|
||||
!didBoxValueChange,
|
||||
"Value of combobox did not change when it was opened"
|
||||
);
|
||||
todo_is(box.getAttributeValue("AXValue"), "peach");
|
||||
} else {
|
||||
ok(
|
||||
!didBoxValueChange,
|
||||
"Value of combobox did not change when it was opened"
|
||||
);
|
||||
|
||||
ok(!didBoxValueChange, "Value of combobox did not change when it was opened");
|
||||
is(box.getAttributeValue("AXValue"), "peach", "After popup value correct");
|
||||
}
|
||||
}
|
||||
|
||||
addAccessibleTask(
|
||||
`
|
||||
@@ -74,8 +64,21 @@ addAccessibleTask(
|
||||
`,
|
||||
async (browser, accDoc) => {
|
||||
info("Test ARIA 1.0 style combobox (entry aria-owns list)");
|
||||
// XXX: Bug 1912520
|
||||
await testComboBox(browser, accDoc, true);
|
||||
|
||||
const box = getNativeInterface(accDoc, "box");
|
||||
is(
|
||||
box.getAttributeValue("AXChildren").length,
|
||||
1,
|
||||
"owned list is not relocated"
|
||||
);
|
||||
// XXX: Bug 1912520 - Remap from aria-owns to aria-controls
|
||||
todo_is(
|
||||
box.getAttributeValue("AXARIAControls").length,
|
||||
1,
|
||||
"box controls list"
|
||||
);
|
||||
|
||||
await testComboBox(browser, accDoc);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user