summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/e10s
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/browser/e10s')
-rw-r--r--accessible/tests/browser/e10s/browser.ini85
-rw-r--r--accessible/tests/browser/e10s/browser_caching_actions.js266
-rw-r--r--accessible/tests/browser/e10s/browser_caching_attributes.js550
-rw-r--r--accessible/tests/browser/e10s/browser_caching_description.js254
-rw-r--r--accessible/tests/browser/e10s/browser_caching_document_props.js78
-rw-r--r--accessible/tests/browser/e10s/browser_caching_domnodeid.js33
-rw-r--r--accessible/tests/browser/e10s/browser_caching_innerHTML.js55
-rw-r--r--accessible/tests/browser/e10s/browser_caching_interfaces.js59
-rw-r--r--accessible/tests/browser/e10s/browser_caching_name.js539
-rw-r--r--accessible/tests/browser/e10s/browser_caching_position.js194
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations.js289
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations_002.js245
-rw-r--r--accessible/tests/browser/e10s/browser_caching_states.js420
-rw-r--r--accessible/tests/browser/e10s/browser_caching_table.js509
-rw-r--r--accessible/tests/browser/e10s/browser_caching_text_bounds.js549
-rw-r--r--accessible/tests/browser/e10s/browser_caching_uniqueid.js30
-rw-r--r--accessible/tests/browser/e10s/browser_caching_value.js384
-rw-r--r--accessible/tests/browser/e10s/browser_events_announcement.js30
-rw-r--r--accessible/tests/browser/e10s/browser_events_caretmove.js22
-rw-r--r--accessible/tests/browser/e10s/browser_events_hide.js44
-rw-r--r--accessible/tests/browser/e10s/browser_events_show.js22
-rw-r--r--accessible/tests/browser/e10s/browser_events_statechange.js71
-rw-r--r--accessible/tests/browser/e10s/browser_events_textchange.js120
-rw-r--r--accessible/tests/browser/e10s/browser_events_vcchange.js87
-rw-r--r--accessible/tests/browser/e10s/browser_obj_group.js812
-rw-r--r--accessible/tests/browser/e10s/browser_text.js369
-rw-r--r--accessible/tests/browser/e10s/browser_text_caret.js453
-rw-r--r--accessible/tests/browser/e10s/browser_text_paragraph_boundary.js22
-rw-r--r--accessible/tests/browser/e10s/browser_text_selection.js312
-rw-r--r--accessible/tests/browser/e10s/browser_text_spelling.js151
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js45
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js325
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_canvas.js28
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js60
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_doc.js320
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_gencontent.js94
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_hidden.js32
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_image.js192
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_imagemap.js190
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_list.js52
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js48
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_listener.js38
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_move.js64
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_optgroup.js100
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_removal.js58
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js73
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_table.js48
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_textleaf.js38
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_visibility.js342
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_whitespace.js69
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html23
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html44
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_imagemap.html21
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml11
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_visibility.html78
-rw-r--r--accessible/tests/browser/e10s/doc_treeupdate_whitespace.html10
-rw-r--r--accessible/tests/browser/e10s/fonts/Ahem.sjs241
-rw-r--r--accessible/tests/browser/e10s/head.js193
58 files changed, 9891 insertions, 0 deletions
diff --git a/accessible/tests/browser/e10s/browser.ini b/accessible/tests/browser/e10s/browser.ini
new file mode 100644
index 0000000000..7fcced46a2
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser.ini
@@ -0,0 +1,85 @@
+[DEFAULT]
+subsuite = a11y
+support-files =
+ head.js
+ doc_treeupdate_ariadialog.html
+ doc_treeupdate_ariaowns.html
+ doc_treeupdate_imagemap.html
+ doc_treeupdate_removal.xhtml
+ doc_treeupdate_visibility.html
+ doc_treeupdate_whitespace.html
+ fonts/Ahem.sjs
+ !/accessible/tests/browser/shared-head.js
+ !/accessible/tests/browser/*.jsm
+ !/accessible/tests/mochitest/*.js
+ !/accessible/tests/mochitest/events/slow_image.sjs
+ !/accessible/tests/mochitest/letters.gif
+ !/accessible/tests/mochitest/moz.png
+
+# Caching tests
+[browser_caching_actions.js]
+[browser_caching_attributes.js]
+[browser_caching_description.js]
+[browser_caching_document_props.js]
+[browser_caching_innerHTML.js]
+skip-if = os != 'win'
+[browser_caching_name.js]
+skip-if = (os == "linux" && bits == 64) || (debug && os == "mac") || (debug && os == "win") #Bug 1388256
+[browser_caching_relations.js]
+[browser_caching_relations_002.js]
+[browser_caching_states.js]
+[browser_caching_table.js]
+[browser_caching_value.js]
+[browser_caching_uniqueid.js]
+[browser_caching_interfaces.js]
+[browser_caching_domnodeid.js]
+[browser_caching_text_bounds.js]
+
+# Events tests
+[browser_events_announcement.js]
+skip-if = os == 'win' # Bug 1288839
+[browser_events_caretmove.js]
+[browser_events_hide.js]
+[browser_events_show.js]
+[browser_events_statechange.js]
+[browser_events_textchange.js]
+[browser_events_vcchange.js]
+
+# Text tests
+[browser_text.js]
+[browser_text_caret.js]
+[browser_text_selection.js]
+[browser_text_spelling.js]
+skip-if = true # Bug 1800400
+[browser_text_paragraph_boundary.js]
+
+# Tree update tests
+[browser_treeupdate_ariadialog.js]
+[browser_treeupdate_ariaowns.js]
+[browser_treeupdate_canvas.js]
+skip-if = (os == 'win' && os_version == '10.0' && bits == 64 && !debug) #Bug 1462638 - Disabled on Win10 opt/pgo for frequent failures
+[browser_treeupdate_cssoverflow.js]
+[browser_treeupdate_doc.js]
+skip-if = os == 'win' # Bug 1288839
+[browser_treeupdate_gencontent.js]
+[browser_treeupdate_hidden.js]
+[browser_treeupdate_image.js]
+[browser_treeupdate_imagemap.js]
+skip-if =
+ win10_2004 && fission && debug # high frequency intermittent
+[browser_treeupdate_list.js]
+[browser_treeupdate_list_editabledoc.js]
+[browser_treeupdate_listener.js]
+[browser_treeupdate_move.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_treeupdate_optgroup.js]
+[browser_treeupdate_removal.js]
+[browser_treeupdate_select_dropdown.js]
+[browser_treeupdate_table.js]
+[browser_treeupdate_textleaf.js]
+[browser_treeupdate_visibility.js]
+[browser_treeupdate_whitespace.js]
+skip-if = true # Failing due to incorrect index of test container children on document load.
+[browser_obj_group.js]
+[browser_caching_position.js]
diff --git a/accessible/tests/browser/e10s/browser_caching_actions.js b/accessible/tests/browser/e10s/browser_caching_actions.js
new file mode 100644
index 0000000000..270208106c
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_actions.js
@@ -0,0 +1,266 @@
+/* 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";
+
+const gClickEvents = ["mousedown", "mouseup", "click"];
+
+const gActionDescrMap = {
+ jump: "Jump",
+ press: "Press",
+ check: "Check",
+ uncheck: "Uncheck",
+ select: "Select",
+ open: "Open",
+ close: "Close",
+ switch: "Switch",
+ click: "Click",
+ collapse: "Collapse",
+ expand: "Expand",
+ activate: "Activate",
+ cycle: "Cycle",
+ "click ancestor": "Click ancestor",
+};
+
+async function testActions(browser, docAcc, id, expectedActions, domEvents) {
+ const acc = findAccessibleChildByID(docAcc, id);
+ is(acc.actionCount, expectedActions.length, "Correct action count");
+
+ let actionNames = [];
+ let actionDescriptions = [];
+ for (let i = 0; i < acc.actionCount; i++) {
+ actionNames.push(acc.getActionName(i));
+ actionDescriptions.push(acc.getActionDescription(i));
+ }
+
+ is(actionNames.join(","), expectedActions.join(","), "Correct action names");
+ is(
+ actionDescriptions.join(","),
+ expectedActions.map(a => gActionDescrMap[a]).join(","),
+ "Correct action descriptions"
+ );
+
+ if (!domEvents) {
+ return;
+ }
+
+ // We need to set up the listener, and wait for the promise in two separate
+ // content tasks.
+ await invokeContentTask(browser, [id, domEvents], (_id, _domEvents) => {
+ let promises = _domEvents.map(
+ evtName =>
+ new Promise(resolve => {
+ const listener = e => {
+ if (e.target.id == _id) {
+ content.removeEventListener(evtName, listener);
+ content.evtPromise = null;
+ resolve(42);
+ }
+ };
+ content.addEventListener(evtName, listener);
+ })
+ );
+ content.evtPromise = Promise.all(promises);
+ });
+
+ acc.doAction(0);
+
+ let eventFired = await invokeContentTask(browser, [], async () => {
+ await content.evtPromise;
+ return true;
+ });
+
+ ok(eventFired, `DOM events fired '${domEvents}'`);
+}
+
+addAccessibleTask(
+ `<ul>
+ <li id="li_clickable1" onclick="">Clickable list item</li>
+ <li id="li_clickable2" onmousedown="">Clickable list item</li>
+ <li id="li_clickable3" onmouseup="">Clickable list item</li>
+ </ul>
+
+ <img id="onclick_img" onclick=""
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+
+ <a id="link1" href="#">linkable textleaf accessible</a>
+ <div id="link2" onclick="">linkable textleaf accessible</div>
+
+ <a id="link3" href="#">
+ <img id="link3img" alt="image in link"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+ </a>
+
+ <div>
+ <label for="TextBox_t2" id="label1">
+ <span>Explicit</span>
+ </label>
+ <input name="in2" id="TextBox_t2" type="text" maxlength="17">
+ </div>
+
+ <div onclick=""><p id="p_in_clickable_div">p in clickable div</p></div>
+ `,
+ async function(browser, docAcc) {
+ is(docAcc.actionCount, 0, "Doc should not have any actions");
+
+ const _testActions = async (id, expectedActions, domEvents) => {
+ await testActions(browser, docAcc, id, expectedActions, domEvents);
+ };
+
+ await _testActions("li_clickable1", ["click"], gClickEvents);
+ await _testActions("li_clickable2", ["click"], gClickEvents);
+ await _testActions("li_clickable3", ["click"], gClickEvents);
+
+ await _testActions("onclick_img", ["click"], gClickEvents);
+ await _testActions("link1", ["jump"], gClickEvents);
+ await _testActions("link2", ["click"], gClickEvents);
+ await _testActions("link3", ["jump"], gClickEvents);
+ await _testActions("link3img", ["click ancestor"], gClickEvents);
+ await _testActions("label1", ["click"], gClickEvents);
+ await _testActions("p_in_clickable_div", ["click ancestor"], gClickEvents);
+
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("li_clickable1")
+ .removeAttribute("onclick");
+ });
+
+ let acc = findAccessibleChildByID(docAcc, "li_clickable1");
+ await untilCacheIs(() => acc.actionCount, 0, "li has no actions");
+ let thrown = false;
+ try {
+ acc.doAction(0);
+ } catch (e) {
+ thrown = true;
+ }
+ ok(thrown, "doAction should throw exception");
+
+ // Remove 'for' from label
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("label1").removeAttribute("for");
+ });
+ acc = findAccessibleChildByID(docAcc, "label1");
+ await untilCacheIs(() => acc.actionCount, 0, "label has no actions");
+ thrown = false;
+ try {
+ acc.doAction(0);
+ ok(false, "doAction should throw exception");
+ } catch (e) {
+ thrown = true;
+ }
+ ok(thrown, "doAction should throw exception");
+
+ // Add 'longdesc' to image
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("onclick_img")
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ .setAttribute("longdesc", "http://example.com");
+ });
+ acc = findAccessibleChildByID(docAcc, "onclick_img");
+ await untilCacheIs(() => acc.actionCount, 2, "img has 2 actions");
+ await _testActions("onclick_img", ["click", "showlongdesc"]);
+
+ // Remove 'onclick' from image with 'longdesc'
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("onclick_img").removeAttribute("onclick");
+ });
+ acc = findAccessibleChildByID(docAcc, "onclick_img");
+ await untilCacheIs(() => acc.actionCount, 1, "img has 1 actions");
+ await _testActions("onclick_img", ["showlongdesc"]);
+
+ // Remove 'href' from link and test linkable child
+ const link1Acc = findAccessibleChildByID(docAcc, "link1");
+ is(
+ link1Acc.firstChild.getActionName(0),
+ "click ancestor",
+ "linkable child has click ancestor action"
+ );
+ await invokeContentTask(browser, [], () => {
+ let link1 = content.document.getElementById("link1");
+ link1.removeAttribute("href");
+ });
+ await untilCacheIs(() => link1Acc.actionCount, 0, "link has no actions");
+ is(link1Acc.firstChild.actionCount, 0, "linkable child's actions removed");
+
+ // Add a click handler to the body. Ensure it propagates to descendants.
+ await invokeContentTask(browser, [], () => {
+ content.document.body.onclick = () => {};
+ });
+ await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
+ await _testActions("link1", ["click ancestor"]);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.body.onclick = null;
+ });
+ await untilCacheIs(() => docAcc.actionCount, 0, "Doc has no actions");
+ is(link1Acc.actionCount, 0, "link has no actions");
+
+ // Add a click handler to the root element. Ensure it propagates to
+ // descendants.
+ await invokeContentTask(browser, [], () => {
+ content.document.documentElement.onclick = () => {};
+ });
+ await untilCacheIs(() => docAcc.actionCount, 1, "Doc has 1 action");
+ await _testActions("link1", ["click ancestor"]);
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test access key.
+ */
+addAccessibleTask(
+ `
+<button id="noKey">noKey</button>
+<button id="key" accesskey="a">key</button>
+ `,
+ async function(browser, docAcc) {
+ const noKey = findAccessibleChildByID(docAcc, "noKey");
+ is(noKey.accessKey, "", "noKey has no accesskey");
+ const key = findAccessibleChildByID(docAcc, "key");
+ is(key.accessKey, MAC ? "⌃⌥a" : "Alt+Shift+a", "key has correct accesskey");
+
+ info("Changing accesskey");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("key").accessKey = "b";
+ });
+ await untilCacheIs(
+ () => key.accessKey,
+ MAC ? "⌃⌥b" : "Alt+Shift+b",
+ "Correct accesskey after change"
+ );
+
+ info("Removing accesskey");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("key").removeAttribute("accesskey");
+ });
+ await untilCacheIs(
+ () => key.accessKey,
+ "",
+ "Empty accesskey after removal"
+ );
+
+ info("Adding accesskey");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("key").accessKey = "c";
+ });
+ await untilCacheIs(
+ () => key.accessKey,
+ MAC ? "⌃⌥c" : "Alt+Shift+c",
+ "Correct accesskey after addition"
+ );
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: false, // Bug 1796846
+ remoteIframe: false, // Bug 1796846
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_attributes.js b/accessible/tests/browser/e10s/browser_caching_attributes.js
new file mode 100644
index 0000000000..aae7eede9f
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_attributes.js
@@ -0,0 +1,550 @@
+/* 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/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Default textbox accessible attributes.
+ */
+const defaultAttributes = {
+ "margin-top": "0px",
+ "margin-right": "0px",
+ "margin-bottom": "0px",
+ "margin-left": "0px",
+ "text-align": "start",
+ "text-indent": "0px",
+ id: "textbox",
+ tag: "input",
+ display: "inline-block",
+};
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {Object} expected attributes for given accessibles
+ * unexpected {Object} unexpected attributes for given accessibles
+ *
+ * action {?AsyncFunction} an optional action that awaits a change in
+ * attributes
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Number} an optional event to wait for
+ * }
+ */
+const attributesTests = [
+ {
+ desc: "Initiall accessible attributes",
+ expected: defaultAttributes,
+ unexpected: {
+ "line-number": "1",
+ "explicit-name": "true",
+ "container-live": "polite",
+ live: "polite",
+ },
+ },
+ {
+ desc: "@line-number attribute is present when textbox is focused",
+ async action(browser) {
+ await invokeFocus(browser, "textbox");
+ },
+ waitFor: EVENT_FOCUS,
+ expected: Object.assign({}, defaultAttributes, { "line-number": "1" }),
+ unexpected: {
+ "explicit-name": "true",
+ "container-live": "polite",
+ live: "polite",
+ },
+ },
+ {
+ desc: "@aria-live sets container-live and live attributes",
+ attrs: [
+ {
+ attr: "aria-live",
+ value: "polite",
+ },
+ ],
+ expected: Object.assign({}, defaultAttributes, {
+ "line-number": "1",
+ "container-live": "polite",
+ live: "polite",
+ }),
+ unexpected: {
+ "explicit-name": "true",
+ },
+ },
+ {
+ desc: "@title attribute sets explicit-name attribute to true",
+ attrs: [
+ {
+ attr: "title",
+ value: "textbox",
+ },
+ ],
+ expected: Object.assign({}, defaultAttributes, {
+ "line-number": "1",
+ "explicit-name": "true",
+ "container-live": "polite",
+ live: "polite",
+ }),
+ unexpected: {},
+ },
+];
+
+/**
+ * Test caching of accessible object attributes
+ */
+addAccessibleTask(
+ `
+ <input id="textbox" value="hello">`,
+ async function(browser, accDoc) {
+ let textbox = findAccessibleChildByID(accDoc, "textbox");
+ for (let {
+ desc,
+ action,
+ attrs,
+ expected,
+ waitFor,
+ unexpected,
+ } of attributesTests) {
+ info(desc);
+ let onUpdate;
+
+ if (waitFor) {
+ onUpdate = waitForEvent(waitFor, "textbox");
+ }
+
+ if (action) {
+ await action(browser);
+ } else if (attrs) {
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, "textbox", attr, value);
+ }
+ }
+
+ await onUpdate;
+ testAttrs(textbox, expected);
+ testAbsentAttrs(textbox, unexpected);
+ }
+ },
+ {
+ // These tests don't work yet with the parent process cache enabled.
+ topLevel: !isCacheEnabled,
+ iframe: !isCacheEnabled,
+ remoteIframe: !isCacheEnabled,
+ }
+);
+
+/**
+ * Test caching of the tag attribute.
+ */
+addAccessibleTask(
+ `
+<p id="p">text</p>
+<textarea id="textarea"></textarea>
+ `,
+ async function(browser, docAcc) {
+ testAttrs(docAcc, { tag: "body" }, true);
+ const p = findAccessibleChildByID(docAcc, "p");
+ testAttrs(p, { tag: "p" }, true);
+ const textLeaf = p.firstChild;
+ testAbsentAttrs(textLeaf, { tag: "" });
+ const textarea = findAccessibleChildByID(docAcc, "textarea");
+ testAttrs(textarea, { tag: "textarea" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of the text-input-type attribute.
+ */
+addAccessibleTask(
+ `
+ <input id="default">
+ <input id="email" type="email">
+ <input id="password" type="password">
+ <input id="text" type="text">
+ <input id="date" type="date">
+ <input id="time" type="time">
+ <input id="checkbox" type="checkbox">
+ <input id="radio" type="radio">
+ `,
+ async function(browser, docAcc) {
+ function testInputType(id, inputType) {
+ if (inputType == undefined) {
+ testAbsentAttrs(findAccessibleChildByID(docAcc, id), {
+ "text-input-type": "",
+ });
+ } else {
+ testAttrs(
+ findAccessibleChildByID(docAcc, id),
+ { "text-input-type": inputType },
+ true
+ );
+ }
+ }
+
+ testInputType("default");
+ testInputType("email", "email");
+ testInputType("password", "password");
+ testInputType("text", "text");
+ testInputType("date", "date");
+ testInputType("time", "time");
+ testInputType("checkbox");
+ testInputType("radio");
+ },
+ { chrome: true, topLevel: true, iframe: false, remoteIframe: false }
+);
+
+/**
+ * Test caching of the display attribute.
+ */
+addAccessibleTask(
+ `
+<div id="div">
+ <ins id="ins">a</ins>
+ <button id="button">b</button>
+</div>
+ `,
+ async function(browser, docAcc) {
+ const div = findAccessibleChildByID(docAcc, "div");
+ testAttrs(div, { display: "block" }, true);
+ const ins = findAccessibleChildByID(docAcc, "ins");
+ testAttrs(ins, { display: "inline" }, true);
+ const textLeaf = ins.firstChild;
+ testAbsentAttrs(textLeaf, { display: "" });
+ const button = findAccessibleChildByID(docAcc, "button");
+ testAttrs(button, { display: "inline-block" }, true);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("ins").style.display = "block";
+ content.document.body.offsetTop; // Flush layout.
+ });
+ await untilCacheIs(
+ () => ins.attributes.getStringProperty("display"),
+ "block",
+ "ins display attribute changed to block"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that there is no display attribute on image map areas.
+ */
+addAccessibleTask(
+ `
+<map name="normalMap">
+ <area id="normalArea" shape="default">
+</map>
+<img src="http://example.com/a11y/accessible/tests/mochitest/moz.png" usemap="#normalMap">
+<audio>
+ <map name="unslottedMap">
+ <area id="unslottedArea" shape="default">
+ </map>
+</audio>
+<img src="http://example.com/a11y/accessible/tests/mochitest/moz.png" usemap="#unslottedMap">
+ `,
+ async function(browser, docAcc) {
+ const normalArea = findAccessibleChildByID(docAcc, "normalArea");
+ testAbsentAttrs(normalArea, { display: "" });
+ const unslottedArea = findAccessibleChildByID(docAcc, "unslottedArea");
+ testAbsentAttrs(unslottedArea, { display: "" });
+ },
+ { topLevel: true }
+);
+
+/**
+ * Test caching of the explicit-name attribute.
+ */
+addAccessibleTask(
+ `
+<h1 id="h1">content</h1>
+<button id="buttonContent">content</button>
+<button id="buttonLabel" aria-label="label">content</button>
+<button id="buttonEmpty"></button>
+<button id="buttonSummary"><details><summary>test</summary></details></button>
+<div id="div"></div>
+ `,
+ async function(browser, docAcc) {
+ const h1 = findAccessibleChildByID(docAcc, "h1");
+ testAbsentAttrs(h1, { "explicit-name": "" });
+ const buttonContent = findAccessibleChildByID(docAcc, "buttonContent");
+ testAbsentAttrs(buttonContent, { "explicit-name": "" });
+ const buttonLabel = findAccessibleChildByID(docAcc, "buttonLabel");
+ testAttrs(buttonLabel, { "explicit-name": "true" }, true);
+ const buttonEmpty = findAccessibleChildByID(docAcc, "buttonEmpty");
+ testAbsentAttrs(buttonEmpty, { "explicit-name": "" });
+ const buttonSummary = findAccessibleChildByID(docAcc, "buttonSummary");
+ testAbsentAttrs(buttonSummary, { "explicit-name": "" });
+ const div = findAccessibleChildByID(docAcc, "div");
+ testAbsentAttrs(div, { "explicit-name": "" });
+
+ info("Setting aria-label on h1");
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, h1);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("h1").setAttribute("aria-label", "label");
+ });
+ await nameChanged;
+ testAttrs(h1, { "explicit-name": "true" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of ARIA attributes that are exposed via object attributes.
+ */
+addAccessibleTask(
+ `
+<div id="currentTrue" aria-current="true">currentTrue</div>
+<div id="currentFalse" aria-current="false">currentFalse</div>
+<div id="currentPage" aria-current="page">currentPage</div>
+<div id="currentBlah" aria-current="blah">currentBlah</div>
+<div id="haspopupMenu" aria-haspopup="menu">haspopup</div>
+<div id="rowColCountPositive" role="table" aria-rowcount="1000" aria-colcount="1000">
+ <div role="row">
+ <div id="rowColIndexPositive" role="cell" aria-rowindex="100" aria-colindex="100">positive</div>
+ </div>
+</div>
+<div id="rowColCountNegative" role="table" aria-rowcount="-1" aria-colcount="-1">
+ <div role="row">
+ <div id="rowColIndexNegative" role="cell" aria-rowindex="-1" aria-colindex="-1">negative</div>
+ </div>
+</div>
+<div id="rowColCountInvalid" role="table" aria-rowcount="z" aria-colcount="z">
+ <div role="row">
+ <div id="rowColIndexInvalid" role="cell" aria-rowindex="z" aria-colindex="z">invalid</div>
+ </div>
+</div>
+<div id="foo" aria-foo="bar">foo</div>
+<div id="mutate" aria-current="true">mutate</div>
+ `,
+ async function(browser, docAcc) {
+ const currentTrue = findAccessibleChildByID(docAcc, "currentTrue");
+ testAttrs(currentTrue, { current: "true" }, true);
+ const currentFalse = findAccessibleChildByID(docAcc, "currentFalse");
+ testAbsentAttrs(currentFalse, { current: "" });
+ const currentPage = findAccessibleChildByID(docAcc, "currentPage");
+ testAttrs(currentPage, { current: "page" }, true);
+ // Test that token normalization works.
+ const currentBlah = findAccessibleChildByID(docAcc, "currentBlah");
+ testAttrs(currentBlah, { current: "true" }, true);
+ const haspopupMenu = findAccessibleChildByID(docAcc, "haspopupMenu");
+ testAttrs(haspopupMenu, { haspopup: "menu" }, true);
+
+ // Test normalization of integer values.
+ const rowColCountPositive = findAccessibleChildByID(
+ docAcc,
+ "rowColCountPositive"
+ );
+ testAttrs(
+ rowColCountPositive,
+ { rowcount: "1000", colcount: "1000" },
+ true
+ );
+ const rowColIndexPositive = findAccessibleChildByID(
+ docAcc,
+ "rowColIndexPositive"
+ );
+ testAttrs(rowColIndexPositive, { rowindex: "100", colindex: "100" }, true);
+ const rowColCountNegative = findAccessibleChildByID(
+ docAcc,
+ "rowColCountNegative"
+ );
+ testAttrs(rowColCountNegative, { rowcount: "-1", colcount: "-1" }, true);
+ const rowColIndexNegative = findAccessibleChildByID(
+ docAcc,
+ "rowColIndexNegative"
+ );
+ testAbsentAttrs(rowColIndexNegative, { rowindex: "", colindex: "" });
+ const rowColCountInvalid = findAccessibleChildByID(
+ docAcc,
+ "rowColCountInvalid"
+ );
+ testAbsentAttrs(rowColCountInvalid, { rowcount: "", colcount: "" });
+ const rowColIndexInvalid = findAccessibleChildByID(
+ docAcc,
+ "rowColIndexInvalid"
+ );
+ testAbsentAttrs(rowColIndexInvalid, { rowindex: "", colindex: "" });
+
+ // Test that unknown aria- attributes get exposed.
+ const foo = findAccessibleChildByID(docAcc, "foo");
+ testAttrs(foo, { foo: "bar" }, true);
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAttrs(mutate, { current: "true" }, true);
+ info("mutate: Removing aria-current");
+ let changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, mutate);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("aria-current");
+ });
+ await changed;
+ testAbsentAttrs(mutate, { current: "" });
+ info("mutate: Adding aria-current");
+ changed = waitForEvent(EVENT_OBJECT_ATTRIBUTE_CHANGED, mutate);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("aria-current", "page");
+ });
+ await changed;
+ testAttrs(mutate, { current: "page" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test support for the xml-roles attribute.
+ */
+addAccessibleTask(
+ `
+<div id="knownRole" role="main">knownRole</div>
+<div id="emptyRole" role="">emptyRole</div>
+<div id="unknownRole" role="foo">unknownRole</div>
+<div id="multiRole" role="foo main">multiRole</div>
+<main id="landmarkMarkup">landmarkMarkup</main>
+<main id="landmarkMarkupWithRole" role="banner">landmarkMarkupWithRole</main>
+<main id="landmarkMarkupWithEmptyRole" role="">landmarkMarkupWithEmptyRole</main>
+<article id="markup">markup</article>
+<article id="markupWithRole" role="banner">markupWithRole</article>
+<article id="markupWithEmptyRole" role="">markupWithEmptyRole</article>
+ `,
+ async function(browser, docAcc) {
+ const knownRole = findAccessibleChildByID(docAcc, "knownRole");
+ testAttrs(knownRole, { "xml-roles": "main" }, true);
+ const emptyRole = findAccessibleChildByID(docAcc, "emptyRole");
+ testAbsentAttrs(emptyRole, { "xml-roles": "" });
+ const unknownRole = findAccessibleChildByID(docAcc, "unknownRole");
+ testAttrs(unknownRole, { "xml-roles": "foo" }, true);
+ const multiRole = findAccessibleChildByID(docAcc, "multiRole");
+ testAttrs(multiRole, { "xml-roles": "foo main" }, true);
+ const landmarkMarkup = findAccessibleChildByID(docAcc, "landmarkMarkup");
+ testAttrs(landmarkMarkup, { "xml-roles": "main" }, true);
+ const landmarkMarkupWithRole = findAccessibleChildByID(
+ docAcc,
+ "landmarkMarkupWithRole"
+ );
+ testAttrs(landmarkMarkupWithRole, { "xml-roles": "banner" }, true);
+ const landmarkMarkupWithEmptyRole = findAccessibleChildByID(
+ docAcc,
+ "landmarkMarkupWithEmptyRole"
+ );
+ testAttrs(landmarkMarkupWithEmptyRole, { "xml-roles": "main" }, true);
+ const markup = findAccessibleChildByID(docAcc, "markup");
+ testAttrs(markup, { "xml-roles": "article" }, true);
+ const markupWithRole = findAccessibleChildByID(docAcc, "markupWithRole");
+ testAttrs(markupWithRole, { "xml-roles": "banner" }, true);
+ const markupWithEmptyRole = findAccessibleChildByID(
+ docAcc,
+ "markupWithEmptyRole"
+ );
+ testAttrs(markupWithEmptyRole, { "xml-roles": "article" }, true);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test lie region attributes.
+ */
+addAccessibleTask(
+ `
+<div id="noLive"><p>noLive</p></div>
+<output id="liveMarkup"><p>liveMarkup</p></output>
+<div id="ariaLive" aria-live="polite"><p>ariaLive</p></div>
+<div id="liveRole" role="log"><p>liveRole</p></div>
+<div id="nonLiveRole" role="group"><p>nonLiveRole</p></div>
+<div id="other" aria-atomic="true" aria-busy="true" aria-relevant="additions"><p>other</p></div>
+ `,
+ async function(browser, docAcc) {
+ const noLive = findAccessibleChildByID(docAcc, "noLive");
+ for (const acc of [noLive, noLive.firstChild]) {
+ testAbsentAttrs(acc, {
+ live: "",
+ "container-live": "",
+ "container-live-role": "",
+ atomic: "",
+ "container-atomic": "",
+ busy: "",
+ "container-busy": "",
+ relevant: "",
+ "container-relevant": "",
+ });
+ }
+ const liveMarkup = findAccessibleChildByID(docAcc, "liveMarkup");
+ testAttrs(liveMarkup, { live: "polite" }, true);
+ testAttrs(liveMarkup.firstChild, { "container-live": "polite" }, true);
+ const ariaLive = findAccessibleChildByID(docAcc, "ariaLive");
+ testAttrs(ariaLive, { live: "polite" }, true);
+ testAttrs(ariaLive.firstChild, { "container-live": "polite" }, true);
+ const liveRole = findAccessibleChildByID(docAcc, "liveRole");
+ testAttrs(liveRole, { live: "polite" }, true);
+ testAttrs(
+ liveRole.firstChild,
+ { "container-live": "polite", "container-live-role": "log" },
+ true
+ );
+ const nonLiveRole = findAccessibleChildByID(docAcc, "nonLiveRole");
+ testAbsentAttrs(nonLiveRole, { live: "" });
+ testAbsentAttrs(nonLiveRole.firstChild, {
+ "container-live": "",
+ "container-live-role": "",
+ });
+ const other = findAccessibleChildByID(docAcc, "other");
+ testAttrs(
+ other,
+ { atomic: "true", busy: "true", relevant: "additions" },
+ true
+ );
+ testAttrs(
+ other.firstChild,
+ {
+ "container-atomic": "true",
+ "container-busy": "true",
+ "container-relevant": "additions",
+ },
+ true
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test the id attribute.
+ */
+addAccessibleTask(
+ `
+<p id="withId">withId</p>
+<div id="noIdParent"><p>noId</p></div>
+ `,
+ async function(browser, docAcc) {
+ const withId = findAccessibleChildByID(docAcc, "withId");
+ testAttrs(withId, { id: "withId" }, true);
+ const noId = findAccessibleChildByID(docAcc, "noIdParent").firstChild;
+ testAbsentAttrs(noId, { id: "" });
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test the valuetext attribute.
+ */
+addAccessibleTask(
+ `
+<div id="valuenow" role="slider" aria-valuenow="1"></div>
+<div id="valuetext" role="slider" aria-valuetext="text"></div>
+<div id="noValue" role="button"></div>
+ `,
+ async function(browser, docAcc) {
+ const valuenow = findAccessibleChildByID(docAcc, "valuenow");
+ testAttrs(valuenow, { valuetext: "1" }, true);
+ const valuetext = findAccessibleChildByID(docAcc, "valuetext");
+ testAttrs(valuetext, { valuetext: "text" }, true);
+ const noValue = findAccessibleChildByID(docAcc, "noValue");
+ testAbsentAttrs(noValue, { valuetext: "valuetext" });
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_description.js b/accessible/tests/browser/e10s/browser_caching_description.js
new file mode 100644
index 0000000000..3b1ebd2960
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_description.js
@@ -0,0 +1,254 @@
+/* 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/name.js */
+loadScripts({ name: "name.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {String} expected description value for a given accessible
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Array} an optional list of accessible events to wait for when
+ * attributes are updated
+ * }
+ */
+const tests = [
+ {
+ desc: "No description when there are no @alt, @title and @aria-describedby",
+ expected: "",
+ },
+ {
+ desc: "Description from @aria-describedby attribute",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "aria description",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@alt attribute which is used as the name",
+ attrs: [
+ {
+ attr: "alt",
+ value: "aria description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when @alt and " +
+ "@aria-describedby are not the same",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description2",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc:
+ "No description change when @alt is dropped but @aria-describedby remains",
+ attrs: [
+ {
+ attr: "alt",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when @title (used for " +
+ "name) and @aria-describedby are not the same",
+ attrs: [
+ {
+ attr: "title",
+ value: "title",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@title attribute which is used as the name",
+ attrs: [
+ {
+ attr: "title",
+ value: "another description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc: "No description with only @title attribute which is used as the name",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @title attribute when @alt and @atitle are not the " +
+ "same",
+ attrs: [
+ {
+ attr: "alt",
+ value: "aria description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "another description",
+ },
+ {
+ desc:
+ "No description from @title since it is the same as the @alt " +
+ "attribute which is used as the name",
+ attrs: [
+ {
+ attr: "alt",
+ value: "another description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@alt (used for name) and @title attributes",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description2",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when it is different " +
+ "from @alt (used for name) and @title attributes",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "aria description",
+ },
+ {
+ desc:
+ "No description from @aria-describedby since it is the same as the " +
+ "@alt attribute (used for name) but different from title",
+ attrs: [
+ {
+ attr: "alt",
+ value: "aria description",
+ },
+ ],
+ waitFor: [[EVENT_NAME_CHANGE, "image"]],
+ expected: "",
+ },
+ {
+ desc:
+ "Description from @aria-describedby attribute when @alt (used for " +
+ "name) and @aria-describedby are not the same but @title and " +
+ "aria-describedby are",
+ attrs: [
+ {
+ attr: "aria-describedby",
+ value: "description2",
+ },
+ ],
+ waitFor: [[EVENT_DESCRIPTION_CHANGE, "image"]],
+ expected: "another description",
+ },
+];
+
+/**
+ * Test caching of accessible object description
+ */
+addAccessibleTask(
+ `
+ <p id="description">aria description</p>
+ <p id="description2">another description</p>
+ <img id="image" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" />`,
+ async function(browser, accDoc) {
+ let imgAcc = findAccessibleChildByID(accDoc, "image");
+
+ for (let { desc, waitFor, attrs, expected } of tests) {
+ info(desc);
+ let onUpdate;
+ if (waitFor) {
+ onUpdate = waitForOrderedEvents(waitFor);
+ }
+ if (attrs) {
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, "image", attr, value);
+ }
+ }
+ await onUpdate;
+ // When attribute change (alt) triggers reorder event, accessible will
+ // become defunct.
+ if (isDefunct(imgAcc)) {
+ imgAcc = findAccessibleChildByID(accDoc, "image");
+ }
+ testDescr(imgAcc, expected);
+ }
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that the description is updated when the content of a hidden aria-describedby
+ * subtree changes.
+ */
+addAccessibleTask(
+ `
+<button id="button" aria-describedby="desc">
+<div id="desc" hidden>a</div>
+ `,
+ async function(browser, docAcc) {
+ const button = findAccessibleChildByID(docAcc, "button");
+ testDescr(button, "a");
+ info("Changing desc textContent");
+ let descChanged = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("desc").textContent = "c";
+ });
+ await descChanged;
+ testDescr(button, "c");
+ info("Prepending text node to desc");
+ descChanged = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("desc")
+ .prepend(content.document.createTextNode("b"));
+ });
+ await descChanged;
+ testDescr(button, "bc");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_document_props.js b/accessible/tests/browser/e10s/browser_caching_document_props.js
new file mode 100644
index 0000000000..e2a51d4531
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_document_props.js
@@ -0,0 +1,78 @@
+/* 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 });
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_whitespace.html",
+ async function(browser, docAcc) {
+ info("Testing top level doc");
+ queryInterfaces(docAcc, [nsIAccessibleDocument]);
+ const topUrl =
+ (browser.isRemoteBrowser ? CURRENT_CONTENT_DIR : CURRENT_DIR) +
+ "e10s/doc_treeupdate_whitespace.html";
+ is(docAcc.URL, topUrl, "Initial URL correct");
+ info("Changing URL");
+ await invokeContentTask(browser, [], () => {
+ content.history.pushState(
+ null,
+ "",
+ content.document.location.href + "/after"
+ );
+ });
+ is(docAcc.URL, topUrl + "/after", "URL correct after change");
+
+ // We can't use the harness to manage iframes for us because it uses data
+ // URIs for in-process iframes, but data URIs don't support
+ // history.pushState.
+
+ async function testIframe() {
+ queryInterfaces(iframeDocAcc, [nsIAccessibleDocument]);
+ is(iframeDocAcc.URL, src, "Initial URL correct");
+ info("Changing URL");
+ await invokeContentTask(browser, [], async () => {
+ await SpecialPowers.spawn(content.iframe, [], () => {
+ content.history.pushState(
+ null,
+ "",
+ content.document.location.href + "/after"
+ );
+ });
+ });
+ is(iframeDocAcc.URL, src + "/after", "URL correct after change");
+ }
+
+ info("Testing same origin (in-process) iframe");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let src = "http://example.com/initial.html";
+ let loaded = waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ evt => evt.accessible.parent.parent == docAcc
+ );
+ await invokeContentTask(browser, [src], cSrc => {
+ content.iframe = content.document.createElement("iframe");
+ content.iframe.src = cSrc;
+ content.document.body.append(content.iframe);
+ });
+ let iframeDocAcc = (await loaded).accessible;
+ await testIframe();
+
+ info("Testing different origin (out-of-process) iframe");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ src = "http://example.net/initial.html";
+ loaded = waitForEvent(
+ EVENT_DOCUMENT_LOAD_COMPLETE,
+ evt => evt.accessible.parent.parent == docAcc
+ );
+ await invokeContentTask(browser, [src], cSrc => {
+ content.iframe.src = cSrc;
+ });
+ iframeDocAcc = (await await loaded).accessible;
+ await testIframe();
+ },
+ { chrome: true, topLevel: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_domnodeid.js b/accessible/tests/browser/e10s/browser_caching_domnodeid.js
new file mode 100644
index 0000000000..30ffbe4415
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_domnodeid.js
@@ -0,0 +1,33 @@
+/* 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";
+
+/**
+ * Test DOM ID caching on remotes.
+ */
+addAccessibleTask(
+ '<div id="div"></div>',
+ async function(browser, accDoc) {
+ const div = findAccessibleChildByID(accDoc, "div");
+ ok(div, "Got accessible with 'div' ID.");
+
+ // We don't await for content task to return because
+ // we want to exercise the untilCacheIs function and
+ // demonstrate that it can await for a passing `is` test.
+ let contentPromise = invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").id = "foo";
+ });
+
+ await untilCacheIs(
+ () => div.id,
+ "foo",
+ "ID is correct and updated in cache"
+ );
+
+ // Don't leave test without the content task promise resolved.
+ await contentPromise;
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_innerHTML.js b/accessible/tests/browser/e10s/browser_caching_innerHTML.js
new file mode 100644
index 0000000000..be7469d55e
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_innerHTML.js
@@ -0,0 +1,55 @@
+/* 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";
+
+/**
+ * Test caching of innerHTML on math elements for Windows clients.
+ */
+addAccessibleTask(
+ `
+<p id="p">test</p>
+<math id="math"><mfrac><mi>x</mi><mi>y</mi></mfrac></math>
+ `,
+ async function(browser, docAcc) {
+ if (!isCacheEnabled) {
+ // Stop the harness from complaining that this file is empty when run with
+ // the cache disabled.
+ todo(false, "Cache disabled for a cache only test");
+ return;
+ }
+
+ const p = findAccessibleChildByID(docAcc, "p");
+ let hasHtml;
+ try {
+ p.cache.getStringProperty("html");
+ hasHtml = true;
+ } catch (e) {
+ hasHtml = false;
+ }
+ ok(!hasHtml, "p doesn't have cached html");
+
+ const math = findAccessibleChildByID(docAcc, "math");
+ is(
+ math.cache.getStringProperty("html"),
+ "<mfrac><mi>x</mi><mi>y</mi></mfrac>",
+ "math cached html is correct"
+ );
+
+ info("Mutating math");
+ await invokeContentTask(browser, [], () => {
+ content.document.querySelectorAll("mi")[1].textContent = "z";
+ });
+ await untilCacheIs(
+ () => math.cache.getStringProperty("html"),
+ "<mfrac><mi>x</mi><mi>z</mi></mfrac>",
+ "math cached html is correct after mutation"
+ );
+ },
+ {
+ topLevel: true,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_interfaces.js b/accessible/tests/browser/e10s/browser_caching_interfaces.js
new file mode 100644
index 0000000000..98e8641076
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_interfaces.js
@@ -0,0 +1,59 @@
+/* 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";
+
+/**
+ * Test caching of accessible interfaces
+ */
+addAccessibleTask(
+ `
+ <img id="img" src="http://example.com/a11y/accessible/tests/mochitest/moz.png">
+ <select id="select" multiple></select>
+ <input id="number-input" type="number">
+ <table id="table">
+ <tr><td id="cell"><a id="link" href="#">hello</a></td></tr>
+ </table>
+ `,
+ async function(browser, accDoc) {
+ ok(
+ accDoc instanceof nsIAccessibleDocument,
+ "Document has Document interface"
+ );
+ ok(
+ accDoc instanceof nsIAccessibleHyperText,
+ "Document has HyperText interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "img") instanceof nsIAccessibleImage,
+ "img has Image interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "select") instanceof
+ nsIAccessibleSelectable,
+ "select has Selectable interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "number-input") instanceof
+ nsIAccessibleValue,
+ "number-input has Value interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "table") instanceof nsIAccessibleTable,
+ "table has Table interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "cell") instanceof nsIAccessibleTableCell,
+ "cell has TableCell interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "link") instanceof nsIAccessibleHyperLink,
+ "link has HyperLink interface"
+ );
+ ok(
+ findAccessibleChildByID(accDoc, "link") instanceof nsIAccessibleHyperText,
+ "link has HyperText interface"
+ );
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_name.js b/accessible/tests/browser/e10s/browser_caching_name.js
new file mode 100644
index 0000000000..73264e03d6
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_name.js
@@ -0,0 +1,539 @@
+/* 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/name.js */
+loadScripts({ name: "name.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Rules for name tests that are inspired by
+ * accessible/tests/mochitest/name/markuprules.xul
+ *
+ * Each element in the list of rules represents a name calculation rule for a
+ * particular test case.
+ *
+ * The rules have the following format:
+ * { attr } - calculated from attribute
+ * { elm } - calculated from another element
+ * { fromsubtree } - calculated from element's subtree
+ *
+ */
+const ARIARule = [{ attr: "aria-labelledby" }, { attr: "aria-label" }];
+const HTMLControlHeadRule = [...ARIARule, { elm: "label" }];
+const rules = {
+ CSSContent: [{ elm: "style" }, { fromsubtree: true }],
+ HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLControl: [
+ ...HTMLControlHeadRule,
+ { fromsubtree: true },
+ { attr: "title" },
+ ],
+ HTMLElm: [...ARIARule, { attr: "title" }],
+ HTMLImg: [...ARIARule, { attr: "alt" }, { attr: "title" }],
+ HTMLImgEmptyAlt: [...ARIARule, { attr: "title" }, { attr: "alt" }],
+ HTMLInputButton: [
+ ...HTMLControlHeadRule,
+ { attr: "value" },
+ { attr: "title" },
+ ],
+ HTMLInputImage: [
+ ...HTMLControlHeadRule,
+ { attr: "alt" },
+ { attr: "value" },
+ { attr: "title" },
+ ],
+ HTMLInputImageNoValidSrc: [
+ ...HTMLControlHeadRule,
+ { attr: "alt" },
+ { attr: "value" },
+ ],
+ HTMLInputReset: [...HTMLControlHeadRule, { attr: "value" }],
+ HTMLInputSubmit: [...HTMLControlHeadRule, { attr: "value" }],
+ HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLLinkImage: [...ARIARule, { fromsubtree: true }, { attr: "title" }],
+ HTMLOption: [
+ ...ARIARule,
+ { attr: "label" },
+ { fromsubtree: true },
+ { attr: "title" },
+ ],
+ HTMLTable: [
+ ...ARIARule,
+ { elm: "caption" },
+ { attr: "summary" },
+ { attr: "title" },
+ ],
+};
+
+const markupTests = [
+ {
+ id: "btn",
+ ruleset: "HTMLControl",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn">test4</label>
+ <button id="btn"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">press me</button>`,
+ expected: ["test2 test3", "test1", "test4", "press me", "test5"],
+ },
+ {
+ id: "btn",
+ ruleset: "HTMLInputButton",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn">test4</label>
+ <input id="btn"
+ type="button"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from al"
+ src="no name from src"
+ data="no name from data"
+ title="name from title"/>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "test4",
+ "name from value",
+ "name from title",
+ ],
+ },
+ {
+ id: "btn-submit",
+ ruleset: "HTMLInputSubmit",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-submit">test4</label>
+ <input id="btn-submit"
+ type="submit"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from atl"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: ["test2 test3", "test1", "test4", "name from value"],
+ },
+ {
+ id: "btn-reset",
+ ruleset: "HTMLInputReset",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-reset">test4</label>
+ <input id="btn-reset"
+ type="reset"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ value="name from value"
+ alt="no name from alt"
+ src="no name from src"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: ["test2 test3", "test1", "test4", "name from value"],
+ },
+ {
+ id: "btn-image",
+ ruleset: "HTMLInputImage",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-image">test4</label>
+ <input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"
+ data="no name from data"
+ title="name from title"/>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "test4",
+ "name from alt",
+ "name from value",
+ "name from title",
+ ],
+ },
+ {
+ id: "btn-image",
+ ruleset: "HTMLInputImageNoValidSrc",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="btn-image">test4</label>
+ <input id="btn-image"
+ type="image"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ alt="name from alt"
+ value="name from value"
+ data="no name from data"
+ title="no name from title"/>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "test4",
+ "name from alt",
+ "name from value",
+ ],
+ },
+ {
+ id: "opt",
+ ruleset: "HTMLOption",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <select>
+ <option id="opt"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ label="test4"
+ title="test5">option1</option>
+ <option>option2</option>
+ </select>`,
+ expected: ["test2 test3", "test1", "test4", "option1", "test5"],
+ },
+ {
+ id: "img",
+ ruleset: "HTMLImg",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <img id="img"
+ aria-label="Logo of Mozilla"
+ aria-labelledby="l1 l2"
+ alt="Mozilla logo"
+ title="This is a logo"
+ src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`,
+ expected: [
+ "test2 test3",
+ "Logo of Mozilla",
+ "Mozilla logo",
+ "This is a logo",
+ ],
+ },
+ {
+ id: "tc",
+ ruleset: "HTMLElm",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="tc">test4</label>
+ <table>
+ <tr>
+ <td id="tc"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test5">
+ <p>This is a paragraph</p>
+ <a href="#">This is a link</a>
+ <ul>
+ <li>This is a list</li>
+ </ul>
+ </td>
+ </tr>
+ </table>`,
+ expected: ["test2 test3", "test1", "test5"],
+ },
+ {
+ id: "gc",
+ ruleset: "HTMLARIAGridCell",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <label for="gc">test4</label>
+ <table>
+ <tr>
+ <td id="gc"
+ role="gridcell"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="This is a paragraph This is a link This is a list">
+ <p>This is a paragraph</p>
+ <a href="#">This is a link</a>
+ <ul>
+ <li>Listitem1</li>
+ <li>Listitem2</li>
+ </ul>
+ </td>
+ </tr>
+ </table>`,
+ expected: [
+ "test2 test3",
+ "test1",
+ "This is a paragraph This is a link \u2022 Listitem1 \u2022 Listitem2",
+ "This is a paragraph This is a link This is a list",
+ ],
+ },
+ {
+ id: "t",
+ ruleset: "HTMLTable",
+ markup: `
+ <span id="l1">lby_tst6_1</span>
+ <span id="l2">lby_tst6_2</span>
+ <label for="t">label_tst6</label>
+ <table id="t"
+ aria-label="arialabel_tst6"
+ aria-labelledby="l1 l2"
+ summary="summary_tst6"
+ title="title_tst6">
+ <caption>caption_tst6</caption>
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>`,
+ expected: [
+ "lby_tst6_1 lby_tst6_2",
+ "arialabel_tst6",
+ "caption_tst6",
+ "summary_tst6",
+ "title_tst6",
+ ],
+ },
+ {
+ id: "btn",
+ ruleset: "CSSContent",
+ markup: `
+ <div role="main">
+ <style>
+ button::before {
+ content: "do not ";
+ }
+ </style>
+ <button id="btn">press me</button>
+ </div>`,
+ expected: ["do not press me", "press me"],
+ },
+ {
+ // TODO: uncomment when Bug-1256382 is resoved.
+ // id: 'li',
+ // ruleset: 'CSSContent',
+ // markup: `
+ // <style>
+ // ul {
+ // list-style-type: decimal;
+ // }
+ // </style>
+ // <ul id="ul">
+ // <li id="li">Listitem</li>
+ // </ul>`,
+ // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`]
+ // }, {
+ id: "a",
+ ruleset: "HTMLLink",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <a id="a"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test4">test5</a>`,
+ expected: ["test2 test3", "test1", "test5", "test4"],
+ },
+ {
+ id: "a-img",
+ ruleset: "HTMLLinkImage",
+ markup: `
+ <span id="l1">test2</span>
+ <span id="l2">test3</span>
+ <a id="a-img"
+ aria-label="test1"
+ aria-labelledby="l1 l2"
+ title="test4"><img alt="test5"/></a>`,
+ expected: ["test2 test3", "test1", "test5", "test4"],
+ },
+];
+
+/**
+ * Test accessible name that is calculated from an attribute, remove the
+ * attribute before proceeding to the next name test. If attribute removal
+ * results in a reorder or text inserted event - wait for it. If accessible
+ * becomes defunct, update its reference using the one that is attached to one
+ * of the above events.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Object} rule current attr rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+async function testAttrRule(browser, target, rule, expected) {
+ let { id, acc } = target;
+ let { attr } = rule;
+
+ testName(acc, expected);
+
+ let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
+ await invokeContentTask(browser, [id, attr], (contentId, contentAttr) => {
+ content.document.getElementById(contentId).removeAttribute(contentAttr);
+ });
+ let event = await nameChange;
+
+ // Update accessible just in case it is now defunct.
+ target.acc = findAccessibleChildByID(event.accessible, id);
+}
+
+/**
+ * Test accessible name that is calculated from an element name, remove the
+ * element before proceeding to the next name test. If element removal results
+ * in a reorder event - wait for it. If accessible becomes defunct, update its
+ * reference using the one that is attached to a possible reorder event.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Object} rule current elm rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+async function testElmRule(browser, target, rule, expected) {
+ let { id, acc } = target;
+ let { elm } = rule;
+
+ testName(acc, expected);
+ let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
+
+ await invokeContentTask(browser, [elm], contentElm => {
+ content.document.querySelector(`${contentElm}`).remove();
+ });
+ let event = await nameChange;
+
+ // Update accessible just in case it is now defunct.
+ target.acc = findAccessibleChildByID(event.accessible, id);
+}
+
+/**
+ * Test accessible name that is calculated from its subtree, remove the subtree
+ * and wait for a reorder event before proceeding to the next name test. If
+ * accessible becomes defunct, update its reference using the one that is
+ * attached to a reorder event.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Object} rule current subtree rule for name calculation
+ * @param {[type]} expected expected name value
+ */
+async function testSubtreeRule(browser, target, rule, expected) {
+ let { id, acc } = target;
+
+ testName(acc, expected);
+ let nameChange = waitForEvent(EVENT_NAME_CHANGE, id);
+
+ await invokeContentTask(browser, [id], contentId => {
+ let elm = content.document.getElementById(contentId);
+ while (elm.firstChild) {
+ elm.firstChild.remove();
+ }
+ });
+ let event = await nameChange;
+
+ // Update accessible just in case it is now defunct.
+ target.acc = findAccessibleChildByID(event.accessible, id);
+}
+
+/**
+ * Iterate over a list of rules and test accessible names for each one of the
+ * rules.
+ * @param {Object} browser current "tabbrowser" element
+ * @param {Object} target { acc, id } structure that contains an
+ * accessible and its content element
+ * id.
+ * @param {Array} ruleset A list of rules to test a target with
+ * @param {Array} expected A list of expected name value for each rule
+ */
+async function testNameRule(browser, target, ruleset, expected) {
+ for (let i = 0; i < ruleset.length; ++i) {
+ let rule = ruleset[i];
+ let testFn;
+ if (rule.attr) {
+ testFn = testAttrRule;
+ } else if (rule.elm) {
+ testFn = testElmRule;
+ } else if (rule.fromsubtree) {
+ testFn = testSubtreeRule;
+ }
+ await testFn(browser, target, rule, expected[i]);
+ }
+}
+
+markupTests.forEach(({ id, ruleset, markup, expected }) =>
+ addAccessibleTask(
+ markup,
+ async function(browser, accDoc) {
+ const observer = {
+ observe(subject, topic, data) {
+ const event = subject.QueryInterface(nsIAccessibleEvent);
+ console.log(eventToString(event));
+ },
+ };
+ Services.obs.addObserver(observer, "accessible-event");
+ // Find a target accessible from an accessible subtree.
+ let acc = findAccessibleChildByID(accDoc, id);
+ let target = { id, acc };
+ await testNameRule(browser, target, rules[ruleset], expected);
+ Services.obs.removeObserver(observer, "accessible-event");
+ },
+ { iframe: true, remoteIframe: true }
+ )
+);
+
+/**
+ * Test caching of the document title.
+ */
+addAccessibleTask(
+ ``,
+ async function(browser, docAcc) {
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, docAcc);
+ await invokeContentTask(browser, [], () => {
+ content.document.title = "new title";
+ });
+ await nameChanged;
+ testName(docAcc, "new title");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that the name is updated when the content of a hidden aria-labelledby
+ * subtree changes.
+ */
+addAccessibleTask(
+ `
+<button id="button" aria-labelledby="label">
+<div id="label" hidden>a</div>
+ `,
+ async function(browser, docAcc) {
+ const button = findAccessibleChildByID(docAcc, "button");
+ testName(button, "a");
+ info("Changing label textContent");
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("label").textContent = "c";
+ });
+ await nameChanged;
+ testName(button, "c");
+ info("Prepending text node to label");
+ nameChanged = waitForEvent(EVENT_NAME_CHANGE, button);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("label")
+ .prepend(content.document.createTextNode("b"));
+ });
+ await nameChanged;
+ testName(button, "bc");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_position.js b/accessible/tests/browser/e10s/browser_caching_position.js
new file mode 100644
index 0000000000..1f0c2ca5c1
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_position.js
@@ -0,0 +1,194 @@
+/* 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/layout.js */
+loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
+
+function getCachedBounds(acc) {
+ let cachedBounds = "";
+ try {
+ cachedBounds = acc.cache.getStringProperty("relative-bounds");
+ } catch (e) {
+ ok(false, "Unable to fetch cached bounds from cache!");
+ }
+ return cachedBounds;
+}
+
+async function testCoordinates(accDoc, id, expectedWidthPx, expectedHeightPx) {
+ let acc = findAccessibleChildByID(accDoc, id, [Ci.nsIAccessibleImage]);
+ if (!acc) {
+ return;
+ }
+
+ let screenX = {};
+ let screenY = {};
+ let windowX = {};
+ let windowY = {};
+ let parentX = {};
+ let parentY = {};
+
+ // get screen coordinates.
+ acc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE,
+ screenX,
+ screenY
+ );
+ // get window coordinates.
+ acc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE,
+ windowX,
+ windowY
+ );
+ // get parent related coordinates.
+ acc.getImagePosition(
+ nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE,
+ parentX,
+ parentY
+ );
+ // XXX For linked images, a negative parentY value is returned, and the
+ // screenY coordinate is the link's screenY coordinate minus 1.
+ // Until this is fixed, set parentY to -1 if it's negative.
+ if (parentY.value < 0) {
+ parentY.value = -1;
+ }
+
+ // See if asking image for child at image's screen coordinates gives
+ // correct accessible. getChildAtPoint operates on screen coordinates.
+ let tempAcc = null;
+ try {
+ tempAcc = acc.getChildAtPoint(screenX.value, screenY.value);
+ } catch (e) {}
+ is(tempAcc, acc, "Wrong accessible returned for position of " + id + "!");
+
+ // get image's parent.
+ let imageParentAcc = null;
+ try {
+ imageParentAcc = acc.parent;
+ } catch (e) {}
+ ok(imageParentAcc, "no parent accessible for " + id + "!");
+
+ if (imageParentAcc) {
+ // See if parent's screen coordinates plus image's parent relative
+ // coordinates equal to image's screen coordinates.
+ let parentAccX = {};
+ let parentAccY = {};
+ let parentAccWidth = {};
+ let parentAccHeight = {};
+ imageParentAcc.getBounds(
+ parentAccX,
+ parentAccY,
+ parentAccWidth,
+ parentAccHeight
+ );
+ is(
+ parentAccX.value + parentX.value,
+ screenX.value,
+ "Wrong screen x coordinate for " + id + "!"
+ );
+ // XXX see bug 456344
+ // is(
+ // parentAccY.value + parentY.value,
+ // screenY.value,
+ // "Wrong screen y coordinate for " + id + "!"
+ // );
+ }
+
+ let [expectedW, expectedH] = CSSToDevicePixels(
+ window,
+ expectedWidthPx,
+ expectedHeightPx
+ );
+ let width = {};
+ let height = {};
+ acc.getImageSize(width, height);
+ is(width.value, expectedW, "Wrong width for " + id + "!");
+ is(height.value, expectedH, "wrong height for " + id + "!");
+}
+
+addAccessibleTask(
+ `
+ <br>Simple image:<br>
+ <img id="nonLinkedImage" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>
+ <br>Linked image:<br>
+ <a href="http://www.mozilla.org"><img id="linkedImage" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a>
+ <br>Image with longdesc:<br>
+ <img id="longdesc" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" longdesc="longdesc_src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with invalid url in longdesc:<br>
+ <img id="invalidLongdesc" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" longdesc="longdesc src.html"
+ alt="Image of Mozilla logo"/>
+ <br>Image with click and longdesc:<br>
+ <img id="clickAndLongdesc" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" longdesc="longdesc_src.html"
+ alt="Another image of Mozilla logo" onclick="alert('Clicked!');"/>
+
+ <br>image described by a link to be treated as longdesc<br>
+ <img id="longdesc2" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" aria-describedby="describing_link"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link" href="longdesc_src.html">link to description of image</a>
+
+ <br>Image described by a link to be treated as longdesc with whitespaces<br>
+ <img id="longdesc3" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" aria-describedby="describing_link2"
+ alt="Second Image of Mozilla logo"/>
+ <a id="describing_link2" href="longdesc src.html">link to description of image</a>
+
+ <br>Image with click:<br>
+ <img id="click" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"
+ alt="A third image of Mozilla logo" onclick="alert('Clicked, too!');"/>
+ `,
+ async function(browser, docAcc) {
+ // Test non-linked image
+ await testCoordinates(docAcc, "nonLinkedImage", 89, 38);
+
+ // Test linked image
+ await testCoordinates(docAcc, "linkedImage", 89, 38);
+
+ // Image with long desc
+ await testCoordinates(docAcc, "longdesc", 89, 38);
+
+ // Image with invalid url in long desc
+ await testCoordinates(docAcc, "invalidLongdesc", 89, 38);
+
+ // Image with click and long desc
+ await testCoordinates(docAcc, "clickAndLongdesc", 89, 38);
+
+ // Image with click
+ await testCoordinates(docAcc, "click", 89, 38);
+
+ // Image with long desc
+ await testCoordinates(docAcc, "longdesc2", 89, 38);
+
+ // Image described by HTML:a@href with whitespaces
+ await testCoordinates(docAcc, "longdesc3", 89, 38);
+ }
+);
+
+addAccessibleTask(
+ `
+ <br>Linked image:<br>
+ <a href="http://www.mozilla.org"><img id="linkedImage" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a>
+ `,
+ async function(browser, docAcc) {
+ const imgAcc = findAccessibleChildByID(docAcc, "linkedImage", [
+ Ci.nsIAccessibleImage,
+ ]);
+ const origCachedBounds = getCachedBounds(imgAcc);
+
+ await invokeContentTask(browser, [], () => {
+ const imgNode = content.document.getElementById("linkedImage");
+ imgNode.style = "margin-left: 1000px; margin-top: 500px;";
+ });
+
+ await untilCacheOk(() => {
+ return origCachedBounds != getCachedBounds(imgAcc);
+ }, "Cached bounds update after mutation");
+ },
+ {
+ // We can only access the `cache` attribute of an accessible when
+ // the cache is enabled and we're in a remote browser.
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_relations.js b/accessible/tests/browser/e10s/browser_caching_relations.js
new file mode 100644
index 0000000000..010b08af2d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations.js
@@ -0,0 +1,289 @@
+/* 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";
+requestLongerTimeout(2);
+
+/**
+ * A test specification that has the following format:
+ * [
+ * attr relevant aria attribute
+ * hostRelation corresponding host relation type
+ * dependantRelation corresponding dependant relation type
+ * ]
+ */
+const attrRelationsSpec = [
+ ["aria-labelledby", RELATION_LABELLED_BY, RELATION_LABEL_FOR],
+ ["aria-describedby", RELATION_DESCRIBED_BY, RELATION_DESCRIPTION_FOR],
+ ["aria-controls", RELATION_CONTROLLER_FOR, RELATION_CONTROLLED_BY],
+ ["aria-flowto", RELATION_FLOWS_TO, RELATION_FLOWS_FROM],
+];
+
+/**
+ * Test caching of relations between accessible objects.
+ */
+addAccessibleTask(
+ `
+ <div id="dependant1">label</div>
+ <div id="dependant2">label2</div>
+ <div role="checkbox" id="host"></div>`,
+ async function(browser, accDoc) {
+ for (let spec of attrRelationsSpec) {
+ await testRelated(browser, accDoc, ...spec);
+ }
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of relations with respect to label objects and their "for" attr.
+ */
+addAccessibleTask(
+ `
+ <input type="checkbox" id="dependant1">
+ <input type="checkbox" id="dependant2">
+ <label id="host">label</label>`,
+ async function(browser, accDoc) {
+ await testRelated(
+ browser,
+ accDoc,
+ "for",
+ RELATION_LABEL_FOR,
+ RELATION_LABELLED_BY
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test rel caching for element with existing relation attribute.
+ */
+addAccessibleTask(
+ `<div id="label">label</div><button id="button" aria-labelledby="label">`,
+ async function(browser, accDoc) {
+ const button = findAccessibleChildByID(accDoc, "button");
+ const label = findAccessibleChildByID(accDoc, "label");
+
+ await testCachedRelation(button, RELATION_LABELLED_BY, label);
+ await testCachedRelation(label, RELATION_LABEL_FOR, button);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of relations with respect to output objects and their "for" attr.
+ */
+addAccessibleTask(
+ `
+ <form oninput="host.value=parseInt(dependant1.value)+parseInt(dependant2.value)">
+ <input type="number" id="dependant1" value="50"> +
+ <input type="number" id="dependant2" value="25"> =
+ <output name="host" id="host"></output>
+ </form>`,
+ async function(browser, accDoc) {
+ await testRelated(
+ browser,
+ accDoc,
+ "for",
+ RELATION_CONTROLLED_BY,
+ RELATION_CONTROLLER_FOR
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test rel caching for <label> element with existing "for" attribute.
+ */
+addAccessibleTask(
+ `data:text/html,<label id="label" for="input">label</label><input id="input">`,
+ async function(browser, accDoc) {
+ const input = findAccessibleChildByID(accDoc, "input");
+ const label = findAccessibleChildByID(accDoc, "label");
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test caching of relations with respect to label objects that are ancestors of
+ * their target.
+ */
+addAccessibleTask(
+ `
+ <label id="host">
+ <input type="checkbox" id="dependant1">
+ </label>`,
+ async function(browser, accDoc) {
+ const input = findAccessibleChildByID(accDoc, "dependant1");
+ const label = findAccessibleChildByID(accDoc, "host");
+
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test EMBEDS on root accessible.
+ */
+addAccessibleTask(
+ `hello world`,
+ async function(browser, primaryDocAcc, secondaryDocAcc) {
+ // The root accessible should EMBED the top level
+ // content document. If this test runs in an iframe,
+ // the test harness will pass in doc accs for both the
+ // iframe (primaryDocAcc) and the top level remote
+ // browser (secondaryDocAcc). We should use the second
+ // one.
+ // If this is not in an iframe, we'll only get
+ // a single docAcc (primaryDocAcc) which refers to
+ // the top level content doc.
+ const topLevelDoc = secondaryDocAcc ? secondaryDocAcc : primaryDocAcc;
+ await testRelation(
+ getRootAccessible(document),
+ RELATION_EMBEDS,
+ topLevelDoc
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test CONTAINING_TAB_PANE
+ */
+addAccessibleTask(
+ `<p id="p">hello world</p>`,
+ async function(browser, primaryDocAcc, secondaryDocAcc) {
+ // The CONTAINING_TAB_PANE of any acc should be the top level
+ // content document. If this test runs in an iframe,
+ // the test harness will pass in doc accs for both the
+ // iframe (primaryDocAcc) and the top level remote
+ // browser (secondaryDocAcc). We should use the second
+ // one.
+ // If this is not in an iframe, we'll only get
+ // a single docAcc (primaryDocAcc) which refers to
+ // the top level content doc.
+ const topLevelDoc = secondaryDocAcc ? secondaryDocAcc : primaryDocAcc;
+ await testCachedRelation(
+ findAccessibleChildByID(primaryDocAcc, "p"),
+ RELATION_CONTAINING_TAB_PANE,
+ topLevelDoc
+ );
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/*
+ * Test relation caching on link
+ */
+addAccessibleTask(
+ `
+ <a id="link" href="#item">a</a>
+ <div id="item">hello</div>
+ <div id="item2">world</div>
+ <a id="link2" href="#anchor">b</a>
+ <a id="namedLink" name="anchor">c</a>`,
+ async function(browser, accDoc) {
+ const link = findAccessibleChildByID(accDoc, "link");
+ const link2 = findAccessibleChildByID(accDoc, "link2");
+ const namedLink = findAccessibleChildByID(accDoc, "namedLink");
+ const item = findAccessibleChildByID(accDoc, "item");
+ const item2 = findAccessibleChildByID(accDoc, "item2");
+
+ await testCachedRelation(link, RELATION_LINKS_TO, item);
+ await testCachedRelation(link2, RELATION_LINKS_TO, namedLink);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("link").href = "";
+ content.document.getElementById("namedLink").name = "newName";
+ });
+
+ await testCachedRelation(link, RELATION_LINKS_TO, null);
+ await testCachedRelation(link2, RELATION_LINKS_TO, null);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("link").href = "#item2";
+ });
+
+ await testCachedRelation(link, RELATION_LINKS_TO, item2);
+ },
+ {
+ chrome: true,
+ // IA2 doesn't have a LINKS_TO relation and Windows non-cached
+ // RemoteAccessible uses IA2, so we can't run these tests in this case.
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ }
+);
+
+/*
+ * Test relation caching for NODE_CHILD_OF and NODE_PARENT_OF with aria trees.
+ */
+addAccessibleTask(
+ `
+ <div role="tree" id="tree">
+ <div role="treeitem" id="treeitem">test</div>
+ <div role="treeitem" id="treeitem2">test</div>
+ </div>`,
+ async function(browser, accDoc) {
+ const tree = findAccessibleChildByID(accDoc, "tree");
+ const treeItem = findAccessibleChildByID(accDoc, "treeitem");
+ const treeItem2 = findAccessibleChildByID(accDoc, "treeitem2");
+
+ await testCachedRelation(tree, RELATION_NODE_PARENT_OF, [
+ treeItem,
+ treeItem2,
+ ]);
+ await testCachedRelation(treeItem, RELATION_NODE_CHILD_OF, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test relation caching for NODE_CHILD_OF and NODE_PARENT_OF with aria lists.
+ */
+addAccessibleTask(
+ `
+ <div id="l1" role="list">
+ <div id="l1i1" role="listitem" aria-level="1">a</div>
+ <div id="l1i2" role="listitem" aria-level="2">b</div>
+ <div id="l1i3" role="listitem" aria-level="1">c</div>
+ </div>`,
+ async function(browser, accDoc) {
+ const list = findAccessibleChildByID(accDoc, "l1");
+ const listItem1 = findAccessibleChildByID(accDoc, "l1i1");
+ const listItem2 = findAccessibleChildByID(accDoc, "l1i2");
+ const listItem3 = findAccessibleChildByID(accDoc, "l1i3");
+
+ await testCachedRelation(list, RELATION_NODE_PARENT_OF, [
+ listItem1,
+ listItem3,
+ ]);
+ await testCachedRelation(listItem1, RELATION_NODE_CHILD_OF, list);
+ await testCachedRelation(listItem3, RELATION_NODE_CHILD_OF, list);
+
+ await testCachedRelation(listItem1, RELATION_NODE_PARENT_OF, listItem2);
+ await testCachedRelation(listItem2, RELATION_NODE_CHILD_OF, listItem1);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test NODE_CHILD_OF relation caching for JAWS window emulation special case.
+ */
+addAccessibleTask(
+ ``,
+ async function(browser, accDoc) {
+ await testCachedRelation(accDoc, RELATION_NODE_CHILD_OF, accDoc.parent);
+ },
+ { topLevel: isCacheEnabled, chrome: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_relations_002.js b/accessible/tests/browser/e10s/browser_caching_relations_002.js
new file mode 100644
index 0000000000..77435b993b
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations_002.js
@@ -0,0 +1,245 @@
+/* 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";
+requestLongerTimeout(2);
+
+/**
+ * Test MEMBER_OF relation caching on HTML radio buttons
+ */
+addAccessibleTask(
+ `
+ <input type="radio" id="r1">I have no name<br>
+ <input type="radio" id="r2">I also have no name<br>
+ <input type="radio" id="r3" name="n">I have a name<br>
+ <input type="radio" id="r4" name="a">I have a different name<br>
+ <fieldset role="radiogroup">
+ <input type="radio" id="r5" name="n">I have an already used name
+ and am in a different part of the tree
+ <input type="radio" id="r6" name="r">I have a different name but am
+ in the same group
+ </fieldset>`,
+ async function(browser, accDoc) {
+ const r1 = findAccessibleChildByID(accDoc, "r1");
+ const r2 = findAccessibleChildByID(accDoc, "r2");
+ const r3 = findAccessibleChildByID(accDoc, "r3");
+ const r4 = findAccessibleChildByID(accDoc, "r4");
+ const r5 = findAccessibleChildByID(accDoc, "r5");
+ const r6 = findAccessibleChildByID(accDoc, "r6");
+
+ await testCachedRelation(r1, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r2, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r3, RELATION_MEMBER_OF, [r3, r5]);
+ await testCachedRelation(r4, RELATION_MEMBER_OF, r4);
+ await testCachedRelation(r5, RELATION_MEMBER_OF, [r3, r5]);
+ await testCachedRelation(r6, RELATION_MEMBER_OF, r6);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("r5").name = "a";
+ });
+
+ await testCachedRelation(r3, RELATION_MEMBER_OF, r3);
+ await testCachedRelation(r4, RELATION_MEMBER_OF, [r5, r4]);
+ await testCachedRelation(r5, RELATION_MEMBER_OF, [r5, r4]);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/*
+ * Test MEMBER_OF relation caching on aria radio buttons
+ */
+addAccessibleTask(
+ `
+ <div role="radio" id="r1">I have no radio group</div><br>
+ <fieldset role="radiogroup" id="fs">
+ <div role="radio" id="r2">hello</div><br>
+ <div role="radio" id="r3">world</div><br>
+ </fieldset>`,
+ async function(browser, accDoc) {
+ const r1 = findAccessibleChildByID(accDoc, "r1");
+ const r2 = findAccessibleChildByID(accDoc, "r2");
+ let r3 = findAccessibleChildByID(accDoc, "r3");
+
+ await testCachedRelation(r1, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r2, RELATION_MEMBER_OF, [r2, r3]);
+ await testCachedRelation(r3, RELATION_MEMBER_OF, [r2, r3]);
+ const r = waitForEvent(EVENT_INNER_REORDER, "fs");
+ await invokeContentTask(browser, [], () => {
+ let innerRadio = content.document.getElementById("r3");
+ content.document.body.appendChild(innerRadio);
+ });
+ await r;
+
+ r3 = findAccessibleChildByID(accDoc, "r3");
+ await testCachedRelation(r1, RELATION_MEMBER_OF, null);
+ await testCachedRelation(r2, RELATION_MEMBER_OF, r2);
+ await testCachedRelation(r3, RELATION_MEMBER_OF, null);
+ },
+ {
+ chrome: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * Test mutation of LABEL relations via accessible shutdown.
+ */
+addAccessibleTask(
+ `
+ <div id="d"></div>
+ <label id="l">
+ <select id="s">
+ `,
+ async function(browser, accDoc) {
+ const label = findAccessibleChildByID(accDoc, "l");
+ const select = findAccessibleChildByID(accDoc, "s");
+ const div = findAccessibleChildByID(accDoc, "d");
+
+ await testCachedRelation(label, RELATION_LABEL_FOR, select);
+ await testCachedRelation(select, RELATION_LABELLED_BY, label);
+ await testCachedRelation(div, RELATION_LABELLED_BY, null);
+
+ const r = waitForEvent(EVENT_REORDER, "l");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("s").remove();
+ });
+ await r;
+ await invokeContentTask(browser, [], () => {
+ const l = content.document.getElementById("l");
+ l.htmlFor = "d";
+ });
+ await testCachedRelation(label, RELATION_LABEL_FOR, div);
+ await testCachedRelation(div, RELATION_LABELLED_BY, label);
+ },
+ {
+ chrome: false,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ topLevel: isCacheEnabled,
+ }
+);
+
+/*
+ * Test mutation of LABEL relations via DOM ID reuse.
+ */
+addAccessibleTask(
+ `
+ <div id="label">before</div><input id="input" aria-labelledby="label">
+ `,
+ async function(browser, accDoc) {
+ let label = findAccessibleChildByID(accDoc, "label");
+ const input = findAccessibleChildByID(accDoc, "input");
+
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+
+ const r = waitForEvent(EVENT_REORDER, accDoc);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("label").remove();
+ let l = content.document.createElement("div");
+ l.id = "label";
+ l.textContent = "after";
+ content.document.body.insertBefore(
+ l,
+ content.document.getElementById("input")
+ );
+ });
+ await r;
+ label = findAccessibleChildByID(accDoc, "label");
+ await testCachedRelation(label, RELATION_LABEL_FOR, input);
+ await testCachedRelation(input, RELATION_LABELLED_BY, label);
+ },
+ {
+ chrome: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * Test LINKS_TO relation caching an anchor with multiple hashes
+ */
+addAccessibleTask(
+ `
+ <a id="link" href="#foo#bar">Origin</a><br>
+ <a id="anchor" name="foo#bar">Destination`,
+ async function(browser, accDoc) {
+ const link = findAccessibleChildByID(accDoc, "link");
+ const anchor = findAccessibleChildByID(accDoc, "anchor");
+
+ await testCachedRelation(link, RELATION_LINKS_TO, anchor);
+ },
+ {
+ chrome: true,
+ // IA2 doesn't have a LINKS_TO relation and Windows non-cached
+ // RemoteAccessible uses IA2, so we can't run these tests in this case.
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ }
+);
+
+/*
+ * Test mutation of LABEL relations via accessible shutdown.
+ */
+addAccessibleTask(
+ `
+ <div id="d"></div>
+ <label id="l">
+ <select id="s">
+ `,
+ async function(browser, accDoc) {
+ const label = findAccessibleChildByID(accDoc, "l");
+ const select = findAccessibleChildByID(accDoc, "s");
+ const div = findAccessibleChildByID(accDoc, "d");
+
+ await testCachedRelation(label, RELATION_LABEL_FOR, select);
+ await testCachedRelation(select, RELATION_LABELLED_BY, label);
+ await testCachedRelation(div, RELATION_LABELLED_BY, null);
+ await untilCacheOk(() => {
+ try {
+ // We should get an acc ID back from this, but we don't have a way of
+ // verifying its correctness -- it should be the ID of the select.
+ return label.cache.getStringProperty("for");
+ } catch (e) {
+ ok(false, "Exception thrown while trying to read from the cache");
+ return false;
+ }
+ }, "Label for relation exists");
+
+ const r = waitForEvent(EVENT_REORDER, "l");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("s").remove();
+ });
+ await r;
+ await untilCacheOk(() => {
+ try {
+ label.cache.getStringProperty("for");
+ } catch (e) {
+ // This property should no longer exist in the cache, so we should
+ // get an exception if we try to fetch it.
+ return true;
+ }
+ return false;
+ }, "Label for relation exists");
+
+ await invokeContentTask(browser, [], () => {
+ const l = content.document.getElementById("l");
+ l.htmlFor = "d";
+ });
+ await testCachedRelation(label, RELATION_LABEL_FOR, div);
+ await testCachedRelation(div, RELATION_LABELLED_BY, label);
+ },
+ {
+ /**
+ * This functionality is broken in our LocalAcccessible implementation,
+ * so we avoid running this test in chrome or when the cache is off.
+ */
+ chrome: false,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ topLevel: isCacheEnabled,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_states.js b/accessible/tests/browser/e10s/browser_caching_states.js
new file mode 100644
index 0000000000..839d2a181b
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_states.js
@@ -0,0 +1,420 @@
+/* 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 */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * expected {Array} expected states for a given accessible that have the
+ * following format:
+ * [
+ * expected state,
+ * expected extra state,
+ * absent state,
+ * absent extra state
+ * ]
+ * attrs {?Array} an optional list of attributes to update
+ * }
+ */
+
+// State caching tests for attribute changes
+const attributeTests = [
+ {
+ desc:
+ "Checkbox with @checked attribute set to true should have checked " +
+ "state",
+ attrs: [
+ {
+ attr: "checked",
+ value: "true",
+ },
+ ],
+ expected: [STATE_CHECKED, 0],
+ },
+ {
+ desc: "Checkbox with no @checked attribute should not have checked state",
+ attrs: [
+ {
+ attr: "checked",
+ },
+ ],
+ expected: [0, 0, STATE_CHECKED],
+ },
+];
+
+// State caching tests for ARIA changes
+const ariaTests = [
+ {
+ desc: "File input has busy state when @aria-busy attribute is set to true",
+ attrs: [
+ {
+ attr: "aria-busy",
+ value: "true",
+ },
+ ],
+ expected: [STATE_BUSY, 0, STATE_REQUIRED | STATE_INVALID],
+ },
+ {
+ desc:
+ "File input has required state when @aria-required attribute is set " +
+ "to true",
+ attrs: [
+ {
+ attr: "aria-required",
+ value: "true",
+ },
+ ],
+ expected: [STATE_REQUIRED, 0, STATE_INVALID],
+ },
+ {
+ desc:
+ "File input has invalid state when @aria-invalid attribute is set to " +
+ "true",
+ attrs: [
+ {
+ attr: "aria-invalid",
+ value: "true",
+ },
+ ],
+ expected: [STATE_INVALID, 0],
+ },
+];
+
+// Extra state caching tests
+const extraStateTests = [
+ {
+ desc:
+ "Input has no extra enabled state when aria and native disabled " +
+ "attributes are set at once",
+ attrs: [
+ {
+ attr: "aria-disabled",
+ value: "true",
+ },
+ {
+ attr: "disabled",
+ value: "true",
+ },
+ ],
+ expected: [0, 0, 0, EXT_STATE_ENABLED],
+ },
+ {
+ desc:
+ "Input has an extra enabled state when aria and native disabled " +
+ "attributes are unset at once",
+ attrs: [
+ {
+ attr: "aria-disabled",
+ },
+ {
+ attr: "disabled",
+ },
+ ],
+ expected: [0, EXT_STATE_ENABLED],
+ },
+];
+
+async function runStateTests(browser, accDoc, id, tests) {
+ let acc = findAccessibleChildByID(accDoc, id);
+ for (let { desc, attrs, expected } of tests) {
+ const [expState, expExtState, absState, absExtState] = expected;
+ info(desc);
+ let onUpdate = waitForEvent(EVENT_STATE_CHANGE, evt => {
+ if (getAccessibleDOMNodeID(evt.accessible) != id) {
+ return false;
+ }
+ // Events can be fired for states other than the ones we're interested
+ // in. If this happens, the states we're expecting might not be exposed
+ // yet.
+ const scEvt = evt.QueryInterface(nsIAccessibleStateChangeEvent);
+ if (scEvt.isExtraState) {
+ if (scEvt.state & expExtState || scEvt.state & absExtState) {
+ return true;
+ }
+ return false;
+ }
+ return scEvt.state & expState || scEvt.state & absState;
+ });
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, id, attr, value);
+ }
+ await onUpdate;
+ testStates(acc, ...expected);
+ }
+}
+
+/**
+ * Test caching of accessible object states
+ */
+addAccessibleTask(
+ `
+ <input id="checkbox" type="checkbox">
+ <input id="file" type="file">
+ <input id="text">`,
+ async function(browser, accDoc) {
+ await runStateTests(browser, accDoc, "checkbox", attributeTests);
+ await runStateTests(browser, accDoc, "file", ariaTests);
+ await runStateTests(browser, accDoc, "text", extraStateTests);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of the focused state.
+ */
+addAccessibleTask(
+ `
+ <button id="b1">b1</button>
+ <button id="b2">b2</button>
+ `,
+ async function(browser, docAcc) {
+ const b1 = findAccessibleChildByID(docAcc, "b1");
+ const b2 = findAccessibleChildByID(docAcc, "b2");
+
+ let focused = waitForEvent(EVENT_FOCUS, b1);
+ await invokeFocus(browser, "b1");
+ await focused;
+ testStates(docAcc, 0, 0, STATE_FOCUSED);
+ testStates(b1, STATE_FOCUSED);
+ testStates(b2, 0, 0, STATE_FOCUSED);
+
+ focused = waitForEvent(EVENT_FOCUS, b2);
+ await invokeFocus(browser, "b2");
+ await focused;
+ testStates(b2, STATE_FOCUSED);
+ testStates(b1, 0, 0, STATE_FOCUSED);
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test that the document initially gets the focused state.
+ * We can't do this in the test above because that test runs in iframes as well
+ * as a top level document.
+ */
+addAccessibleTask(
+ `
+ <button id="b1">b1</button>
+ <button id="b2">b2</button>
+ `,
+ async function(browser, docAcc) {
+ testStates(docAcc, STATE_FOCUSED);
+ }
+);
+
+/**
+ * Test caching of the focused state in iframes.
+ */
+addAccessibleTask(
+ `
+ <button id="button">button</button>
+ `,
+ async function(browser, iframeDocAcc, topDocAcc) {
+ testStates(topDocAcc, STATE_FOCUSED);
+ const button = findAccessibleChildByID(iframeDocAcc, "button");
+ testStates(button, 0, 0, STATE_FOCUSED);
+ let focused = waitForEvent(EVENT_FOCUS, button);
+ info("Focusing button in iframe");
+ button.takeFocus();
+ await focused;
+ testStates(topDocAcc, 0, 0, STATE_FOCUSED);
+ testStates(button, STATE_FOCUSED);
+ },
+ { topLevel: false, iframe: true, remoteIframe: true }
+);
+
+function checkOpacity(acc, present) {
+ // eslint-disable-next-line no-unused-vars
+ let [_, extraState] = getStates(acc);
+ let currOpacity = extraState & EXT_STATE_OPAQUE;
+ return present ? currOpacity : !currOpacity;
+}
+
+/**
+ * Test caching of the OPAQUE1 state.
+ */
+addAccessibleTask(
+ `
+ <div id="div">hello world</div>
+ `,
+ async function(browser, docAcc) {
+ const div = findAccessibleChildByID(docAcc, "div");
+ await untilCacheOk(() => checkOpacity(div, true), "Found opaque state");
+
+ await invokeContentTask(browser, [], () => {
+ let elm = content.document.getElementById("div");
+ elm.style = "opacity: 0.4;";
+ elm.offsetTop; // Flush layout.
+ });
+
+ await untilCacheOk(
+ () => checkOpacity(div, false),
+ "Did not find opaque state"
+ );
+
+ await invokeContentTask(browser, [], () => {
+ let elm = content.document.getElementById("div");
+ elm.style = "opacity: 1;";
+ elm.offsetTop; // Flush layout.
+ });
+
+ await untilCacheOk(() => checkOpacity(div, true), "Found opaque state");
+ },
+ { iframe: true, remoteIframe: true, chrome: true }
+);
+
+/**
+ * Test caching of the editable state.
+ */
+addAccessibleTask(
+ `<div id="div" contenteditable></div>`,
+ async function(browser, docAcc) {
+ const div = findAccessibleChildByID(docAcc, "div");
+ testStates(div, 0, EXT_STATE_EDITABLE, 0, 0);
+ // Ensure that a contentEditable descendant doesn't cause editable to be
+ // exposed on the document.
+ testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE);
+
+ info("Setting contentEditable on the body");
+ let stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, true, true),
+ waitForStateChange(docAcc, STATE_READONLY, false, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.body.contentEditable = true;
+ });
+ await stateChanged;
+ testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0);
+
+ info("Clearing contentEditable on the body");
+ stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, false, true),
+ waitForStateChange(docAcc, STATE_READONLY, true, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.body.contentEditable = false;
+ });
+ await stateChanged;
+ testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE);
+
+ info("Clearing contentEditable on div");
+ stateChanged = waitForStateChange(div, EXT_STATE_EDITABLE, false, true);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").contentEditable = false;
+ });
+ await stateChanged;
+ testStates(div, 0, 0, 0, EXT_STATE_EDITABLE);
+
+ info("Setting contentEditable on div");
+ stateChanged = waitForStateChange(div, EXT_STATE_EDITABLE, true, true);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").contentEditable = true;
+ });
+ await stateChanged;
+ testStates(div, 0, EXT_STATE_EDITABLE, 0, 0);
+
+ info("Setting designMode on document");
+ stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, true, true),
+ waitForStateChange(docAcc, STATE_READONLY, false, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.designMode = "on";
+ });
+ await stateChanged;
+ testStates(docAcc, 0, EXT_STATE_EDITABLE, STATE_READONLY, 0);
+
+ info("Clearing designMode on document");
+ stateChanged = Promise.all([
+ waitForStateChange(docAcc, EXT_STATE_EDITABLE, false, true),
+ waitForStateChange(docAcc, STATE_READONLY, true, false),
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.designMode = "off";
+ });
+ await stateChanged;
+ testStates(docAcc, STATE_READONLY, 0, 0, EXT_STATE_EDITABLE);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true, chrome: true }
+);
+
+/**
+ * Test caching of the stale and busy states.
+ */
+addAccessibleTask(
+ `<iframe id="iframe"></iframe>`,
+ async function(browser, docAcc) {
+ const iframe = findAccessibleChildByID(docAcc, "iframe");
+ info("Setting iframe src");
+ // This iframe won't finish loading. Thus, it will get the stale state and
+ // won't fire a document load complete event. We use the reorder event on
+ // the iframe to know when the document has been created.
+ let reordered = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("iframe").src =
+ 'data:text/html,<img src="http://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs">';
+ });
+ const iframeDoc = (await reordered).accessible.firstChild;
+ testStates(iframeDoc, STATE_BUSY, EXT_STATE_STALE, 0, 0);
+
+ info("Finishing load of iframe doc");
+ let loadCompleted = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, iframeDoc);
+ await fetch(
+ "https://example.com/a11y/accessible/tests/mochitest/events/slow_image.sjs?complete"
+ );
+ await loadCompleted;
+ testStates(iframeDoc, 0, 0, STATE_BUSY, EXT_STATE_STALE);
+ },
+ { topLevel: true, chrome: true }
+);
+
+/**
+ * Test implicit selected state.
+ */
+addAccessibleTask(
+ `
+<div role="tablist">
+ <div id="noSel" role="tab" tabindex="0">noSel</div>
+ <div id="selFalse" role="tab" aria-selected="false" tabindex="0">selFalse</div>
+</div>
+<div role="listbox" aria-multiselectable="true">
+ <div id="multiNoSel" role="option" tabindex="0">multiNoSel</div>
+</div>
+ `,
+ async function(browser, docAcc) {
+ const noSel = findAccessibleChildByID(docAcc, "noSel");
+ testStates(noSel, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0);
+ info("Focusing noSel");
+ let focused = waitForEvent(EVENT_FOCUS, noSel);
+ noSel.takeFocus();
+ await focused;
+ testStates(noSel, STATE_FOCUSED | STATE_SELECTED, 0, 0, 0);
+
+ const selFalse = findAccessibleChildByID(docAcc, "selFalse");
+ testStates(selFalse, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0);
+ info("Focusing selFalse");
+ focused = waitForEvent(EVENT_FOCUS, selFalse);
+ selFalse.takeFocus();
+ await focused;
+ testStates(selFalse, STATE_FOCUSED, 0, STATE_SELECTED, 0);
+
+ const multiNoSel = findAccessibleChildByID(docAcc, "multiNoSel");
+ testStates(multiNoSel, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0);
+ info("Focusing multiNoSel");
+ focused = waitForEvent(EVENT_FOCUS, multiNoSel);
+ multiNoSel.takeFocus();
+ await focused;
+ testStates(multiNoSel, STATE_FOCUSED, 0, STATE_SELECTED, 0);
+ },
+ { topLevel: true, iframe: true, remoteIframe: true, chrome: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_table.js b/accessible/tests/browser/e10s/browser_caching_table.js
new file mode 100644
index 0000000000..3a34fe4b9a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_table.js
@@ -0,0 +1,509 @@
+/* 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/. */
+
+/**
+ * Test tables for both local and remote Accessibles. There is more extensive
+ * coverage in ../../mochitest/table. These tests are primarily to ensure that
+ * the cache works as expected and that there is consistency between local and
+ * remote.
+ */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/table.js */
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts(
+ { name: "table.js", dir: MOCHITESTS_DIR },
+ { name: "attributes.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test table counts, indexes, extents and implicit headers.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <thead>
+ <tr><th id="a">a</th><th id="bc" colspan="2">bc</th><th id="d">d</th></tr>
+ </thead>
+ <tbody>
+ <tr><th id="ei" rowspan="2">ei</th><td id="fj" rowspan="0">fj</td><td id="g">g</td><td id="h">h</td></tr>
+ <tr><td id="k">k</td></tr>
+ </tbody>
+</table>
+ `,
+ async function(browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 3, "table rowCount correct");
+ is(table.columnCount, 4, "table columnCount correct");
+ testTableIndexes(table, [
+ [0, 1, 1, 2],
+ [3, 4, 5, 6],
+ [3, 4, 7, -1],
+ ]);
+ const cells = {};
+ for (const id of ["a", "bc", "d", "ei", "fj", "g", "h", "k"]) {
+ cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
+ }
+ is(cells.a.rowExtent, 1, "a rowExtent correct");
+ is(cells.a.columnExtent, 1, "a columnExtent correct");
+ is(cells.bc.rowExtent, 1, "bc rowExtent correct");
+ is(cells.bc.columnExtent, 2, "bc columnExtent correct");
+ is(cells.ei.rowExtent, 2, "ei rowExtent correct");
+ is(cells.fj.rowExtent, 2, "fj rowExtent correct");
+ testHeaderCells([
+ {
+ cell: cells.ei,
+ rowHeaderCells: [],
+ columnHeaderCells: [cells.a],
+ },
+ {
+ cell: cells.g,
+ rowHeaderCells: [cells.ei],
+ columnHeaderCells: [cells.bc],
+ },
+ {
+ cell: cells.k,
+ rowHeaderCells: [cells.ei],
+ columnHeaderCells: [cells.bc],
+ },
+ ]);
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test table explicit headers.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <tr><th id="a">a</th><th id="b">b</th></tr>
+ <tr><td id="c" headers="b d">c</td><th scope="row" id="d">d</th></tr>
+ <tr><td id="e" headers="c f">e</td><td id="f">f</td></tr>
+</table>
+ `,
+ async function(browser, docAcc) {
+ const cells = {};
+ for (const id of ["a", "b", "c", "d", "e", "f"]) {
+ cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
+ }
+ testHeaderCells([
+ {
+ cell: cells.c,
+ rowHeaderCells: [cells.d],
+ columnHeaderCells: [cells.b],
+ },
+ {
+ cell: cells.e,
+ rowHeaderCells: [cells.f],
+ columnHeaderCells: [cells.c],
+ },
+ ]);
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test that an inner table doesn't impact an outer table.
+ */
+addAccessibleTask(
+ `
+<table id="outerTable">
+ <tr><th id="outerCell">outerCell<table id="innerTable">
+ <tr><th id="innerCell">a</th></tr></table>
+ </table></th></tr>
+</table>
+ `,
+ async function(browser, docAcc) {
+ const outerTable = findAccessibleChildByID(docAcc, "outerTable", [
+ nsIAccessibleTable,
+ ]);
+ is(outerTable.rowCount, 1, "outerTable rowCount correct");
+ is(outerTable.columnCount, 1, "outerTable columnCount correct");
+ const outerCell = findAccessibleChildByID(docAcc, "outerCell");
+ is(
+ outerTable.getCellAt(0, 0),
+ outerCell,
+ "outerTable returns correct cell"
+ );
+ const innerTable = findAccessibleChildByID(docAcc, "innerTable", [
+ nsIAccessibleTable,
+ ]);
+ is(innerTable.rowCount, 1, "innerTable rowCount correct");
+ is(innerTable.columnCount, 1, "innerTable columnCount correct");
+ const innerCell = findAccessibleChildByID(docAcc, "innerCell");
+ is(
+ innerTable.getCellAt(0, 0),
+ innerCell,
+ "innerTable returns correct cell"
+ );
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test table caption and summary.
+ */
+addAccessibleTask(
+ `
+<table id="t1">
+ <caption id="c1">c1</caption>
+ <tr><th>a</th></tr>
+</table>
+<table id="t2" summary="s2">
+ <tr><th>a</th></tr>
+</table>
+<table id="t3" summary="s3">
+ <caption id="c3">c3</caption>
+ <tr><th>a</th></tr>
+</table>
+ `,
+ async function(browser, docAcc) {
+ const t1 = findAccessibleChildByID(docAcc, "t1", [nsIAccessibleTable]);
+ const c1 = findAccessibleChildByID(docAcc, "c1");
+ is(t1.caption, c1, "t1 caption correct");
+ ok(!t1.summary, "t1 no summary");
+ const t2 = findAccessibleChildByID(docAcc, "t2", [nsIAccessibleTable]);
+ ok(!t2.caption, "t2 caption is null");
+ is(t2.summary, "s2", "t2 summary correct");
+ const t3 = findAccessibleChildByID(docAcc, "t3", [nsIAccessibleTable]);
+ const c3 = findAccessibleChildByID(docAcc, "c3");
+ is(t3.caption, c3, "t3 caption correct");
+ is(t3.summary, "s3", "t3 summary correct");
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test table layout guess.
+ */
+addAccessibleTask(
+ `
+<table id="layout"><tr><td>a</td></tr></table>
+<table id="data"><tr><th>a</th></tr></table>
+<table id="mutate"><tr><td>a</td><td>b</td></tr></table>
+<div id="newTableContainer"></div>
+ `,
+ async function(browser, docAcc) {
+ const layout = findAccessibleChildByID(docAcc, "layout");
+ testAttrs(layout, { "layout-guess": "true" }, true);
+ const data = findAccessibleChildByID(docAcc, "data");
+ testAbsentAttrs(data, { "layout-guess": "true" });
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAttrs(mutate, { "layout-guess": "true" }, true);
+
+ info("mutate: Adding 5 rows");
+ let reordered = waitForEvent(EVENT_REORDER, mutate);
+ await invokeContentTask(browser, [], () => {
+ const frag = content.document.createDocumentFragment();
+ for (let r = 0; r < 6; ++r) {
+ const tr = content.document.createElement("tr");
+ tr.innerHTML = "<td>a</td><td>b</td>";
+ frag.append(tr);
+ }
+ content.document.getElementById("mutate").tBodies[0].append(frag);
+ });
+ await reordered;
+ testAbsentAttrs(mutate, { "layout-guess": "true" });
+
+ info("mutate: Removing 5 rows");
+ reordered = waitForEvent(EVENT_REORDER, mutate);
+ await invokeContentTask(browser, [], () => {
+ // Pause refresh driver so all the children removals below will
+ // be collated into the same tick and only one 'reorder' event will
+ // be dispatched.
+ content.windowUtils.advanceTimeAndRefresh(100);
+
+ let tBody = content.document.getElementById("mutate").tBodies[0];
+ for (let r = 0; r < 6; ++r) {
+ tBody.lastChild.remove();
+ }
+
+ // Resume refresh driver
+ content.windowUtils.restoreNormalRefresh();
+ });
+ await reordered;
+ testAttrs(mutate, { "layout-guess": "true" }, true);
+
+ info("mutate: Adding new table");
+ let shown = waitForEvent(EVENT_SHOW, "newTable");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById(
+ "newTableContainer"
+ ).innerHTML = `<table id="newTable"><tr><th>a</th></tr></table>`;
+ });
+ let newTable = (await shown).accessible;
+ testAbsentAttrs(newTable, { "layout-guess": "true" });
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test table layout guess with border styling changes.
+ */
+addAccessibleTask(
+ `
+ <table id="layout"><tr><td id="cell">a</td><td>b</td></tr>
+ <tr><td>c</td><td>d</td></tr><tr><td>c</td><td>d</td></tr></table>
+ `,
+ async function(browser, docAcc) {
+ const layout = findAccessibleChildByID(docAcc, "layout");
+ testAttrs(layout, { "layout-guess": "true" }, true);
+ info("changing border style on table cell");
+ let styleChanged = waitForEvent(EVENT_TABLE_STYLING_CHANGED, layout);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("cell").style.border = "1px solid black";
+ });
+ if (!isCacheEnabled) {
+ // this event doesn't get fired when the cache is on, so we can't await it
+ await styleChanged;
+ }
+ await untilCacheOk(() => {
+ // manually verify the attribute doesn't exist, since `testAbsentAttrs`
+ // has internal calls to ok() which fail if the cache hasn't yet updated
+ for (let prop of layout.attributes.enumerate()) {
+ if (prop.key == "layout-guess") {
+ return false;
+ }
+ }
+ return true;
+ }, "Table is a data table");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test ARIA grid.
+ */
+addAccessibleTask(
+ `
+<div id="grid" role="grid">
+ <div role="rowgroup">
+ <div role="row"><div id="a" role="columnheader">a</div><div id="b" role="columnheader">b</div></div>
+ </div>
+ <div tabindex="-1">
+ <div role="row"><div id="c" role="rowheader">c</div><div id="d" role="gridcell">d</div></div>
+ </div>
+</div>
+ `,
+ async function(browser, docAcc) {
+ const grid = findAccessibleChildByID(docAcc, "grid", [nsIAccessibleTable]);
+ is(grid.rowCount, 2, "grid rowCount correct");
+ is(grid.columnCount, 2, "grid columnCount correct");
+ testTableIndexes(grid, [
+ [0, 1],
+ [2, 3],
+ ]);
+ const cells = {};
+ for (const id of ["a", "b", "c", "d"]) {
+ cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
+ }
+ is(cells.a.rowExtent, 1, "a rowExtent correct");
+ is(cells.a.columnExtent, 1, "a columnExtent correct");
+ testHeaderCells([
+ {
+ cell: cells.c,
+ rowHeaderCells: [],
+ columnHeaderCells: [cells.a],
+ },
+ {
+ cell: cells.d,
+ rowHeaderCells: [cells.c],
+ columnHeaderCells: [cells.b],
+ },
+ ]);
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+function setNodeHidden(browser, id, hidden) {
+ return invokeContentTask(browser, [id, hidden], (cId, cHidden) => {
+ content.document.getElementById(cId).hidden = cHidden;
+ });
+}
+
+/**
+ * Test that the table is updated correctly when it is mutated.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <tr id="r1"><td>a</td><td id="b">b</td></tr>
+ <tr id="r2" hidden><td>c</td><td>d</td></tr>
+</table>
+<div id="owner"></div>
+ `,
+ async function(browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 1, "table rowCount correct");
+ is(table.columnCount, 2, "table columnCount correct");
+ testTableIndexes(table, [[0, 1]]);
+ info("Showing r2");
+ let reordered = waitForEvent(EVENT_REORDER, table);
+ await setNodeHidden(browser, "r2", false);
+ await reordered;
+ is(table.rowCount, 2, "table rowCount correct");
+ testTableIndexes(table, [
+ [0, 1],
+ [2, 3],
+ ]);
+ info("Hiding r2");
+ reordered = waitForEvent(EVENT_REORDER, table);
+ await setNodeHidden(browser, "r2", true);
+ await reordered;
+ is(table.rowCount, 1, "table rowCount correct");
+ testTableIndexes(table, [[0, 1]]);
+ info("Hiding b");
+ reordered = waitForEvent(EVENT_REORDER, "r1");
+ await setNodeHidden(browser, "b", true);
+ await reordered;
+ is(table.columnCount, 1, "table columnCount correct");
+ testTableIndexes(table, [[0]]);
+ info("Showing b");
+ reordered = waitForEvent(EVENT_REORDER, "r1");
+ await setNodeHidden(browser, "b", false);
+ await reordered;
+ is(table.columnCount, 2, "table columnCount correct");
+ if (isCacheEnabled) {
+ info("Moving b out of table using aria-owns");
+ reordered = waitForEvent(EVENT_REORDER, "r1");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("owner").setAttribute("aria-owns", "b");
+ });
+ await reordered;
+ is(table.columnCount, 1, "table columnCount correct");
+ } else {
+ todo(
+ false,
+ "CachedTableAccessible disabled, so counts broken when cell moved with aria-owns"
+ );
+ }
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test the handling of ARIA tables with display: contents.
+ */
+addAccessibleTask(
+ `
+<div id="table" role="table" style="display: contents;">
+ <div role="row"><div role="cell">a</div></div>
+</div>
+ `,
+ async function(browser, docAcc) {
+ // XXX We don't create a TableAccessible in this case (bug 1494196). For
+ // now, just ensure we don't crash (bug 1793073).
+ const table = findAccessibleChildByID(docAcc, "table");
+ let queryOk = false;
+ try {
+ table.QueryInterface(nsIAccessibleTable);
+ queryOk = true;
+ } catch (e) {}
+ todo(queryOk, "Got nsIAccessibleTable");
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test a broken ARIA table with an invalid cell.
+ */
+addAccessibleTask(
+ `
+<div id="table" role="table">
+ <div role="main">
+ <div role="row">
+ <div id="cell" role="cell">a</div>
+ </div>
+ </div>
+</div>
+ `,
+ async function(browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 0, "table rowCount correct");
+ is(table.columnCount, 0, "table columnCount correct");
+ const cell = findAccessibleChildByID(docAcc, "cell");
+ let queryOk = false;
+ try {
+ cell.QueryInterface(nsIAccessibleTableCell);
+ queryOk = true;
+ } catch (e) {}
+ ok(!queryOk, "Got nsIAccessibleTableCell on an invalid cell");
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test that building the cache for a malformed table with an iframe inside a
+ * row doesn't crash (bug 1800780).
+ */
+addAccessibleTask(
+ `<table><tr id="tr"></tr></table>`,
+ async function(browser, docAcc) {
+ let reordered = waitForEvent(EVENT_REORDER, "tr");
+ await invokeContentTask(browser, [], () => {
+ const iframe = content.document.createElement("iframe");
+ content.document.getElementById("tr").append(iframe);
+ });
+ await reordered;
+ },
+ { topLevel: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_text_bounds.js b/accessible/tests/browser/e10s/browser_caching_text_bounds.js
new file mode 100644
index 0000000000..0f50599293
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_text_bounds.js
@@ -0,0 +1,549 @@
+/* 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/layout.js */
+loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
+
+async function testTextNode(accDoc, browser, id) {
+ await testTextRange(accDoc, browser, id, 0, -1);
+}
+
+async function testChar(accDoc, browser, id, idx) {
+ await testTextRange(accDoc, browser, id, idx, idx + 1);
+}
+
+async function testTextRange(accDoc, browser, id, start, end) {
+ const r = await invokeContentTask(
+ browser,
+ [id, start, end],
+ (_id, _start, _end) => {
+ const htNode = content.document.getElementById(_id);
+ let [eX, eY, eW, eH] = [
+ Number.MAX_SAFE_INTEGER,
+ Number.MAX_SAFE_INTEGER,
+ 0,
+ 0,
+ ];
+ let traversed = 0;
+ let localStart = _start;
+ let endTraversal = false;
+ for (let element of htNode.childNodes) {
+ // ignore whitespace, but not embedded elements
+ let isEmbeddedElement = false;
+ if (element.length == undefined) {
+ let potentialTextContainer = element;
+ while (
+ potentialTextContainer &&
+ potentialTextContainer.length == undefined
+ ) {
+ potentialTextContainer = element.firstChild;
+ }
+ if (potentialTextContainer && potentialTextContainer.length) {
+ // If we can reach some text from this container, use that as part
+ // of our range. This is important when testing with intervening inline
+ // elements. ie. <pre><code>ab%0acd
+ element = potentialTextContainer;
+ } else if (element.firstChild) {
+ isEmbeddedElement = true;
+ } else {
+ continue;
+ }
+ }
+ if (element.length + traversed < _start) {
+ // If our start index is not within this
+ // node, keep looking.
+ traversed += element.length;
+ localStart -= element.length;
+ continue;
+ }
+
+ let rect;
+ if (isEmbeddedElement) {
+ rect = element.getBoundingClientRect();
+ } else {
+ const range = content.document.createRange();
+ range.setStart(element, localStart);
+
+ if (_end != -1 && _end - traversed <= element.length) {
+ // If the current node contains
+ // our end index, stop here.
+ endTraversal = true;
+ range.setEnd(element, _end - traversed);
+ } else {
+ range.setEnd(element, element.length);
+ }
+
+ rect = range.getBoundingClientRect();
+ }
+
+ const oldX = eX == Number.MAX_SAFE_INTEGER ? 0 : eX;
+ const oldY = eY == Number.MAX_SAFE_INTEGER ? 0 : eY;
+ eX = Math.min(eX, rect.x);
+ eY = Math.min(eY, rect.y);
+ eW = Math.abs(Math.max(oldX + eW, rect.x + rect.width) - eX);
+ eH = Math.abs(Math.max(oldY + eH, rect.y + rect.height) - eY);
+
+ if (endTraversal) {
+ break;
+ }
+ localStart = 0;
+ traversed += element.length;
+ }
+ return [Math.round(eX), Math.round(eY), Math.round(eW), Math.round(eH)];
+ }
+ );
+ let hyperTextNode = findAccessibleChildByID(accDoc, id);
+
+ // test against parent-relative coords, because getBoundingClientRect
+ // is relative to the document, not the screen. this won't work on nested
+ // elements (ie. any hypertext whose parent is not the doc).
+ if (end != -1 && end - start == 1) {
+ // If we're only testing a character, use this function because it calls
+ // CharBounds() directly instead of TextBounds().
+ testTextPos(hyperTextNode, start, [r[0], r[1]], COORDTYPE_PARENT_RELATIVE);
+ } else {
+ testTextBounds(hyperTextNode, start, end, r, COORDTYPE_PARENT_RELATIVE);
+ }
+}
+
+/**
+ * Test the text range boundary for simple LtR text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' style='font-family: monospace;'>ل</p>
+ <p id='p3' dir='ltr' style='font-family: monospace;'>Привіт Світ</p>
+ <pre id='p4' style='font-family: monospace;'>a%0abcdef</pre>
+ `,
+ async function(browser, accDoc) {
+ info("Testing simple LtR text");
+ if (isWinNoCache) {
+ ok(true, "skipping tests, running on windows without cache");
+ // We have to do this in at least one of these sub-tasks because
+ // otherwise the test harness complains this file is empty when
+ // it runs on windows without the cache enabled.
+ return;
+ }
+
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ await testTextNode(accDoc, browser, "p4");
+ },
+ {
+ iframe: true,
+ }
+);
+
+/**
+ * Test the partial text range boundary for LtR text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' dir='ltr' style='font-family: monospace;'>Привіт Світ</p>
+ `,
+ async function(browser, accDoc) {
+ info("Testing partial ranges in LtR text");
+ await testTextRange(accDoc, browser, "p1", 0, 4);
+ await testTextRange(accDoc, browser, "p1", 2, 8);
+ await testTextRange(accDoc, browser, "p1", 12, 17);
+ await testTextRange(accDoc, browser, "p2", 0, 4);
+ await testTextRange(accDoc, browser, "p2", 2, 8);
+ await testTextRange(accDoc, browser, "p2", 6, 11);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test the text boundary for multiline LtR text
+ */
+addAccessibleTask(
+ `
+ <p id='p4' dir='ltr' style='font-family: monospace;'>Привіт Світ<br>Привіт Світ</p>
+ <p id='p5' dir='ltr' style='font-family: monospace;'>Привіт Світ<br> Я ще трохи тексту в другому рядку</p>
+ <p id='p6' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p>
+ <p id='p7' style='font-family: monospace;'>hello world<br>hello world</p>
+ `,
+ async function(browser, accDoc) {
+ info("Testing multiline LtR text");
+ await testTextNode(accDoc, browser, "p4");
+ await testTextNode(accDoc, browser, "p5");
+ // await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache wrong w, h in iframe (line wrapping)
+ await testTextNode(accDoc, browser, "p7");
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test the text boundary for simple RtL text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' dir='rtl' style='font-family: monospace;'>ل</p>
+ <p id='p3' dir='rtl' style='font-family: monospace;'>لل لللل لل</p>
+ <pre id='p4' dir='rtl' style='font-family: monospace;'>a%0abcdef</pre>
+ `,
+ async function(browser, accDoc) {
+ info("Testing simple RtL text");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ await testTextNode(accDoc, browser, "p4");
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test the text boundary for multiline RtL text
+ */
+addAccessibleTask(
+ `
+ <p id='p4' dir='rtl' style='font-family: monospace;'>لل لللل لل<br>لل لللل لل</p>
+ <p id='p5' dir='rtl' style='font-family: monospace;'>لل لللل لل<br> لل لل لل لل ل لل لل لل</p>
+ <p id='p6' dir='rtl' style='font-family: monospace;'>hello world I'm on line one<br> and I'm a separate line two with slightly more text</p>
+ <p id='p7' dir='rtl' style='font-family: monospace;'>hello world<br>hello world</p>
+ `,
+ async function(browser, accDoc) {
+ info("Testing multiline RtL text");
+ await testTextNode(accDoc, browser, "p4");
+ if (!isCacheEnabled) {
+ await testTextNode(accDoc, browser, "p5"); // w/ cache fails x, w - off by one char
+ }
+ // await testTextNode(accDoc, browser, "p6"); // w/o cache, fails width (a 259, e 250), w/ cache fails w, h in iframe (line wrapping)
+ await testTextNode(accDoc, browser, "p7");
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test the partial text range boundary for RtL text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' dir='rtl' style='font-family: monospace;'>Tilimilitryamdiya</p>
+ <p id='p2' dir='rtl' style='font-family: monospace;'>لل لللل لل</p>
+ `,
+ async function(browser, accDoc) {
+ info("Testing partial ranges in RtL text");
+ await testTextRange(accDoc, browser, "p1", 0, 4);
+ await testTextRange(accDoc, browser, "p1", 2, 8);
+ await testTextRange(accDoc, browser, "p1", 12, 17);
+ await testTextRange(accDoc, browser, "p2", 0, 4);
+ await testTextRange(accDoc, browser, "p2", 2, 8);
+ await testTextRange(accDoc, browser, "p2", 6, 10);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test simple vertical text in rl and lr layouts
+ */
+addAccessibleTask(
+ `
+ <div style="writing-mode: vertical-rl;">
+ <p id='p1'>你好世界</p>
+ <p id='p2'>hello world</p>
+ <br>
+ <p id='p3'>こんにちは世界</p>
+ </div>
+ <div style="writing-mode: vertical-lr;">
+ <p id='p4'>你好世界</p>
+ <p id='p5'>hello world</p>
+ <br>
+ <p id='p6'>こんにちは世界</p>
+ </div>
+ `,
+ async function(browser, accDoc) {
+ info("Testing vertical-rl");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ info("Testing vertical-lr");
+ await testTextNode(accDoc, browser, "p4");
+ await testTextNode(accDoc, browser, "p5");
+ await testTextNode(accDoc, browser, "p6");
+ },
+ {
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test multiline vertical-rl text
+ */
+addAccessibleTask(
+ `
+ <p id='p1' style='writing-mode: vertical-rl;'>你好世界<br>你好世界</p>
+ <p id='p2' style='writing-mode: vertical-rl;'>hello world<br>hello world</p>
+ <br>
+ <p id='p3' style='writing-mode: vertical-rl;'>你好世界<br> 你好世界 你好世界</p>
+ <p id='p4' style='writing-mode: vertical-rl;'>hello world<br> hello world hello world</p>
+ `,
+ async function(browser, accDoc) {
+ info("Testing vertical-rl multiline");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "p3");
+ // await testTextNode(accDoc, browser, "p4"); // off by 4 with caching, iframe
+ },
+ {
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test text with embedded chars
+ */
+addAccessibleTask(
+ `<p id='p1' style='font-family: monospace;'>hello <a href="google.com">world</a></p>
+ <p id='p2' style='font-family: monospace;'>hello<br><a href="google.com">world</a></p>
+ <div id='d3'><p></p>hello world</div>
+ <div id='d4'>hello world<p></p></div>
+ <div id='d5'>oh<p></p>hello world</div>`,
+ async function(browser, accDoc) {
+ info("Testing embedded chars");
+ await testTextNode(accDoc, browser, "p1");
+ await testTextNode(accDoc, browser, "p2");
+ await testTextNode(accDoc, browser, "d3");
+ await testTextNode(accDoc, browser, "d4");
+ await testTextNode(accDoc, browser, "d5");
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test bounds after text mutations.
+ */
+addAccessibleTask(
+ `<p id="p">a</p>`,
+ async function(browser, docAcc) {
+ await testTextNode(docAcc, browser, "p");
+ const p = findAccessibleChildByID(docAcc, "p");
+ info("Appending a character to text leaf");
+ let textInserted = waitForEvent(EVENT_TEXT_INSERTED, p);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("p").firstChild.data = "ab";
+ });
+ await textInserted;
+ await testTextNode(docAcc, browser, "p");
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test character bounds on the insertion point at the end of a text box.
+ */
+addAccessibleTask(
+ `<input id="input" value="a">`,
+ async function(browser, docAcc) {
+ const input = findAccessibleChildByID(docAcc, "input");
+ testTextPos(input, 1, [0, 0], COORDTYPE_SCREEN_RELATIVE);
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test character bounds after non-br line break.
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ }
+ </style>
+ <pre id="t">XX
+XXX</pre>`,
+ async function(browser, docAcc) {
+ await testChar(docAcc, browser, "t", 3);
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test character bounds in a pre with padding.
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ padding: 20px;
+ }
+ </style>
+ <pre id="t">XX
+XXX</pre>`,
+ async function(browser, docAcc) {
+ await testTextNode(docAcc, browser, "t");
+ await testChar(docAcc, browser, "t", 3);
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test text bounds with an invalid end offset.
+ */
+addAccessibleTask(
+ `<p id="p">a</p>`,
+ async function(browser, docAcc) {
+ const p = findAccessibleChildByID(docAcc, "p");
+ testTextBounds(p, 0, 2, [0, 0, 0, 0], COORDTYPE_SCREEN_RELATIVE);
+ },
+ { chrome: true, topLevel: !isWinNoCache }
+);
+
+/**
+ * Test character bounds in an intervening inline element with non-br line breaks
+ */
+addAccessibleTask(
+ `
+ <style>
+ @font-face {
+ font-family: Ahem;
+ src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+ }
+ pre {
+ font: 20px/20px Ahem;
+ }
+ </style>
+ <pre id="t"><code>XX
+XXX
+XX
+X</pre>`,
+ async function(browser, docAcc) {
+ await testChar(docAcc, browser, "t", 0);
+ await testChar(docAcc, browser, "t", 3);
+ await testChar(docAcc, browser, "t", 7);
+ await testChar(docAcc, browser, "t", 10);
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ }
+);
+
+// XXX: There's a fuzziness here of about 8 pixels, implying we aren't taking into
+// account some kind of margin or padding. See bug 1809695.
+// /**
+// * Test character bounds in an intervening inline element with margins
+// * and with non-br line breaks
+// */
+// addAccessibleTask(
+// `
+// <style>
+// @font-face {
+// font-family: Ahem;
+// src: url(${CURRENT_CONTENT_DIR}e10s/fonts/Ahem.sjs);
+// }
+// </style>
+// <div>hello<pre id="t" style="margin-left:100px;margin-top:30px;background-color:blue;">XX
+// XXX
+// XX
+// X</pre></div>`,
+// async function(browser, docAcc) {
+// await testChar(docAcc, browser, "t", 0);
+// await testChar(docAcc, browser, "t", 3);
+// await testChar(docAcc, browser, "t", 7);
+// await testChar(docAcc, browser, "t", 10);
+// },
+// {
+// chrome: true,
+// topLevel: !isWinNoCache,
+// iframe: !isWinNoCache,
+// }
+// );
+
+/**
+ * Test text bounds in a textarea after scrolling.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea" rows="1">a
+b
+c</textarea>
+ `,
+ async function(browser, docAcc) {
+ // We can't use testChar because Range.getBoundingClientRect isn't supported
+ // inside textareas.
+ const textarea = findAccessibleChildByID(docAcc, "textarea");
+ textarea.QueryInterface(nsIAccessibleText);
+ const oldY = {};
+ textarea.getCharacterExtents(
+ 4,
+ {},
+ oldY,
+ {},
+ {},
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ info("Moving textarea caret to c");
+ await invokeContentTask(browser, [], () => {
+ const textareaDom = content.document.getElementById("textarea");
+ textareaDom.focus();
+ textareaDom.selectionStart = 4;
+ });
+ await waitForContentPaint(browser);
+ const newY = {};
+ textarea.getCharacterExtents(
+ 4,
+ {},
+ newY,
+ {},
+ {},
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ ok(newY.value < oldY.value, "y coordinate smaller after scrolling down");
+ },
+ { chrome: true, topLevel: !isWinNoCache, iframe: !isWinNoCache }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_uniqueid.js b/accessible/tests/browser/e10s/browser_caching_uniqueid.js
new file mode 100644
index 0000000000..287f896c36
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_uniqueid.js
@@ -0,0 +1,30 @@
+/* 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";
+
+/**
+ * Test UniqueID property.
+ */
+addAccessibleTask(
+ '<div id="div"></div>',
+ async function(browser, accDoc) {
+ const div = findAccessibleChildByID(accDoc, "div");
+ const accUniqueID = await invokeContentTask(browser, [], () => {
+ const accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+ Ci.nsIAccessibilityService
+ );
+
+ return accService.getAccessibleFor(content.document.getElementById("div"))
+ .uniqueID;
+ });
+
+ is(
+ accUniqueID,
+ div.uniqueID,
+ "Both proxy and the accessible return correct unique ID."
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_caching_value.js b/accessible/tests/browser/e10s/browser_caching_value.js
new file mode 100644
index 0000000000..dd23567729
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_value.js
@@ -0,0 +1,384 @@
+/* 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/states.js */
+/* import-globals-from ../../mochitest/value.js */
+loadScripts(
+ { name: "states.js", dir: MOCHITESTS_DIR },
+ { name: "value.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * id {String} given accessible DOMNode ID
+ * expected {String} expected value for a given accessible
+ * action {?AsyncFunction} an optional action that awaits a value change
+ * attrs {?Array} an optional list of attributes to update
+ * waitFor {?Number} an optional value change event to wait for
+ * }
+ */
+const valueTests = [
+ {
+ desc: "Initially value is set to 1st element of select",
+ id: "select",
+ expected: "1st",
+ },
+ {
+ desc: "Value should update to 3rd when 3 is pressed",
+ id: "select",
+ async action(browser) {
+ await invokeFocus(browser, "select");
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("3", {}, content);
+ });
+ },
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: "3rd",
+ },
+ {
+ desc: "Initially value is set to @aria-valuenow for slider",
+ id: "slider",
+ expected: ["5", 5, 0, 7, 0],
+ },
+ {
+ desc: "Value should change when @aria-valuenow is updated",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuenow",
+ value: "6",
+ },
+ ],
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: ["6", 6, 0, 7, 0],
+ },
+ {
+ desc: "Value should change when @aria-valuetext is set",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuetext",
+ value: "plain",
+ },
+ ],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: ["plain", 6, 0, 7, 0],
+ },
+ {
+ desc: "Value should change when @aria-valuetext is updated",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuetext",
+ value: "hey!",
+ },
+ ],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: ["hey!", 6, 0, 7, 0],
+ },
+ {
+ desc:
+ "Value should change to @aria-valuetext when @aria-valuenow is removed",
+ id: "slider",
+ attrs: [
+ {
+ attr: "aria-valuenow",
+ },
+ ],
+ expected: ["hey!", 3.5, 0, 7, 0],
+ },
+ {
+ desc: "Initially value is not set for combobox",
+ id: "combobox",
+ expected: "",
+ },
+ {
+ desc: "Value should change when @value attribute is updated",
+ id: "combobox",
+ attrs: [
+ {
+ attr: "value",
+ value: "hello",
+ },
+ ],
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: "hello",
+ },
+ {
+ desc: "Initially value corresponds to @value attribute for progress",
+ id: "progress",
+ expected: "22%",
+ },
+ {
+ desc: "Value should change when @value attribute is updated",
+ id: "progress",
+ attrs: [
+ {
+ attr: "value",
+ value: "50",
+ },
+ ],
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: "50%",
+ },
+ {
+ desc: "Initially value corresponds to @value attribute for range",
+ id: "range",
+ expected: "6",
+ },
+ {
+ desc: "Value should change when slider is moved",
+ id: "range",
+ async action(browser) {
+ await invokeFocus(browser, "range");
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("VK_LEFT", {}, content);
+ });
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: "5",
+ },
+ {
+ desc: "Initially textbox value is text subtree",
+ id: "textbox",
+ expected: "Some rich text",
+ },
+ {
+ desc: "Textbox value changes when subtree changes",
+ id: "textbox",
+ async action(browser) {
+ await invokeContentTask(browser, [], () => {
+ let boldText = content.document.createElement("strong");
+ boldText.textContent = " bold";
+ content.document.getElementById("textbox").appendChild(boldText);
+ });
+ },
+ waitFor: EVENT_TEXT_VALUE_CHANGE,
+ expected: "Some rich text bold",
+ },
+];
+
+/**
+ * Test caching of accessible object values
+ */
+addAccessibleTask(
+ `
+ <div id="slider" role="slider" aria-valuenow="5"
+ aria-valuemin="0" aria-valuemax="7">slider</div>
+ <select id="select">
+ <option>1st</option>
+ <option>2nd</option>
+ <option>3rd</option>
+ </select>
+ <input id="combobox" role="combobox" aria-autocomplete="inline">
+ <progress id="progress" value="22" max="100"></progress>
+ <input type="range" id="range" min="0" max="10" value="6">
+ <div contenteditable="yes" role="textbox" id="textbox">Some <a href="#">rich</a> text</div>`,
+ async function(browser, accDoc) {
+ for (let { desc, id, action, attrs, expected, waitFor } of valueTests) {
+ info(desc);
+ let acc = findAccessibleChildByID(accDoc, id);
+ let onUpdate;
+
+ if (waitFor) {
+ onUpdate = waitForEvent(waitFor, id);
+ }
+
+ if (action) {
+ await action(browser);
+ } else if (attrs) {
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, id, attr, value);
+ }
+ }
+
+ await onUpdate;
+ if (Array.isArray(expected)) {
+ acc.QueryInterface(nsIAccessibleValue);
+ testValue(acc, ...expected);
+ } else {
+ is(acc.value, expected, `Correct value for ${prettyName(acc)}`);
+ }
+ }
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of link URL values.
+ */
+addAccessibleTask(
+ `<a id="link" href="https://example.com/">Test</a>`,
+ async function(browser, docAcc) {
+ const link = findAccessibleChildByID(docAcc, "link");
+ is(link.value, "https://example.com/", "link initial value correct");
+ const textLeaf = link.firstChild;
+ is(textLeaf.value, "https://example.com/", "link initial value correct");
+
+ info("Changing link href");
+ await invokeSetAttribute(browser, "link", "href", "https://example.net/");
+ await untilCacheIs(
+ () => link.value,
+ "https://example.net/",
+ "link value correct after change"
+ );
+
+ info("Removing link href");
+ await invokeSetAttribute(browser, "link", "href");
+ await untilCacheIs(() => link.value, "", "link value empty after removal");
+
+ info("Setting link href");
+ await invokeSetAttribute(browser, "link", "href", "https://example.com/");
+ await untilCacheIs(
+ () => link.value,
+ "https://example.com/",
+ "link value correct after change"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of active state for select options - see bug 1788143.
+ */
+addAccessibleTask(
+ `
+ <select id="select">
+ <option id="first_option">First</option>
+ <option id="second_option">Second</option>
+ </select>`,
+ async function(browser, docAcc) {
+ const select = findAccessibleChildByID(docAcc, "select");
+ is(select.value, "First", "Select initial value correct");
+
+ // Focus the combo box.
+ await invokeFocus(browser, "select");
+
+ // Select the second option (drop-down collapsed).
+ let p = waitForEvents({
+ expected: [
+ [EVENT_SELECTION, "second_option"],
+ [EVENT_TEXT_VALUE_CHANGE, "select"],
+ ],
+ unexpected: [
+ stateChangeEventArgs("second_option", EXT_STATE_ACTIVE, true, true),
+ stateChangeEventArgs("first_option", EXT_STATE_ACTIVE, false, true),
+ ],
+ });
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("select").selectedIndex = 1;
+ });
+ await p;
+
+ is(select.value, "Second", "Select value correct after changing option");
+
+ // Expand the combobox dropdown.
+ p = waitForEvent(EVENT_STATE_CHANGE, "ContentSelectDropdown");
+ EventUtils.synthesizeKey("VK_SPACE");
+ await p;
+
+ p = waitForEvents({
+ expected: [
+ [EVENT_SELECTION, "first_option"],
+ [EVENT_TEXT_VALUE_CHANGE, "select"],
+ [EVENT_HIDE, "ContentSelectDropdown"],
+ ],
+ unexpected: [
+ stateChangeEventArgs("first_option", EXT_STATE_ACTIVE, true, true),
+ stateChangeEventArgs("second_option", EXT_STATE_ACTIVE, false, true),
+ ],
+ });
+
+ // Press the up arrow to select the first option (drop-down expanded).
+ // Then, press Enter to confirm the selection and close the dropdown.
+ // We do both of these together to unify testing across platforms, since
+ // events are not entirely consistent on Windows vs. Linux + macOS.
+ EventUtils.synthesizeKey("VK_UP");
+ EventUtils.synthesizeKey("VK_RETURN");
+ await p;
+
+ is(
+ select.value,
+ "First",
+ "Select value correct after changing option back"
+ );
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test combobox values for non-editable comboboxes.
+ */
+addAccessibleTask(
+ `
+ <div id="combo-div-1" role="combobox">value</div>
+ <div id="combo-div-2" role="combobox">
+ <div role="listbox">
+ <div role="option">value</div>
+ </div>
+ </div>
+ <div id="combo-div-3" role="combobox">
+ <div role="group">value</div>
+ </div>
+ <div id="combo-div-4" role="combobox">foo
+ <div role="listbox">
+ <div role="option">bar</div>
+ </div>
+ </div>
+
+ <input id="combo-input-1" role="combobox" value="value" disabled></input>
+ <input id="combo-input-2" role="combobox" value="value" disabled>testing</input>
+
+ <div id="combo-div-selected" role="combobox">
+ <div role="listbox">
+ <div aria-selected="true" role="option">value</div>
+ </div>
+ </div>
+`,
+ async function(browser, docAcc) {
+ const comboDiv1 = findAccessibleChildByID(docAcc, "combo-div-1");
+ const comboDiv2 = findAccessibleChildByID(docAcc, "combo-div-2");
+ const comboDiv3 = findAccessibleChildByID(docAcc, "combo-div-3");
+ const comboDiv4 = findAccessibleChildByID(docAcc, "combo-div-4");
+ const comboInput1 = findAccessibleChildByID(docAcc, "combo-input-1");
+ const comboInput2 = findAccessibleChildByID(docAcc, "combo-input-2");
+ const comboDivSelected = findAccessibleChildByID(
+ docAcc,
+ "combo-div-selected"
+ );
+
+ // Text as a descendant of the combobox: included in the value.
+ is(comboDiv1.value, "value", "Combobox value correct");
+
+ // Text as the descendant of a listbox: excluded from the value.
+ is(comboDiv2.value, "", "Combobox value correct");
+
+ // Text as the descendant of some other role that includes text in name computation.
+ // Here, the group role contains the text node with "value" in it.
+ is(comboDiv3.value, "value", "Combobox value correct");
+
+ // Some descendant text included, but text descendant of a listbox excluded.
+ is(comboDiv4.value, "foo", "Combobox value correct");
+
+ // Combobox inputs with explicit value report that value.
+ is(comboInput1.value, "value", "Combobox value correct");
+ is(comboInput2.value, "value", "Combobox value correct");
+
+ // Combobox role with aria-selected reports correct value.
+ is(comboDivSelected.value, "value", "Combobox value correct");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_announcement.js b/accessible/tests/browser/e10s/browser_events_announcement.js
new file mode 100644
index 0000000000..2de6d4b005
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_announcement.js
@@ -0,0 +1,30 @@
+/* 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";
+
+addAccessibleTask(
+ `<p id="p">abc</p>`,
+ async function(browser, accDoc) {
+ let acc = findAccessibleChildByID(accDoc, "p");
+ let onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc);
+ acc.announce("please", nsIAccessibleAnnouncementEvent.POLITE);
+ let evt = await onAnnounce;
+ evt.QueryInterface(nsIAccessibleAnnouncementEvent);
+ is(evt.announcement, "please", "announcement matches.");
+ is(evt.priority, nsIAccessibleAnnouncementEvent.POLITE, "priority matches");
+
+ onAnnounce = waitForEvent(EVENT_ANNOUNCEMENT, acc);
+ acc.announce("do it", nsIAccessibleAnnouncementEvent.ASSERTIVE);
+ evt = await onAnnounce;
+ evt.QueryInterface(nsIAccessibleAnnouncementEvent);
+ is(evt.announcement, "do it", "announcement matches.");
+ is(
+ evt.priority,
+ nsIAccessibleAnnouncementEvent.ASSERTIVE,
+ "priority matches"
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_caretmove.js b/accessible/tests/browser/e10s/browser_events_caretmove.js
new file mode 100644
index 0000000000..a39d16e710
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_caretmove.js
@@ -0,0 +1,22 @@
+/* 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";
+
+/**
+ * Test caret move event and its interface:
+ * - caretOffset
+ */
+addAccessibleTask(
+ '<input id="textbox" value="hello"/>',
+ async function(browser) {
+ let onCaretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, "textbox");
+ await invokeFocus(browser, "textbox");
+ let event = await onCaretMoved;
+
+ let caretMovedEvent = event.QueryInterface(nsIAccessibleCaretMoveEvent);
+ is(caretMovedEvent.caretOffset, 5, "Correct caret offset.");
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_hide.js b/accessible/tests/browser/e10s/browser_events_hide.js
new file mode 100644
index 0000000000..d46921d051
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_hide.js
@@ -0,0 +1,44 @@
+/* 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";
+
+/**
+ * Test hide event and its interface:
+ * - targetParent
+ * - targetNextSibling
+ * - targetPrevSibling
+ */
+addAccessibleTask(
+ `
+ <div id="parent">
+ <div id="previous"></div>
+ <div id="to-hide"></div>
+ <div id="next"></div>
+ </div>`,
+ async function(browser, accDoc) {
+ let acc = findAccessibleChildByID(accDoc, "to-hide");
+ let onHide = waitForEvent(EVENT_HIDE, acc);
+ await invokeSetStyle(browser, "to-hide", "visibility", "hidden");
+ let event = await onHide;
+ let hideEvent = event.QueryInterface(Ci.nsIAccessibleHideEvent);
+
+ is(
+ getAccessibleDOMNodeID(hideEvent.targetParent),
+ "parent",
+ "Correct target parent."
+ );
+ is(
+ getAccessibleDOMNodeID(hideEvent.targetNextSibling),
+ "next",
+ "Correct target next sibling."
+ );
+ is(
+ getAccessibleDOMNodeID(hideEvent.targetPrevSibling),
+ "previous",
+ "Correct target previous sibling."
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_show.js b/accessible/tests/browser/e10s/browser_events_show.js
new file mode 100644
index 0000000000..d464d8fb9d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_show.js
@@ -0,0 +1,22 @@
+/* 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";
+
+/**
+ * Test show event
+ */
+addAccessibleTask(
+ '<div id="div" style="visibility: hidden;"></div>',
+ async function(browser) {
+ let onShow = waitForEvent(EVENT_SHOW, "div");
+ await invokeSetStyle(browser, "div", "visibility", "visible");
+ let showEvent = await onShow;
+ ok(
+ showEvent.accessibleDocument instanceof nsIAccessibleDocument,
+ "Accessible document not present."
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_statechange.js b/accessible/tests/browser/e10s/browser_events_statechange.js
new file mode 100644
index 0000000000..a027a974e4
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_statechange.js
@@ -0,0 +1,71 @@
+/* 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 */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+function checkStateChangeEvent(event, state, isExtraState, isEnabled) {
+ let scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+ is(scEvent.state, state, "Correct state of the statechange event.");
+ is(
+ scEvent.isExtraState,
+ isExtraState,
+ "Correct extra state bit of the statechange event."
+ );
+ is(scEvent.isEnabled, isEnabled, "Correct state of statechange event state");
+}
+
+// Insert mock source into the iframe to be able to verify the right document
+// body id.
+let iframeSrc = `data:text/html,
+ <html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Inner Iframe</title>
+ </head>
+ <body id='iframe'></body>
+ </html>`;
+
+/**
+ * Test state change event and its interface:
+ * - state
+ * - isExtraState
+ * - isEnabled
+ */
+addAccessibleTask(
+ `
+ <iframe id="iframe" src="${iframeSrc}"></iframe>
+ <input id="checkbox" type="checkbox" />`,
+ async function(browser) {
+ // Test state change
+ let onStateChange = waitForEvent(EVENT_STATE_CHANGE, "checkbox");
+ // Set checked for a checkbox.
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("checkbox").checked = true;
+ });
+ let event = await onStateChange;
+
+ checkStateChangeEvent(event, STATE_CHECKED, false, true);
+ testStates(event.accessible, STATE_CHECKED, 0);
+
+ // Test extra state
+ onStateChange = waitForEvent(EVENT_STATE_CHANGE, "iframe");
+ // Set design mode on.
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("iframe").contentDocument.designMode =
+ "on";
+ });
+ event = await onStateChange;
+
+ checkStateChangeEvent(event, EXT_STATE_EDITABLE, true, true);
+ testStates(event.accessible, 0, EXT_STATE_EDITABLE);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_textchange.js b/accessible/tests/browser/e10s/browser_events_textchange.js
new file mode 100644
index 0000000000..5c3359a379
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_textchange.js
@@ -0,0 +1,120 @@
+/* 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";
+
+function checkTextChangeEvent(
+ event,
+ id,
+ text,
+ start,
+ end,
+ isInserted,
+ isFromUserInput
+) {
+ let tcEvent = event.QueryInterface(nsIAccessibleTextChangeEvent);
+ is(tcEvent.start, start, `Correct start offset for ${prettyName(id)}`);
+ is(tcEvent.length, end - start, `Correct length for ${prettyName(id)}`);
+ is(
+ tcEvent.isInserted,
+ isInserted,
+ `Correct isInserted flag for ${prettyName(id)}`
+ );
+ is(tcEvent.modifiedText, text, `Correct text for ${prettyName(id)}`);
+ is(
+ tcEvent.isFromUserInput,
+ isFromUserInput,
+ `Correct value of isFromUserInput for ${prettyName(id)}`
+ );
+ ok(
+ tcEvent.accessibleDocument instanceof nsIAccessibleDocument,
+ "Accessible document not present."
+ );
+}
+
+async function changeText(browser, id, value, events) {
+ let onEvents = waitForOrderedEvents(
+ events.map(({ isInserted }) => {
+ let eventType = isInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
+ return [eventType, id];
+ })
+ );
+ // Change text in the subtree.
+ await invokeContentTask(browser, [id, value], (contentId, contentValue) => {
+ content.document.getElementById(
+ contentId
+ ).firstChild.textContent = contentValue;
+ });
+ let resolvedEvents = await onEvents;
+
+ events.forEach(({ isInserted, str, offset }, idx) =>
+ checkTextChangeEvent(
+ resolvedEvents[idx],
+ id,
+ str,
+ offset,
+ offset + str.length,
+ isInserted,
+ false
+ )
+ );
+}
+
+async function removeTextFromInput(browser, id, value, start, end) {
+ let onTextRemoved = waitForEvent(EVENT_TEXT_REMOVED, id);
+ // Select text and delete it.
+ await invokeContentTask(
+ browser,
+ [id, start, end],
+ (contentId, contentStart, contentEnd) => {
+ let el = content.document.getElementById(contentId);
+ el.focus();
+ el.setSelectionRange(contentStart, contentEnd);
+ }
+ );
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.sendChar("VK_DELETE", content);
+ });
+
+ let event = await onTextRemoved;
+ checkTextChangeEvent(event, id, value, start, end, false, true);
+}
+
+/**
+ * Test text change event and its interface:
+ * - start
+ * - length
+ * - isInserted
+ * - modifiedText
+ * - isFromUserInput
+ */
+addAccessibleTask(
+ `
+ <p id="p">abc</p>
+ <input id="input" value="input" />`,
+ async function(browser) {
+ let events = [
+ { isInserted: false, str: "abc", offset: 0 },
+ { isInserted: true, str: "def", offset: 0 },
+ ];
+ await changeText(browser, "p", "def", events);
+
+ // Adding text should not send events with diffs for non-editable text.
+ // We do this to avoid screen readers reading out confusing diffs for
+ // live regions.
+ events = [
+ { isInserted: false, str: "def", offset: 0 },
+ { isInserted: true, str: "deDEFf", offset: 0 },
+ ];
+ await changeText(browser, "p", "deDEFf", events);
+
+ // Test isFromUserInput property.
+ await removeTextFromInput(browser, "input", "n", 1, 2);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_events_vcchange.js b/accessible/tests/browser/e10s/browser_events_vcchange.js
new file mode 100644
index 0000000000..8ba59d8a1d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_vcchange.js
@@ -0,0 +1,87 @@
+/* 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";
+
+addAccessibleTask(
+ `
+ <p id="p1">abc</p>
+ <input id="input1" value="input" />`,
+ async function(browser) {
+ let onVCChanged = waitForEvent(
+ EVENT_VIRTUALCURSOR_CHANGED,
+ matchContentDoc
+ );
+ await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ let vc = CommonUtils.getAccessible(
+ content.document,
+ Ci.nsIAccessibleDocument
+ ).virtualCursor;
+ vc.position = CommonUtils.getAccessible(
+ "p1",
+ null,
+ null,
+ null,
+ content.document
+ );
+ });
+ let vccEvent = (await onVCChanged).QueryInterface(
+ nsIAccessibleVirtualCursorChangeEvent
+ );
+ is(vccEvent.newAccessible.id, "p1", "New position is correct");
+ is(vccEvent.newStartOffset, -1, "New start offset is correct");
+ is(vccEvent.newEndOffset, -1, "New end offset is correct");
+ ok(!vccEvent.isFromUserInput, "not user initiated");
+
+ onVCChanged = waitForEvent(EVENT_VIRTUALCURSOR_CHANGED, matchContentDoc);
+ await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ let vc = CommonUtils.getAccessible(
+ content.document,
+ Ci.nsIAccessibleDocument
+ ).virtualCursor;
+ vc.moveNextByText(Ci.nsIAccessiblePivot.CHAR_BOUNDARY);
+ });
+ vccEvent = (await onVCChanged).QueryInterface(
+ nsIAccessibleVirtualCursorChangeEvent
+ );
+ is(vccEvent.newAccessible.id, vccEvent.oldAccessible.id, "Same position");
+ is(vccEvent.newStartOffset, 0, "New start offset is correct");
+ is(vccEvent.newEndOffset, 1, "New end offset is correct");
+ ok(vccEvent.isFromUserInput, "user initiated");
+
+ onVCChanged = waitForEvent(EVENT_VIRTUALCURSOR_CHANGED, matchContentDoc);
+ await invokeContentTask(browser, [], () => {
+ const { CommonUtils } = ChromeUtils.importESModule(
+ "chrome://mochitests/content/browser/accessible/tests/browser/Common.sys.mjs"
+ );
+ let vc = CommonUtils.getAccessible(
+ content.document,
+ Ci.nsIAccessibleDocument
+ ).virtualCursor;
+ vc.position = CommonUtils.getAccessible(
+ "input1",
+ null,
+ null,
+ null,
+ content.document
+ );
+ });
+ vccEvent = (await onVCChanged).QueryInterface(
+ nsIAccessibleVirtualCursorChangeEvent
+ );
+ isnot(vccEvent.oldAccessible, vccEvent.newAccessible, "positions differ");
+ is(vccEvent.oldAccessible.id, "p1", "Old position is correct");
+ is(vccEvent.newAccessible.id, "input1", "New position is correct");
+ is(vccEvent.newStartOffset, -1, "New start offset is correct");
+ is(vccEvent.newEndOffset, -1, "New end offset is correct");
+ ok(!vccEvent.isFromUserInput, "not user initiated");
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_obj_group.js b/accessible/tests/browser/e10s/browser_obj_group.js
new file mode 100644
index 0000000000..de4ab64e5c
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_obj_group.js
@@ -0,0 +1,812 @@
+/* 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/attributes.js */
+loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR });
+
+/**
+ * select elements
+ */
+addAccessibleTask(
+ `<select>
+ <option id="opt1-nosize">option1</option>
+ <option id="opt2-nosize">option2</option>
+ <option id="opt3-nosize">option3</option>
+ <option id="opt4-nosize">option4</option>
+ </select>
+
+ <select size="4">
+ <option id="opt1">option1</option>
+ <option id="opt2">option2</option>
+ </select>
+
+ <select size="4">
+ <optgroup id="select2_optgroup" label="group">
+ <option id="select2_opt1">option1</option>
+ <option id="select2_opt2">option2</option>
+ </optgroup>
+ <option id="select2_opt3">option3</option>
+ <option id="select2_opt4">option4</option>
+ </select>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select with no size attribute.
+ testGroupAttrs(getAcc("opt1-nosize"), 1, 4);
+ testGroupAttrs(getAcc("opt2-nosize"), 2, 4);
+ testGroupAttrs(getAcc("opt3-nosize"), 3, 4);
+ testGroupAttrs(getAcc("opt4-nosize"), 4, 4);
+
+ // Container should have item count and not hierarchical
+ testGroupParentAttrs(getAcc("opt1-nosize").parent, 4, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select
+ testGroupAttrs(getAcc("opt1"), 1, 2);
+ testGroupAttrs(getAcc("opt2"), 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML select with optgroup
+ testGroupAttrs(getAcc("select2_opt3"), 1, 2, 1);
+ testGroupAttrs(getAcc("select2_opt4"), 2, 2, 1);
+ testGroupAttrs(getAcc("select2_opt1"), 1, 2, 2);
+ testGroupAttrs(getAcc("select2_opt2"), 2, 2, 2);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+/**
+ * HTML radios
+ */
+addAccessibleTask(
+ `<form>
+ <input type="radio" id="radio1" name="group1"/>
+ <input type="radio" id="radio2" name="group1"/>
+ </form>
+
+ <input type="radio" id="radio3" name="group2"/>
+ <label><input type="radio" id="radio4" name="group2"/></label>
+
+ <form>
+ <input type="radio" style="display: none;" name="group3">
+ <input type="radio" id="radio5" name="group3">
+ <input type="radio" id="radio6" name="group4">
+ </form>
+
+ <input type="radio" id="radio7">`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within form
+ testGroupAttrs(getAcc("radio1"), 1, 2);
+ testGroupAttrs(getAcc("radio2"), 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" within document
+ testGroupAttrs(getAcc("radio3"), 1, 2);
+ // radio4 is wrapped in a label
+ testGroupAttrs(getAcc("radio4"), 2, 2);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // Hidden HTML input@type="radio"
+ testGroupAttrs(getAcc("radio5"), 1, 1);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" with different name but same parent
+ testGroupAttrs(getAcc("radio6"), 1, 1);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML input@type="radio" with no name
+ testGroupAttrs(getAcc("radio7"), 0, 0);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+/**
+ * lists
+ */
+addAccessibleTask(
+ `<ul id="ul">
+ <li id="li1">Oranges</li>
+ <li id="li2">Apples</li>
+ <li id="li3">Bananas</li>
+ </ul>
+
+ <ol id="ol">
+ <li id="li4">Oranges</li>
+ <li id="li5">Apples</li>
+ <li id="li6">Bananas
+ <ul id="ol_nested">
+ <li id="n_li4">Oranges</li>
+ <li id="n_li5">Apples</li>
+ <li id="n_li6">Bananas</li>
+ </ul>
+ </li>
+ </ol>
+
+ <span role="list" id="aria-list_1">
+ <span role="listitem" id="li7">Oranges</span>
+ <span role="listitem" id="li8">Apples</span>
+ <span role="listitem" id="li9">Bananas</span>
+ </span>
+
+ <span role="list" id="aria-list_2">
+ <span role="listitem" id="li10">Oranges</span>
+ <span role="listitem" id="li11">Apples</span>
+ <span role="listitem" id="li12">Bananas
+ <span role="list" id="aria-list_2_1">
+ <span role="listitem" id="n_li10">Oranges</span>
+ <span role="listitem" id="n_li11">Apples</span>
+ <span role="listitem" id="n_li12">Bananas</span>
+ </span>
+ </span>
+ </span>
+
+ <div role="list" id="aria-list_3">
+ <div role="listitem" id="lgt_li1">Item 1
+ <div role="group">
+ <div role="listitem" id="lgt_li1_nli1">Item 1A</div>
+ <div role="listitem" id="lgt_li1_nli2">Item 1B</div>
+ </div>
+ </div>
+ <div role="listitem" id="lgt_li2">Item 2
+ <div role="group">
+ <div role="listitem" id="lgt_li2_nli1">Item 2A</div>
+ <div role="listitem" id="lgt_li2_nli2">Item 2B</div>
+ </div>
+ </div>
+ </div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol
+ testGroupAttrs(getAcc("li1"), 1, 3);
+ testGroupAttrs(getAcc("li2"), 2, 3);
+ testGroupAttrs(getAcc("li3"), 3, 3);
+
+ // ul should have item count and not hierarchical
+ testGroupParentAttrs(getAcc("ul"), 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML ul/ol (nested lists)
+
+ testGroupAttrs(getAcc("li4"), 1, 3, 1);
+ testGroupAttrs(getAcc("li5"), 2, 3, 1);
+ testGroupAttrs(getAcc("li6"), 3, 3, 1);
+ // ol with nested list should have 1st level item count and be hierarchical
+ testGroupParentAttrs(getAcc("ol"), 3, true);
+
+ testGroupAttrs(getAcc("n_li4"), 1, 3, 2);
+ testGroupAttrs(getAcc("n_li5"), 2, 3, 2);
+ testGroupAttrs(getAcc("n_li6"), 3, 3, 2);
+ // nested ol should have item count and be hierarchical
+ testGroupParentAttrs(getAcc("ol_nested"), 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list
+ testGroupAttrs(getAcc("li7"), 1, 3);
+ testGroupAttrs(getAcc("li8"), 2, 3);
+ testGroupAttrs(getAcc("li9"), 3, 3);
+ // simple flat aria list
+ testGroupParentAttrs(getAcc("aria-list_1"), 3, false);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> list -> listitem)
+ testGroupAttrs(getAcc("li10"), 1, 3, 1);
+ testGroupAttrs(getAcc("li11"), 2, 3, 1);
+ testGroupAttrs(getAcc("li12"), 3, 3, 1);
+ // aria list with nested list
+ testGroupParentAttrs(getAcc("aria-list_2"), 3, true);
+
+ testGroupAttrs(getAcc("n_li10"), 1, 3, 2);
+ testGroupAttrs(getAcc("n_li11"), 2, 3, 2);
+ testGroupAttrs(getAcc("n_li12"), 3, 3, 2);
+ // nested aria list.
+ testGroupParentAttrs(getAcc("aria-list_2_1"), 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list (nested lists: list -> listitem -> group -> listitem)
+ testGroupAttrs(getAcc("lgt_li1"), 1, 2, 1);
+ testGroupAttrs(getAcc("lgt_li1_nli1"), 1, 2, 2);
+ testGroupAttrs(getAcc("lgt_li1_nli2"), 2, 2, 2);
+ testGroupAttrs(getAcc("lgt_li2"), 2, 2, 1);
+ testGroupAttrs(getAcc("lgt_li2_nli1"), 1, 2, 2);
+ testGroupAttrs(getAcc("lgt_li2_nli2"), 2, 2, 2);
+ // aria list with nested list
+ testGroupParentAttrs(getAcc("aria-list_3"), 2, true);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul role="menubar" id="menubar">
+ <li role="menuitem" aria-haspopup="true" id="menu_item1">File
+ <ul role="menu" id="menu">
+ <li role="menuitem" id="menu_item1.1">New</li>
+ <li role="menuitem" id="menu_item1.2">Open…</li>
+ <li role="separator">-----</li>
+ <li role="menuitem" id="menu_item1.3">Item</li>
+ <li role="menuitemradio" id="menu_item1.4">Radio</li>
+ <li role="menuitemcheckbox" id="menu_item1.5">Checkbox</li>
+ </ul>
+ </li>
+ <li role="menuitem" aria-haspopup="false" id="menu_item2">Help</li>
+ </ul>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA menu (menuitem, separator, menuitemradio and menuitemcheckbox)
+ testGroupAttrs(getAcc("menu_item1"), 1, 2);
+ testGroupAttrs(getAcc("menu_item2"), 2, 2);
+ testGroupAttrs(getAcc("menu_item1.1"), 1, 2);
+ testGroupAttrs(getAcc("menu_item1.2"), 2, 2);
+ testGroupAttrs(getAcc("menu_item1.3"), 1, 3);
+ testGroupAttrs(getAcc("menu_item1.4"), 2, 3);
+ testGroupAttrs(getAcc("menu_item1.5"), 3, 3);
+ // menu bar item count
+ testGroupParentAttrs(getAcc("menubar"), 2, false);
+ // Bug 1492529. Menu should have total number of items 5 from both sets,
+ // but only has the first 2 item set.
+ todoAttr(getAcc("menu"), "child-item-count", "5");
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul id="tablist_1" role="tablist">
+ <li id="tab_1" role="tab">Crust</li>
+ <li id="tab_2" role="tab">Veges</li>
+ <li id="tab_3" role="tab">Carnivore</li>
+ </ul>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tab
+ testGroupAttrs(getAcc("tab_1"), 1, 3);
+ testGroupAttrs(getAcc("tab_2"), 2, 3);
+ testGroupAttrs(getAcc("tab_3"), 3, 3);
+ // tab list tab count
+ testGroupParentAttrs(getAcc("tablist_1"), 3, false);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul id="rg1" role="radiogroup">
+ <li id="r1" role="radio" aria-checked="false">Thai</li>
+ <li id="r2" role="radio" aria-checked="false">Subway</li>
+ <li id="r3" role="radio" aria-checked="false">Jimmy Johns</li>
+ </ul>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA radio
+ testGroupAttrs(getAcc("r1"), 1, 3);
+ testGroupAttrs(getAcc("r2"), 2, 3);
+ testGroupAttrs(getAcc("r3"), 3, 3);
+ // explicit aria radio group
+ testGroupParentAttrs(getAcc("rg1"), 3, false);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<table role="tree" id="tree_1">
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="true" aria-level="1"
+ id="ti1">vegetables</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti2">cucumber</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti3">carrot</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-expanded="false" aria-level="1"
+ id="ti4">cars</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti5">mercedes</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti6">BMW</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="2" id="ti7">Audi</td>
+ </tr>
+ <tr role="presentation">
+ <td role="treeitem" aria-level="1" id="ti8">people</td>
+ </tr>
+ </table>
+
+ <ul role="tree" id="tree_2">
+ <li role="treeitem" id="tree2_ti1">Item 1
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree2_ti1b">Item 1B</li>
+ </ul>
+ </li>
+ <li role="treeitem" id="tree2_ti2">Item 2
+ <ul role="group">
+ <li role="treeitem" id="tree2_ti2a">Item 2A</li>
+ <li role="treeitem" id="tree2_ti2b">Item 2B</li>
+ </ul>
+ </li>
+ </div>
+
+ <div role="tree" id="tree_3">
+ <div role="treeitem" id="tree3_ti1">Item 1</div>
+ <div role="group">
+ <li role="treeitem" id="tree3_ti1a">Item 1A</li>
+ <li role="treeitem" id="tree3_ti1b">Item 1B</li>
+ </div>
+ <div role="treeitem" id="tree3_ti2">Item 2</div>
+ <div role="group">
+ <div role="treeitem" id="tree3_ti2a">Item 2A</div>
+ <div role="treeitem" id="tree3_ti2b">Item 2B</div>
+ </div>
+ </div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree
+ testGroupAttrs(getAcc("ti1"), 1, 3, 1);
+ testGroupAttrs(getAcc("ti2"), 1, 2, 2);
+ testGroupAttrs(getAcc("ti3"), 2, 2, 2);
+ testGroupAttrs(getAcc("ti4"), 2, 3, 1);
+ testGroupAttrs(getAcc("ti5"), 1, 3, 2);
+ testGroupAttrs(getAcc("ti6"), 2, 3, 2);
+ testGroupAttrs(getAcc("ti7"), 3, 3, 2);
+ testGroupAttrs(getAcc("ti8"), 3, 3, 1);
+ testGroupParentAttrs(getAcc("tree_1"), 3, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem -> group -> treeitem)
+ testGroupAttrs(getAcc("tree2_ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("tree2_ti1a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree2_ti1b"), 2, 2, 2);
+ testGroupAttrs(getAcc("tree2_ti2"), 2, 2, 1);
+ testGroupAttrs(getAcc("tree2_ti2a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree2_ti2b"), 2, 2, 2);
+ testGroupParentAttrs(getAcc("tree_2"), 2, true);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA tree (tree -> treeitem, group -> treeitem)
+ testGroupAttrs(getAcc("tree3_ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("tree3_ti1a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree3_ti1b"), 2, 2, 2);
+ testGroupAttrs(getAcc("tree3_ti2"), 2, 2, 1);
+ testGroupAttrs(getAcc("tree3_ti2a"), 1, 2, 2);
+ testGroupAttrs(getAcc("tree3_ti2b"), 2, 2, 2);
+ testGroupParentAttrs(getAcc("tree_3"), 2, true);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<table role="grid" id="grid">
+ <tr role="row" id="grid_row1">
+ <td role="gridcell" id="grid_cell1">cell1</td>
+ <td role="gridcell" id="grid_cell2">cell2</td>
+ </tr>
+ <tr role="row" id="grid_row2">
+ <td role="gridcell" id="grid_cell3">cell3</td>
+ <td role="gridcell" id="grid_cell4">cell4</td>
+ </tr>
+ </table>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA grid
+ testGroupAttrs(getAcc("grid_row1"), 1, 2);
+ testAbsentAttrs(getAcc("grid_cell1"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("grid_cell2"), { posinset: "", setsize: "" });
+
+ testGroupAttrs(getAcc("grid_row2"), 2, 2);
+ testAbsentAttrs(getAcc("grid_cell3"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("grid_cell4"), { posinset: "", setsize: "" });
+ testGroupParentAttrs(getAcc("grid"), 2, false, false);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="treegrid" id="treegrid" aria-colcount="4">
+ <div role="row" aria-level="1" id="treegrid_row1">
+ <div role="gridcell" id="treegrid_cell1">cell1</div>
+ <div role="gridcell" id="treegrid_cell2">cell2</div>
+ </div>
+ <div role="row" aria-level="2" id="treegrid_row2">
+ <div role="gridcell" id="treegrid_cell3">cell1</div>
+ <div role="gridcell" id="treegrid_cell4">cell2</div>
+ </div>
+ <div role="row" id="treegrid_row3">
+ <div role="gridcell" id="treegrid_cell5">cell1</div>
+ <div role="gridcell" id="treegrid_cell6">cell2</div>
+ </div>
+ </div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA treegrid
+ testGroupAttrs(getAcc("treegrid_row1"), 1, 2, 1);
+ testAbsentAttrs(getAcc("treegrid_cell1"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("treegrid_cell2"), { posinset: "", setsize: "" });
+
+ testGroupAttrs(getAcc("treegrid_row2"), 1, 1, 2);
+ testAbsentAttrs(getAcc("treegrid_cell3"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("treegrid_cell4"), { posinset: "", setsize: "" });
+
+ testGroupAttrs(getAcc("treegrid_row3"), 2, 2, 1);
+ testAbsentAttrs(getAcc("treegrid_cell5"), { posinset: "", setsize: "" });
+ testAbsentAttrs(getAcc("treegrid_cell6"), { posinset: "", setsize: "" });
+
+ testGroupParentAttrs(getAcc("treegrid"), 2, true);
+ // row child item count provided by parent grid's aria-colcount
+ testGroupParentAttrs(getAcc("treegrid_row1"), 4, false);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div id="headings">
+ <h1 id="h1">heading1</h1>
+ <h2 id="h2">heading2</h2>
+ <h3 id="h3">heading3</h3>
+ <h4 id="h4">heading4</h4>
+ <h5 id="h5">heading5</h5>
+ <h6 id="h6">heading6</h6>
+ <div id="ariaHeadingNoLevel" role="heading">ariaHeadingNoLevel</div>
+ </div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // HTML headings
+ testGroupAttrs(getAcc("h1"), 0, 0, 1);
+ testGroupAttrs(getAcc("h2"), 0, 0, 2);
+ testGroupAttrs(getAcc("h3"), 0, 0, 3);
+ testGroupAttrs(getAcc("h4"), 0, 0, 4);
+ testGroupAttrs(getAcc("h5"), 0, 0, 5);
+ testGroupAttrs(getAcc("h6"), 0, 0, 6);
+ testGroupAttrs(getAcc("ariaHeadingNoLevel"), 0, 0, 2);
+ // No child item counts or "tree" flag for parent of headings
+ testAbsentAttrs(getAcc("headings"), { "child-item-count": "", tree: "" });
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<ul id="combo1" role="combobox">Password
+ <li id="combo1_opt1" role="option">Xyzzy</li>
+ <li id="combo1_opt2" role="option">Plughs</li>
+ <li id="combo1_opt3" role="option">Shazaam</li>
+ <li id="combo1_opt4" role="option">JoeSentMe</li>
+ </ul>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA combobox
+ testGroupAttrs(getAcc("combo1_opt1"), 1, 4);
+ testGroupAttrs(getAcc("combo1_opt2"), 2, 4);
+ testGroupAttrs(getAcc("combo1_opt3"), 3, 4);
+ testGroupAttrs(getAcc("combo1_opt4"), 4, 4);
+ testGroupParentAttrs(getAcc("combo1"), 4, false);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="table" aria-colcount="4" aria-rowcount="2" id="table">
+ <div role="row" id="table_row" aria-rowindex="2">
+ <div role="cell" id="table_cell" aria-colindex="3">cell</div>
+ </div>
+ </div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA table
+ testGroupAttrs(getAcc("table_cell"), 3, 4);
+ testGroupAttrs(getAcc("table_row"), 2, 2);
+
+ // grid child item count provided by aria-rowcount
+ testGroupParentAttrs(getAcc("table"), 2, false);
+ // row child item count provided by parent grid's aria-colcount
+ testGroupParentAttrs(getAcc("table_row"), 4, false);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="grid" aria-readonly="true">
+ <div tabindex="-1">
+ <div role="row" id="wrapped_row_1">
+ <div role="gridcell">cell content</div>
+ </div>
+ </div>
+ <div tabindex="-1">
+ <div role="row" id="wrapped_row_2">
+ <div role="gridcell">cell content</div>
+ </div>
+ </div>
+ </div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // Attributes calculated even when row is wrapped in a div.
+ testGroupAttrs(getAcc("wrapped_row_1"), 1, 2, null);
+ testGroupAttrs(getAcc("wrapped_row_2"), 2, 2, null);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="list" aria-owns="t1_li1 t1_li2 t1_li3" id="aria-list_4">
+ <div role="listitem" id="t1_li2">Apples</div>
+ <div role="listitem" id="t1_li1">Oranges</div>
+ </div>
+ <div role="listitem" id="t1_li3">Bananas</div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // ////////////////////////////////////////////////////////////////////////
+ // ARIA list constructed by ARIA owns
+ testGroupAttrs(getAcc("t1_li1"), 1, 3);
+ testGroupAttrs(getAcc("t1_li2"), 2, 3);
+ testGroupAttrs(getAcc("t1_li3"), 3, 3);
+ testGroupParentAttrs(getAcc("aria-list_4"), 3, false);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<!-- ARIA comments, 1 level, group pos and size calculation -->
+ <article>
+ <p id="comm_single_1" role="comment">Comment 1</p>
+ <p id="comm_single_2" role="comment">Comment 2</p>
+ </article>
+
+ <!-- Nested comments -->
+ <article>
+ <div id="comm_nested_1" role="comment"><p>Comment 1 level 1</p>
+ <div id="comm_nested_1_1" role="comment"><p>Comment 1 level 2</p></div>
+ <div id="comm_nested_1_2" role="comment"><p>Comment 2 level 2</p></div>
+ </div>
+ <div id="comm_nested_2" role="comment"><p>Comment 2 level 1</p>
+ <div id="comm_nested_2_1" role="comment"><p>Comment 3 level 2</p>
+ <div id="comm_nested_2_1_1" role="comment"><p>Comment 1 level 3</p></div>
+ </div>
+ </div>
+ <div id="comm_nested_3" role="comment"><p>Comment 3 level 1</p></div>
+ </article>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // Test group attributes of ARIA comments
+ testGroupAttrs(getAcc("comm_single_1"), 1, 2, 1);
+ testGroupAttrs(getAcc("comm_single_2"), 2, 2, 1);
+ testGroupAttrs(getAcc("comm_nested_1"), 1, 3, 1);
+ testGroupAttrs(getAcc("comm_nested_1_1"), 1, 2, 2);
+ testGroupAttrs(getAcc("comm_nested_1_2"), 2, 2, 2);
+ testGroupAttrs(getAcc("comm_nested_2"), 2, 3, 1);
+ testGroupAttrs(getAcc("comm_nested_2_1"), 1, 1, 2);
+ testGroupAttrs(getAcc("comm_nested_2_1_1"), 1, 1, 3);
+ testGroupAttrs(getAcc("comm_nested_3"), 3, 3, 1);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+addAccessibleTask(
+ `<div role="tree" id="tree4"><div role="treeitem"
+ id="tree4_ti1">Item 1</div><div role="treeitem"
+ id="tree4_ti2">Item 2</div></div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ // Test that group position information updates after deleting node.
+ testGroupAttrs(getAcc("tree4_ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("tree4_ti2"), 2, 2, 1);
+ testGroupParentAttrs(getAcc("tree4"), 2, true);
+
+ let p = waitForEvent(EVENT_REORDER, "tree4");
+ invokeContentTask(browser, [], () => {
+ content.document.getElementById("tree4_ti1").remove();
+ });
+
+ await p;
+ testGroupAttrs(getAcc("tree4_ti2"), 1, 1, 1);
+ testGroupParentAttrs(getAcc("tree4"), 1, true);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+// Verify that intervening SECTION accs in ARIA compound widgets do not split
+// up the group info for descendant owned elements. Test various types of
+// widgets that should all be treated the same.
+addAccessibleTask(
+ `<div role="tree" id="tree">
+ <div tabindex="0">
+ <div role="treeitem" id="ti1">treeitem 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="treeitem" id="ti2">treeitem 2</div>
+ </div>
+ </div>
+ <div role="listbox" id="listbox">
+ <div tabindex="0">
+ <div role="option" id="opt1">option 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="option" id="opt2">option 2</div>
+ </div>
+ </div>
+ <div role="list" id="list">
+ <div tabindex="0">
+ <div role="listitem" id="li1">listitem 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="listitem" id="li2">listitem 2</div>
+ </div>
+ </div>
+ <div role="menu" id="menu">
+ <div tabindex="0">
+ <div role="menuitem" id="mi1">menuitem 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="menuitem" id="mi2">menuitem 2</div>
+ </div>
+ </div>
+ <div role="radiogroup" id="radiogroup">
+ <div tabindex="0">
+ <div role="radio" id="r1">radio 1</div>
+ </div>
+ <div tabindex="0">
+ <div role="radio" id="r2">radio 2</div>
+ </div>
+ </div>
+`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ testGroupAttrs(getAcc("ti1"), 1, 2, 1);
+ testGroupAttrs(getAcc("ti2"), 2, 2, 1);
+
+ testGroupAttrs(getAcc("opt1"), 1, 2, 0);
+ testGroupAttrs(getAcc("opt2"), 2, 2, 0);
+
+ testGroupAttrs(getAcc("li1"), 1, 2, 0);
+ testGroupAttrs(getAcc("li2"), 2, 2, 0);
+
+ testGroupAttrs(getAcc("mi1"), 1, 2, 0);
+ testGroupAttrs(getAcc("mi2"), 2, 2, 0);
+
+ testGroupAttrs(getAcc("r1"), 1, 2, 0);
+ testGroupAttrs(getAcc("r2"), 2, 2, 0);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
+
+// Verify that non-generic accessibles (like buttons) correctly split the group
+// info of descendant owned elements.
+addAccessibleTask(
+ `<div role="tree" id="tree">
+ <div role="button">
+ <div role="treeitem" id="ti1">first</div>
+ </div>
+ <div tabindex="0">
+ <div role="treeitem" id="ti2">second</div>
+ </div>
+ </div>`,
+ async function(browser, accDoc) {
+ let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+ testGroupAttrs(getAcc("ti1"), 1, 1, 1);
+ testGroupAttrs(getAcc("ti2"), 1, 1, 1);
+ },
+ {
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ chrome: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_text.js b/accessible/tests/browser/e10s/browser_text.js
new file mode 100644
index 0000000000..0971c493dc
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_text.js
@@ -0,0 +1,369 @@
+/* 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/text.js */
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts(
+ { name: "text.js", dir: MOCHITESTS_DIR },
+ { name: "attributes.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test line and word offsets for various cases for both local and remote
+ * Accessibles. There is more extensive coverage in ../../mochitest/text. These
+ * tests don't need to duplicate all of that, since much of the underlying code
+ * is unified. They should ensure that the cache works as expected and that
+ * there is consistency between local and remote.
+ */
+addAccessibleTask(
+ `
+<p id="br">ab cd<br>ef gh</p>
+<pre id="pre">ab cd
+ef gh</pre>
+<p id="linksStartEnd"><a href="https://example.com/">a</a>b<a href="https://example.com/">c</a></p>
+<p id="linksBreaking">a<a href="https://example.com/">b<br>c</a>d</p>
+<p id="p">a<br role="presentation">b</p>
+<p id="leafThenWrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span>a</span>bc</p>
+ `,
+ async function(browser, docAcc) {
+ for (const id of ["br", "pre"]) {
+ const acc = findAccessibleChildByID(docAcc, id);
+ if (isWinNoCache) {
+ todo(
+ false,
+ "Cache disabled, so RemoteAccessible doesn't support CharacterCount on Windows"
+ );
+ } else {
+ testCharacterCount([acc], 11);
+ }
+ testTextAtOffset(acc, BOUNDARY_LINE_START, [
+ [0, 5, "ab cd\n", 0, 6],
+ [6, 11, "ef gh", 6, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_LINE_START, [
+ [0, 5, "", 0, 0],
+ [6, 11, "ab cd\n", 0, 6],
+ ]);
+ testTextAfterOffset(acc, BOUNDARY_LINE_START, [
+ [0, 5, "ef gh", 6, 11],
+ [6, 11, "", 11, 11],
+ ]);
+ if (isWinNoCache) {
+ todo(
+ false,
+ "Cache disabled, so RemoteAccessible doesn't support BOUNDARY_LINE_END on Windows"
+ );
+ } else {
+ testTextAtOffset(acc, BOUNDARY_LINE_END, [
+ [0, 5, "ab cd", 0, 5],
+ [6, 11, "\nef gh", 5, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_LINE_END, [
+ [0, 5, "", 0, 0],
+ [6, 11, "ab cd", 0, 5],
+ ]);
+ testTextAfterOffset(acc, BOUNDARY_LINE_END, [
+ [0, 5, "\nef gh", 5, 11],
+ [6, 11, "", 11, 11],
+ ]);
+ }
+ testTextAtOffset(acc, BOUNDARY_WORD_START, [
+ [0, 2, "ab ", 0, 3],
+ [3, 5, "cd\n", 3, 6],
+ [6, 8, "ef ", 6, 9],
+ [9, 11, "gh", 9, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_WORD_START, [
+ [0, 2, "", 0, 0],
+ [3, 5, "ab ", 0, 3],
+ [6, 8, "cd\n", 3, 6],
+ [9, 11, "ef ", 6, 9],
+ ]);
+ testTextAfterOffset(acc, BOUNDARY_WORD_START, [
+ [0, 2, "cd\n", 3, 6],
+ [3, 5, "ef ", 6, 9],
+ [6, 8, "gh", 9, 11],
+ [9, 11, "", 11, 11],
+ ]);
+ if (isWinNoCache) {
+ todo(
+ false,
+ "Cache disabled, so RemoteAccessible doesn't support BOUNDARY_WORD_END on Windows"
+ );
+ } else {
+ testTextAtOffset(acc, BOUNDARY_WORD_END, [
+ [0, 1, "ab", 0, 2],
+ [2, 4, " cd", 2, 5],
+ [5, 7, "\nef", 5, 8],
+ [8, 11, " gh", 8, 11],
+ ]);
+ testTextBeforeOffset(acc, BOUNDARY_WORD_END, [
+ [0, 2, "", 0, 0],
+ [3, 5, "ab", 0, 2],
+ // See below for offset 6.
+ [7, 8, " cd", 2, 5],
+ [9, 11, "\nef", 5, 8],
+ ]);
+ if (id == "br" && !isCacheEnabled) {
+ todo(
+ false,
+ "Cache disabled, so TextBeforeOffset BOUNDARY_WORD_END returns incorrect result after br"
+ );
+ } else {
+ testTextBeforeOffset(acc, BOUNDARY_WORD_END, [[6, 6, " cd", 2, 5]]);
+ }
+ testTextAfterOffset(acc, BOUNDARY_WORD_END, [
+ [0, 2, " cd", 2, 5],
+ [3, 5, "\nef", 5, 8],
+ [6, 8, " gh", 8, 11],
+ [9, 11, "", 11, 11],
+ ]);
+ }
+ testTextAtOffset(acc, BOUNDARY_PARAGRAPH, [
+ [0, 5, "ab cd\n", 0, 6],
+ [6, 11, "ef gh", 6, 11],
+ ]);
+ }
+ const linksStartEnd = findAccessibleChildByID(docAcc, "linksStartEnd");
+ testTextAtOffset(linksStartEnd, BOUNDARY_LINE_START, [
+ [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
+ ]);
+ testTextAtOffset(linksStartEnd, BOUNDARY_WORD_START, [
+ [0, 3, `${kEmbedChar}b${kEmbedChar}`, 0, 3],
+ ]);
+ const linksBreaking = findAccessibleChildByID(docAcc, "linksBreaking");
+ testTextAtOffset(linksBreaking, BOUNDARY_LINE_START, [
+ [0, 0, `a${kEmbedChar}`, 0, 2],
+ [1, 1, `a${kEmbedChar}d`, 0, 3],
+ [2, 3, `${kEmbedChar}d`, 1, 3],
+ ]);
+ if (isCacheEnabled) {
+ testTextAtOffset(linksBreaking, BOUNDARY_WORD_START, [
+ [0, 0, `a${kEmbedChar}`, 0, 2],
+ [1, 1, `a${kEmbedChar}d`, 0, 3],
+ [2, 3, `${kEmbedChar}d`, 1, 3],
+ ]);
+ } else {
+ todo(
+ false,
+ "TextLeafPoint disabled, so word boundaries are incorrect for linksBreaking"
+ );
+ }
+ const p = findAccessibleChildByID(docAcc, "p");
+ testTextAtOffset(p, BOUNDARY_LINE_START, [
+ [0, 0, "a", 0, 1],
+ [1, 2, "b", 1, 2],
+ ]);
+ testTextAtOffset(p, BOUNDARY_PARAGRAPH, [[0, 2, "ab", 0, 2]]);
+ const leafThenWrap = findAccessibleChildByID(docAcc, "leafThenWrap");
+ testTextAtOffset(leafThenWrap, BOUNDARY_LINE_START, [
+ [0, 1, "ab", 0, 2],
+ [2, 3, "c", 2, 3],
+ ]);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test line offsets after text mutation.
+ */
+addAccessibleTask(
+ `
+<p id="initBr"><br></p>
+<p id="rewrap" style="font-family: monospace; width: 2ch; word-break: break-word;"><span id="rewrap1">ac</span>def</p>
+ `,
+ async function(browser, docAcc) {
+ const initBr = findAccessibleChildByID(docAcc, "initBr");
+ testTextAtOffset(initBr, BOUNDARY_LINE_START, [
+ [0, 0, "\n", 0, 1],
+ [1, 1, "", 1, 1],
+ ]);
+ info("initBr: Inserting text before br");
+ let reordered = waitForEvent(EVENT_REORDER, initBr);
+ await invokeContentTask(browser, [], () => {
+ const initBrNode = content.document.getElementById("initBr");
+ initBrNode.insertBefore(
+ content.document.createTextNode("a"),
+ initBrNode.firstElementChild
+ );
+ });
+ await reordered;
+ testTextAtOffset(initBr, BOUNDARY_LINE_START, [
+ [0, 1, "a\n", 0, 2],
+ [2, 2, "", 2, 2],
+ ]);
+
+ const rewrap = findAccessibleChildByID(docAcc, "rewrap");
+ testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
+ [0, 1, "ac", 0, 2],
+ [2, 3, "de", 2, 4],
+ [4, 5, "f", 4, 5],
+ ]);
+ info("rewrap: Changing ac to abc");
+ reordered = waitForEvent(EVENT_REORDER, rewrap);
+ await invokeContentTask(browser, [], () => {
+ const rewrap1 = content.document.getElementById("rewrap1");
+ rewrap1.textContent = "abc";
+ });
+ await reordered;
+ testTextAtOffset(rewrap, BOUNDARY_LINE_START, [
+ [0, 1, "ab", 0, 2],
+ [2, 3, "cd", 2, 4],
+ [4, 6, "ef", 4, 6],
+ ]);
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test retrieval of text offsets when an invalid offset is given.
+ */
+addAccessibleTask(
+ `<p id="p">test</p>`,
+ async function(browser, docAcc) {
+ const p = findAccessibleChildByID(docAcc, "p");
+ testTextAtOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
+ testTextBeforeOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
+ testTextAfterOffset(p, BOUNDARY_LINE_START, [[5, 5, "", 0, 0]]);
+ },
+ {
+ // The old HyperTextAccessible implementation doesn't crash, but it returns
+ // different offsets. This doesn't matter because they're invalid either
+ // way. Since the new HyperTextAccessibleBase implementation is all we will
+ // have soon, just test that.
+ chrome: isCacheEnabled,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
+
+/**
+ * Test HyperText embedded object methods.
+ */
+addAccessibleTask(
+ `<div id="container">a<a id="link" href="https://example.com/">b</a>c</div>`,
+ async function(browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "container", [
+ nsIAccessibleHyperText,
+ ]);
+ is(container.linkCount, 1, "container linkCount is 1");
+ let link = container.getLinkAt(0);
+ queryInterfaces(link, [nsIAccessible, nsIAccessibleHyperText]);
+ is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
+ is(container.getLinkIndex(link), 0, "getLinkIndex for link is 0");
+ is(link.startIndex, 1, "link's startIndex is 1");
+ is(link.endIndex, 2, "link's endIndex is 2");
+ is(container.getLinkIndexAtOffset(1), 0, "getLinkIndexAtOffset(1) is 0");
+ is(container.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
+ is(link.linkCount, 0, "link linkCount is 0");
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Test HyperText embedded object methods near a list bullet.
+ */
+addAccessibleTask(
+ `<ul><li id="li"><a id="link" href="https://example.com/">a</a></li></ul>`,
+ async function(browser, docAcc) {
+ const li = findAccessibleChildByID(docAcc, "li", [nsIAccessibleHyperText]);
+ let link = li.getLinkAt(0);
+ queryInterfaces(link, [nsIAccessible]);
+ is(getAccessibleDOMNodeID(link), "link", "LinkAt 0 is the link");
+ is(li.getLinkIndex(link), 0, "getLinkIndex for link is 0");
+ is(link.startIndex, 2, "link's startIndex is 2");
+ is(li.getLinkIndexAtOffset(2), 0, "getLinkIndexAtOffset(2) is 0");
+ is(li.getLinkIndexAtOffset(0), -1, "getLinkIndexAtOffset(0) is -1");
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ }
+);
+
+const boldAttrs = { "font-weight": "700" };
+
+/**
+ * Test text attribute methods.
+ */
+addAccessibleTask(
+ `
+<p id="plain">ab</p>
+<p id="bold" style="font-weight: bold;">ab</p>
+<p id="partialBold">ab<b>cd</b>ef</p>
+<p id="consecutiveBold">ab<b>cd</b><b>ef</b>gh</p>
+<p id="embeddedObjs">ab<a href="https://example.com/">cd</a><a href="https://example.com/">ef</a><a href="https://example.com/">gh</a>ij</p>
+<p id="empty"></p>
+<p id="fontFamilies" style="font-family: sans-serif;">ab<span style="font-family: monospace;">cd</span><span style="font-family: monospace;">ef</span>gh</p>
+ `,
+ async function(browser, docAcc) {
+ let defAttrs = {
+ "text-position": "baseline",
+ "font-style": "normal",
+ "font-weight": "400",
+ };
+
+ const plain = findAccessibleChildByID(docAcc, "plain");
+ testDefaultTextAttrs(plain, defAttrs, true);
+ for (let offset = 0; offset <= 2; ++offset) {
+ testTextAttrs(plain, offset, {}, defAttrs, 0, 2, true);
+ }
+
+ const bold = findAccessibleChildByID(docAcc, "bold");
+ defAttrs["font-weight"] = "700";
+ testDefaultTextAttrs(bold, defAttrs, true);
+ testTextAttrs(bold, 0, {}, defAttrs, 0, 2, true);
+
+ const partialBold = findAccessibleChildByID(docAcc, "partialBold");
+ defAttrs["font-weight"] = "400";
+ testDefaultTextAttrs(partialBold, defAttrs, true);
+ testTextAttrs(partialBold, 0, {}, defAttrs, 0, 2, true);
+ testTextAttrs(partialBold, 2, boldAttrs, defAttrs, 2, 4, true);
+ testTextAttrs(partialBold, 4, {}, defAttrs, 4, 6, true);
+
+ const consecutiveBold = findAccessibleChildByID(docAcc, "consecutiveBold");
+ testDefaultTextAttrs(consecutiveBold, defAttrs, true);
+ testTextAttrs(consecutiveBold, 0, {}, defAttrs, 0, 2, true);
+ testTextAttrs(consecutiveBold, 2, boldAttrs, defAttrs, 2, 6, true);
+ testTextAttrs(consecutiveBold, 6, {}, defAttrs, 6, 8, true);
+
+ const embeddedObjs = findAccessibleChildByID(docAcc, "embeddedObjs");
+ testDefaultTextAttrs(embeddedObjs, defAttrs, true);
+ testTextAttrs(embeddedObjs, 0, {}, defAttrs, 0, 2, true);
+ for (let offset = 2; offset <= 4; ++offset) {
+ // attrs and defAttrs should be completely empty, so we pass
+ // false for aSkipUnexpectedAttrs.
+ testTextAttrs(embeddedObjs, offset, {}, {}, 2, 5, false);
+ }
+ testTextAttrs(embeddedObjs, 5, {}, defAttrs, 5, 7, true);
+
+ const empty = findAccessibleChildByID(docAcc, "empty");
+ testDefaultTextAttrs(empty, defAttrs, true);
+ testTextAttrs(empty, 0, {}, defAttrs, 0, 0, true);
+
+ const fontFamilies = findAccessibleChildByID(docAcc, "fontFamilies", [
+ nsIAccessibleHyperText,
+ ]);
+ testDefaultTextAttrs(fontFamilies, defAttrs, true);
+ testTextAttrs(fontFamilies, 0, {}, defAttrs, 0, 2, true);
+ testTextAttrs(fontFamilies, 2, {}, defAttrs, 2, 6, true);
+ testTextAttrs(fontFamilies, 6, {}, defAttrs, 6, 8, true);
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_text_caret.js b/accessible/tests/browser/e10s/browser_text_caret.js
new file mode 100644
index 0000000000..b2e032a0b0
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_text_caret.js
@@ -0,0 +1,453 @@
+/* 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/text.js */
+loadScripts({ name: "text.js", dir: MOCHITESTS_DIR });
+
+/**
+ * Test caret retrieval.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea"
+ spellcheck="false"
+ style="scrollbar-width: none; font-family: 'Liberation Mono', monospace;"
+ cols="6">ab cd e</textarea>
+<textarea id="empty"></textarea>
+ `,
+ async function(browser, docAcc) {
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.takeFocus();
+ let evt = await caretMoved;
+ is(textarea.caretOffset, 0, "Initial caret offset is 0");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "a",
+ 0,
+ 1,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "ab ",
+ 0,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 1, "Caret offset is 1 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "b",
+ 1,
+ 2,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "ab ",
+ 0,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 2, "Caret offset is 2 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ " ",
+ 2,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "ab ",
+ 0,
+ 3,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 3, "Caret offset is 3 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "c",
+ 3,
+ 4,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 4, "Caret offset is 4 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "d",
+ 4,
+ 5,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 5, "Caret offset is 5 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ " ",
+ 5,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 6, "Caret offset is 6 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(evt.isAtEndOfLine, "Caret is at end of line");
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "",
+ 6,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "cd ",
+ 3,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "ab cd ",
+ 0,
+ 6,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 6, "Caret offset remains 6 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ // Caret is at start of second line.
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ evt = await caretMoved;
+ is(textarea.caretOffset, 7, "Caret offset is 7 after ArrowRight");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(evt.isAtEndOfLine, "Caret is at end of line");
+ // Caret is at end of textarea.
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_CHAR,
+ "",
+ 7,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_WORD_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+ testTextAtOffset(
+ kCaretOffset,
+ BOUNDARY_LINE_START,
+ "e",
+ 6,
+ 7,
+ textarea,
+ kOk,
+ kOk,
+ kOk
+ );
+
+ const empty = findAccessibleChildByID(docAcc, "empty", [nsIAccessibleText]);
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, empty);
+ empty.takeFocus();
+ evt = await caretMoved;
+ is(empty.caretOffset, 0, "Caret offset in empty textarea is 0");
+ evt.QueryInterface(nsIAccessibleCaretMoveEvent);
+ ok(!evt.isAtEndOfLine, "Caret is not at end of line");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test setting the caret.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea">ab\nc</textarea>
+<div id="editable" contenteditable>
+ <p id="p">a<a id="link" href="https://example.com/">b</a></p>
+</div>
+ `,
+ async function(browser, docAcc) {
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ info("textarea: Set caret offset to 0");
+ let focused = waitForEvent(EVENT_FOCUS, textarea);
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.caretOffset = 0;
+ await focused;
+ await caretMoved;
+ is(textarea.caretOffset, 0, "textarea caret correct");
+ // Test setting caret to another line.
+ info("textarea: Set caret offset to 3");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.caretOffset = 3;
+ await caretMoved;
+ is(textarea.caretOffset, 3, "textarea caret correct");
+ // Test setting caret to the end.
+ info("textarea: Set caret offset to 4 (end)");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.caretOffset = 4;
+ await caretMoved;
+ is(textarea.caretOffset, 4, "textarea caret correct");
+
+ const editable = findAccessibleChildByID(docAcc, "editable", [
+ nsIAccessibleText,
+ ]);
+ focused = waitForEvent(EVENT_FOCUS, editable);
+ editable.takeFocus();
+ await focused;
+ const p = findAccessibleChildByID(docAcc, "p", [nsIAccessibleText]);
+ info("p: Set caret offset to 0");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p);
+ p.caretOffset = 0;
+ await focused;
+ await caretMoved;
+ is(p.caretOffset, 0, "p caret correct");
+ const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
+ info("link: Set caret offset to 0");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, link);
+ link.caretOffset = 0;
+ await caretMoved;
+ is(link.caretOffset, 0, "link caret correct");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_text_paragraph_boundary.js b/accessible/tests/browser/e10s/browser_text_paragraph_boundary.js
new file mode 100644
index 0000000000..04e64520e8
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_text_paragraph_boundary.js
@@ -0,0 +1,22 @@
+/* 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";
+
+// Test that we don't crash the parent process when querying the paragraph
+// boundary on an Accessible which has remote ProxyAccessible descendants.
+addAccessibleTask(
+ `test`,
+ async function testParagraphBoundaryWithRemoteDescendants(browser, accDoc) {
+ const root = getRootAccessible(document).QueryInterface(
+ Ci.nsIAccessibleText
+ );
+ let start = {};
+ let end = {};
+ // The offsets will change as the Firefox UI changes. We don't really care
+ // what they are, just that we don't crash.
+ root.getTextAtOffset(0, nsIAccessibleText.BOUNDARY_PARAGRAPH, start, end);
+ ok(true, "Getting paragraph boundary succeeded");
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_text_selection.js b/accessible/tests/browser/e10s/browser_text_selection.js
new file mode 100644
index 0000000000..fc0529b07e
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_text_selection.js
@@ -0,0 +1,312 @@
+/* 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/text.js */
+loadScripts({ name: "text.js", dir: MOCHITESTS_DIR });
+
+function waitForSelectionChange(selectionAcc, caretAcc) {
+ if (!caretAcc) {
+ caretAcc = selectionAcc;
+ }
+ return waitForEvents(
+ [
+ [EVENT_TEXT_SELECTION_CHANGED, selectionAcc],
+ // We must swallow the caret events as well to avoid confusion with later,
+ // unrelated caret events.
+ [EVENT_TEXT_CARET_MOVED, caretAcc],
+ ],
+ true
+ );
+}
+
+function changeDomSelection(
+ browser,
+ anchorId,
+ anchorOffset,
+ focusId,
+ focusOffset
+) {
+ return invokeContentTask(
+ browser,
+ [anchorId, anchorOffset, focusId, focusOffset],
+ (
+ contentAnchorId,
+ contentAnchorOffset,
+ contentFocusId,
+ contentFocusOffset
+ ) => {
+ // We want the text node, so we use firstChild.
+ content.window
+ .getSelection()
+ .setBaseAndExtent(
+ content.document.getElementById(contentAnchorId).firstChild,
+ contentAnchorOffset,
+ content.document.getElementById(contentFocusId).firstChild,
+ contentFocusOffset
+ );
+ }
+ );
+}
+
+function testSelectionRange(
+ browser,
+ root,
+ startContainer,
+ startOffset,
+ endContainer,
+ endOffset
+) {
+ if (browser.isRemoteBrowser && !isCacheEnabled) {
+ todo(
+ false,
+ "selectionRanges not implemented for non-cached RemoteAccessible"
+ );
+ return;
+ }
+ let selRange = root.selectionRanges.queryElementAt(0, nsIAccessibleTextRange);
+ testTextRange(
+ selRange,
+ getAccessibleDOMNodeID(root),
+ startContainer,
+ startOffset,
+ endContainer,
+ endOffset
+ );
+}
+
+/**
+ * Test text selection.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea">ab</textarea>
+<div id="editable" contenteditable>
+ <p id="p1">a</p>
+ <p id="p2">bc</p>
+ <p id="pWithLink">d<a id="link" href="https://example.com/">e</a><span id="textAfterLink">f</span></p>
+</div>
+ `,
+ async function(browser, docAcc) {
+ queryInterfaces(docAcc, [nsIAccessibleText]);
+
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing textarea");
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ textarea.takeFocus();
+ await caretMoved;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 0);
+ is(textarea.selectionCount, 0, "textarea selectionCount is 0");
+ is(docAcc.selectionCount, 0, "document selectionCount is 0");
+
+ info("Selecting a in textarea");
+ let selChanged = waitForSelectionChange(textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ await selChanged;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 1);
+ testTextGetSelection(textarea, 0, 1, 0);
+
+ info("Selecting b in textarea");
+ selChanged = waitForSelectionChange(textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ await selChanged;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 2);
+ testTextGetSelection(textarea, 0, 2, 0);
+
+ info("Unselecting b in textarea");
+ selChanged = waitForSelectionChange(textarea);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ await selChanged;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 1);
+ testTextGetSelection(textarea, 0, 1, 0);
+
+ info("Unselecting a in textarea");
+ // We don't fire selection changed when the selection collapses.
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ await caretMoved;
+ testSelectionRange(browser, textarea, textarea, 0, textarea, 0);
+ is(textarea.selectionCount, 0, "textarea selectionCount is 0");
+
+ const editable = findAccessibleChildByID(docAcc, "editable", [
+ nsIAccessibleText,
+ ]);
+ const p1 = findAccessibleChildByID(docAcc, "p1", [nsIAccessibleText]);
+ info("Focusing editable, caret to start");
+ caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, p1);
+ await changeDomSelection(browser, "p1", 0, "p1", 0);
+ await caretMoved;
+ testSelectionRange(browser, editable, p1, 0, p1, 0);
+ is(editable.selectionCount, 0, "editable selectionCount is 0");
+ is(p1.selectionCount, 0, "p1 selectionCount is 0");
+ is(docAcc.selectionCount, 0, "document selectionCount is 0");
+
+ info("Selecting a in editable");
+ selChanged = waitForSelectionChange(p1);
+ await changeDomSelection(browser, "p1", 0, "p1", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, p1, 0, p1, 1);
+ testTextGetSelection(editable, 0, 1, 0);
+ testTextGetSelection(p1, 0, 1, 0);
+ const p2 = findAccessibleChildByID(docAcc, "p2", [nsIAccessibleText]);
+ if (isCacheEnabled && browser.isRemoteBrowser) {
+ is(p2.selectionCount, 0, "p2 selectionCount is 0");
+ } else {
+ todo(
+ false,
+ "Siblings report wrong selection in non-cache implementation"
+ );
+ }
+
+ // Selecting across two Accessibles with only a partial selection in the
+ // second.
+ info("Selecting ab in editable");
+ selChanged = waitForSelectionChange(editable, p2);
+ await changeDomSelection(browser, "p1", 0, "p2", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, p1, 0, p2, 1);
+ testTextGetSelection(editable, 0, 2, 0);
+ testTextGetSelection(p1, 0, 1, 0);
+ testTextGetSelection(p2, 0, 1, 0);
+
+ const pWithLink = findAccessibleChildByID(docAcc, "pWithLink", [
+ nsIAccessibleText,
+ ]);
+ const link = findAccessibleChildByID(docAcc, "link", [nsIAccessibleText]);
+ // Selecting both text and a link.
+ info("Selecting de in editable");
+ selChanged = waitForSelectionChange(pWithLink, link);
+ await changeDomSelection(browser, "pWithLink", 0, "link", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, pWithLink, 0, link, 1);
+ testTextGetSelection(editable, 2, 3, 0);
+ testTextGetSelection(pWithLink, 0, 2, 0);
+ testTextGetSelection(link, 0, 1, 0);
+
+ // Selecting a link and text on either side.
+ info("Selecting def in editable");
+ selChanged = waitForSelectionChange(pWithLink, pWithLink);
+ await changeDomSelection(browser, "pWithLink", 0, "textAfterLink", 1);
+ await selChanged;
+ testSelectionRange(browser, editable, pWithLink, 0, pWithLink, 3);
+ testTextGetSelection(editable, 2, 3, 0);
+ testTextGetSelection(pWithLink, 0, 3, 0);
+ testTextGetSelection(link, 0, 1, 0);
+
+ // Noncontiguous selection.
+ info("Selecting a in editable");
+ selChanged = waitForSelectionChange(p1);
+ await changeDomSelection(browser, "p1", 0, "p1", 1);
+ await selChanged;
+ info("Adding c to selection in editable");
+ selChanged = waitForSelectionChange(p2);
+ await invokeContentTask(browser, [], () => {
+ const r = content.document.createRange();
+ const p2text = content.document.getElementById("p2").firstChild;
+ r.setStart(p2text, 0);
+ r.setEnd(p2text, 1);
+ content.window.getSelection().addRange(r);
+ });
+ await selChanged;
+ if (browser.isRemoteBrowser && !isCacheEnabled) {
+ todo(
+ false,
+ "selectionRanges not implemented for non-cached RemoteAccessible"
+ );
+ } else {
+ let selRanges = editable.selectionRanges;
+ is(selRanges.length, 2, "2 selection ranges");
+ testTextRange(
+ selRanges.queryElementAt(0, nsIAccessibleTextRange),
+ "range 0",
+ p1,
+ 0,
+ p1,
+ 1
+ );
+ testTextRange(
+ selRanges.queryElementAt(1, nsIAccessibleTextRange),
+ "range 1",
+ p2,
+ 0,
+ p2,
+ 1
+ );
+ }
+ is(editable.selectionCount, 2, "editable selectionCount is 2");
+ testTextGetSelection(editable, 0, 1, 0);
+ testTextGetSelection(editable, 1, 2, 1);
+ if (isCacheEnabled && browser.isRemoteBrowser) {
+ is(p1.selectionCount, 1, "p1 selectionCount is 1");
+ testTextGetSelection(p1, 0, 1, 0);
+ is(p2.selectionCount, 1, "p2 selectionCount is 1");
+ testTextGetSelection(p2, 0, 1, 0);
+ } else {
+ todo(
+ false,
+ "Siblings report wrong selection in non-cache implementation"
+ );
+ }
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ }
+);
+
+/**
+ * Tabbing to an input selects all its text. Test that the cached selection
+ *reflects this. This has to be done separately from the other selection tests
+ * because prior contentEditable selection changes the events that get fired.
+ */
+addAccessibleTask(
+ `
+<button id="before">Before</button>
+<input id="input" value="test">
+ `,
+ async function(browser, docAcc) {
+ // The tab order is different when there's an iframe, so focus a control
+ // before the input to make tab consistent.
+ info("Focusing before");
+ const before = findAccessibleChildByID(docAcc, "before");
+ // Focusing a button fires a selection event. We must swallow this to
+ // avoid confusing the later test.
+ let events = waitForOrderedEvents([
+ [EVENT_FOCUS, before],
+ [EVENT_TEXT_SELECTION_CHANGED, docAcc],
+ ]);
+ before.takeFocus();
+ await events;
+
+ const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]);
+ info("Tabbing to input");
+ events = waitForEvents(
+ {
+ expected: [
+ [EVENT_FOCUS, input],
+ [EVENT_TEXT_SELECTION_CHANGED, input],
+ ],
+ unexpected: [[EVENT_TEXT_SELECTION_CHANGED, docAcc]],
+ },
+ "input",
+ false,
+ (args, task) => invokeContentTask(browser, args, task)
+ );
+ EventUtils.synthesizeKey("KEY_Tab");
+ await events;
+ testSelectionRange(browser, input, input, 0, input, 4);
+ testTextGetSelection(input, 0, 4, 0);
+ },
+ {
+ chrome: true,
+ topLevel: !isWinNoCache,
+ iframe: !isWinNoCache,
+ remoteIframe: !isWinNoCache,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_text_spelling.js b/accessible/tests/browser/e10s/browser_text_spelling.js
new file mode 100644
index 0000000000..a228796e3d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_text_spelling.js
@@ -0,0 +1,151 @@
+/* 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/text.js */
+/* import-globals-from ../../mochitest/attributes.js */
+loadScripts(
+ { name: "text.js", dir: MOCHITESTS_DIR },
+ { name: "attributes.js", dir: MOCHITESTS_DIR }
+);
+
+const boldAttrs = { "font-weight": "700" };
+
+/*
+ * Given a text accessible and a list of ranges
+ * check if those ranges match the misspelled ranges in the accessible.
+ */
+function misspelledRangesMatch(acc, ranges) {
+ let offset = 0;
+ let expectedRanges = [...ranges];
+ let charCount = acc.characterCount;
+ while (offset < charCount) {
+ let start = {};
+ let end = {};
+ let attributes = acc.getTextAttributes(false, offset, start, end);
+ offset = end.value;
+ try {
+ if (attributes.getStringProperty("invalid") == "spelling") {
+ let expected = expectedRanges.shift();
+ if (
+ !expected ||
+ expected[0] != start.value ||
+ expected[1] != end.value
+ ) {
+ return false;
+ }
+ }
+ } catch (err) {}
+ }
+
+ return !expectedRanges.length;
+}
+
+/*
+ * Returns a promise that resolves after a text attribute changed event
+ * brings us to a state where the misspelled ranges match.
+ */
+async function waitForMisspelledRanges(acc, ranges) {
+ await waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED);
+ await untilCacheOk(
+ () => misspelledRangesMatch(acc, ranges),
+ `Misspelled ranges match: ${JSON.stringify(ranges)}`
+ );
+}
+
+/**
+ * Test spelling errors.
+ */
+addAccessibleTask(
+ `
+<textarea id="textarea" spellcheck="true">test tset tset test</textarea>
+<div contenteditable id="editable" spellcheck="true">plain<span> ts</span>et <b>bold</b></div>
+ `,
+ async function(browser, docAcc) {
+ const textarea = findAccessibleChildByID(docAcc, "textarea", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing textarea");
+ let spellingChanged = waitForMisspelledRanges(textarea, [
+ [5, 9],
+ [10, 14],
+ ]);
+ textarea.takeFocus();
+ await spellingChanged;
+
+ // Test removal of a spelling error.
+ info('textarea: Changing first "tset" to "test"');
+ // setTextRange fires multiple EVENT_TEXT_ATTRIBUTE_CHANGED, so replace by
+ // selecting and typing instead.
+ spellingChanged = waitForMisspelledRanges(textarea, [[10, 14]]);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("textarea").setSelectionRange(5, 9);
+ });
+ EventUtils.sendString("test");
+ // Move the cursor to trigger spell check.
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ await spellingChanged;
+
+ // Test addition of a spelling error.
+ info('textarea: Changing it back to "tset"');
+ spellingChanged = waitForMisspelledRanges(textarea, [
+ [5, 9],
+ [10, 14],
+ ]);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("textarea").setSelectionRange(5, 9);
+ });
+ EventUtils.sendString("tset");
+ EventUtils.synthesizeKey("KEY_ArrowRight");
+ await spellingChanged;
+
+ // Ensure that changing the text without changing any spelling errors
+ // correctly updates offsets.
+ info('textarea: Changing first "test" to "the"');
+ // Spelling errors don't change, so we won't get
+ // EVENT_TEXT_ATTRIBUTE_CHANGED. We change the text, wait for the insertion
+ // and then select a character so we know when the change is done.
+ let inserted = waitForEvent(EVENT_TEXT_INSERTED, textarea);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("textarea").setSelectionRange(0, 4);
+ });
+ EventUtils.sendString("the");
+ await inserted;
+ let selected = waitForEvent(EVENT_TEXT_SELECTION_CHANGED, textarea);
+ EventUtils.synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ await selected;
+ const expectedRanges = [
+ [4, 8],
+ [9, 13],
+ ];
+ await untilCacheOk(
+ () => misspelledRangesMatch(textarea, expectedRanges),
+ `Misspelled ranges match: ${JSON.stringify(expectedRanges)}`
+ );
+
+ const editable = findAccessibleChildByID(docAcc, "editable", [
+ nsIAccessibleText,
+ ]);
+ info("Focusing editable");
+ spellingChanged = waitForMisspelledRanges(editable, [[6, 10]]);
+ editable.takeFocus();
+ await spellingChanged;
+ // Test normal text and spelling errors crossing text nodes.
+ testTextAttrs(editable, 0, {}, {}, 0, 6, true); // "plain "
+ // Ensure we detect the spelling error even though there is a style change
+ // after it.
+ testTextAttrs(editable, 6, { invalid: "spelling" }, {}, 6, 10, true); // "tset"
+ testTextAttrs(editable, 10, {}, {}, 10, 11, true); // " "
+ // Ensure a style change is still detected in the presence of a spelling
+ // error.
+ testTextAttrs(editable, 11, boldAttrs, {}, 11, 15, true); // "bold"
+ },
+ {
+ chrome: true,
+ topLevel: isCacheEnabled,
+ iframe: isCacheEnabled,
+ remoteIframe: isCacheEnabled,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js
new file mode 100644
index 0000000000..8b4a575d75
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js
@@ -0,0 +1,45 @@
+/* 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 });
+
+// Test ARIA Dialog
+addAccessibleTask(
+ "e10s/doc_treeupdate_ariadialog.html",
+ async function(browser, accDoc) {
+ testAccessibleTree(accDoc, {
+ role: ROLE_DOCUMENT,
+ children: [],
+ });
+
+ // Make dialog visible and update its inner content.
+ let onShow = waitForEvent(EVENT_SHOW, "dialog");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("dialog").style.display = "block";
+ });
+ await onShow;
+
+ testAccessibleTree(accDoc, {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_DIALOG,
+ children: [
+ {
+ role: ROLE_PUSHBUTTON,
+ children: [{ role: ROLE_TEXT_LEAF }],
+ },
+ {
+ role: ROLE_ENTRY,
+ },
+ ],
+ },
+ ],
+ });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
new file mode 100644
index 0000000000..33522d6bab
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
@@ -0,0 +1,325 @@
+/* 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 });
+
+async function testContainer1(browser, accDoc) {
+ 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 invokeSetAttribute(browser, id, "aria-owns", "t1_button t1_subdiv");
+ 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 invokeSetAttribute(browser, id, "aria-owns");
+ await onReorder;
+
+ // children follow the DOM order
+ tree = {
+ SECTION: [{ PUSHBUTTON: [] }, { CHECKBUTTON: [{ SECTION: [] }] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================ Set ARIA owns ========================================= */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, id, "aria-owns", "t1_button t1_subdiv");
+ 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) {
+ 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 invokeSetAttribute(browser, id2, "aria-owns", "t3_child");
+ 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) {
+ 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 invokeSetAttribute(browser, id, "aria-owns", val);
+ 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 }
+);
+
+// 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: [] }] }],
+ },
+ ],
+ },
+ ],
+ });
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_canvas.js b/accessible/tests/browser/e10s/browser_treeupdate_canvas.js
new file mode 100644
index 0000000000..5fcd1eb773
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_canvas.js
@@ -0,0 +1,28 @@
+/* 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 });
+
+addAccessibleTask(
+ `
+ <canvas id="canvas">
+ <div id="dialog" role="dialog" style="display: none;"></div>
+ </canvas>`,
+ async function(browser, accDoc) {
+ let canvas = findAccessibleChildByID(accDoc, "canvas");
+ let dialog = findAccessibleChildByID(accDoc, "dialog");
+
+ testAccessibleTree(canvas, { CANVAS: [] });
+
+ let onShow = waitForEvent(EVENT_SHOW, "dialog");
+ await invokeSetStyle(browser, "dialog", "display", "block");
+ await onShow;
+
+ testAccessibleTree(dialog, { DIALOG: [] });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js
new file mode 100644
index 0000000000..629f9fb89f
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js
@@ -0,0 +1,60 @@
+/* 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 });
+
+addAccessibleTask(
+ `
+ <div id="container"><div id="scrollarea" style="overflow:auto;"><input>`,
+ async function(browser, accDoc) {
+ const id1 = "container";
+ const container = findAccessibleChildByID(accDoc, id1);
+
+ /* ================= Change scroll range ================================== */
+ let tree = {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // scroll area
+ ENTRY: [], // child content
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id1);
+ await invokeContentTask(browser, [id1], id => {
+ let doc = content.document;
+ doc.getElementById("scrollarea").style.width = "20px";
+ doc.getElementById(id).appendChild(doc.createElement("input"));
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // scroll area
+ ENTRY: [], // child content
+ },
+ ],
+ },
+ {
+ ENTRY: [], // inserted input
+ },
+ ],
+ };
+ testAccessibleTree(container, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_doc.js b/accessible/tests/browser/e10s/browser_treeupdate_doc.js
new file mode 100644
index 0000000000..98f399695c
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_doc.js
@@ -0,0 +1,320 @@
+/* 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 });
+
+const iframeSrc = `data:text/html,
+ <html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Inner Iframe</title>
+ </head>
+ <body id='inner-iframe'></body>
+ </html>`;
+
+addAccessibleTask(
+ `
+ <iframe id="iframe" src="${iframeSrc}"></iframe>`,
+ async function(browser, accDoc) {
+ // ID of the iframe that is being tested
+ const id = "inner-iframe";
+
+ let iframe = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree check =================================== */
+ let tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Write iframe document ================================ */
+ let reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let newHTMLNode = docNode.createElement("html");
+ let newBodyNode = docNode.createElement("body");
+ let newTextNode = docNode.createTextNode("New Wave");
+ newBodyNode.id = contentId;
+ newBodyNode.appendChild(newTextNode);
+ newHTMLNode.appendChild(newBodyNode);
+ docNode.replaceChild(newHTMLNode, docNode.documentElement);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "New Wave",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Replace iframe HTML element ========================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ // We can't use open/write/close outside of iframe document because of
+ // security error.
+ let script = docNode.createElement("script");
+ script.textContent = `
+ document.open();
+ document.write('<body id="${contentId}">hello</body>');
+ document.close();`;
+ docNode.body.appendChild(script);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "hello",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Replace iframe body ================================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let newBodyNode = docNode.createElement("body");
+ let newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.id = contentId;
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute("role", "application");
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_APPLICATION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "New Hello",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Open iframe document ================================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ // Open document.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let script = docNode.createElement("script");
+ script.textContent = `
+ function closeMe() {
+ document.write('Works?');
+ document.close();
+ }
+ window.closeMe = closeMe;
+ document.open();
+ document.write('<body id="${contentId}"></body>');`;
+ docNode.body.appendChild(script);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Close iframe document ================================ */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ // Write and close document.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ docNode.write("Works?");
+ docNode.close();
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Works?",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Remove HTML from iframe document ===================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ // Remove HTML element.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ docNode.firstChild.remove();
+ });
+ let event = await reorderEventPromise;
+
+ ok(
+ event.accessible instanceof nsIAccessibleDocument,
+ "Reorder should happen on the document"
+ );
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Insert HTML to iframe document ======================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ // Insert HTML element.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let html = docNode.createElement("html");
+ let body = docNode.createElement("body");
+ let text = docNode.createTextNode("Haha");
+ body.appendChild(text);
+ body.id = contentId;
+ html.appendChild(body);
+ docNode.appendChild(html);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Haha",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Remove body from iframe document ===================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ // Remove body element.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ docNode.documentElement.removeChild(docNode.body);
+ });
+ event = await reorderEventPromise;
+
+ ok(
+ event.accessible instanceof nsIAccessibleDocument,
+ "Reorder should happen on the document"
+ );
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================ Insert element under document element while body missed */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let inputNode = (content.window.inputNode = docNode.createElement(
+ "input"
+ ));
+ docNode.documentElement.appendChild(inputNode);
+ });
+ event = await reorderEventPromise;
+
+ ok(
+ event.accessible instanceof nsIAccessibleDocument,
+ "Reorder should happen on the document"
+ );
+ tree = {
+ DOCUMENT: [{ ENTRY: [] }],
+ };
+ testAccessibleTree(iframe, tree);
+
+ reorderEventPromise = waitForEvent(EVENT_REORDER, iframe);
+ await invokeContentTask(browser, [], () => {
+ let docEl = content.document.getElementById("iframe").contentDocument
+ .documentElement;
+ // Remove aftermath of this test before next test starts.
+ docEl.firstChild.remove();
+ });
+ // Make sure reorder event was fired and that the input was removed.
+ await reorderEventPromise;
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Insert body to iframe document ======================= */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ // Write and close document.
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ // Insert body element.
+ let body = docNode.createElement("body");
+ let text = docNode.createTextNode("Yo ho ho i butylka roma!");
+ body.appendChild(text);
+ body.id = contentId;
+ docNode.documentElement.appendChild(body);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_DOCUMENT,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "Yo ho ho i butylka roma!",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+
+ /* ================= Change source ======================================== */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, "iframe");
+ await invokeSetAttribute(
+ browser,
+ "iframe",
+ "src",
+ `data:text/html,<html><body id="${id}"><input></body></html>`
+ );
+ event = await reorderEventPromise;
+
+ tree = {
+ INTERNAL_FRAME: [{ DOCUMENT: [{ ENTRY: [] }] }],
+ };
+ testAccessibleTree(event.accessible, tree);
+ iframe = findAccessibleChildByID(event.accessible, id);
+
+ /* ================= Replace iframe body on ARIA role body ================ */
+ reorderEventPromise = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let docNode = content.document.getElementById("iframe").contentDocument;
+ let newBodyNode = docNode.createElement("body");
+ let newTextNode = docNode.createTextNode("New Hello");
+ newBodyNode.appendChild(newTextNode);
+ newBodyNode.setAttribute("role", "application");
+ newBodyNode.id = contentId;
+ docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+ });
+ await reorderEventPromise;
+
+ tree = {
+ role: ROLE_APPLICATION,
+ children: [
+ {
+ role: ROLE_TEXT_LEAF,
+ name: "New Hello",
+ },
+ ],
+ };
+ testAccessibleTree(iframe, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js b/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js
new file mode 100644
index 0000000000..ca1150f9dd
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_gencontent.js
@@ -0,0 +1,94 @@
+/* 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 });
+
+addAccessibleTask(
+ `
+ <style>
+ .gentext:before {
+ content: "START"
+ }
+ .gentext:after {
+ content: "END"
+ }
+ </style>
+ <div id="container1"></div>
+ <div id="container2"><div id="container2_child">text</div></div>`,
+ async function(browser, accDoc) {
+ const id1 = "container1";
+ const id2 = "container2";
+ let container1 = findAccessibleChildByID(accDoc, id1);
+ let container2 = findAccessibleChildByID(accDoc, id2);
+
+ let tree = {
+ SECTION: [], // container
+ };
+ testAccessibleTree(container1, tree);
+
+ tree = {
+ SECTION: [
+ {
+ // container2
+ SECTION: [
+ {
+ // container2 child
+ TEXT_LEAF: [], // primary text
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container2, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id1);
+ // Create and add an element with CSS generated content to container1
+ await invokeContentTask(browser, [id1], id => {
+ let node = content.document.createElement("div");
+ node.textContent = "text";
+ node.setAttribute("class", "gentext");
+ content.document.getElementById(id).appendChild(node);
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ // container
+ {
+ SECTION: [
+ // inserted node
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] }, // :after
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container1, tree);
+
+ onReorder = waitForEvent(EVENT_REORDER, "container2_child");
+ // Add CSS generated content to an element in container2's subtree
+ await invokeSetAttribute(browser, "container2_child", "class", "gentext");
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ // container2
+ {
+ SECTION: [
+ // container2 child
+ { STATICTEXT: [] }, // :before
+ { TEXT_LEAF: [] }, // primary text
+ { STATICTEXT: [] }, // :after
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(container2, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_hidden.js b/accessible/tests/browser/e10s/browser_treeupdate_hidden.js
new file mode 100644
index 0000000000..725999db36
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_hidden.js
@@ -0,0 +1,32 @@
+/* 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 });
+
+async function setHidden(browser, value) {
+ let onReorder = waitForEvent(EVENT_REORDER, "container");
+ await invokeSetAttribute(browser, "child", "hidden", value);
+ await onReorder;
+}
+
+addAccessibleTask(
+ '<div id="container"><input id="child"></div>',
+ async function(browser, accDoc) {
+ let container = findAccessibleChildByID(accDoc, "container");
+
+ testAccessibleTree(container, { SECTION: [{ ENTRY: [] }] });
+
+ // Set @hidden attribute
+ await setHidden(browser, "true");
+ testAccessibleTree(container, { SECTION: [] });
+
+ // Remove @hidden attribute
+ await setHidden(browser);
+ testAccessibleTree(container, { SECTION: [{ ENTRY: [] }] });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_image.js b/accessible/tests/browser/e10s/browser_treeupdate_image.js
new file mode 100644
index 0000000000..3e548d6a41
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_image.js
@@ -0,0 +1,192 @@
+/* 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 });
+
+const IMG_ID = "img";
+const ALT_TEXT = "some-text";
+const ARIA_LABEL = "some-label";
+
+// Verify that granting alt text adds the graphic accessible.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" alt=""/>`,
+ async function(browser, accDoc) {
+ // Test initial state; the img has empty alt text so it should not be in the tree.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+
+ // Add the alt text. The graphic should have been inserted into the tree.
+ info(`Adding alt text "${ALT_TEXT}" to img id '${IMG_ID}'`);
+ const shown = waitForEvent(EVENT_SHOW, IMG_ID);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ await shown;
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the graphic accessible exists even with a missing alt attribute.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png"/>`,
+ async function(browser, accDoc) {
+ // Test initial state; the img has no alt attribute so the name is empty.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: null,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+
+ // Add the alt text. The graphic should still be present in the tree.
+ info(`Adding alt attribute with text "${ALT_TEXT}" to id ${IMG_ID}`);
+ const shown = waitForEvent(EVENT_NAME_CHANGE, IMG_ID);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ await shown;
+ tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that removing alt text removes the graphic accessible.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" alt="${ALT_TEXT}"/>`,
+ async function(browser, accDoc) {
+ // Test initial state; the img has alt text so it should be in the tree.
+ let acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+
+ // Set the alt text empty. The graphic should have been removed from the tree.
+ info(`Setting empty alt text for img id ${IMG_ID}`);
+ const hidden = waitForEvent(EVENT_HIDE, acc);
+ await invokeContentTask(browser, [IMG_ID, "alt", ""], (id, attr, value) => {
+ let elm = content.document.getElementById(id);
+ elm.setAttribute(attr, value);
+ });
+ await hidden;
+ acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the presence of an aria-label creates an accessible, even if
+// there is no alt text.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" aria-label="${ARIA_LABEL}" alt=""/>`,
+ async function(browser, accDoc) {
+ // Test initial state; the img has empty alt text, but it does have an
+ // aria-label, so it should be in the tree.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ARIA_LABEL,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+
+ // Add the alt text. The graphic should still be in the tree.
+ info(`Adding alt text "${ALT_TEXT}" to img id '${IMG_ID}'`);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ tree = {
+ role: ROLE_GRAPHIC,
+ name: ARIA_LABEL,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the presence of a click listener results in the graphic
+// accessible's presence in the tree.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" alt=""/>`,
+ async function(browser, accDoc) {
+ // Add a click listener to the img element.
+ info(`Adding click listener to img id '${IMG_ID}'`);
+ const shown = waitForEvent(EVENT_SHOW, IMG_ID);
+ await invokeContentTask(browser, [IMG_ID], id => {
+ content.document.getElementById(id).addEventListener("click", () => {});
+ });
+ await shown;
+
+ // Test initial state; the img has empty alt text, but it does have a click
+ // listener, so it should be in the tree.
+ let acc = findAccessibleChildByID(accDoc, IMG_ID);
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: null,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that the presentation role prevents creation of the graphic accessible.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" role="presentation"/>`,
+ async function(browser, accDoc) {
+ // Test initial state; the img is presentational and should not be in the tree.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+
+ // Add some alt text. There should still be no accessible for the img in the tree.
+ info(`Adding alt attribute with text "${ALT_TEXT}" to id ${IMG_ID}`);
+ await invokeSetAttribute(browser, IMG_ID, "alt", ALT_TEXT);
+ ok(!acc, "Image has no Accessible");
+
+ // Remove the presentation role. The accessible should be created.
+ info(`Removing presentation role from img id ${IMG_ID}`);
+ const shown = waitForEvent(EVENT_SHOW, IMG_ID);
+ await invokeSetAttribute(browser, IMG_ID, "role", "");
+ await shown;
+ let tree = {
+ role: ROLE_GRAPHIC,
+ name: ALT_TEXT,
+ children: [],
+ };
+ testAccessibleTree(acc, tree);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that setting empty alt text on a hidden image does not crash.
+// See Bug 1799208 for more info.
+addAccessibleTask(
+ `<img id="${IMG_ID}" src="${MOCHITESTS_DIR}/moz.png" hidden/>`,
+ async function(browser, accDoc) {
+ // Test initial state; should be no accessible since img is hidden.
+ const acc = findAccessibleChildByID(accDoc, IMG_ID);
+ ok(!acc, "Image has no Accessible");
+
+ // Add empty alt text. We shouldn't crash.
+ info(`Adding empty alt text "" to img id '${IMG_ID}'`);
+ await invokeContentTask(browser, [IMG_ID, "alt", ""], (id, attr, value) => {
+ let elm = content.document.getElementById(id);
+ elm.setAttribute(attr, value);
+ });
+ ok(true, "Setting empty alt text on a hidden image did not crash");
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js
new file mode 100644
index 0000000000..e9a4930f2c
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_imagemap.js
@@ -0,0 +1,190 @@
+/* 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 });
+
+async function testImageMap(browser, accDoc) {
+ const id = "imgmap";
+ const acc = findAccessibleChildByID(accDoc, id);
+
+ /* ================= Initial tree test ==================================== */
+ let tree = {
+ IMAGE_MAP: [{ role: ROLE_LINK, name: "b", children: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Insert area ========================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let areaElm = content.document.createElement("area");
+ let mapNode = content.document.getElementById("map");
+ areaElm.setAttribute(
+ "href",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ );
+ areaElm.setAttribute("coords", "0,0,13,14");
+ areaElm.setAttribute("alt", "a");
+ areaElm.setAttribute("shape", "rect");
+ mapNode.insertBefore(areaElm, mapNode.firstChild);
+ });
+ await onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: "a", children: [] },
+ { role: ROLE_LINK, name: "b", children: [] },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Append area ========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let areaElm = content.document.createElement("area");
+ let mapNode = content.document.getElementById("map");
+ areaElm.setAttribute(
+ "href",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.bbc.co.uk/radio4/atoz/index.shtml#c"
+ );
+ areaElm.setAttribute("coords", "34,0,47,14");
+ areaElm.setAttribute("alt", "c");
+ areaElm.setAttribute("shape", "rect");
+ mapNode.appendChild(areaElm);
+ });
+ await onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: "a", children: [] },
+ { role: ROLE_LINK, name: "b", children: [] },
+ { role: ROLE_LINK, name: "c", children: [] },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Remove area ========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let mapNode = content.document.getElementById("map");
+ mapNode.removeChild(mapNode.firstElementChild);
+ });
+ await onReorder;
+
+ tree = {
+ IMAGE_MAP: [
+ { role: ROLE_LINK, name: "b", children: [] },
+ { role: ROLE_LINK, name: "c", children: [] },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+async function testContainer(browser) {
+ const id = "container";
+ /* ================= Remove name on map =================================== */
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, "map", "name");
+ let event = await onReorder;
+ const acc = event.accessible;
+
+ let tree = {
+ SECTION: [{ GRAPHIC: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Restore name on map ================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetAttribute(browser, "map", "name", "atoz_map");
+ // XXX: force repainting of the image (see bug 745788 for details).
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeMouse(
+ content.document.getElementById("imgmap"),
+ 10,
+ 10,
+ { type: "mousemove" },
+ content
+ );
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ IMAGE_MAP: [{ LINK: [] }, { LINK: [] }],
+ },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Remove map =========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [], () => {
+ let mapNode = content.document.getElementById("map");
+ mapNode.remove();
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [{ GRAPHIC: [] }],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Insert map =========================================== */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ let map = content.document.createElement("map");
+ let area = content.document.createElement("area");
+
+ map.setAttribute("name", "atoz_map");
+ map.setAttribute("id", "map");
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ area.setAttribute("href", "http://www.bbc.co.uk/radio4/atoz/index.shtml#b");
+ area.setAttribute("coords", "17,0,30,14");
+ area.setAttribute("alt", "b");
+ area.setAttribute("shape", "rect");
+
+ map.appendChild(area);
+ content.document.getElementById(contentId).appendChild(map);
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ IMAGE_MAP: [{ LINK: [] }],
+ },
+ ],
+ };
+ testAccessibleTree(acc, tree);
+
+ /* ================= Hide image map ======================================= */
+ onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeSetStyle(browser, "imgmap", "display", "none");
+ await onReorder;
+
+ tree = {
+ SECTION: [],
+ };
+ testAccessibleTree(acc, tree);
+}
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_imagemap.html",
+ async function(browser, accDoc) {
+ await waitForImageMap(browser, accDoc);
+ await testImageMap(browser, accDoc);
+ await testContainer(browser);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_list.js b/accessible/tests/browser/e10s/browser_treeupdate_list.js
new file mode 100644
index 0000000000..d14b983c10
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_list.js
@@ -0,0 +1,52 @@
+/* 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 });
+
+async function setDisplayAndWaitForReorder(browser, value) {
+ let onReorder = waitForEvent(EVENT_REORDER, "ul");
+ await invokeSetStyle(browser, "li", "display", value);
+ return onReorder;
+}
+
+addAccessibleTask(
+ `
+ <ul id="ul">
+ <li id="li">item1</li>
+ </ul>`,
+ async function(browser, accDoc) {
+ let li = findAccessibleChildByID(accDoc, "li");
+ let bullet = li.firstChild;
+ let accTree = {
+ role: ROLE_LISTITEM,
+ children: [
+ {
+ role: ROLE_LISTITEM_MARKER,
+ children: [],
+ },
+ {
+ role: ROLE_TEXT_LEAF,
+ children: [],
+ },
+ ],
+ };
+ testAccessibleTree(li, accTree);
+
+ await setDisplayAndWaitForReorder(browser, "none");
+
+ ok(isDefunct(li), "Check that li is defunct.");
+ ok(isDefunct(bullet), "Check that bullet is defunct.");
+
+ let event = await setDisplayAndWaitForReorder(browser, "list-item");
+
+ testAccessibleTree(
+ findAccessibleChildByID(event.accessible, "li"),
+ accTree
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js b/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js
new file mode 100644
index 0000000000..9c672f3c7c
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js
@@ -0,0 +1,48 @@
+/* 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 });
+
+addAccessibleTask(
+ '<ol id="list"></ol>',
+ async function(browser, accDoc) {
+ let list = findAccessibleChildByID(accDoc, "list");
+
+ testAccessibleTree(list, {
+ role: ROLE_LIST,
+ children: [],
+ });
+
+ await invokeSetAttribute(
+ browser,
+ currentContentDoc(),
+ "contentEditable",
+ "true"
+ );
+ let onReorder = waitForEvent(EVENT_REORDER, "list");
+ await invokeContentTask(browser, [], () => {
+ let li = content.document.createElement("li");
+ li.textContent = "item";
+ content.document.getElementById("list").appendChild(li);
+ });
+ await onReorder;
+
+ testAccessibleTree(list, {
+ role: ROLE_LIST,
+ children: [
+ {
+ role: ROLE_LISTITEM,
+ children: [
+ { role: ROLE_LISTITEM_MARKER, name: "1. ", children: [] },
+ { role: ROLE_TEXT_LEAF, children: [] },
+ ],
+ },
+ ],
+ });
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_listener.js b/accessible/tests/browser/e10s/browser_treeupdate_listener.js
new file mode 100644
index 0000000000..35baf28667
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_listener.js
@@ -0,0 +1,38 @@
+/* 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 });
+
+addAccessibleTask(
+ '<span id="parent"><span id="child"></span></span>',
+ async function(browser, accDoc) {
+ is(
+ findAccessibleChildByID(accDoc, "parent"),
+ null,
+ "Check that parent is not accessible."
+ );
+ is(
+ findAccessibleChildByID(accDoc, "child"),
+ null,
+ "Check that child is not accessible."
+ );
+
+ let onReorder = waitForEvent(EVENT_REORDER, matchContentDoc);
+ // Add an event listener to parent.
+ await invokeContentTask(browser, [], () => {
+ content.window.dummyListener = () => {};
+ content.document
+ .getElementById("parent")
+ .addEventListener("click", content.window.dummyListener);
+ });
+ await onReorder;
+
+ let tree = { TEXT: [] };
+ testAccessibleTree(findAccessibleChildByID(accDoc, "parent"), tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_move.js b/accessible/tests/browser/e10s/browser_treeupdate_move.js
new file mode 100644
index 0000000000..7246073c72
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_move.js
@@ -0,0 +1,64 @@
+/* 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 */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts(
+ { name: "role.js", dir: MOCHITESTS_DIR },
+ { name: "states.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test moving Accessibles:
+ * 1. A moved Accessible keeps the same Accessible.
+ * 2. If the moved Accessible is focused, it remains focused.
+ * 3. A child of the moved Accessible also keeps the same Accessible.
+ * 4. A child removed at the same time as the move gets shut down.
+ */
+addAccessibleTask(
+ `
+<div id="scrollable" role="presentation" style="height: 1px;">
+ <div contenteditable id="textbox" role="textbox">
+ <h1 id="heading">Heading</h1>
+ <p id="para">Para</p>
+ </div>
+ <iframe id="iframe" src="https://example.com/"></iframe>
+</div>
+ `,
+ async function(browser, docAcc) {
+ const textbox = findAccessibleChildByID(docAcc, "textbox");
+ const heading = findAccessibleChildByID(docAcc, "heading");
+ const para = findAccessibleChildByID(docAcc, "para");
+ const iframe = findAccessibleChildByID(docAcc, "iframe");
+ const iframeDoc = iframe.firstChild;
+ ok(iframeDoc, "iframe contains a document");
+
+ let focused = waitForEvent(EVENT_FOCUS, textbox);
+ textbox.takeFocus();
+ await focused;
+ testStates(textbox, STATE_FOCUSED, 0, 0, EXT_STATE_DEFUNCT);
+
+ let reordered = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ // scrollable wasn't in the a11y tree, but this will force it to be created.
+ // textbox will be moved inside it.
+ content.document.getElementById("scrollable").style.overflow = "scroll";
+ content.document.getElementById("heading").remove();
+ });
+ await reordered;
+ // Despite the move, ensure textbox is still alive and is focused.
+ testStates(textbox, STATE_FOCUSED, 0, 0, EXT_STATE_DEFUNCT);
+ // Ensure para (a child of textbox) is also still alive.
+ ok(!isDefunct(para), "para is alive");
+ // heading was a child of textbox, but was removed when textbox
+ // was moved. Ensure it is dead.
+ ok(isDefunct(heading), "heading is dead");
+ // Ensure the iframe and its embedded document are alive.
+ ok(!isDefunct(iframe), "iframe is alive");
+ ok(!isDefunct(iframeDoc), "iframeDoc is alive");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
new file mode 100644
index 0000000000..55a9a26b6d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_optgroup.js
@@ -0,0 +1,100 @@
+/* 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 });
+
+addAccessibleTask(
+ '<select id="select"></select>',
+ async function(browser, accDoc) {
+ let select = findAccessibleChildByID(accDoc, "select");
+
+ let onEvent = waitForEvent(EVENT_REORDER, "select");
+ // Create a combobox with grouping and 2 standalone options
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ let contentSelect = doc.getElementById("select");
+ let optGroup = doc.createElement("optgroup");
+
+ for (let i = 0; i < 2; i++) {
+ let opt = doc.createElement("option");
+ opt.value = i;
+ opt.text = "Option: Value " + i;
+ optGroup.appendChild(opt);
+ }
+ contentSelect.add(optGroup, null);
+
+ for (let i = 0; i < 2; i++) {
+ let opt = doc.createElement("option");
+ contentSelect.add(opt, null);
+ }
+ contentSelect.firstChild.firstChild.id = "option1Node";
+ });
+ let event = await onEvent;
+ let option1Node = findAccessibleChildByID(event.accessible, "option1Node");
+
+ let tree = {
+ COMBOBOX: [
+ {
+ COMBOBOX_LIST: [
+ {
+ GROUPING: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }],
+ },
+ {
+ COMBOBOX_OPTION: [],
+ },
+ {
+ COMBOBOX_OPTION: [],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(select, tree);
+ ok(!isDefunct(option1Node), "option shouldn't be defunct");
+
+ onEvent = waitForEvent(EVENT_REORDER, "select");
+ // Remove grouping from combobox
+ await invokeContentTask(browser, [], () => {
+ let contentSelect = content.document.getElementById("select");
+ contentSelect.firstChild.remove();
+ });
+ await onEvent;
+
+ tree = {
+ COMBOBOX: [
+ {
+ COMBOBOX_LIST: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }],
+ },
+ ],
+ };
+ testAccessibleTree(select, tree);
+ ok(
+ isDefunct(option1Node),
+ "removed option shouldn't be accessible anymore!"
+ );
+
+ onEvent = waitForEvent(EVENT_REORDER, "select");
+ // Remove all options from combobox
+ await invokeContentTask(browser, [], () => {
+ let contentSelect = content.document.getElementById("select");
+ while (contentSelect.length) {
+ contentSelect.remove(0);
+ }
+ });
+ await onEvent;
+
+ tree = {
+ COMBOBOX: [
+ {
+ COMBOBOX_LIST: [],
+ },
+ ],
+ };
+ testAccessibleTree(select, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_removal.js b/accessible/tests/browser/e10s/browser_treeupdate_removal.js
new file mode 100644
index 0000000000..eb791525b3
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_removal.js
@@ -0,0 +1,58 @@
+/* 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 });
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_removal.xhtml",
+ async function(browser, accDoc) {
+ ok(
+ isAccessible(findAccessibleChildByID(accDoc, "the_table")),
+ "table should be accessible"
+ );
+
+ // Move the_table element into hidden subtree.
+ let onReorder = waitForEvent(EVENT_REORDER, matchContentDoc);
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("the_displaynone")
+ .appendChild(content.document.getElementById("the_table"));
+ });
+ await onReorder;
+
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_table")),
+ "table in display none tree shouldn't be accessible"
+ );
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_row")),
+ "row shouldn't be accessible"
+ );
+
+ // Remove the_row element (since it did not have accessible, no event needed).
+ await invokeContentTask(browser, [], () => {
+ content.document.body.removeChild(
+ content.document.getElementById("the_row")
+ );
+ });
+
+ // make sure no accessibles have stuck around.
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_row")),
+ "row shouldn't be accessible"
+ );
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_table")),
+ "table shouldn't be accessible"
+ );
+ ok(
+ !isAccessible(findAccessibleChildByID(accDoc, "the_displayNone")),
+ "display none things shouldn't be accessible"
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js b/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js
new file mode 100644
index 0000000000..f1d517276d
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js
@@ -0,0 +1,73 @@
+/* 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 });
+
+const snippet = `
+<select id="select">
+ <option>o1</option>
+ <optgroup label="g1">
+ <option>g1o1</option>
+ <option>g1o2</option>
+ </optgroup>
+ <optgroup label="g2">
+ <option>g2o1</option>
+ <option>g2o2</option>
+ </optgroup>
+ <option>o2</option>
+</select>
+`;
+
+addAccessibleTask(
+ snippet,
+ async function(browser, accDoc) {
+ await invokeFocus(browser, "select");
+ // Expand the select. A dropdown item should get focus.
+ // Note that the dropdown is rendered in the parent process.
+ let focused = waitForEvent(
+ EVENT_FOCUS,
+ event => event.accessible.role == ROLE_COMBOBOX_OPTION,
+ "Dropdown item focused after select expanded"
+ );
+ await invokeContentTask(browser, [], () => {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: true }, content);
+ });
+ info("Waiting for parent focus");
+ let event = await focused;
+ let dropdown = event.accessible.parent;
+
+ let selectedOptionChildren = [];
+ if (MAC) {
+ // Checkmark is part of the Mac menu styling.
+ selectedOptionChildren = [{ STATICTEXT: [] }];
+ }
+ let tree = {
+ COMBOBOX_LIST: [
+ { COMBOBOX_OPTION: selectedOptionChildren },
+ { GROUPING: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }] },
+ { GROUPING: [{ COMBOBOX_OPTION: [] }, { COMBOBOX_OPTION: [] }] },
+ { COMBOBOX_OPTION: [] },
+ ],
+ };
+ testAccessibleTree(dropdown, tree);
+
+ // Collapse the select. Focus should return to the select.
+ focused = waitForEvent(
+ EVENT_FOCUS,
+ "select",
+ "select focused after collapsed"
+ );
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
+ info("Waiting for child focus");
+ await focused;
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_table.js b/accessible/tests/browser/e10s/browser_treeupdate_table.js
new file mode 100644
index 0000000000..5c2903225a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_table.js
@@ -0,0 +1,48 @@
+/* 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 });
+
+addAccessibleTask(
+ `
+ <table id="table">
+ <tr>
+ <td>cell1</td>
+ <td>cell2</td>
+ </tr>
+ </table>`,
+ async function(browser, accDoc) {
+ let table = findAccessibleChildByID(accDoc, "table");
+
+ let tree = {
+ TABLE: [
+ { ROW: [{ CELL: [{ TEXT_LEAF: [] }] }, { CELL: [{ TEXT_LEAF: [] }] }] },
+ ],
+ };
+ testAccessibleTree(table, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "table");
+ await invokeContentTask(browser, [], () => {
+ // append a caption, it should appear as a first element in the
+ // accessible tree.
+ let doc = content.document;
+ let caption = doc.createElement("caption");
+ caption.textContent = "table caption";
+ doc.getElementById("table").appendChild(caption);
+ });
+ await onReorder;
+
+ tree = {
+ TABLE: [
+ { CAPTION: [{ TEXT_LEAF: [] }] },
+ { ROW: [{ CELL: [{ TEXT_LEAF: [] }] }, { CELL: [{ TEXT_LEAF: [] }] }] },
+ ],
+ };
+ testAccessibleTree(table, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js
new file mode 100644
index 0000000000..6f89105b86
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_textleaf.js
@@ -0,0 +1,38 @@
+/* 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 });
+
+async function removeTextData(browser, accessible, id, role) {
+ let tree = {
+ role,
+ children: [{ role: ROLE_TEXT_LEAF, name: "text" }],
+ };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, id);
+ await invokeContentTask(browser, [id], contentId => {
+ content.document.getElementById(contentId).firstChild.textContent = "";
+ });
+ await onReorder;
+
+ tree = { role, children: [] };
+ testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask(
+ `
+ <p id="p">text</p>
+ <pre id="pre">text</pre>`,
+ async function(browser, accDoc) {
+ let p = findAccessibleChildByID(accDoc, "p");
+ let pre = findAccessibleChildByID(accDoc, "pre");
+ await removeTextData(browser, p, "p", ROLE_PARAGRAPH);
+ await removeTextData(browser, pre, "pre", ROLE_TEXT_CONTAINER);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_visibility.js b/accessible/tests/browser/e10s/browser_treeupdate_visibility.js
new file mode 100644
index 0000000000..4583056586
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_visibility.js
@@ -0,0 +1,342 @@
+/* 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 });
+
+async function testTreeOnHide(browser, accDoc, containerID, id, before, after) {
+ let acc = findAccessibleChildByID(accDoc, containerID);
+ testAccessibleTree(acc, before);
+
+ let onReorder = waitForEvent(EVENT_REORDER, containerID);
+ await invokeSetStyle(browser, id, "visibility", "hidden");
+ await onReorder;
+
+ testAccessibleTree(acc, after);
+}
+
+async function test3(browser, accessible) {
+ let tree = {
+ SECTION: [
+ // container
+ {
+ SECTION: [
+ // parent
+ {
+ SECTION: [
+ // child
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ // parent2
+ {
+ SECTION: [
+ // child2
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "t3_container");
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ doc.getElementById("t3_container").style.color = "red";
+ doc.getElementById("t3_parent").style.visibility = "hidden";
+ doc.getElementById("t3_parent2").style.visibility = "hidden";
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ // container
+ {
+ SECTION: [
+ // child
+ { TEXT_LEAF: [] },
+ ],
+ },
+ {
+ SECTION: [
+ // child2
+ { TEXT_LEAF: [] },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(accessible, tree);
+}
+
+async function test4(browser, accessible) {
+ let tree = {
+ SECTION: [{ TABLE: [{ ROW: [{ CELL: [] }] }] }],
+ };
+ testAccessibleTree(accessible, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "t4_parent");
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ doc.getElementById("t4_container").style.color = "red";
+ doc.getElementById("t4_child").style.visibility = "visible";
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ {
+ TABLE: [
+ {
+ ROW: [
+ {
+ CELL: [
+ {
+ SECTION: [
+ {
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ };
+ testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_visibility.html",
+ async function(browser, accDoc) {
+ let t3Container = findAccessibleChildByID(accDoc, "t3_container");
+ let t4Container = findAccessibleChildByID(accDoc, "t4_container");
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t1_container",
+ "t1_parent",
+ {
+ SECTION: [
+ {
+ SECTION: [
+ {
+ SECTION: [{ TEXT_LEAF: [] }],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ SECTION: [{ TEXT_LEAF: [] }],
+ },
+ ],
+ }
+ );
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t2_container",
+ "t2_grandparent",
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // grand parent
+ SECTION: [
+ {
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ }
+ );
+
+ await test3(browser, t3Container);
+ await test4(browser, t4Container);
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t5_container",
+ "t5_subcontainer",
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // subcontainer
+ TABLE: [
+ {
+ ROW: [
+ {
+ CELL: [
+ {
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ }
+ );
+
+ await testTreeOnHide(
+ browser,
+ accDoc,
+ "t6_container",
+ "t6_subcontainer",
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // subcontainer
+ TABLE: [
+ {
+ ROW: [
+ {
+ CELL: [
+ {
+ TABLE: [
+ {
+ // nested table
+ ROW: [
+ {
+ CELL: [
+ {
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // container
+ SECTION: [
+ {
+ // child
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ {
+ SECTION: [
+ {
+ // child2
+ TEXT_LEAF: [],
+ },
+ ],
+ },
+ ],
+ }
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js
new file mode 100644
index 0000000000..36c1f62e39
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_whitespace.js
@@ -0,0 +1,69 @@
+/* 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 });
+
+addAccessibleTask(
+ "e10s/doc_treeupdate_whitespace.html",
+ async function(browser, accDoc) {
+ let container1 = findAccessibleChildByID(accDoc, "container1");
+ let container2Parent = findAccessibleChildByID(accDoc, "container2-parent");
+
+ let tree = {
+ SECTION: [
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ { TEXT_LEAF: [] },
+ { GRAPHIC: [] },
+ ],
+ };
+ testAccessibleTree(container1, tree);
+
+ let onReorder = waitForEvent(EVENT_REORDER, "container1");
+ // Remove img1 from container1
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ doc.getElementById("container1").removeChild(doc.getElementById("img1"));
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [{ GRAPHIC: [] }, { TEXT_LEAF: [] }, { GRAPHIC: [] }],
+ };
+ testAccessibleTree(container1, tree);
+
+ tree = {
+ SECTION: [{ LINK: [] }, { LINK: [{ GRAPHIC: [] }] }],
+ };
+ testAccessibleTree(container2Parent, tree);
+
+ onReorder = waitForEvent(EVENT_REORDER, "container2-parent");
+ // Append an img with valid src to container2
+ await invokeContentTask(browser, [], () => {
+ let doc = content.document;
+ let img = doc.createElement("img");
+ img.setAttribute(
+ "src",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/a11y/accessible/tests/mochitest/moz.png"
+ );
+ doc.getElementById("container2").appendChild(img);
+ });
+ await onReorder;
+
+ tree = {
+ SECTION: [
+ { LINK: [{ GRAPHIC: [] }] },
+ { TEXT_LEAF: [] },
+ { LINK: [{ GRAPHIC: [] }] },
+ ],
+ };
+ testAccessibleTree(container2Parent, tree);
+ },
+ { iframe: true, remoteIframe: true }
+);
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html
new file mode 100644
index 0000000000..9d08854b9a
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update ARIA Dialog Test</title>
+ </head>
+ <body id="body">
+ <div id="dialog" role="dialog" style="display: none;">
+ <table id="table" role="presentation"
+ style="display: block; position: fixed; top: 88px; left: 312.5px; z-index: 10010;">
+ <tbody>
+ <tr>
+ <td role="presentation">
+ <div role="presentation">
+ <a id="a" role="button">text</a>
+ </div>
+ <input id="input">
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html
new file mode 100644
index 0000000000..38b5c333a1
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html
@@ -0,0 +1,44 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update ARIA Owns Test</title>
+ </head>
+ <body id="body">
+ <div id="t1_container" aria-owns="t1_checkbox t1_button">
+ <div role="button" id="t1_button"></div>
+ <div role="checkbox" id="t1_checkbox">
+ <span id="t1_span">
+ <div id="t1_subdiv"></div>
+ </span>
+ </div>
+ </div>
+ <div id="t1_group" role="group"></div>
+ <div id="t1_grouptmp" role="group"></div>
+
+ <div id="t2_container1" aria-owns="t2_owned"></div>
+ <div id="t2_container2">
+ <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
+ </div>
+
+ <div id="t3_container1" aria-owns="t3_child"></div>
+ <div id="t3_child" role="checkbox"></div>
+ <div id="t3_container2"></div>
+
+ <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
+ <div id="t4_container2">
+ <div id="t4_child1" style="display:none" role="checkbox"></div>
+ <div id="t4_child2" role="radio"></div>
+ </div>
+
+ <div id="t5_container">
+ <div role="button" id="t5_button"></div>
+ <div role="checkbox" id="t5_checkbox"></div>
+ <div role="radio" id="t5_radio"></div>
+ </div>
+
+ <div id="t6_container" aria-owns="t6_fake">
+ <span id="t6_span">hey</span>
+ </div>
+ <div id="t6_fake" role="group"></div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html
new file mode 100644
index 0000000000..4dd230fc28
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_imagemap.html
@@ -0,0 +1,21 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Imagemap Test</title>
+ </head>
+ <body id="body">
+ <map name="atoz_map" id="map">
+ <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14" alt="b" shape="rect">
+ </map>
+
+ <div id="container">
+ <img id="imgmap" width="447" height="15"
+ usemap="#atoz_map"
+ src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"><!--
+ Important: no whitespace between the <img> and the </div>, so we
+ don't end up with textframes there, because those would be reflected
+ in our accessible tree in some cases.
+ --></div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml
new file mode 100644
index 0000000000..9c59fb9d11
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Removal Test</title>
+ </head>
+ <body id="body">
+ <div id="the_displaynone" style="display: none;"></div>
+ <table id="the_table"></table>
+ <tr id="the_row"></tr>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_visibility.html b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html
new file mode 100644
index 0000000000..00213b2b70
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_visibility.html
@@ -0,0 +1,78 @@
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Tree Update Visibility Test</title>
+ </head>
+ <body id="body">
+ <!-- hide parent while child stays visible -->
+ <div id="t1_container">
+ <div id="t1_parent">
+ <div id="t1_child" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- hide grandparent while its children stay visible -->
+ <div id="t2_container">
+ <div id="t2_grandparent">
+ <div id="t2_parent">
+ <div id="t2_child" style="visibility: visible">text</div>
+ <div id="t2_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </div>
+
+ <!-- change container style, hide parents while their children stay visible -->
+ <div id="t3_container">
+ <div id="t3_parent">
+ <div id="t3_child" style="visibility: visible">text</div>
+ </div>
+ <div id="t3_parent2">
+ <div id="t3_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+
+ <!-- change container style, show child inside the table -->
+ <div id="t4_container">
+ <table>
+ <tr>
+ <td id="t4_parent">
+ <div id="t4_child" style="visibility: hidden;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+
+ <!-- hide subcontainer while child inside the table stays visible -->
+ <div id="t5_container">
+ <div id="t5_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <div id="t5_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+
+ <!-- hide subcontainer while its child and child inside the nested table stays visible -->
+ <div id="t6_container">
+ <div id="t6_subcontainer">
+ <table>
+ <tr>
+ <td>
+ <table>
+ <tr>
+ <td>
+ <div id="t6_child" style="visibility: visible;">text</div>
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ <div id="t6_child2" style="visibility: visible">text</div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html
new file mode 100644
index 0000000000..f17dbbd60e
--- /dev/null
+++ b/accessible/tests/browser/e10s/doc_treeupdate_whitespace.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <meta charset="utf-8"/>
+ <title>Whitespace text accessible creation/destruction</title>
+ </head>
+ <body id="body">
+ <div id="container1"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img id="img1" src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"> </div>
+ <div id="container2-parent"> <a id="container2"></a> <a><img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a> </div>
+ </body>
+</html>
diff --git a/accessible/tests/browser/e10s/fonts/Ahem.sjs b/accessible/tests/browser/e10s/fonts/Ahem.sjs
new file mode 100644
index 0000000000..e801a801ab
--- /dev/null
+++ b/accessible/tests/browser/e10s/fonts/Ahem.sjs
@@ -0,0 +1,241 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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";
+
+/*
+ * A CORS-enabled font resource.
+ */
+
+const FONT_BYTES = atob(
+ "AAEAAAALAIAAAwAwT1MvMnhQSo0AAAE4AAAAYGNtYXAP1hZGAAAFbAAABnJnYXNwABcACQAAMLAA" +
+ "AAAQZ2x5ZkmzdNoAAAvgAAAaZGhlYWTWok4cAAAAvAAAADZoaGVhBwoEFgAAAPQAAAAkaG10eLkg" +
+ "AH0AAAGYAAAD1GxvY2EgdSciAAAmRAAAAextYXhwAPgACQAAARgAAAAgbmFtZX4UjLgAACgwAAAG" +
+ "aHBvc3SN0B2KAAAumAAAAhgAAQAAAAEAQhIXUWdfDzz1AAkD6AAAAACzb19ZAAAAAMAtq0kAAP84" +
+ "A+gDIAAAAAMAAgAAAAAAAAABAAADIP84AAAD6AAAAAAD6AABAAAAAAAAAAAAAAAAAAAA9QABAAAA" +
+ "9QAIAAIAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAID6AGQAAUAAAK8AooAAACPArwCigAAAcUAMgED" +
+ "AAACAAQJAAAAAAAAgAAArxAAIEgAAAAAAAAAAFczQwAAQAAg8AIDIP84AAADIADIIAABEUAAAAAD" +
+ "IAMgAAAAIAAAA+gAfQAAAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD" +
+ "6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPo" +
+ "AAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gA" +
+ "AAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAAA+gAAAPoAAAD6AAA" +
+ "A+gAAAPoAAAD6AAAA+gAAAPoAAAAAAADAAAAAwAABEwAAQAAAAAAHAADAAEAAAImAAYCCgAAAAAB" +
+ "AAABAAAAAAAAAAAAAAAAAAAAAQACAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AAAAAQAAAAAAAwAEAAUABgAHAAgACQAAAAoACwAMAA0ADgAPABAAEQASABMAFAAVABYAFwAYABkA" +
+ "GgAbABwAHQAeAB8AIAAhACIAIwAkACUAJgAnACgAKQAqACsALAAtAC4ALwAwADEAMgAzADQANQA2" +
+ "ADcAOAA5ADoAOwA8AD0APgA/AEAAQQBCAEMARABFAEYARwBIAEkASgBLAEwATQBOAE8AUABRAFIA" +
+ "UwBUAFUAVgBXAFgAWQBaAFsAXABdAF4AXwBgAAAAYQBiAGMAZABlAGYAZwBoAGkAagBrAGwAbQBu" +
+ "AG8AcABxAHIAcwB0AHUAdgB3AHgAeQB6AHsAfAB9AH4AfwCAANsAgQCCAIMAhADdAIUAhgCHAIgA" +
+ "4wCJAIoA6gCLAIwA6ACNAOsA7ACOAI8A5ADmAOUA1ADpAJAAkQDTAJIAkwCUAJUAlgDnANEA7QDS" +
+ "AJcAmADeAAMAmgCbAJwAzgDPANUA1gDYANkAnQCeAJ8A7gCgANAA4gChAOAA4QAAAAAA3ACiANcA" +
+ "2gDfAKMApAClAKYApwCoAKkAqgCrAKwArQAAAK4ArwCwALEAsgCzALQAtQC2ALcAuAC5ALoAuwC8" +
+ "AAQCJgAAAE4AQAAFAA4AJgB+AP8BMQFTAXgBkgLHAskC3QOUA6kDvAPAIBAgFCAaIB4gIiAmIDAg" +
+ "OiBEISIhJiICIgYiDyISIhoiHiIrIkgiYCJlIvIlyvAC//8AAAAgACgAoAExAVIBeAGSAsYCyQLY" +
+ "A5QDqQO8A8AgECATIBggHCAgICYgMCA5IEQhIiEmIgIiBiIPIhEiGSIeIisiSCJgImQi8iXK8AD/" +
+ "///j/+IAAP+B/3z/WP8/AAD97AAA/T79KvzT/RTf/+DCAADgvOC74Ljgr+Cn4J7fwd+t3uLezN7W" +
+ "AAAAAN7K3r7epd6K3ofd+9skEO8AAQAAAAAASgAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAP4A" +
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAOwA7gAAAAAAAAAAAAAAAAAAAAAAAACZAJUAggCDAKEAjgC9" +
+ "AIQAigCIAJAAlwCWAMQAhwC1AIEAjQDHAMgAiQCPAIUAogC5AMYAkQCYAMoAyQDLAJQAmgClAKMA" +
+ "mwBhAGIAiwBjAKcAZACkAKYAqwCoAKkAqgC+AGUArgCsAK0AnABmAMUAjACxAK8AsABnAMAAwgCG" +
+ "AGkAaABqAGwAawBtAJIAbgBwAG8AcQByAHQAcwB1AHYAvwB3AHkAeAB6AHwAewCfAJMAfgB9AH8A" +
+ "gADBAMMAoACzALwAtgC3ALgAuwC0ALoAnQCeANcA5gDEAKIA5wAEAiYAAABOAEAABQAOACYAfgD/" +
+ "ATEBUwF4AZICxwLJAt0DlAOpA7wDwCAQIBQgGiAeICIgJiAwIDogRCEiISYiAiIGIg8iEiIaIh4i" +
+ "KyJIImAiZSLyJcrwAv//AAAAIAAoAKABMQFSAXgBkgLGAskC2AOUA6kDvAPAIBAgEyAYIBwgICAm" +
+ "IDAgOSBEISIhJiICIgYiDyIRIhkiHiIrIkgiYCJkIvIlyvAA////4//iAAD/gf98/1j/PwAA/ewA" +
+ "AP0+/Sr80/0U3//gwgAA4Lzgu+C44K/gp+Ce38Hfrd7i3sze1gAAAADeyt6+3qXeit6H3fvbJBDv" +
+ "AAEAAAAAAEoAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAD+AAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
+ "AADsAO4AAAAAAAAAAAAAAAAAAAAAAAAAmQCVAIIAgwChAI4AvQCEAIoAiACQAJcAlgDEAIcAtQCB" +
+ "AI0AxwDIAIkAjwCFAKIAuQDGAJEAmADKAMkAywCUAJoApQCjAJsAYQBiAIsAYwCnAGQApACmAKsA" +
+ "qACpAKoAvgBlAK4ArACtAJwAZgDFAIwAsQCvALAAZwDAAMIAhgBpAGgAagBsAGsAbQCSAG4AcABv" +
+ "AHEAcgB0AHMAdQB2AL8AdwB5AHgAegB8AHsAnwCTAH4AfQB/AIAAwQDDAKAAswC8ALYAtwC4ALsA" +
+ "tAC6AJ0AngDXAOYAxACiAOcAAAACAH0AAANrAyAAAwAHAAAzESERJSERIX0C7v2PAfT+DAMg/OB9" +
+ "AiYAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gD" +
+ "IAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMg" +
+ "AAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAA" +
+ "AwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAAD" +
+ "AAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AAAAAMAADEhFSED6PwYyAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAAAAAPoAyAAAwAAESERIQPo/BgDIPzgAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gD" +
+ "IAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMg" +
+ "AAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAA" +
+ "AwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAAD" +
+ "AAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPo" +
+ "AyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gD" +
+ "IAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMg" +
+ "AAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAA" +
+ "AwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAAD" +
+ "AAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMA" +
+ "ABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAA" +
+ "ESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAAR" +
+ "IREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEh" +
+ "ESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESER" +
+ "IQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREh" +
+ "A+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED" +
+ "6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo" +
+ "/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8" +
+ "GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwY" +
+ "AyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgD" +
+ "IPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg" +
+ "/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8" +
+ "GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwY" +
+ "AAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgA" +
+ "AAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAA" +
+ "AAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAA" +
+ "AQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAB" +
+ "AAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEA" +
+ "AP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA" +
+ "/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/" +
+ "OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84" +
+ "A+gDIAADAAARIREhA+j8GAMg/BgAAAABAAD/OAPoAyAAAwAAESERIQPo/BgDIPwYAAAAAQAA/zgD" +
+ "6AMgAAMAABEhESED6PwYAyD8GAAAAAEAAP84A+gDIAADAAARIREhA+j8GAMg/BgAAAAAABQAFAAU" +
+ "ABQAIgAwAD4ATABaAGgAdgCEAJIAoACuALwAygDYAOYA9AECARABHgEsAToBSAFWAWQBcgGAAY4B" +
+ "nAGqAbgBxgHUAeIB8AH+AgwCGgIoAjYCRAJSAmACbgJ8AooCmAKmArQCwgLQAt4C7AL6AwgDFgMk" +
+ "AzIDQANOA1wDagN4A4YDlAOiA7ADvgPMA9oD6AP2BAQEEgQgBC4EPARKBFgEZARyBIAEjgScBKoE" +
+ "uATGBNQE4gTwBP4FDAUaBSgFNgVEBVIFYAVuBXwFigWYBaYFtAXCBdAF3gXsBfoGCAYWBiQGMgZA" +
+ "Bk4GXAZqBngGhgaUBqIGsAa+BswG2gboBvYHBAcSByAHLgc8B0oHWAdmB3QHggeQB54HrAe6B8gH" +
+ "1gfkB/IIAAgOCBwIKgg4CDgIRghUCGIIcAh+CIwImgioCLYIxAjSCOAI7gj8CQoJGAkmCTQJQglQ" +
+ "CV4JbAl6CYgJlgmkCbIJwAnOCdwJ6gn4CgYKFAoiCjAKPgpMCloKaAp2CoQKkgqgCq4KvArKCtgK" +
+ "5gr0CwILEAseCywLOgtIC1YLZAtyC4ALjgucC6oLuAvGC9QL4gvwC/4MDAwaDCgMNgxEDFIMYAxu" +
+ "DHwMigyYDKYMtAzCDNAM3gzsDPoNCA0WDSQNMgAAABsBSgAAAAAAAAAAAZ4AAAAAAAAAAAABAAgB" +
+ "ngAAAAAAAAACAA4BpgAAAAAAAAADACABtAAAAAAAAAAEAAgB1AAAAAAAAAAFABYB3AAAAAAAAAAG" +
+ "AAgB8gABAAAAAAAAAM8B+gABAAAAAAABAAQCyQABAAAAAAACAAcCzQABAAAAAAADABAC1AABAAAA" +
+ "AAAEAAQC5AABAAAAAAAFAAsC6AABAAAAAAAGAAQC8wABAAAAAAAQAAQC9wABAAAAAAARAAcC+wAB" +
+ "AAAAAAASAAQDAgADAAEECQAAAZ4DBgADAAEECQABAAgEpAADAAEECQACAA4ErAADAAEECQADACAE" +
+ "ugADAAEECQAEAAgE2gADAAEECQAFABYE4gADAAEECQAGAAgE+AADAAEECQAQAAgFAAADAAEECQAR" +
+ "AA4FCAADAAEECQASAAgFFgBNAG8AcwB0ACAAYwBoAGEAcgBhAGMAdABlAHIAcwAgAGEAcgBlACAA" +
+ "dABoAGUAIABlAG0AIABzAHEAdQBhAHIAZQAsACAAZQB4AGMAZQBwAHQAIAAmAEUAQQBjAHUAdABl" +
+ "ACAAYQBuAGQAIAAiAHAAIgAsACAAdwBoAGkAYwBoACAAcwBoAG8AdwAgAGEAcwBjAGUAbgB0AC8A" +
+ "ZABlAHMAYwBlAG4AdAAgAGYAcgBvAG0AIAB0AGgAZQAgAGIAYQBzAGUAbABpAG4AZQAuACAAVQBz" +
+ "AGUAZgB1AGwAIABmAG8AcgAgAHQAZQBzAHQAaQBuAGcAIABjAG8AbQBwAG8AcwBpAHQAaQBvAG4A" +
+ "IABzAHkAcwB0AGUAbQBzAC4AIABQAHIAbwBkAHUAYwBlAGQAIABiAHkAIABUAG8AZABkACAARgBh" +
+ "AGgAcgBuAGUAcgAgAGYAbwByACAAdABoAGUAIABDAFMAUwAgAFMAYQBtAHUAcgBhAGkAJwBzACAA" +
+ "YgByAG8AdwBzAGUAcgAgAHQAZQBzAHQAaQBuAGcALgBBAGgAZQBtAFIAZQBnAHUAbABhAHIAVgBl" +
+ "AHIAcwBpAG8AbgAgADEALgAxACAAQQBoAGUAbQBBAGgAZQBtAFYAZQByAHMAaQBvAG4AIAAxAC4A" +
+ "MQBBAGgAZQBtTW9zdCBjaGFyYWN0ZXJzIGFyZSB0aGUgZW0gc3F1YXJlLCBleGNlcHQgJkVBY3V0" +
+ "ZSBhbmQgInAiLCB3aGljaCBzaG93IGFzY2VudC9kZXNjZW50IGZyb20gdGhlIGJhc2VsaW5lLiBV" +
+ "c2VmdWwgZm9yIHRlc3RpbmcgY29tcG9zaXRpb24gc3lzdGVtcy4gUHJvZHVjZWQgYnkgVG9kZCBG" +
+ "YWhybmVyIGZvciB0aGUgQ1NTIFNhbXVyYWkncyBicm93c2VyIHRlc3RpbmcuQWhlbVJlZ3VsYXJW" +
+ "ZXJzaW9uIDEuMSBBaGVtQWhlbVZlcnNpb24gMS4xQWhlbUFoZW1SZWd1bGFyQWhlbQBNAG8AcwB0" +
+ "ACAAYwBoAGEAcgBhAGMAdABlAHIAcwAgAGEAcgBlACAAdABoAGUAIABlAG0AIABzAHEAdQBhAHIA" +
+ "ZQAsACAAZQB4AGMAZQBwAHQAIAAmAEUAQQBjAHUAdABlACAAYQBuAGQAIAAiAHAAIgAsACAAdwBo" +
+ "AGkAYwBoACAAcwBoAG8AdwAgAGEAcwBjAGUAbgB0AC8AZABlAHMAYwBlAG4AdAAgAGYAcgBvAG0A" +
+ "IAB0AGgAZQAgAGIAYQBzAGUAbABpAG4AZQAuACAAVQBzAGUAZgB1AGwAIABmAG8AcgAgAHQAZQBz" +
+ "AHQAaQBuAGcAIABjAG8AbQBwAG8AcwBpAHQAaQBvAG4AIABzAHkAcwB0AGUAbQBzAC4AIABQAHIA" +
+ "bwBkAHUAYwBlAGQAIABiAHkAIABUAG8AZABkACAARgBhAGgAcgBuAGUAcgAgAGYAbwByACAAdABo" +
+ "AGUAIABDAFMAUwAgAFMAYQBtAHUAcgBhAGkAJwBzACAAYgByAG8AdwBzAGUAcgAgAHQAZQBzAHQA" +
+ "aQBuAGcALgBBAGgAZQBtAFIAZQBnAHUAbABhAHIAVgBlAHIAcwBpAG8AbgAgADEALgAxACAAQQBo" +
+ "AGUAbQBBAGgAZQBtAFYAZQByAHMAaQBvAG4AIAAxAC4AMQBBAGgAZQBtAEEAaABlAG0AUgBlAGcA" +
+ "dQBsAGEAcgBBAGgAZQBtAAIAAAAAAAD/ewAUAAAAAQAAAAAAAAAAAAAAAAAAAAAA9QAAAQIAAgAD" +
+ "AAQABQAGAAcACAAJAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAA" +
+ "IQAiACMAJAAlACYAJwAoACkAKgArACwALQAuAC8AMAAxADIAMwA0ADUANgA3ADgAOQA6ADsAPAA9" +
+ "AD4APwBAAEEAQgBDAEQARQBGAEcASABJAEoASwBMAE0ATgBPAFAAUQBSAFMAVABVAFYAVwBYAFkA" +
+ "WgBbAFwAXQBeAF8AYABhAGIAYwBkAGUAZgBnAGgAaQBqAGsAbABtAG4AbwBwAHEAcgBzAHQAdQB2" +
+ "AHcAeAB5AHoAewB8AH0AfgB/AIAAgQCDAIQAhQCGAIgAiQCKAIsAjQCOAJAAkQCTAJYAlwCdAJ4A" +
+ "oAChAKIAowCkAKkAqgCsAK0ArgCvALYAtwC4ALoAvQDDAMcAyADJAMoAywDMAM0AzgDPANAA0QDT" +
+ "ANQA1QDWANcA2ADZANoA2wDcAN0A3gDfAOAA4QDoAOkA6gDrAOwA7QDuAO8A8ADxAPIA8wD0APUA" +
+ "9gAAAAAAsACxALsApgCoAJ8AmwCyALMAxAC0ALUAxQCCAMIAhwCrAMYAvgC/ALwAjACYAJoAmQCl" +
+ "AJIAnACPAJQAlQCnALkA0gDAAMEBAwACAQQETlVMTAJIVANERUwAAAADAAgAAgAQAAH//wAD"
+);
+
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Content-Type", "application/octet-stream", false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.write(FONT_BYTES);
+}
diff --git a/accessible/tests/browser/e10s/head.js b/accessible/tests/browser/e10s/head.js
new file mode 100644
index 0000000000..517cb69222
--- /dev/null
+++ b/accessible/tests/browser/e10s/head.js
@@ -0,0 +1,193 @@
+/* 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";
+
+/* exported testCachedRelation, testRelated */
+
+// Load the shared-head file first.
+/* import-globals-from ../shared-head.js */
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
+ this
+);
+
+// Loading and common.js from accessible/tests/mochitest/ for all tests, as
+// well as promisified-events.js and relations.js.
+/* import-globals-from ../../mochitest/relations.js */
+loadScripts(
+ { name: "common.js", dir: MOCHITESTS_DIR },
+ { name: "promisified-events.js", dir: MOCHITESTS_DIR },
+ { name: "relations.js", dir: MOCHITESTS_DIR }
+);
+
+/**
+ * Test the accessible relation.
+ *
+ * @param identifier [in] identifier to get an accessible, may be ID
+ * attribute or DOM element or accessible object
+ * @param relType [in] relation type (see constants above)
+ * @param relatedIdentifiers [in] identifier or array of identifiers of
+ * expected related accessibles
+ */
+async function testCachedRelation(identifier, relType, relatedIdentifiers) {
+ const relDescr = getRelationErrorMsg(identifier, relType);
+ const relDescrStart = getRelationErrorMsg(identifier, relType, true);
+ info(`Testing ${relDescr}`);
+
+ if (!relatedIdentifiers) {
+ await untilCacheOk(function() {
+ let r = getRelationByType(identifier, relType);
+ if (r) {
+ info(`Fetched ${r.targetsCount} relations from cache`);
+ } else {
+ info("Could not fetch relations");
+ }
+ return r && !r.targetsCount;
+ }, relDescrStart + " has no targets, as expected");
+ return;
+ }
+
+ const relatedIds =
+ relatedIdentifiers instanceof Array
+ ? relatedIdentifiers
+ : [relatedIdentifiers];
+ await untilCacheOk(function() {
+ let r = getRelationByType(identifier, relType);
+ if (r) {
+ info(
+ `Fetched ${r.targetsCount} relations from cache, looking for ${relatedIds.length}`
+ );
+ } else {
+ info("Could not fetch relations");
+ }
+
+ return r && r.targetsCount == relatedIds.length;
+ }, "Found correct number of expected relations");
+
+ let targets = [];
+ for (let idx = 0; idx < relatedIds.length; idx++) {
+ targets.push(getAccessible(relatedIds[idx]));
+ }
+
+ if (targets.length != relatedIds.length) {
+ return;
+ }
+
+ await untilCacheOk(function() {
+ const relation = getRelationByType(identifier, relType);
+ const actualTargets = relation ? relation.getTargets() : null;
+ if (!actualTargets) {
+ info("Could not fetch relations");
+ return false;
+ }
+
+ // Check if all given related accessibles are targets of obtained relation.
+ for (let idx = 0; idx < targets.length; idx++) {
+ let isFound = false;
+ for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) {
+ if (targets[idx] == relatedAcc) {
+ isFound = true;
+ break;
+ }
+ }
+
+ if (!isFound) {
+ info(
+ prettyName(relatedIds[idx]) +
+ " could not be found in relation: " +
+ relDescr
+ );
+ return false;
+ }
+ }
+
+ return true;
+ }, "All given related accessibles are targets of fetched relation.");
+
+ await untilCacheOk(function() {
+ const relation = getRelationByType(identifier, relType);
+ const actualTargets = relation ? relation.getTargets() : null;
+ if (!actualTargets) {
+ info("Could not fetch relations");
+ return false;
+ }
+
+ // Check if all obtained targets are given related accessibles.
+ for (let relatedAcc of actualTargets.enumerate(Ci.nsIAccessible)) {
+ let wasFound = false;
+ for (let idx = 0; idx < targets.length; idx++) {
+ if (relatedAcc == targets[idx]) {
+ wasFound = true;
+ }
+ }
+ if (!wasFound) {
+ info(
+ prettyName(relatedAcc) +
+ " was found, but shouldn't be in relation: " +
+ relDescr
+ );
+ return false;
+ }
+ }
+ return true;
+ }, "No unexpected targets found.");
+}
+
+async function testRelated(
+ browser,
+ accDoc,
+ attr,
+ hostRelation,
+ dependantRelation
+) {
+ let host = findAccessibleChildByID(accDoc, "host");
+ let dependant1 = findAccessibleChildByID(accDoc, "dependant1");
+ let dependant2 = findAccessibleChildByID(accDoc, "dependant2");
+
+ /**
+ * Test data has the format of:
+ * {
+ * desc {String} description for better logging
+ * attrs {?Array} an optional list of attributes to update
+ * expected {Array} expected relation values for dependant1, dependant2
+ * and host respectively.
+ * }
+ */
+ const tests = [
+ {
+ desc: "No attribute",
+ expected: [null, null, null],
+ },
+ {
+ desc: "Set attribute",
+ attrs: [{ key: attr, value: "dependant1" }],
+ expected: [host, null, dependant1],
+ },
+ {
+ desc: "Change attribute",
+ attrs: [{ key: attr, value: "dependant2" }],
+ expected: [null, host, dependant2],
+ },
+ {
+ desc: "Remove attribute",
+ attrs: [{ key: attr }],
+ expected: [null, null, null],
+ },
+ ];
+
+ for (let { desc, attrs, expected } of tests) {
+ info(desc);
+
+ if (attrs) {
+ for (let { key, value } of attrs) {
+ await invokeSetAttribute(browser, "host", key, value);
+ }
+ }
+
+ await testCachedRelation(dependant1, dependantRelation, expected[0]);
+ await testCachedRelation(dependant2, dependantRelation, expected[1]);
+ await testCachedRelation(host, hostRelation, expected[2]);
+ }
+}