738 lines
21 KiB
JavaScript
738 lines
21 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
/* 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);
|
|
|
|
function invokeSetAriaOwns(
|
|
browser,
|
|
id,
|
|
children = null,
|
|
elementReflection = false
|
|
) {
|
|
if (!elementReflection) {
|
|
return invokeSetAttribute(browser, id, "aria-owns", children);
|
|
}
|
|
|
|
return invokeContentTask(
|
|
browser,
|
|
[id, children],
|
|
(contentId, contentChildrenIds) => {
|
|
let elm = content.document.getElementById(contentId);
|
|
if (contentChildrenIds) {
|
|
elm.ariaOwnsElements = contentChildrenIds
|
|
.split(" ")
|
|
.map(childId => content.document.getElementById(childId));
|
|
} else {
|
|
elm.ariaOwnsElements = null;
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
async function testContainer1(browser, accDoc, elementReflection = false) {
|
|
const id = "t1_container";
|
|
const docID = getAccessibleDOMNodeID(accDoc);
|
|
const acc = findAccessibleChildByID(accDoc, id);
|
|
|
|
/* ================= Initial tree test ==================================== */
|
|
// children are swapped by ARIA owns
|
|
let tree = {
|
|
SECTION: [{ CHECKBUTTON: [{ SECTION: [] }] }, { PUSHBUTTON: [] }],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Change ARIA owns ====================================== */
|
|
let onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeSetAriaOwns(
|
|
browser,
|
|
id,
|
|
"t1_button t1_subdiv",
|
|
elementReflection
|
|
);
|
|
await onReorder;
|
|
|
|
// children are swapped again, button and subdiv are appended to
|
|
// the children.
|
|
tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // checkbox, native order
|
|
{ PUSHBUTTON: [] }, // button, rearranged by ARIA own
|
|
{ SECTION: [] }, // subdiv from the subtree, ARIA owned
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Remove ARIA owns ====================================== */
|
|
onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeSetAriaOwns(browser, id, null, elementReflection);
|
|
await onReorder;
|
|
|
|
// children follow the DOM order
|
|
tree = {
|
|
SECTION: [{ PUSHBUTTON: [] }, { CHECKBUTTON: [{ SECTION: [] }] }],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Set ARIA owns ========================================= */
|
|
onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeSetAriaOwns(
|
|
browser,
|
|
id,
|
|
"t1_button t1_subdiv",
|
|
elementReflection
|
|
);
|
|
await onReorder;
|
|
|
|
// children are swapped again, button and subdiv are appended to
|
|
// the children.
|
|
tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // checkbox
|
|
{ PUSHBUTTON: [] }, // button, rearranged by ARIA own
|
|
{ SECTION: [] }, // subdiv from the subtree, ARIA owned
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Add ID to ARIA owns =================================== */
|
|
onReorder = waitForEvent(EVENT_REORDER, docID);
|
|
await invokeSetAttribute(
|
|
browser,
|
|
id,
|
|
"aria-owns",
|
|
"t1_button t1_subdiv t1_group"
|
|
);
|
|
await onReorder;
|
|
|
|
// children are swapped again, button and subdiv are appended to
|
|
// the children.
|
|
tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // t1_checkbox
|
|
{ PUSHBUTTON: [] }, // button, t1_button
|
|
{ SECTION: [] }, // subdiv from the subtree, t1_subdiv
|
|
{ GROUPING: [] }, // group from outside, t1_group
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Append element ======================================== */
|
|
onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeContentTask(browser, [id], contentId => {
|
|
let div = content.document.createElement("div");
|
|
div.setAttribute("id", "t1_child3");
|
|
div.setAttribute("role", "radio");
|
|
content.document.getElementById(contentId).appendChild(div);
|
|
});
|
|
await onReorder;
|
|
|
|
// children are invalidated, they includes aria-owns swapped kids and
|
|
// newly inserted child.
|
|
tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // existing explicit, t1_checkbox
|
|
{ RADIOBUTTON: [] }, // new explicit, t1_child3
|
|
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
|
|
{ SECTION: [] }, // ARIA owned, t1_subdiv
|
|
{ GROUPING: [] }, // ARIA owned, t1_group
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Remove element ======================================== */
|
|
onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.getElementById("t1_span").remove();
|
|
});
|
|
await onReorder;
|
|
|
|
// subdiv should go away
|
|
tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // explicit, t1_checkbox
|
|
{ RADIOBUTTON: [] }, // explicit, t1_child3
|
|
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
|
|
{ GROUPING: [] }, // ARIA owned, t1_group
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Remove ID ============================================= */
|
|
onReorder = waitForEvent(EVENT_REORDER, docID);
|
|
await invokeSetAttribute(browser, "t1_group", "id");
|
|
await onReorder;
|
|
|
|
tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] },
|
|
{ RADIOBUTTON: [] },
|
|
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
/* ================ Set ID ================================================ */
|
|
onReorder = waitForEvent(EVENT_REORDER, docID);
|
|
await invokeSetAttribute(browser, "t1_grouptmp", "id", "t1_group");
|
|
await onReorder;
|
|
|
|
tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] },
|
|
{ RADIOBUTTON: [] },
|
|
{ PUSHBUTTON: [] }, // ARIA owned, t1_button
|
|
{ GROUPING: [] }, // ARIA owned, t1_group, previously t1_grouptmp
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
}
|
|
|
|
async function removeContainer(browser, accDoc) {
|
|
const id = "t2_container1";
|
|
const acc = findAccessibleChildByID(accDoc, id);
|
|
|
|
let tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // ARIA owned, 't2_owned'
|
|
],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
let onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document
|
|
.getElementById("t2_container2")
|
|
.removeChild(content.document.getElementById("t2_container3"));
|
|
});
|
|
await onReorder;
|
|
|
|
tree = {
|
|
SECTION: [],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
}
|
|
|
|
async function stealAndRecacheChildren(browser, accDoc, elementReflection) {
|
|
const id1 = "t3_container1";
|
|
const id2 = "t3_container2";
|
|
const acc1 = findAccessibleChildByID(accDoc, id1);
|
|
const acc2 = findAccessibleChildByID(accDoc, id2);
|
|
|
|
/* ================ Attempt to steal from other ARIA owns ================= */
|
|
let onReorder = waitForEvent(EVENT_REORDER, id2);
|
|
await invokeSetAriaOwns(browser, id2, "t3_child", elementReflection);
|
|
await invokeContentTask(browser, [id2], id => {
|
|
let div = content.document.createElement("div");
|
|
div.setAttribute("role", "radio");
|
|
content.document.getElementById(id).appendChild(div);
|
|
});
|
|
await onReorder;
|
|
|
|
let tree = {
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // ARIA owned
|
|
],
|
|
};
|
|
testAccessibleTree(acc1, tree);
|
|
|
|
tree = {
|
|
SECTION: [{ RADIOBUTTON: [] }],
|
|
};
|
|
testAccessibleTree(acc2, tree);
|
|
}
|
|
|
|
async function showHiddenElement(browser, accDoc) {
|
|
const id = "t4_container1";
|
|
const acc = findAccessibleChildByID(accDoc, id);
|
|
|
|
let tree = {
|
|
SECTION: [{ RADIOBUTTON: [] }],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
let onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeSetStyle(browser, "t4_child1", "display", "block");
|
|
await onReorder;
|
|
|
|
tree = {
|
|
SECTION: [{ CHECKBUTTON: [] }, { RADIOBUTTON: [] }],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
}
|
|
|
|
async function rearrangeARIAOwns(browser, accDoc, elementReflection) {
|
|
const id = "t5_container";
|
|
const acc = findAccessibleChildByID(accDoc, id);
|
|
const tests = [
|
|
{
|
|
val: "t5_checkbox t5_radio t5_button",
|
|
roleList: ["CHECKBUTTON", "RADIOBUTTON", "PUSHBUTTON"],
|
|
},
|
|
{
|
|
val: "t5_radio t5_button t5_checkbox",
|
|
roleList: ["RADIOBUTTON", "PUSHBUTTON", "CHECKBUTTON"],
|
|
},
|
|
];
|
|
|
|
for (let { val, roleList } of tests) {
|
|
let onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeSetAriaOwns(browser, id, val, elementReflection);
|
|
await onReorder;
|
|
|
|
let tree = { SECTION: [] };
|
|
for (let role of roleList) {
|
|
let ch = {};
|
|
ch[role] = [];
|
|
tree.SECTION.push(ch);
|
|
}
|
|
testAccessibleTree(acc, tree);
|
|
}
|
|
}
|
|
|
|
async function removeNotARIAOwnedEl(browser, accDoc) {
|
|
const id = "t6_container";
|
|
const acc = findAccessibleChildByID(accDoc, id);
|
|
|
|
let tree = {
|
|
SECTION: [{ TEXT_LEAF: [] }, { GROUPING: [] }],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
|
|
let onReorder = waitForEvent(EVENT_REORDER, id);
|
|
await invokeContentTask(browser, [id], contentId => {
|
|
content.document
|
|
.getElementById(contentId)
|
|
.removeChild(content.document.getElementById("t6_span"));
|
|
});
|
|
await onReorder;
|
|
|
|
tree = {
|
|
SECTION: [{ GROUPING: [] }],
|
|
};
|
|
testAccessibleTree(acc, tree);
|
|
}
|
|
|
|
addAccessibleTask(
|
|
"e10s/doc_treeupdate_ariaowns.html",
|
|
async function (browser, accDoc) {
|
|
await testContainer1(browser, accDoc);
|
|
await removeContainer(browser, accDoc);
|
|
await stealAndRecacheChildren(browser, accDoc);
|
|
await showHiddenElement(browser, accDoc);
|
|
await rearrangeARIAOwns(browser, accDoc);
|
|
await removeNotARIAOwnedEl(browser, accDoc);
|
|
},
|
|
{ iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
addAccessibleTask(
|
|
"e10s/doc_treeupdate_ariaowns.html",
|
|
async function (browser, accDoc) {
|
|
await testContainer1(browser, accDoc, true);
|
|
await removeContainer(browser, accDoc);
|
|
await stealAndRecacheChildren(browser, accDoc, true);
|
|
await showHiddenElement(browser, accDoc);
|
|
await rearrangeARIAOwns(browser, accDoc, true);
|
|
await removeNotARIAOwnedEl(browser, accDoc);
|
|
},
|
|
{ iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
// Test owning an ancestor which isn't created yet with an iframe in the
|
|
// subtree.
|
|
addAccessibleTask(
|
|
`
|
|
<span id="a">
|
|
<div id="b" aria-owns="c"></div>
|
|
</span>
|
|
<div id="c">
|
|
<iframe></iframe>
|
|
</div>
|
|
<script>
|
|
document.getElementById("c").setAttribute("aria-owns", "a");
|
|
</script>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
testAccessibleTree(accDoc, {
|
|
DOCUMENT: [
|
|
{
|
|
// b
|
|
SECTION: [
|
|
{
|
|
// c
|
|
SECTION: [{ INTERNAL_FRAME: [{ DOCUMENT: [] }] }],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
);
|
|
|
|
// Verify that removing the parent of a DOM-sibling aria-owned child keeps the
|
|
// formerly-owned child in the tree.
|
|
addAccessibleTask(
|
|
`<input id='x'></input><div aria-owns='x'></div>`,
|
|
async function (browser, accDoc) {
|
|
testAccessibleTree(accDoc, {
|
|
DOCUMENT: [{ SECTION: [{ ENTRY: [] }] }],
|
|
});
|
|
|
|
info("Removing the div that aria-owns a DOM sibling");
|
|
let onReorder = waitForEvent(EVENT_REORDER, accDoc);
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.querySelector("div").remove();
|
|
});
|
|
await onReorder;
|
|
|
|
info("Verifying that the formerly-owned child is still present");
|
|
testAccessibleTree(accDoc, {
|
|
DOCUMENT: [{ ENTRY: [] }],
|
|
});
|
|
},
|
|
{ chrome: true, iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
// Verify that removing the parent of multiple DOM-sibling aria-owned children
|
|
// keeps all formerly-owned children in the tree.
|
|
addAccessibleTask(
|
|
`<input id='x'></input><input id='y'><div aria-owns='x y'></div>`,
|
|
async function (browser, accDoc) {
|
|
testAccessibleTree(accDoc, {
|
|
DOCUMENT: [
|
|
{
|
|
SECTION: [{ ENTRY: [] }, { ENTRY: [] }],
|
|
},
|
|
],
|
|
});
|
|
|
|
info("Removing the div that aria-owns DOM siblings");
|
|
let onReorder = waitForEvent(EVENT_REORDER, accDoc);
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.querySelector("div").remove();
|
|
});
|
|
await onReorder;
|
|
|
|
info("Verifying that the formerly-owned children are still present");
|
|
testAccessibleTree(accDoc, {
|
|
DOCUMENT: [{ ENTRY: [] }, { ENTRY: [] }],
|
|
});
|
|
},
|
|
{ chrome: true, iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
// Verify that reordering owned elements by changing the aria-owns attribute
|
|
// properly reorders owned elements.
|
|
addAccessibleTask(
|
|
`
|
|
<div id="container" aria-owns="b d c a">
|
|
<div id="a" role="button"></div>
|
|
<div id="b" role="checkbox"></div>
|
|
</div>
|
|
<div id="c" role="radio"></div>
|
|
<div id="d"></div>`,
|
|
async function (browser, accDoc) {
|
|
testAccessibleTree(accDoc, {
|
|
DOCUMENT: [
|
|
{
|
|
SECTION: [
|
|
{ CHECKBUTTON: [] }, // b
|
|
{ SECTION: [] }, // d
|
|
{ RADIOBUTTON: [] }, // c
|
|
{ PUSHBUTTON: [] }, // a
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
info("Removing the div that aria-owns other elements");
|
|
let onReorder = waitForEvent(EVENT_REORDER, accDoc);
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.querySelector("#container").remove();
|
|
});
|
|
await onReorder;
|
|
|
|
info(
|
|
"Verify DOM children are removed, order of remaining elements is correct"
|
|
);
|
|
testAccessibleTree(accDoc, {
|
|
DOCUMENT: [
|
|
{ RADIOBUTTON: [] }, // c
|
|
{ SECTION: [] }, // d
|
|
],
|
|
});
|
|
},
|
|
{ chrome: true, iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
// Verify that we avoid sending unwanted hide events when doing multiple
|
|
// aria-owns relocations in a single tick. Note that we're avoiding testing
|
|
// chrome here since parent process locals don't track moves in the same way,
|
|
// meaning our mechanism for avoiding duplicate hide events doesn't work.
|
|
addAccessibleTask(
|
|
`
|
|
<div id='b' aria-owns='a'></div>
|
|
<div id='d'></div>
|
|
<dd id='f'>
|
|
<div id='a' aria-owns='d'></div>
|
|
</dd>
|
|
`,
|
|
async function (browser, accDoc) {
|
|
const b = findAccessibleChildByID(accDoc, "b");
|
|
const waitFor = {
|
|
expected: [
|
|
[EVENT_HIDE, b],
|
|
[EVENT_SHOW, "d"],
|
|
[EVENT_REORDER, accDoc],
|
|
],
|
|
unexpected: [
|
|
[EVENT_HIDE, "d"],
|
|
[EVENT_REORDER, "a"],
|
|
],
|
|
};
|
|
info(
|
|
"Verifying that events are fired properly after doing two aria-owns relocations"
|
|
);
|
|
await contentSpawnMutation(browser, waitFor, function () {
|
|
content.document.querySelector("#b").remove();
|
|
content.document.querySelector("#f").remove();
|
|
});
|
|
},
|
|
{ chrome: false, iframe: true, remoteIframe: true }
|
|
);
|
|
|
|
/**
|
|
* Test relation defaults via element internals
|
|
*/
|
|
addAccessibleTask(
|
|
`
|
|
|
|
<div role="listbox">
|
|
<div role="listitem" id="l1"></div>
|
|
<div role="listitem" id="l2"></div>
|
|
<div role="listitem" id="l3"></div>
|
|
</div>
|
|
<custom-listbox id="listbox"></custom-listbox>
|
|
<div role="listbox">
|
|
<div role="listitem" id="l4"></div>
|
|
</div>
|
|
|
|
<script>
|
|
customElements.define("custom-listbox",
|
|
class extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
this.tabIndex = "0"
|
|
this._internals = this.attachInternals();
|
|
this._internals.role = "listbox";
|
|
this._internals.ariaOwnsElements = Array.from(this.previousElementSibling.children)
|
|
}
|
|
}
|
|
);
|
|
</script>`,
|
|
async function (browser, accDoc) {
|
|
let listbox = findAccessibleChildByID(accDoc, "listbox");
|
|
is(listbox.children.length, 3, "got children");
|
|
let onReorder = waitForEvent(EVENT_REORDER, "listbox");
|
|
invokeSetAriaOwns(browser, "listbox", "l4");
|
|
await onReorder;
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test insertion of relocated by ID child after initial load
|
|
*/
|
|
addAccessibleTask(
|
|
`<div id='a' aria-owns='b'></div>`,
|
|
async function (browser, accDoc) {
|
|
const a = findAccessibleChildByID(accDoc, "a");
|
|
is(a.children.length, 0, "'a' has no children");
|
|
const waitFor = {
|
|
expected: [
|
|
[EVENT_SHOW, "b"],
|
|
[EVENT_INNER_REORDER, a],
|
|
[EVENT_REORDER, accDoc],
|
|
],
|
|
};
|
|
await contentSpawnMutation(browser, waitFor, function () {
|
|
const b = content.document.createElement("div");
|
|
b.id = "b";
|
|
content.document.body.appendChild(b);
|
|
});
|
|
is(getAccessibleDOMNodeID(a.firstChild), "b", "'a' owns relocated child");
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Test insertion of relocated by child element reflection after initial load
|
|
*/
|
|
addAccessibleTask(`<div id='a'></div>`, async function (browser, accDoc) {
|
|
const a = findAccessibleChildByID(accDoc, "a");
|
|
is(a.children.length, 0, "'a' has no children");
|
|
|
|
// Create div and add it to a's ariaOwnsElements.
|
|
// The refresh ticks called in contentSpawnMutation
|
|
// will cause a relocation to be scheduled and performed.
|
|
// Nothing will happen because 'b' is not parented yet.
|
|
let waitFor = {
|
|
unexpected: [
|
|
[EVENT_SHOW, "b"],
|
|
[EVENT_INNER_REORDER, a],
|
|
[EVENT_REORDER, accDoc],
|
|
],
|
|
};
|
|
await contentSpawnMutation(browser, waitFor, function () {
|
|
content.b = content.document.createElement("div");
|
|
content.b.id = "b";
|
|
content.document.getElementById("a").ariaOwnsElements = [content.b];
|
|
});
|
|
|
|
// Parent 'b'. It should relocate into 'a'.
|
|
waitFor = {
|
|
expected: [
|
|
[EVENT_SHOW, "b"],
|
|
[EVENT_INNER_REORDER, a],
|
|
[EVENT_REORDER, accDoc],
|
|
],
|
|
};
|
|
await contentSpawnMutation(browser, waitFor, function () {
|
|
content.document.body.appendChild(content.b);
|
|
});
|
|
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"
|
|
);
|
|
}
|
|
);
|
|
|
|
addAccessibleTask(
|
|
`
|
|
<div id="box" role="combobox"
|
|
aria-owns="listbox"
|
|
aria-expanded="true"
|
|
aria-haspopup="listbox"
|
|
aria-autocomplete="list"
|
|
contenteditable="true"></div>
|
|
<ul role="listbox" id="listbox">
|
|
<li role="option">apple</li>
|
|
<li role="option">peach</li>
|
|
</ul>
|
|
`,
|
|
async (browser, accDoc) => {
|
|
const combobox = findAccessibleChildByID(accDoc, "box");
|
|
const listbox = findAccessibleChildByID(accDoc, "listbox");
|
|
|
|
testStates(combobox, 0, EXT_STATE_EDITABLE, 0, 0);
|
|
is(combobox.childCount, 0, "combobox has no children");
|
|
await testCachedRelation(combobox, RELATION_CONTROLLER_FOR, [listbox]);
|
|
await testCachedRelation(listbox, RELATION_CONTROLLED_BY, [combobox]);
|
|
|
|
let expectedEvents = Promise.all([
|
|
waitForStateChange(combobox, EXT_STATE_EDITABLE, false, true),
|
|
waitForEvent(EVENT_REORDER, accDoc),
|
|
]);
|
|
await invokeContentTask(browser, [], () => {
|
|
content.document.getElementById("box").contentEditable = false;
|
|
});
|
|
await expectedEvents;
|
|
await testCachedRelation(combobox, RELATION_CONTROLLER_FOR, []);
|
|
await testCachedRelation(listbox, RELATION_CONTROLLED_BY, []);
|
|
is(combobox.childCount, 1, "combobox has listbox");
|
|
}
|
|
);
|