diff --git a/accessible/base/AccIterator.cpp b/accessible/base/AccIterator.cpp index 8d3ce431509f..9602a6bb7575 100644 --- a/accessible/base/AccIterator.cpp +++ b/accessible/base/AccIterator.cpp @@ -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) { diff --git a/accessible/base/AccIterator.h b/accessible/base/AccIterator.h index 8c627c646fcb..3a678c87d267 100644 --- a/accessible/base/AccIterator.h +++ b/accessible/base/AccIterator.h @@ -225,7 +225,7 @@ class AssociatedElementsIterator : public AccIterable { /** * Return next element. */ - nsIContent* NextElem(); + dom::Element* NextElem(); /** * Return the element with the given ID. diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index b8f594587856..661d02c744da 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -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>* 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; diff --git a/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js index df1dcacfb283..c26eabab7011 100644 --- a/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js +++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js @@ -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(`
`, 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( + ` + +
+

Hello

+
+ `, + 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( + ` +
+ +
+

Hello

+

+ `, + 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" + ); + } +); diff --git a/accessible/tests/browser/mac/browser_combobox.js b/accessible/tests/browser/mac/browser_combobox.js index 2fd325581440..474ded15cc15 100644 --- a/accessible/tests/browser/mac/browser_combobox.js +++ b/accessible/tests/browser/mac/browser_combobox.js @@ -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,19 +20,9 @@ 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" - ); - is(box.getAttributeValue("AXValue"), "peach", "After popup value correct"); - } + + 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); } );