summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/e10s
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /accessible/tests/browser/e10s
parentInitial commit. (diff)
downloadfirefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.tar.xz
firefox-e51783d008170d9ab27d25da98ca3a38b0a41b67.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/tests/browser/e10s')
-rw-r--r--accessible/tests/browser/e10s/browser.toml125
-rw-r--r--accessible/tests/browser/e10s/browser_caching_actions.js295
-rw-r--r--accessible/tests/browser/e10s/browser_caching_attributes.js763
-rw-r--r--accessible/tests/browser/e10s/browser_caching_description.js280
-rw-r--r--accessible/tests/browser/e10s/browser_caching_document_props.js80
-rw-r--r--accessible/tests/browser/e10s/browser_caching_domnodeid.js32
-rw-r--r--accessible/tests/browser/e10s/browser_caching_hyperlink.js228
-rw-r--r--accessible/tests/browser/e10s/browser_caching_innerHTML.js48
-rw-r--r--accessible/tests/browser/e10s/browser_caching_interfaces.js59
-rw-r--r--accessible/tests/browser/e10s/browser_caching_large_update.js66
-rw-r--r--accessible/tests/browser/e10s/browser_caching_name.js542
-rw-r--r--accessible/tests/browser/e10s/browser_caching_position.js194
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations.js291
-rw-r--r--accessible/tests/browser/e10s/browser_caching_relations_002.js366
-rw-r--r--accessible/tests/browser/e10s/browser_caching_states.js552
-rw-r--r--accessible/tests/browser/e10s/browser_caching_table.js665
-rw-r--r--accessible/tests/browser/e10s/browser_caching_text_bounds.js737
-rw-r--r--accessible/tests/browser/e10s/browser_caching_uniqueid.js30
-rw-r--r--accessible/tests/browser/e10s/browser_caching_value.js457
-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.js119
-rw-r--r--accessible/tests/browser/e10s/browser_file_input.js77
-rw-r--r--accessible/tests/browser/e10s/browser_language.js29
-rw-r--r--accessible/tests/browser/e10s/browser_obj_group.js430
-rw-r--r--accessible/tests/browser/e10s/browser_obj_group_002.js390
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js45
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js457
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_canvas.js28
-rw-r--r--accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js105
-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.js84
-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.js192
58 files changed, 10137 insertions, 0 deletions
diff --git a/accessible/tests/browser/e10s/browser.toml b/accessible/tests/browser/e10s/browser.toml
new file mode 100644
index 0000000000..dfac6b5219
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser.toml
@@ -0,0 +1,125 @@
+[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",
+]
+prefs = [
+ "javascript.options.asyncstack_capture_debuggee_only=false",
+ "dom.element.popover.enabled=true"
+]
+
+# Caching tests
+["browser_caching_actions.js"]
+
+["browser_caching_attributes.js"]
+
+["browser_caching_description.js"]
+
+["browser_caching_document_props.js"]
+
+["browser_caching_domnodeid.js"]
+
+["browser_caching_hyperlink.js"]
+
+["browser_caching_innerHTML.js"]
+skip-if = ["os != 'win'"]
+
+["browser_caching_interfaces.js"]
+
+["browser_caching_large_update.js"]
+
+["browser_caching_name.js"]
+
+["browser_caching_position.js"]
+
+["browser_caching_relations.js"]
+
+["browser_caching_relations_002.js"]
+
+["browser_caching_states.js"]
+
+["browser_caching_table.js"]
+
+["browser_caching_text_bounds.js"]
+
+["browser_caching_uniqueid.js"]
+
+["browser_caching_value.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_file_input.js"]
+
+["browser_language.js"]
+
+["browser_obj_group.js"]
+
+["browser_obj_group_002.js"]
+
+# Tree update tests
+["browser_treeupdate_ariadialog.js"]
+
+["browser_treeupdate_ariaowns.js"]
+
+["browser_treeupdate_canvas.js"]
+
+["browser_treeupdate_csscontentvisibility.js"]
+
+["browser_treeupdate_cssoverflow.js"]
+
+["browser_treeupdate_doc.js"]
+
+["browser_treeupdate_gencontent.js"]
+
+["browser_treeupdate_hidden.js"]
+
+["browser_treeupdate_image.js"]
+
+["browser_treeupdate_imagemap.js"]
+
+["browser_treeupdate_list.js"]
+
+["browser_treeupdate_list_editabledoc.js"]
+
+["browser_treeupdate_listener.js"]
+
+["browser_treeupdate_move.js"]
+
+["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"]
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..893c818b75
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_actions.js
@@ -0,0 +1,295 @@
+/* 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>
+
+ <a href="about:mozilla" id="link4" target="_blank" rel="opener">
+ <img src="../moz.png" id="link4img">
+ </a>
+ <a id="link5" onmousedown="">
+ <img src="../moz.png" id="link5img">
+ </a>
+ <a id="link6" onclick="">
+ <img src="../moz.png" id="link6img">
+ </a>
+ <a id="link7" onmouseup="">
+ <img src="../moz.png" id="link7img">
+ </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("link4", ["jump"], gClickEvents);
+ await _testActions("link4img", ["click ancestor"], gClickEvents);
+ await _testActions("link5", ["click"], gClickEvents);
+ await _testActions("link5img", ["click ancestor"], gClickEvents);
+ await _testActions("link6", ["click"], gClickEvents);
+ await _testActions("link6img", ["click ancestor"], gClickEvents);
+ await _testActions("link7", ["click"], gClickEvents);
+ await _testActions("link7img", ["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
+ let link1Acc = findAccessibleChildByID(docAcc, "link1");
+ is(
+ link1Acc.firstChild.getActionName(0),
+ "click ancestor",
+ "linkable child has click ancestor action"
+ );
+ let onRecreation = waitForEvents({
+ expected: [
+ [EVENT_HIDE, link1Acc],
+ [EVENT_SHOW, "link1"],
+ ],
+ });
+ await invokeContentTask(browser, [], () => {
+ let link1 = content.document.getElementById("link1");
+ link1.removeAttribute("href");
+ });
+ await onRecreation;
+ link1Acc = findAccessibleChildByID(docAcc, "link1");
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ 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..139015061f
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_attributes.js
@@ -0,0 +1,763 @@
+/* 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.
+ topLevel: false,
+ iframe: false,
+ remoteIframe: false,
+ }
+);
+
+/**
+ * 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>
+<p>
+ <span id="presentationalSpan" role="none"
+ style="display: block; position: absolute; top: 0; left: 0; translate: 1px;">
+ a
+ </span>
+</p>
+ `,
+ 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"
+ );
+
+ // This span has role="none", but we force a generic Accessible because it
+ // has a transform. role="none" might have been used to avoid exposing
+ // display: block, so ensure we don't expose that.
+ const presentationalSpan = findAccessibleChildByID(
+ docAcc,
+ "presentationalSpan"
+ );
+ testAbsentAttrs(presentationalSpan, { display: "" });
+ },
+ { 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 }
+);
+
+function untilCacheAttrIs(acc, attr, val, msg) {
+ return untilCacheOk(() => {
+ try {
+ return acc.attributes.getStringProperty(attr) == val;
+ } catch (e) {
+ return false;
+ }
+ }, msg);
+}
+
+function untilCacheAttrAbsent(acc, attr, msg) {
+ return untilCacheOk(() => {
+ try {
+ acc.attributes.getStringProperty(attr);
+ } catch (e) {
+ return true;
+ }
+ return false;
+ }, msg);
+}
+
+/**
+ * Test the class attribute.
+ */
+addAccessibleTask(
+ `
+<div id="oneClass" class="c1">oneClass</div>
+<div id="multiClass" class="c1 c2">multiClass</div>
+<div id="noClass">noClass</div>
+<div id="mutate">mutate</div>
+ `,
+ async function (browser, docAcc) {
+ const oneClass = findAccessibleChildByID(docAcc, "oneClass");
+ testAttrs(oneClass, { class: "c1" }, true);
+ const multiClass = findAccessibleChildByID(docAcc, "multiClass");
+ testAttrs(multiClass, { class: "c1 c2" }, true);
+ const noClass = findAccessibleChildByID(docAcc, "noClass");
+ testAbsentAttrs(noClass, { class: "" });
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAbsentAttrs(mutate, { class: "" });
+ info("Adding class to mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").className = "c1 c2";
+ });
+ await untilCacheAttrIs(mutate, "class", "c1 c2", "mutate class correct");
+ info("Removing class from mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("class");
+ });
+ await untilCacheAttrAbsent(mutate, "class", "mutate class not present");
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test the src attribute.
+ */
+const kImgUrl = "https://example.com/a11y/accessible/tests/mochitest/moz.png";
+addAccessibleTask(
+ `
+<img id="noAlt" src="${kImgUrl}">
+<img id="alt" alt="alt" src="${kImgUrl}">
+<img id="mutate">
+ `,
+ async function (browser, docAcc) {
+ const noAlt = findAccessibleChildByID(docAcc, "noAlt");
+ testAttrs(noAlt, { src: kImgUrl }, true);
+ const alt = findAccessibleChildByID(docAcc, "alt");
+ testAttrs(alt, { src: kImgUrl }, true);
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAbsentAttrs(mutate, { src: "" });
+ info("Adding src to mutate");
+ await invokeContentTask(browser, [kImgUrl], url => {
+ content.document.getElementById("mutate").src = url;
+ });
+ await untilCacheAttrIs(mutate, "src", kImgUrl, "mutate src correct");
+ info("Removing src from mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("src");
+ });
+ await untilCacheAttrAbsent(mutate, "src", "mutate src not present");
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test the placeholder attribute.
+ */
+addAccessibleTask(
+ `
+<input id="htmlWithLabel" aria-label="label" placeholder="HTML">
+<input id="htmlNoLabel" placeholder="HTML">
+<input id="ariaWithLabel" aria-label="label" aria-placeholder="ARIA">
+<input id="ariaNoLabel" aria-placeholder="ARIA">
+<input id="both" aria-label="label" placeholder="HTML" aria-placeholder="ARIA">
+<input id="mutate" placeholder="HTML">
+ `,
+ async function (browser, docAcc) {
+ const htmlWithLabel = findAccessibleChildByID(docAcc, "htmlWithLabel");
+ testAttrs(htmlWithLabel, { placeholder: "HTML" }, true);
+ const htmlNoLabel = findAccessibleChildByID(docAcc, "htmlNoLabel");
+ // placeholder is used as name, so not exposed as attribute.
+ testAbsentAttrs(htmlNoLabel, { placeholder: "" });
+ const ariaWithLabel = findAccessibleChildByID(docAcc, "ariaWithLabel");
+ testAttrs(ariaWithLabel, { placeholder: "ARIA" }, true);
+ const ariaNoLabel = findAccessibleChildByID(docAcc, "ariaNoLabel");
+ // No label doesn't impact aria-placeholder.
+ testAttrs(ariaNoLabel, { placeholder: "ARIA" }, true);
+ const both = findAccessibleChildByID(docAcc, "both");
+ testAttrs(both, { placeholder: "HTML" }, true);
+
+ const mutate = findAccessibleChildByID(docAcc, "mutate");
+ testAbsentAttrs(mutate, { placeholder: "" });
+ info("Adding label to mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("aria-label", "label");
+ });
+ await untilCacheAttrIs(
+ mutate,
+ "placeholder",
+ "HTML",
+ "mutate placeholder correct"
+ );
+ info("Removing mutate placeholder");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").removeAttribute("placeholder");
+ });
+ await untilCacheAttrAbsent(
+ mutate,
+ "placeholder",
+ "mutate placeholder not present"
+ );
+ info("Setting mutate aria-placeholder");
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("aria-placeholder", "ARIA");
+ });
+ await untilCacheAttrIs(
+ mutate,
+ "placeholder",
+ "ARIA",
+ "mutate placeholder correct"
+ );
+ info("Setting mutate placeholder");
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("mutate")
+ .setAttribute("placeholder", "HTML");
+ });
+ await untilCacheAttrIs(
+ mutate,
+ "placeholder",
+ "HTML",
+ "mutate placeholder correct"
+ );
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test the ispopup attribute.
+ */
+addAccessibleTask(
+ `<div id="popover" popover>popover</div>`,
+ async function testIspopup(browser, docAcc) {
+ info("Showing popover");
+ let shown = waitForEvent(EVENT_SHOW, "popover");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("popover").showPopover();
+ });
+ let popover = (await shown).accessible;
+ testAttrs(popover, { ispopup: "auto" }, true);
+ info("Setting popover to null");
+ // Setting popover causes the Accessible to be recreated.
+ shown = waitForEvent(EVENT_SHOW, "popover");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("popover").popover = null;
+ });
+ popover = (await shown).accessible;
+ testAbsentAttrs(popover, { ispopup: "" });
+ info("Setting popover to manual and showing");
+ shown = waitForEvent(EVENT_SHOW, "popover");
+ await invokeContentTask(browser, [], () => {
+ const popoverDom = content.document.getElementById("popover");
+ popoverDom.popover = "manual";
+ popoverDom.showPopover();
+ });
+ popover = (await shown).accessible;
+ testAttrs(popover, { ispopup: "manual" }, true);
+ },
+ { chrome: true, topLevel: 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..d489620e16
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_description.js
@@ -0,0 +1,280 @@
+/* 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 }
+);
+
+/**
+ * Test aria-description, including mutations.
+ */
+addAccessibleTask(
+ `<button id="button" aria-description="a">button</button>`,
+ async function (browser, docAcc) {
+ const button = findAccessibleChildByID(docAcc, "button");
+ testDescr(button, "a");
+ info("Changing aria-description");
+ let changed = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeSetAttribute(browser, "button", "aria-description", "b");
+ await changed;
+ testDescr(button, "b");
+ info("Removing aria-description");
+ changed = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeSetAttribute(browser, "button", "aria-description");
+ await changed;
+ testDescr(button, "");
+ info("Setting aria-description");
+ changed = waitForEvent(EVENT_DESCRIPTION_CHANGE, button);
+ await invokeSetAttribute(browser, "button", "aria-description", "c");
+ await changed;
+ testDescr(button, "c");
+ },
+ { 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..cf3a4e5721
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_document_props.js
@@ -0,0 +1,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";
+
+/* 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");
+ is(docAcc.mimeType, "text/html", "Mime type is 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");
+ is(iframeDocAcc.mimeType, "text/html", "Mime type is 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 = "https://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 = "https://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..722cc9a970
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_domnodeid.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";
+
+/**
+ * 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.");
+
+ let contentPromise = invokeContentTask(browser, [], () => {
+ content.document.getElementById("div").id = "foo";
+ });
+ // 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.
+ 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_hyperlink.js b/accessible/tests/browser/e10s/browser_caching_hyperlink.js
new file mode 100644
index 0000000000..4b3f8a1bda
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_hyperlink.js
@@ -0,0 +1,228 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function testLinkIndexAtOffset(id, offset, index) {
+ let htAcc = getAccessible(id, [nsIAccessibleHyperText]);
+ is(
+ htAcc.getLinkIndexAtOffset(offset),
+ index,
+ "Wrong link index at offset " + offset + " for ID " + id + "!"
+ );
+}
+
+function testThis(
+ paragraph,
+ docURI,
+ id,
+ charIndex,
+ expectedLinkIndex,
+ expectedAnchors,
+ expectedURIs,
+ valid = true
+) {
+ testLinkIndexAtOffset(paragraph, charIndex, expectedLinkIndex);
+
+ let linkAcc = paragraph.getLinkAt(expectedLinkIndex);
+ ok(linkAcc, "No accessible for link " + id + "!");
+
+ is(linkAcc.valid, valid, `${id} is valid.`);
+
+ let linkIndex = paragraph.getLinkIndex(linkAcc);
+ is(linkIndex, expectedLinkIndex, "Wrong link index for " + id + "!");
+
+ is(linkAcc.anchorCount, expectedAnchors.length, "Correct number of anchors");
+ for (let i = 0; i < expectedAnchors.length; i++) {
+ let uri = linkAcc.getURI(i);
+ is(
+ (uri ? uri.spec : "").replace(docURI, ""),
+ expectedURIs[i],
+ `Wrong anchor URI at ${i} for "${id}"`
+ );
+ is(
+ getAccessibleDOMNodeID(linkAcc.getAnchor(i)),
+ expectedAnchors[i],
+ `Wrong anchor at ${i} for "${id}"`
+ );
+ }
+}
+
+/**
+ * Test hyperlinks
+ */
+addAccessibleTask(
+ `
+ <p id="testParagraph"><br
+ >Simple link:<br
+ ><a id="NormalHyperlink" href="https://www.mozilla.org">Mozilla Foundation</a><br
+ >ARIA link:<br
+ ><span id="AriaHyperlink" role="link"
+ onclick="window.open('https://www.mozilla.org/');"
+ tabindex="0">Mozilla Foundation Home</span><br
+ >Invalid, non-focusable hyperlink:<br
+ ><span id="InvalidAriaHyperlink" role="link" aria-invalid="true"
+ onclick="window.open('https:/www.mozilla.org/');">Invalid link</span><br
+ >Image map:<br
+ ><map name="atoz_map"><area href="https://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+ coords="17,0,30,14"
+ id="b"
+ shape="rect"></area
+ ><area href="https://www.bbc.co.uk/radio4/atoz/index.shtml#a"
+ coords="0,0,13,14"
+ id="a"
+ shape="rect"></area></map
+ ><img width="447" id="imgmap"
+ height="15"
+ usemap="#atoz_map"
+ src="../letters.gif"></img><br
+ >Empty link:<br
+ ><a id="emptyLink" href=""><img src=""></img></a><br
+ >Link with embedded span<br
+ ><a id="LinkWithSpan" href="https://www.heise.de/"><span lang="de">Heise Online</span></a><br
+ >Named anchor, must not have "linked" state for it to be exposed correctly:<br
+ ><a id="namedAnchor" name="named_anchor">This should never be of state_linked</a>
+ </p>
+ `,
+ function (browser, accDoc) {
+ const paragraph = findAccessibleChildByID(accDoc, "testParagraph", [
+ nsIAccessibleHyperText,
+ ]);
+ is(paragraph.linkCount, 7, "Wrong link count for paragraph!");
+
+ const docURI = accDoc.URL;
+ // normal hyperlink
+ testThis(
+ paragraph,
+ docURI,
+ "NormalHyperlink",
+ 14,
+ 0,
+ ["NormalHyperlink"],
+ ["https://www.mozilla.org/"]
+ );
+
+ // ARIA hyperlink
+ testThis(
+ paragraph,
+ docURI,
+ "AriaHyperlink",
+ 27,
+ 1,
+ ["AriaHyperlink"],
+ [""]
+ );
+
+ // ARIA hyperlink with status invalid
+ testThis(
+ paragraph,
+ docURI,
+ "InvalidAriaHyperlink",
+ 63,
+ 2,
+ ["InvalidAriaHyperlink"],
+ [""],
+ false
+ );
+
+ // image map, but not its link children. They are not part of hypertext.
+ testThis(
+ paragraph,
+ docURI,
+ "imgmap",
+ 76,
+ 3,
+ ["b", "a"],
+ [
+ "https://www.bbc.co.uk/radio4/atoz/index.shtml#b",
+ "https://www.bbc.co.uk/radio4/atoz/index.shtml#a",
+ ]
+ );
+
+ // empty hyperlink
+ testThis(paragraph, docURI, "emptyLink", 90, 4, ["emptyLink"], [""]);
+
+ // normal hyperlink with embedded span
+ testThis(
+ paragraph,
+ docURI,
+ "LinkWithSpan",
+ 116,
+ 5,
+ ["LinkWithSpan"],
+ ["https://www.heise.de/"]
+ );
+
+ // Named anchor
+ testThis(paragraph, docURI, "namedAnchor", 193, 6, ["namedAnchor"], [""]);
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test paragraph with link
+ */
+addAccessibleTask(
+ `
+ <p id="p"><a href="http://mozilla.org">mozilla.org</a></p>
+ `,
+ function (browser, accDoc) {
+ // Paragraph with link
+ const p = findAccessibleChildByID(accDoc, "p", [nsIAccessibleHyperText]);
+ const link = p.getLinkAt(0);
+ is(link, p.getChildAt(0), "Wrong link for p2");
+ is(p.linkCount, 1, "Wrong link count for p2");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * Test paragraph with link
+ */
+addAccessibleTask(
+ `
+ <p id="p"><a href="www">mozilla</a><a href="www">mozilla</a><span> te</span><span>xt </span><a href="www">mozilla</a></p>
+ `,
+ function (browser, accDoc) {
+ // Paragraph with link
+ const p = findAccessibleChildByID(accDoc, "p", [nsIAccessibleHyperText]);
+
+ // getLinkIndexAtOffset, causes the offsets to be cached;
+ testLinkIndexAtOffset(p, 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset(p, 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset(p, 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset(p, 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset(p, 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset(p, 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset(p, 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset(p, 9, 2); // the end, latest link
+
+ // the second pass to make sure link indexes are calculated propertly from
+ // cached offsets.
+ testLinkIndexAtOffset(p, 0, 0); // 1st 'mozilla' link
+ testLinkIndexAtOffset(p, 1, 1); // 2nd 'mozilla' link
+ testLinkIndexAtOffset(p, 2, -1); // ' ' of ' te' text node
+ testLinkIndexAtOffset(p, 3, -1); // 't' of ' te' text node
+ testLinkIndexAtOffset(p, 5, -1); // 'x' of 'xt ' text node
+ testLinkIndexAtOffset(p, 7, -1); // ' ' of 'xt ' text node
+ testLinkIndexAtOffset(p, 8, 2); // 3d 'mozilla' link
+ testLinkIndexAtOffset(p, 9, 2); // the end, latest link
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ 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..7baee32e26
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_innerHTML.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";
+
+/**
+ * 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) {
+ 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: true,
+ remoteIframe: true,
+ }
+);
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..c83d486bc6
--- /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_large_update.js b/accessible/tests/browser/e10s/browser_caching_large_update.js
new file mode 100644
index 0000000000..ccf8a86921
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_large_update.js
@@ -0,0 +1,66 @@
+/* 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 a large update which adds many thousands of Accessibles with a
+ * lot of content in each.
+ */
+addAccessibleTask(
+ `<main id="main" hidden></main>`,
+ async function (browser, docAcc) {
+ let shown = waitForEvent(EVENT_SHOW, "main");
+ await invokeContentTask(browser, [], () => {
+ // Make a long string.
+ let text = "";
+ for (let i = 0; i < 100; ++i) {
+ text += "a";
+ }
+ // Create lots of nodes which include the long string.
+ const contMain = content.document.getElementById("main");
+ // 15000 children of main.
+ for (let w = 0; w < 15000; ++w) {
+ // Each of those goes 9 deep.
+ let parent = contMain;
+ for (let d = 0; d < 10; ++d) {
+ const div = content.document.createElement("div");
+ div.setAttribute("aria-label", `${w} ${d} ${text}`);
+ parent.append(div);
+ parent = div;
+ }
+ }
+ contMain.hidden = false;
+ });
+ const main = (await shown).accessible;
+ is(main.childCount, 15000, "main has correct number of children");
+
+ // We don't want to output passes for every check, since that would output
+ // hundreds of thousands of lines, which slows the test to a crawl. Instead,
+ // output any failures and keep track of overall success/failure.
+ let treeOk = true;
+ function check(val, msg) {
+ if (!val) {
+ ok(false, msg);
+ treeOk = false;
+ }
+ }
+
+ info("Checking tree");
+ for (let w = 0; w < 15000; ++w) {
+ let acc = main.getChildAt(w);
+ let parent = main;
+ for (let d = 0; d < 10; ++d) {
+ check(acc, `Got child ${w} depth ${d}`);
+ const name = `${w} ${d}`;
+ check(acc.name.startsWith(name + " "), `${name}: correct name`);
+ check(acc.parent == parent, `${name}: correct parent`);
+ parent = acc;
+ acc = acc.firstChild;
+ }
+ }
+ // check() sets treeOk to false for any failure.
+ ok(treeOk, "Tree is correct");
+ }
+);
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..55f506b85a
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_name.js
@@ -0,0 +1,542 @@
+/* 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);
+
+/* 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"
+ href=""
+ 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"
+ href=""
+ 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..5de246bb91
--- /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: true,
+ iframe: true,
+ }
+);
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..cc83930830
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations.js
@@ -0,0 +1,291 @@
+/* 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],
+ ["aria-details", RELATION_DETAILS, RELATION_DETAILS_FOR],
+ ["aria-errormessage", RELATION_ERRORMSG, RELATION_ERRORMSG_FOR],
+];
+
+/**
+ * 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/*
+ * 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: true, 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..072656eb5e
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_relations_002.js
@@ -0,0 +1,366 @@
+/* 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: true,
+ remoteIframe: true,
+ topLevel: true,
+ }
+);
+
+/*
+ * 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: 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);
+ 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: true,
+ remoteIframe: true,
+ topLevel: true,
+ }
+);
+
+/**
+ * Test label relations on HTML figure/figcaption.
+ */
+addAccessibleTask(
+ `
+<figure id="figure1">
+ before
+ <figcaption id="caption1">caption1</figcaption>
+ after
+</figure>
+<figure id="figure2" aria-labelledby="label">
+ <figcaption id="caption2">caption2</figure>
+</figure>
+<div id="label">label</div>
+ `,
+ async function (browser, docAcc) {
+ const figure1 = findAccessibleChildByID(docAcc, "figure1");
+ let caption1 = findAccessibleChildByID(docAcc, "caption1");
+ await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1);
+ await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1);
+
+ info("Hiding caption1");
+ let mutated = waitForEvent(EVENT_HIDE, caption1);
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("caption1").hidden = true;
+ });
+ await mutated;
+ await testCachedRelation(figure1, RELATION_LABELLED_BY, null);
+
+ info("Showing caption1");
+ mutated = waitForEvent(EVENT_SHOW, "caption1");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("caption1").hidden = false;
+ });
+ caption1 = (await mutated).accessible;
+ await testCachedRelation(figure1, RELATION_LABELLED_BY, caption1);
+ await testCachedRelation(caption1, RELATION_LABEL_FOR, figure1);
+
+ const figure2 = findAccessibleChildByID(docAcc, "figure2");
+ const caption2 = findAccessibleChildByID(docAcc, "caption2");
+ const label = findAccessibleChildByID(docAcc, "label");
+ await testCachedRelation(figure2, RELATION_LABELLED_BY, [label, caption2]);
+ await testCachedRelation(caption2, RELATION_LABEL_FOR, figure2);
+ await testCachedRelation(label, RELATION_LABEL_FOR, figure2);
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test details relations on popovers and their invokers.
+ */
+addAccessibleTask(
+ `
+<button id="hide" popovertarget="popover" popovertargetaction="hide">hide</button>
+<button id="toggle1" popovertarget="popover">toggle1</button>
+<button id="toggle2">toggle2</button>
+<button id="toggleSibling">toggleSibling</button>
+<div id="popover" popover>popover</div>
+<div id="details">details</div>
+ `,
+ async function testPopover(browser, docAcc) {
+ // The popover is hidden, so nothing should be referring to it.
+ const hide = findAccessibleChildByID(docAcc, "hide");
+ await testCachedRelation(hide, RELATION_DETAILS, []);
+ const toggle1 = findAccessibleChildByID(docAcc, "toggle1");
+ await testCachedRelation(toggle1, RELATION_DETAILS, []);
+ const toggle2 = findAccessibleChildByID(docAcc, "toggle2");
+ await testCachedRelation(toggle2, RELATION_DETAILS, []);
+ const toggleSibling = findAccessibleChildByID(docAcc, "toggleSibling");
+ await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
+
+ info("Showing popover");
+ let shown = waitForEvent(EVENT_SHOW, "popover");
+ toggle1.doAction(0);
+ const popover = (await shown).accessible;
+ await testCachedRelation(toggle1, RELATION_DETAILS, popover);
+ // toggle2 shouldn't have a details relation because it doesn't have a
+ // popovertarget.
+ await testCachedRelation(toggle2, RELATION_DETAILS, []);
+ // hide shouldn't have a details relation because its action is hide.
+ await testCachedRelation(hide, RELATION_DETAILS, []);
+ // toggleSibling shouldn't have a details relation because it is a sibling
+ // of the popover.
+ await testCachedRelation(toggleSibling, RELATION_DETAILS, []);
+ await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
+
+ info("Setting toggle2 popovertargetaction");
+ await invokeSetAttribute(browser, "toggle2", "popovertarget", "popover");
+ await testCachedRelation(toggle2, RELATION_DETAILS, popover);
+ await testCachedRelation(popover, RELATION_DETAILS_FOR, [toggle1, toggle2]);
+
+ info("Removing toggle2 popovertarget");
+ await invokeSetAttribute(browser, "toggle2", "popovertarget", null);
+ await testCachedRelation(toggle2, RELATION_DETAILS, []);
+ await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
+
+ info("Setting aria-details on toggle1");
+ await invokeSetAttribute(browser, "toggle1", "aria-details", "details");
+ const details = findAccessibleChildByID(docAcc, "details");
+ // aria-details overrides popover.
+ await testCachedRelation(toggle1, RELATION_DETAILS, details);
+ await testCachedRelation(popover, RELATION_DETAILS_FOR, []);
+
+ info("Removing aria-details from toggle1");
+ await invokeSetAttribute(browser, "toggle1", "aria-details", null);
+ await testCachedRelation(toggle1, RELATION_DETAILS, popover);
+ await testCachedRelation(popover, RELATION_DETAILS_FOR, toggle1);
+
+ info("Hiding popover");
+ let hidden = waitForEvent(EVENT_HIDE, popover);
+ toggle1.doAction(0);
+ // The relations between toggle1 and popover are removed when popover shuts
+ // down. However, this doesn't cause a cache update notification. Therefore,
+ // to avoid timing out in testCachedRelation, we must wait for a hide event
+ // first.
+ await hidden;
+ await testCachedRelation(toggle1, RELATION_DETAILS, []);
+ },
+ { chrome: false, topLevel: true }
+);
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..37f8c46966
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_states.js
@@ -0,0 +1,552 @@
+/* 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 }
+);
+
+/**
+ * Test caching of the focusable state in iframes which are initially visibility: hidden.
+ */
+addAccessibleTask(
+ `
+<button id="button"></button>
+<span id="span" tabindex="-1">span</span>`,
+ async function (browser, topDocAcc) {
+ info("Changing visibility on iframe");
+ let reordered = waitForEvent(EVENT_REORDER, topDocAcc);
+ await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], iframeId => {
+ content.document.getElementById(iframeId).style.visibility = "";
+ });
+ await reordered;
+ // The iframe doc a11y tree might not be built yet.
+ const iframeDoc = await TestUtils.waitForCondition(() =>
+ findAccessibleChildByID(topDocAcc, DEFAULT_IFRAME_DOC_BODY_ID)
+ );
+ // Log/verify whether this is an in-process or OOP iframe.
+ await comparePIDs(browser, gIsRemoteIframe);
+ const button = findAccessibleChildByID(iframeDoc, "button");
+ testStates(button, STATE_FOCUSABLE);
+ const span = findAccessibleChildByID(iframeDoc, "span");
+ ok(span, "span Accessible exists");
+ testStates(span, STATE_FOCUSABLE);
+ },
+ {
+ topLevel: false,
+ iframe: true,
+ remoteIframe: true,
+ iframeAttrs: { style: "visibility: hidden;" },
+ skipFissionDocLoad: true,
+ }
+);
+
+function checkOpacity(acc, present) {
+ 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 }
+);
+
+/**
+ * Test invalid state determined via DOM.
+ */
+addAccessibleTask(
+ `<input type="email" id="email">`,
+ async function (browser, docAcc) {
+ const email = findAccessibleChildByID(docAcc, "email");
+ info("Focusing email");
+ let focused = waitForEvent(EVENT_FOCUS, email);
+ email.takeFocus();
+ await focused;
+ info("Typing a");
+ let invalidChanged = waitForStateChange(email, STATE_INVALID, true);
+ EventUtils.sendString("a");
+ await invalidChanged;
+ testStates(email, STATE_INVALID);
+ info("Typing @b");
+ invalidChanged = waitForStateChange(email, STATE_INVALID, false);
+ EventUtils.sendString("@b");
+ await invalidChanged;
+ testStates(email, 0, 0, STATE_INVALID);
+ info("Typing backspace");
+ invalidChanged = waitForStateChange(email, STATE_INVALID, true);
+ EventUtils.synthesizeKey("KEY_Backspace");
+ await invalidChanged;
+ testStates(email, STATE_INVALID);
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
+
+/**
+ * Test caching of the expanded state for popover target element.
+ */
+addAccessibleTask(
+ `
+ <button id="show-popover-btn" popovertarget="mypopover" popovertargetaction="show">Show popover</button>
+ <button id="hide-popover-btn" popovertarget="mypopover" popovertargetaction="hide">Hide popover</button>
+ <button id="toggle">toggle</button>
+ <div id="mypopover" popover>
+ Popover content
+ <button id="hide-inside" popovertarget="mypopover" popovertargetaction="hide">Hide inside popover</button>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ const show = findAccessibleChildByID(docAcc, "show-popover-btn");
+ const hide = findAccessibleChildByID(docAcc, "hide-popover-btn");
+ testStates(show, STATE_COLLAPSED, 0);
+ testStates(hide, STATE_COLLAPSED, 0);
+ const toggle = findAccessibleChildByID(docAcc, "toggle");
+ testStates(
+ toggle,
+ 0,
+ 0,
+ STATE_EXPANDED | STATE_COLLAPSED,
+ EXT_STATE_EXPANDABLE
+ );
+
+ info("Setting toggle's popovertarget");
+ let stateChanged = waitForStateChange(
+ toggle,
+ EXT_STATE_EXPANDABLE,
+ true,
+ true
+ );
+ await invokeContentTask(browser, [], () => {
+ content.document
+ .getElementById("toggle")
+ .setAttribute("popovertarget", "mypopover");
+ });
+ await stateChanged;
+
+ // Changes to the popover should fire events on all invokers.
+ const changeEvents = [
+ [EVENT_STATE_CHANGE, show],
+ [EVENT_STATE_CHANGE, hide],
+ [EVENT_STATE_CHANGE, toggle],
+ ];
+ info("Expanding popover");
+ let onShowing = waitForEvents(changeEvents);
+ await show.doAction(0);
+ await onShowing;
+ testStates(show, STATE_EXPANDED, 0);
+ testStates(hide, STATE_EXPANDED, 0);
+ testStates(toggle, STATE_EXPANDED, 0);
+ const hideInside = findAccessibleChildByID(show, "hide-inside");
+ testStates(hideInside, 0, 0, STATE_EXPANDED | STATE_COLLAPSED, 0);
+
+ info("Collapsing popover");
+ let onHiding = waitForEvents(changeEvents);
+ await hide.doAction(0);
+ await onHiding;
+ testStates(hide, STATE_COLLAPSED, 0);
+ testStates(show, STATE_COLLAPSED, 0);
+ testStates(toggle, STATE_COLLAPSED, 0);
+ },
+ { chrome: true, topLevel: true, remoteIframe: 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..9c8bcb9616
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_table.js
@@ -0,0 +1,665 @@
+/* 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("cell").style.border = "1px solid black";
+ content.document.body.offsetTop; // Flush layout.
+ });
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+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");
+ 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");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 1, "table rowCount correct");
+ is(table.columnCount, 1, "table columnCount correct");
+ },
+ {
+ chrome: true,
+ topLevel: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ remoteIframe: true,
+ }
+);
+
+/**
+ * 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 }
+);
+
+/**
+ * Verify that table row and column information is correct when there are
+ * intervening generics between the table and a rowgroup.
+ */
+addAccessibleTask(
+ `
+<div id="table" role="grid">
+ <div role="rowgroup">
+ <div role="row">
+ <div role="columnheader">a</div>
+ </div>
+ </div>
+ <div tabindex="-1" style="height: 1px; overflow: auto;">
+ <div role="rowgroup">
+ <div role="row">
+ <div id="cell" role="gridcell">b</div>
+ </div>
+ </div>
+ </div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+
+ info("Verifying that the table row and column counts are correct.");
+ is(table.rowCount, 2, "table rowCount correct");
+ is(table.columnCount, 1, "table columnCount correct");
+
+ info("Verifying that the cell row and column extents are correct.");
+ const cell = findAccessibleChildByID(docAcc, "cell", [
+ nsIAccessibleTableCell,
+ ]);
+ is(cell.rowExtent, 1, "cell rowExtent correct");
+ is(cell.columnExtent, 1, "cell colExtent correct");
+ is(cell.rowIndex, 1, "cell rowIndex correct");
+ is(cell.columnIndex, 0, "cell columnIndex correct");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Verify that table row and column information is correct when there are
+ * intervening generics between rows and cells.
+ */
+addAccessibleTask(
+ `
+<div id="table" role="grid">
+ <div role="rowgroup">
+ <div role="row">
+ <div role="columnheader">a</div>
+ </div>
+ </div>
+ <div role="rowgroup">
+ <div role="row">
+ <div tabindex="-1" style="height: 1px; overflow: auto;">
+ <div id="cell" role="gridcell">b</div>
+ </div>
+ </div>
+ </div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+
+ info("Verifying that the table row and column counts are correct.");
+ is(table.rowCount, 2, "table rowCount correct");
+ is(table.columnCount, 1, "table columnCount correct");
+
+ info("Verifying that the cell row and column extents are correct.");
+ const cell = findAccessibleChildByID(docAcc, "cell", [
+ nsIAccessibleTableCell,
+ ]);
+ is(cell.rowExtent, 1, "cell rowExtent correct");
+ is(cell.columnExtent, 1, "cell colExtent correct");
+ is(cell.rowIndex, 1, "cell rowIndex correct");
+ is(cell.columnIndex, 0, "cell columnIndex correct");
+ },
+ { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Verify that we don't crash for authoring error like <table role="gridcell">.
+ */
+addAccessibleTask(
+ `<table id="table" role="gridcell">`,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table");
+ ok(table, "Retrieved table Accessible");
+ },
+ { chrome: true, topLevel: true }
+);
+
+/**
+ * Test ARIA tables in SVG.
+ */
+addAccessibleTask(
+ `
+<svg id="table" role="table">
+ <text id="caption" role="caption">caption</text>
+ <g role="row">
+ <text id="a" role="columnheader">a</text>
+ <text id="b" role="columnheader">b</text>
+ </g>
+ <g role="row">
+ <text id="c" role="cell">c</text>
+ <text id="d" role="cell">d</text>
+ </g>
+</svg>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 2, "table rowCount correct");
+ is(table.columnCount, 2, "table columnCount correct");
+ const caption = findAccessibleChildByID(docAcc, "caption");
+ is(table.caption, caption, "table caption correct");
+ testTableIndexes(table, [
+ [0, 1],
+ [2, 3],
+ ]);
+ const cells = {};
+ for (const id of ["a", "b", "c", "d"]) {
+ cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
+ }
+ testHeaderCells([
+ {
+ cell: cells.c,
+ rowHeaderCells: [],
+ columnHeaderCells: [cells.a],
+ },
+ {
+ cell: cells.d,
+ rowHeaderCells: [],
+ columnHeaderCells: [cells.b],
+ },
+ ]);
+ },
+ { chrome: true, topLevel: true, remoteIframe: true }
+);
+
+/**
+ * Verify that we don't crash for authoring error like <tr role="grid">.
+ */
+addAccessibleTask(
+ `
+<table id="table">
+ <tr><th>a</th></tr>
+ <tr role="grid"><td id="b">b</td></tr>
+</table>
+ `,
+ async function (browser, docAcc) {
+ const table = findAccessibleChildByID(docAcc, "table", [
+ nsIAccessibleTable,
+ ]);
+ is(table.rowCount, 1, "table rowCount correct");
+ is(table.columnCount, 1, "table columnCount correct");
+ const b = findAccessibleChildByID(docAcc, "b");
+ let queryOk = false;
+ try {
+ b.QueryInterface(nsIAccessibleTableCell);
+ queryOk = true;
+ } catch (e) {}
+ ok(!queryOk, "No nsIAccessibleTableCell on invalid cell b");
+ }
+);
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..3e37bf7490
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_text_bounds.js
@@ -0,0 +1,737 @@
+/* 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(3);
+
+/* import-globals-from ../../mochitest/layout.js */
+loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
+
+// Note that testTextNode, testChar and testTextRange currently don't handle
+// white space in the code that doesn't get rendered on screen. To work around
+// this, ensure that containers you want to test are all on a single line in the
+// test snippet.
+
+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);
+
+ // Add in the doc's screen coords because getBoundingClientRect
+ // is relative to the document, not the screen. This assumes the doc's
+ // screen coords are correct. We use getBoundsInCSSPixels to avoid factoring
+ // in the DPR ourselves.
+ let x = {};
+ let y = {};
+ let w = {};
+ let h = {};
+ accDoc.getBoundsInCSSPixels(x, y, w, h);
+ r[0] += x.value;
+ r[1] += y.value;
+ 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_SCREEN_RELATIVE);
+ } else {
+ testTextBounds(hyperTextNode, start, end, r, COORDTYPE_SCREEN_RELATIVE);
+ }
+}
+
+/**
+ * Since testTextChar can't handle non-rendered white space, this function first
+ * uses testTextChar to verify the first character and then ensures all
+ * characters thereafter have an incrementing x and a non-0 width.
+ */
+async function testLineWithNonRenderedSpace(docAcc, browser, id, length) {
+ await testChar(docAcc, browser, id, 0);
+ const acc = findAccessibleChildByID(docAcc, id, [nsIAccessibleText]);
+ let prevX = -1;
+ for (let offset = 0; offset < length; ++offset) {
+ const x = {};
+ const y = {};
+ const w = {};
+ const h = {};
+ acc.getCharacterExtents(offset, x, y, w, h, COORDTYPE_SCREEN_RELATIVE);
+ ok(x.value > prevX, `${id}: offset ${offset} x is larger (${x.value})`);
+ prevX = x.value;
+ ok(w.value > 0, `${id}: offset ${offset} width > 0`);
+ }
+}
+
+/**
+ * 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");
+ 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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");
+ //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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: !true }
+);
+
+/**
+ * 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 role="none">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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * 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: true, iframe: !true }
+);
+
+/**
+ * Test magic offsets with GetCharacter/RangeExtents.
+ */
+addAccessibleTask(
+ `<input id="input" value="abc">`,
+ async function (browser, docAcc) {
+ const input = findAccessibleChildByID(docAcc, "input", [nsIAccessibleText]);
+ info("Setting caret and focusing input");
+ let caretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, input);
+ await invokeContentTask(browser, [], () => {
+ const inputDom = content.document.getElementById("input");
+ inputDom.selectionStart = inputDom.selectionEnd = 1;
+ inputDom.focus();
+ });
+ await caretMoved;
+ is(input.caretOffset, 1, "input caretOffset is 1");
+ let expectedX = {};
+ let expectedY = {};
+ let expectedW = {};
+ let expectedH = {};
+ let magicX = {};
+ let magicY = {};
+ let magicW = {};
+ let magicH = {};
+ input.getCharacterExtents(
+ 1,
+ expectedX,
+ expectedY,
+ expectedW,
+ expectedH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ input.getCharacterExtents(
+ nsIAccessibleText.TEXT_OFFSET_CARET,
+ magicX,
+ magicY,
+ magicW,
+ magicH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ Assert.deepEqual(
+ [magicX.value, magicY.value, magicW.value, magicH.value],
+ [expectedX.value, expectedY.value, expectedW.value, expectedH.value],
+ "GetCharacterExtents correct with TEXT_OFFSET_CARET"
+ );
+ input.getRangeExtents(
+ 1,
+ 3,
+ expectedX,
+ expectedY,
+ expectedW,
+ expectedH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ input.getRangeExtents(
+ nsIAccessibleText.TEXT_OFFSET_CARET,
+ nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT,
+ magicX,
+ magicY,
+ magicW,
+ magicH,
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ Assert.deepEqual(
+ [magicX.value, magicY.value, magicW.value, magicH.value],
+ [expectedX.value, expectedY.value, expectedW.value, expectedH.value],
+ "GetRangeExtents correct with TEXT_OFFSET_CARET/END_OF_TEXT"
+ );
+ },
+ { chrome: true, topLevel: true, remoteIframe: !true }
+);
+
+/**
+ * Test wrapped text and pre-formatted text beginning with an empty line.
+ */
+addAccessibleTask(
+ `
+<style>
+ #wrappedText {
+ width: 3ch;
+ font-family: monospace;
+ }
+</style>
+<p id="wrappedText"><a href="https://example.com/">a</a>b cd</p>
+<p id="emptyFirstLine" style="white-space: pre-line;">
+foo</p>
+ `,
+ async function (browser, docAcc) {
+ await testChar(docAcc, browser, "wrappedText", 0);
+ await testChar(docAcc, browser, "wrappedText", 1);
+ await testChar(docAcc, browser, "wrappedText", 2);
+ await testChar(docAcc, browser, "wrappedText", 3);
+ await testChar(docAcc, browser, "wrappedText", 4);
+
+ // We can't use testChar for emptyFirstLine because it doesn't handle white
+ // space properly. Instead, verify that the first character is at the top
+ // left of the text leaf.
+ const emptyFirstLine = findAccessibleChildByID(docAcc, "emptyFirstLine", [
+ nsIAccessibleText,
+ ]);
+ const emptyFirstLineLeaf = emptyFirstLine.firstChild;
+ const leafX = {};
+ const leafY = {};
+ emptyFirstLineLeaf.getBounds(leafX, leafY, {}, {});
+ testTextPos(
+ emptyFirstLine,
+ 0,
+ [leafX.value, leafY.value],
+ COORDTYPE_SCREEN_RELATIVE
+ );
+ },
+ { chrome: true, topLevel: true, remoteIframe: !true }
+);
+
+/**
+ * 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><code id="t" role="group">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: true,
+ iframe: true,
+ }
+);
+
+/**
+ * Test character bounds where content white space isn't rendered.
+ */
+addAccessibleTask(
+ `
+<p id="single">a b</p>
+<p id="multi"><ins>a </ins>
+b</p>
+<pre id="pre">a b</pre>
+ `,
+ async function (browser, docAcc) {
+ await testLineWithNonRenderedSpace(docAcc, browser, "single", 3);
+ await testLineWithNonRenderedSpace(docAcc, browser, "multi", 2);
+ for (let offset = 0; offset < 4; ++offset) {
+ await testChar(docAcc, browser, "pre", offset);
+ }
+ },
+ { chrome: true, topLevel: true }
+);
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..92eb2fe998
--- /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..2b968b5948
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_caching_value.js
@@ -0,0 +1,457 @@
+/* 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 */
+loadScripts({ name: "states.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 currentValue is called",
+ id: "slider",
+ async action(browser, acc) {
+ acc.QueryInterface(nsIAccessibleValue);
+ acc.currentValue = 4;
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: ["4", 4, 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: "Setting currentValue on a progress accessible should fail",
+ id: "progress",
+ async action(browser, acc) {
+ acc.QueryInterface(nsIAccessibleValue);
+ try {
+ acc.currentValue = 25;
+ ok(false, "Setting currValue on progress element should fail");
+ } catch (e) {}
+ },
+ 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: "Value should change when currentValue is called",
+ id: "range",
+ async action(browser, acc) {
+ acc.QueryInterface(nsIAccessibleValue);
+ acc.currentValue = 4;
+ },
+ waitFor: EVENT_VALUE_CHANGE,
+ expected: "4",
+ },
+ {
+ 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",
+ },
+];
+
+/**
+ * Like testValue in accessible/tests/mochitest/value.js, but waits for cache
+ * updates.
+ */
+async function testValue(acc, value, currValue, minValue, maxValue, minIncr) {
+ const pretty = prettyName(acc);
+ await untilCacheIs(() => acc.value, value, `Wrong value of ${pretty}`);
+
+ await untilCacheIs(
+ () => acc.currentValue,
+ currValue,
+ `Wrong current value of ${pretty}`
+ );
+ await untilCacheIs(
+ () => acc.minimumValue,
+ minValue,
+ `Wrong minimum value of ${pretty}`
+ );
+ await untilCacheIs(
+ () => acc.maximumValue,
+ maxValue,
+ `Wrong maximum value of ${pretty}`
+ );
+ await untilCacheIs(
+ () => acc.minimumIncrement,
+ minIncr,
+ `Wrong minimum increment value of ${pretty}`
+ );
+}
+
+/**
+ * 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, acc);
+ } else if (attrs) {
+ for (let { attr, value } of attrs) {
+ await invokeSetAttribute(browser, id, attr, value);
+ }
+ }
+
+ await onUpdate;
+ if (Array.isArray(expected)) {
+ acc.QueryInterface(nsIAccessibleValue);
+ await 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) {
+ let 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");
+ let onRecreation = waitForEvents({
+ expected: [
+ [EVENT_HIDE, link],
+ [EVENT_SHOW, "link"],
+ ],
+ });
+ await invokeSetAttribute(browser, "link", "href");
+ await onRecreation;
+ link = findAccessibleChildByID(docAcc, "link");
+ await untilCacheIs(() => link.value, "", "link value empty after removal");
+
+ info("Setting link href");
+ onRecreation = waitForEvents({
+ expected: [
+ [EVENT_HIDE, link],
+ [EVENT_SHOW, "link"],
+ ],
+ });
+ await invokeSetAttribute(browser, "link", "href", "https://example.com/");
+ await onRecreation;
+ link = findAccessibleChildByID(docAcc, "link");
+ 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..046a7706e3
--- /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..dff6586bf3
--- /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..77bd70c0f6
--- /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..fb03ce2329
--- /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..a510c5b9b5
--- /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..f39ecea8c4
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_events_textchange.js
@@ -0,0 +1,119 @@
+/* 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_file_input.js b/accessible/tests/browser/e10s/browser_file_input.js
new file mode 100644
index 0000000000..238e48740e
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_file_input.js
@@ -0,0 +1,77 @@
+/* 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 });
+
+addAccessibleTask(
+ `
+<input type="file" id="noName">
+<input type="file" id="ariaLabel" aria-label="ariaLabel">
+<label>wrappingLabel <input type="file" id="wrappingLabel"></label>
+<label for="labelFor">labelFor</label> <input type="file" id="labelFor">
+<input type="file" id="title" title="title">
+ `,
+ async function (browser, docAcc) {
+ const browseButton = "Browse…";
+ const noFileSuffix = `${browseButton} No file selected.`;
+ const noName = findAccessibleChildByID(docAcc, "noName");
+ testName(noName, noFileSuffix);
+ const ariaLabel = findAccessibleChildByID(docAcc, "ariaLabel");
+ testName(ariaLabel, `ariaLabel ${noFileSuffix}`);
+ const wrappingLabel = findAccessibleChildByID(docAcc, "wrappingLabel");
+ testName(wrappingLabel, `wrappingLabel ${noFileSuffix}`);
+ const labelFor = findAccessibleChildByID(docAcc, "labelFor");
+ testName(labelFor, `labelFor ${noFileSuffix}`);
+ const title = findAccessibleChildByID(docAcc, "title");
+ testName(title, noFileSuffix);
+ testDescr(title, "title");
+
+ // Test that the name of the button changes correctly when a file is chosen.
+ function chooseFile(id) {
+ return invokeContentTask(browser, [id], contentId => {
+ const MockFilePicker = content.SpecialPowers.MockFilePicker;
+ MockFilePicker.init(content);
+ MockFilePicker.useBlobFile();
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ const input = content.document.getElementById(contentId);
+ const inputReceived = new Promise(resolve =>
+ input.addEventListener(
+ "input",
+ event => {
+ MockFilePicker.cleanup();
+ resolve(event.target.files[0].name);
+ },
+ { once: true }
+ )
+ );
+ input.click();
+ return inputReceived;
+ });
+ }
+
+ info("noName: Choosing file");
+ let nameChanged = waitForEvent(EVENT_NAME_CHANGE, "noName");
+ const fn = await chooseFile("noName");
+ // e.g. "Browse…helloworld.txt"
+ const withFileSuffix = `${browseButton} ${fn}`;
+ await nameChanged;
+ testName(noName, withFileSuffix);
+
+ info("ariaLabel: Choosing file");
+ nameChanged = waitForEvent(EVENT_NAME_CHANGE, "ariaLabel");
+ await chooseFile("ariaLabel");
+ await nameChanged;
+ testName(ariaLabel, `ariaLabel ${withFileSuffix}`);
+
+ info("wrappingLabel: Choosing file");
+ nameChanged = waitForEvent(EVENT_NAME_CHANGE, "wrappingLabel");
+ await chooseFile("wrappingLabel");
+ await nameChanged;
+ testName(wrappingLabel, `wrappingLabel ${withFileSuffix}`);
+ },
+ { topLevel: true, chrome: true }
+);
diff --git a/accessible/tests/browser/e10s/browser_language.js b/accessible/tests/browser/e10s/browser_language.js
new file mode 100644
index 0000000000..684d915693
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_language.js
@@ -0,0 +1,29 @@
+/* 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(
+ `
+<script>
+ // We can't include the html element in snippets, so set lang on it here.
+ document.documentElement.lang = "en";
+</script>
+<div id="inheritEn"></div>
+<div id="de" lang="de">
+ <div id="inheritDe"></div>
+ <div id="fr" lang="fr"></div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ is(docAcc.language, "en", "Document language correct");
+ const inheritEn = findAccessibleChildByID(docAcc, "inheritEn");
+ is(inheritEn.language, "en", "inheritEn language correct");
+ const de = findAccessibleChildByID(docAcc, "de");
+ is(de.language, "de", "de language correct");
+ const fr = findAccessibleChildByID(docAcc, "fr");
+ is(fr.language, "fr", "fr language correct");
+ },
+ { chrome: true, topLevel: 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..7e22b8b491
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_obj_group.js
@@ -0,0 +1,430 @@
+/* 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
diff --git a/accessible/tests/browser/e10s/browser_obj_group_002.js b/accessible/tests/browser/e10s/browser_obj_group_002.js
new file mode 100644
index 0000000000..54cad4a019
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_obj_group_002.js
@@ -0,0 +1,390 @@
+/* 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 });
+
+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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ 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: true,
+ iframe: true,
+ remoteIframe: true,
+ chrome: true,
+ }
+);
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..6d5995531e
--- /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..78f52d3162
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js
@@ -0,0 +1,457 @@
+/* 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: [] }] }],
+ },
+ ],
+ },
+ ],
+ });
+ }
+);
+
+// Verify that removing the parent of a DOM-sibling aria-owned child keeps the
+// formerly-owned child in the tree.
+addAccessibleTask(
+ `<input id='x'></input><div aria-owns='x'></div>`,
+ async function (browser, accDoc) {
+ testAccessibleTree(accDoc, {
+ DOCUMENT: [{ SECTION: [{ ENTRY: [] }] }],
+ });
+
+ info("Removing the div that aria-owns a DOM sibling");
+ let onReorder = waitForEvent(EVENT_REORDER, accDoc);
+ await invokeContentTask(browser, [], () => {
+ content.document.querySelector("div").remove();
+ });
+ await onReorder;
+
+ info("Verifying that the formerly-owned child is still present");
+ testAccessibleTree(accDoc, {
+ DOCUMENT: [{ ENTRY: [] }],
+ });
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that removing the parent of multiple DOM-sibling aria-owned children
+// keeps all formerly-owned children in the tree.
+addAccessibleTask(
+ `<input id='x'></input><input id='y'><div aria-owns='x y'></div>`,
+ async function (browser, accDoc) {
+ testAccessibleTree(accDoc, {
+ DOCUMENT: [
+ {
+ SECTION: [{ ENTRY: [] }, { ENTRY: [] }],
+ },
+ ],
+ });
+
+ info("Removing the div that aria-owns DOM siblings");
+ let onReorder = waitForEvent(EVENT_REORDER, accDoc);
+ await invokeContentTask(browser, [], () => {
+ content.document.querySelector("div").remove();
+ });
+ await onReorder;
+
+ info("Verifying that the formerly-owned children are still present");
+ testAccessibleTree(accDoc, {
+ DOCUMENT: [{ ENTRY: [] }, { ENTRY: [] }],
+ });
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that reordering owned elements by changing the aria-owns attribute
+// properly reorders owned elements.
+addAccessibleTask(
+ `
+<div id="container" aria-owns="b d c a">
+ <div id="a" role="button"></div>
+ <div id="b" role="checkbox"></div>
+</div>
+<div id="c" role="radio"></div>
+<div id="d"></div>`,
+ async function (browser, accDoc) {
+ testAccessibleTree(accDoc, {
+ DOCUMENT: [
+ {
+ SECTION: [
+ { CHECKBUTTON: [] }, // b
+ { SECTION: [] }, // d
+ { RADIOBUTTON: [] }, // c
+ { PUSHBUTTON: [] }, // a
+ ],
+ },
+ ],
+ });
+
+ info("Removing the div that aria-owns other elements");
+ let onReorder = waitForEvent(EVENT_REORDER, accDoc);
+ await invokeContentTask(browser, [], () => {
+ content.document.querySelector("#container").remove();
+ });
+ await onReorder;
+
+ info(
+ "Verify DOM children are removed, order of remaining elements is correct"
+ );
+ testAccessibleTree(accDoc, {
+ DOCUMENT: [
+ { RADIOBUTTON: [] }, // c
+ { SECTION: [] }, // d
+ ],
+ });
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+// Verify that we avoid sending unwanted hide events when doing multiple
+// aria-owns relocations in a single tick. Note that we're avoiding testing
+// chrome here since parent process locals don't track moves in the same way,
+// meaning our mechanism for avoiding duplicate hide events doesn't work.
+addAccessibleTask(
+ `
+<div id='b' aria-owns='a'></div>
+<div id='d'></div>
+<dd id='f'>
+ <div id='a' aria-owns='d'></div>
+</dd>
+ `,
+ async function (browser, accDoc) {
+ const b = findAccessibleChildByID(accDoc, "b");
+ const waitFor = {
+ expected: [
+ [EVENT_HIDE, b],
+ [EVENT_SHOW, "d"],
+ [EVENT_REORDER, accDoc],
+ ],
+ unexpected: [
+ [EVENT_HIDE, "d"],
+ [EVENT_REORDER, "a"],
+ ],
+ };
+ info(
+ "Verifying that events are fired properly after doing two aria-owns relocations"
+ );
+ await contentSpawnMutation(browser, waitFor, function () {
+ content.document.querySelector("#b").remove();
+ content.document.querySelector("#f").remove();
+ });
+ },
+ { chrome: false, iframe: true, remoteIframe: true }
+);
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..ad7338f725
--- /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_csscontentvisibility.js b/accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js
new file mode 100644
index 0000000000..8a1f93d0be
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js
@@ -0,0 +1,105 @@
+/* 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 = `
+ <style>
+ #target {
+ width: 150px;
+ height: 150px;
+ background-color: lightblue;
+ }
+ #child {
+ width: 100px;
+ height: 100px;
+ background-color: lightgreen;
+ }
+ #content-child {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ display: contents;
+ }
+ .hidden {
+ content-visibility: hidden;
+ }
+ .auto {
+ content-visibility: auto;
+ }
+ #hidden-subtree-2 {
+ visibility: hidden;
+ }
+ </style>
+ <div class="hidden" id="target">
+ <div id="child">A</div>
+ <div id="content-child">B</div>
+ <div id="hidden-subtree-1" class="hidden">C</div>
+ <div id="hidden-subtree-2">D</div>
+ <div id="shadow-host"></div>
+ </div>
+ <script>
+ const host = document.querySelector("#shadow-host");
+ const shadowRoot = host.attachShadow({ mode: "open" });
+ shadowRoot.innerHTML = "<div id='shadowDiv'>E</div>";
+ </script>
+ `;
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["layout.css.content-visibility.enabled", true]],
+ });
+});
+
+async function setContentVisibility(browser, value) {
+ let mutationPromise = (() => {
+ switch (value) {
+ case "hidden":
+ return waitForEvent(EVENT_REORDER, "target");
+ case "auto":
+ return waitForEvents({
+ expected: [
+ [EVENT_REORDER, "child"],
+ [EVENT_REORDER, "content-child"],
+ [EVENT_REORDER, "shadowDiv"],
+ [EVENT_REORDER, "target"],
+ ],
+ });
+ default:
+ throw new Error(`unexpected content-visibility: ${value}`);
+ }
+ })();
+
+ // Change the value of `content-visibility` property for the target
+ info(`Setting content-visibility: ${value}`);
+ await invokeSetAttribute(browser, "target", "class", value);
+ await mutationPromise;
+}
+
+addAccessibleTask(
+ snippet,
+ async function (browser, accDoc) {
+ const target = findAccessibleChildByID(accDoc, "target");
+
+ info("Initial Accessibility Structure Test");
+ testAccessibleTree(target, { SECTION: [] });
+
+ await setContentVisibility(browser, "auto");
+ testAccessibleTree(target, {
+ SECTION: [
+ { SECTION: [{ TEXT_LEAF: [] }] } /* child */,
+ { SECTION: [{ TEXT_LEAF: [] }] } /* content-child */,
+ { SECTION: [] } /* hidden-subtree-1 */,
+ { SECTION: [{ SECTION: [{ TEXT_LEAF: [] }] }] } /* shadow-host */,
+ ],
+ });
+
+ await setContentVisibility(browser, "hidden");
+ testAccessibleTree(target, { SECTION: [] });
+ },
+ { iframe: true, remoteIframe: true, chrome: 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..4d18f1c08d
--- /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..982b039762
--- /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..95406d96cf
--- /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..d3817a003b
--- /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..cf45de65e0
--- /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..82fbd3427e
--- /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..2ca14d5572
--- /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..dd678d93fa
--- /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..735f7871af
--- /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..8ed6188ef3
--- /dev/null
+++ b/accessible/tests/browser/e10s/browser_treeupdate_move.js
@@ -0,0 +1,84 @@
+/* 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 }
+);
+
+/**
+ * Test that moving a subtree containing an iframe doesn't cause assertions or
+ * crashes. Note that aria-owns moves Accessibles even if it is set before load.
+ */
+addAccessibleTask(
+ `
+<div id="container">
+ <iframe id="iframe"></iframe>
+ <div aria-owns="iframe"></div>
+</div>
+ `,
+ async function (browser, docAcc) {
+ const container = findAccessibleChildByID(docAcc, "container");
+ testAccessibleTree(container, {
+ SECTION: [{ SECTION: [{ INTERNAL_FRAME: [{ DOCUMENT: [] }] }] }],
+ });
+ },
+ { topLevel: 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..ec7eed0919
--- /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..6b5246f0bf
--- /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..a82fc4c04d
--- /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..c188a06044
--- /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..0c617e7026
--- /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..636a00e210
--- /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..78ab47cd51
--- /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..089391523d
--- /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: absolute; 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..18a65d8192
--- /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" href=""></a> <a href=""><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..bdbcb7445f
--- /dev/null
+++ b/accessible/tests/browser/e10s/head.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";
+
+/* exported testCachedRelation, testRelated */
+
+// Load the shared-head file first.
+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]);
+ }
+}