From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- accessible/tests/browser/e10s/browser.toml | 125 ++++ .../tests/browser/e10s/browser_caching_actions.js | 295 ++++++++ .../browser/e10s/browser_caching_attributes.js | 763 +++++++++++++++++++++ .../browser/e10s/browser_caching_description.js | 280 ++++++++ .../browser/e10s/browser_caching_document_props.js | 80 +++ .../browser/e10s/browser_caching_domnodeid.js | 32 + .../browser/e10s/browser_caching_hyperlink.js | 228 ++++++ .../browser/e10s/browser_caching_innerHTML.js | 48 ++ .../browser/e10s/browser_caching_interfaces.js | 59 ++ .../browser/e10s/browser_caching_large_update.js | 66 ++ .../tests/browser/e10s/browser_caching_name.js | 542 +++++++++++++++ .../tests/browser/e10s/browser_caching_position.js | 194 ++++++ .../browser/e10s/browser_caching_relations.js | 291 ++++++++ .../browser/e10s/browser_caching_relations_002.js | 366 ++++++++++ .../tests/browser/e10s/browser_caching_states.js | 552 +++++++++++++++ .../tests/browser/e10s/browser_caching_table.js | 665 ++++++++++++++++++ .../browser/e10s/browser_caching_text_bounds.js | 737 ++++++++++++++++++++ .../tests/browser/e10s/browser_caching_uniqueid.js | 30 + .../tests/browser/e10s/browser_caching_value.js | 457 ++++++++++++ .../browser/e10s/browser_events_announcement.js | 30 + .../tests/browser/e10s/browser_events_caretmove.js | 22 + .../tests/browser/e10s/browser_events_hide.js | 44 ++ .../tests/browser/e10s/browser_events_show.js | 22 + .../browser/e10s/browser_events_statechange.js | 71 ++ .../browser/e10s/browser_events_textchange.js | 119 ++++ .../tests/browser/e10s/browser_file_input.js | 77 +++ accessible/tests/browser/e10s/browser_language.js | 29 + accessible/tests/browser/e10s/browser_obj_group.js | 430 ++++++++++++ .../tests/browser/e10s/browser_obj_group_002.js | 390 +++++++++++ .../browser/e10s/browser_treeupdate_ariadialog.js | 45 ++ .../browser/e10s/browser_treeupdate_ariaowns.js | 457 ++++++++++++ .../browser/e10s/browser_treeupdate_canvas.js | 28 + .../browser_treeupdate_csscontentvisibility.js | 105 +++ .../browser/e10s/browser_treeupdate_cssoverflow.js | 60 ++ .../tests/browser/e10s/browser_treeupdate_doc.js | 320 +++++++++ .../browser/e10s/browser_treeupdate_gencontent.js | 94 +++ .../browser/e10s/browser_treeupdate_hidden.js | 32 + .../tests/browser/e10s/browser_treeupdate_image.js | 192 ++++++ .../browser/e10s/browser_treeupdate_imagemap.js | 190 +++++ .../tests/browser/e10s/browser_treeupdate_list.js | 52 ++ .../e10s/browser_treeupdate_list_editabledoc.js | 48 ++ .../browser/e10s/browser_treeupdate_listener.js | 38 + .../tests/browser/e10s/browser_treeupdate_move.js | 84 +++ .../browser/e10s/browser_treeupdate_optgroup.js | 100 +++ .../browser/e10s/browser_treeupdate_removal.js | 58 ++ .../e10s/browser_treeupdate_select_dropdown.js | 73 ++ .../tests/browser/e10s/browser_treeupdate_table.js | 48 ++ .../browser/e10s/browser_treeupdate_textleaf.js | 38 + .../browser/e10s/browser_treeupdate_visibility.js | 342 +++++++++ .../browser/e10s/browser_treeupdate_whitespace.js | 69 ++ .../browser/e10s/doc_treeupdate_ariadialog.html | 23 + .../browser/e10s/doc_treeupdate_ariaowns.html | 44 ++ .../browser/e10s/doc_treeupdate_imagemap.html | 21 + .../browser/e10s/doc_treeupdate_removal.xhtml | 11 + .../browser/e10s/doc_treeupdate_visibility.html | 78 +++ .../browser/e10s/doc_treeupdate_whitespace.html | 10 + accessible/tests/browser/e10s/fonts/Ahem.sjs | 241 +++++++ accessible/tests/browser/e10s/head.js | 192 ++++++ 58 files changed, 10137 insertions(+) create mode 100644 accessible/tests/browser/e10s/browser.toml create mode 100644 accessible/tests/browser/e10s/browser_caching_actions.js create mode 100644 accessible/tests/browser/e10s/browser_caching_attributes.js create mode 100644 accessible/tests/browser/e10s/browser_caching_description.js create mode 100644 accessible/tests/browser/e10s/browser_caching_document_props.js create mode 100644 accessible/tests/browser/e10s/browser_caching_domnodeid.js create mode 100644 accessible/tests/browser/e10s/browser_caching_hyperlink.js create mode 100644 accessible/tests/browser/e10s/browser_caching_innerHTML.js create mode 100644 accessible/tests/browser/e10s/browser_caching_interfaces.js create mode 100644 accessible/tests/browser/e10s/browser_caching_large_update.js create mode 100644 accessible/tests/browser/e10s/browser_caching_name.js create mode 100644 accessible/tests/browser/e10s/browser_caching_position.js create mode 100644 accessible/tests/browser/e10s/browser_caching_relations.js create mode 100644 accessible/tests/browser/e10s/browser_caching_relations_002.js create mode 100644 accessible/tests/browser/e10s/browser_caching_states.js create mode 100644 accessible/tests/browser/e10s/browser_caching_table.js create mode 100644 accessible/tests/browser/e10s/browser_caching_text_bounds.js create mode 100644 accessible/tests/browser/e10s/browser_caching_uniqueid.js create mode 100644 accessible/tests/browser/e10s/browser_caching_value.js create mode 100644 accessible/tests/browser/e10s/browser_events_announcement.js create mode 100644 accessible/tests/browser/e10s/browser_events_caretmove.js create mode 100644 accessible/tests/browser/e10s/browser_events_hide.js create mode 100644 accessible/tests/browser/e10s/browser_events_show.js create mode 100644 accessible/tests/browser/e10s/browser_events_statechange.js create mode 100644 accessible/tests/browser/e10s/browser_events_textchange.js create mode 100644 accessible/tests/browser/e10s/browser_file_input.js create mode 100644 accessible/tests/browser/e10s/browser_language.js create mode 100644 accessible/tests/browser/e10s/browser_obj_group.js create mode 100644 accessible/tests/browser/e10s/browser_obj_group_002.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_ariadialog.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_ariaowns.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_canvas.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_csscontentvisibility.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_cssoverflow.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_doc.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_gencontent.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_hidden.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_image.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_imagemap.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_list.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_list_editabledoc.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_listener.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_move.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_optgroup.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_removal.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_select_dropdown.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_table.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_textleaf.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_visibility.js create mode 100644 accessible/tests/browser/e10s/browser_treeupdate_whitespace.js create mode 100644 accessible/tests/browser/e10s/doc_treeupdate_ariadialog.html create mode 100644 accessible/tests/browser/e10s/doc_treeupdate_ariaowns.html create mode 100644 accessible/tests/browser/e10s/doc_treeupdate_imagemap.html create mode 100644 accessible/tests/browser/e10s/doc_treeupdate_removal.xhtml create mode 100644 accessible/tests/browser/e10s/doc_treeupdate_visibility.html create mode 100644 accessible/tests/browser/e10s/doc_treeupdate_whitespace.html create mode 100644 accessible/tests/browser/e10s/fonts/Ahem.sjs create mode 100644 accessible/tests/browser/e10s/head.js (limited to 'accessible/tests/browser/e10s') 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( + ` + + + + linkable textleaf accessible +
linkable textleaf accessible
+ + + image in link + + + + + + + + + + + + + + + +
+ + +
+ +

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

text

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

+ + a + +

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

content

+ + + + +
+ `, + 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( + ` +
currentTrue
+
currentFalse
+
currentPage
+
currentBlah
+
haspopup
+
+
+
positive
+
+
+
+
+
negative
+
+
+
+
+
invalid
+
+
+
foo
+
mutate
+ `, + 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( + ` +
knownRole
+
emptyRole
+
unknownRole
+
multiRole
+
landmarkMarkup
+
landmarkMarkupWithRole
+
landmarkMarkupWithEmptyRole
+
markup
+ +
markupWithEmptyRole
+ `, + 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( + ` +

noLive

+

liveMarkup

+

ariaLive

+

liveRole

+

nonLiveRole

+

other

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

withId

+

noId

+ `, + 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( + ` +
+
+
+ `, + 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( + ` +
oneClass
+
multiClass
+
noClass
+
mutate
+ `, + 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( + ` + +alt + + `, + 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( + ` + + + + + + + `, + 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( + `
popover
`, + 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( + ` +

aria description

+

another description

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


Simple link:
Mozilla Foundation
ARIA link:
Mozilla Foundation Home
Invalid, non-focusable hyperlink:
Invalid link
Image map:

Empty link:

Link with embedded span
Heise Online
Named anchor, must not have "linked" state for it to be exposed correctly:
This should never be of state_linked +

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

mozilla.org

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

mozillamozilla text mozilla

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

test

+xy + `, + 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"), + "xy", + "math cached html is correct" + ); + + info("Mutating math"); + await invokeContentTask(browser, [], () => { + content.document.querySelectorAll("mi")[1].textContent = "z"; + }); + await untilCacheIs( + () => math.cache.getStringProperty("html"), + "xz", + "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( + ` + + + + + +
hello
+ `, + 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( + `
`, + 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: ` + test2 + test3 + + `, + expected: ["test2 test3", "test1", "test4", "press me", "test5"], + }, + { + id: "btn", + ruleset: "HTMLInputButton", + markup: ` + test2 + test3 + + `, + expected: [ + "test2 test3", + "test1", + "test4", + "name from value", + "name from title", + ], + }, + { + id: "btn-submit", + ruleset: "HTMLInputSubmit", + markup: ` + test2 + test3 + + `, + expected: ["test2 test3", "test1", "test4", "name from value"], + }, + { + id: "btn-reset", + ruleset: "HTMLInputReset", + markup: ` + test2 + test3 + + `, + expected: ["test2 test3", "test1", "test4", "name from value"], + }, + { + id: "btn-image", + ruleset: "HTMLInputImage", + markup: ` + test2 + test3 + + `, + expected: [ + "test2 test3", + "test1", + "test4", + "name from alt", + "name from value", + "name from title", + ], + }, + { + id: "btn-image", + ruleset: "HTMLInputImageNoValidSrc", + markup: ` + test2 + test3 + + `, + expected: [ + "test2 test3", + "test1", + "test4", + "name from alt", + "name from value", + ], + }, + { + id: "opt", + ruleset: "HTMLOption", + markup: ` + test2 + test3 + `, + expected: ["test2 test3", "test1", "test4", "option1", "test5"], + }, + { + id: "img", + ruleset: "HTMLImg", + markup: ` + test2 + test3 + Mozilla logo`, + expected: [ + "test2 test3", + "Logo of Mozilla", + "Mozilla logo", + "This is a logo", + ], + }, + { + id: "tc", + ruleset: "HTMLElm", + markup: ` + test2 + test3 + + + + + +
+

This is a paragraph

+ This is a link +
    +
  • This is a list
  • +
+
`, + expected: ["test2 test3", "test1", "test5"], + }, + { + id: "gc", + ruleset: "HTMLARIAGridCell", + markup: ` + test2 + test3 + + + + + +
+

This is a paragraph

+ This is a link +
    +
  • Listitem1
  • +
  • Listitem2
  • +
+
`, + 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: ` + lby_tst6_1 + lby_tst6_2 + + + + + + + +
caption_tst6
cell1cell2
`, + expected: [ + "lby_tst6_1 lby_tst6_2", + "arialabel_tst6", + "caption_tst6", + "summary_tst6", + "title_tst6", + ], + }, + { + id: "btn", + ruleset: "CSSContent", + markup: ` +
+ + +
`, + expected: ["do not press me", "press me"], + }, + { + // TODO: uncomment when Bug-1256382 is resoved. + // id: 'li', + // ruleset: 'CSSContent', + // markup: ` + // + // `, + // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`] + // }, { + id: "a", + ruleset: "HTMLLink", + markup: ` + test2 + test3 + test5`, + expected: ["test2 test3", "test1", "test5", "test4"], + }, + { + id: "a-img", + ruleset: "HTMLLinkImage", + markup: ` + test2 + test3 + test5`, + 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( + ` + + + `, + 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( + ` + + + `, + async function (browser, docAcc) { + testStates(docAcc, STATE_FOCUSED); + } +); + +/** + * Test caching of the focused state in iframes. + */ +addAccessibleTask( + ` + + `, + 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( + ` + +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( + ` +
hello world
+ `, + 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( + `
`, + 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( + ``, + 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,'; + }); + 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( + ` +
+ + +
+
+
multiNoSel
+
+ `, + 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( + ``, + 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( + ` + + + +
+ Popover content + +
+ `, + 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( + ` + + + + + + + + +
abcd
eifjgh
k
+ `, + 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( + ` + + + + +
ab
cd
ef
+ `, + 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( + ` + +
outerCell +
a
+
+ + `, + 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( + ` + + + +
c1
a
+ + +
a
+ + + +
c3
a
+ `, + 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( + ` +
a
+
a
+
ab
+
+ `, + 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 = "ab"; + 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 = `
a
`; + }); + 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( + ` + +
ab
cd
cd
+ `, + 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( + ` +
+
+
a
b
+
+
+
c
d
+
+
+ `, + 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( + ` + + + +
ab
+
+ `, + 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( + ` +
+
a
+
+ `, + 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( + ` +
+
+
+
a
+
+
+
+ `, + 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( + `
`, + 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( + ` +
+
+
+
a
+
+
+
+
+
+
b
+
+
+
+
+ `, + 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( + ` +
+
+
+
a
+
+
+
+
+
+
b
+
+
+
+
+ `, + 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 . + */ +addAccessibleTask( + `
`, + async function (browser, docAcc) { + const table = findAccessibleChildByID(docAcc, "table"); + ok(table, "Retrieved table Accessible"); + }, + { chrome: true, topLevel: true } +); + +/** + * Test ARIA tables in SVG. + */ +addAccessibleTask( + ` + + caption + + a + b + + + c + d + + + `, + 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 . + */ +addAccessibleTask( + ` +
+ + +
a
b
+ `, + 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.
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(
+  `
+  

Tilimilitryamdiya

+

ل

+

Привіт Світ

+
a%0abcdef
+ `, + 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( + ` +

Tilimilitryamdiya

+

Привіт Світ

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

Привіт Світ
Привіт Світ

+

Привіт Світ
Я ще трохи тексту в другому рядку

+

hello world I'm on line one
and I'm a separate line two with slightly more text

+

hello world
hello world

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

Tilimilitryamdiya

+

ل

+

لل لللل لل

+
a%0abcdef
+ `, + 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( + ` +

لل لللل لل
لل لللل لل

+

لل لللل لل
لل لل لل لل ل لل لل لل

+

hello world I'm on line one
and I'm a separate line two with slightly more text

+

hello world
hello world

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

Tilimilitryamdiya

+

لل لللل لل

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

你好世界

+

hello world

+
+

こんにちは世界

+
+
+

你好世界

+

hello world

+
+

こんにちは世界

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

你好世界
你好世界

+

hello world
hello world

+
+

你好世界
你好世界 你好世界

+

hello world
hello world hello world

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

hello world

+

hello
world

+

hello world
+
hello world

+
oh

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

a

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

a

`, + 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( + ` + +
XX
+XXX
+XX
+X
`, + 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( + ` + +
hello
XX
+XXX
+XX
+X
`, + 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( + ` + + `, + 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( + ``, + 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( + ` + +

ab cd

+

+foo

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

a b

+

a +b

+
a  b
+ `, + 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( + '
', + 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( + ` +
slider
+ + + + +
Some rich text
`, + 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( + `Test`, + 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( + ` + `, + 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( + ` +
value
+
+
+
value
+
+
+
+
value
+
+
foo +
+
bar
+
+
+ + + testing + +
+
+
value
+
+
+`, + 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( + `

abc

`, + 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( + '', + 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( + ` +
+ +
+ +
`, + 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( + '', + 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, + + + + Inner Iframe + + + `; + +/** + * Test state change event and its interface: + * - state + * - isExtraState + * - isEnabled + */ +addAccessibleTask( + ` + + `, + 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( + ` +

abc

+ `, + 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( + ` + + + + + + `, + 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( + ` + +
+
+
+
+
+ `, + 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( + ` + + + + `, + 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( + `
+ + +
+ + + + +
+ + + +
+ + `, + 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( + `
    +
  • Oranges
  • +
  • Apples
  • +
  • Bananas
  • +
+ +
    +
  1. Oranges
  2. +
  3. Apples
  4. +
  5. Bananas +
      +
    • Oranges
    • +
    • Apples
    • +
    • Bananas
    • +
    +
  6. +
+ + + Oranges + Apples + Bananas + + + + Oranges + Apples + Bananas + + Oranges + Apples + Bananas + + + + +
+
Item 1 +
+
Item 1A
+
Item 1B
+
+
+
Item 2 +
+
Item 2A
+
Item 2B
+
+
+
`, + 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( + ``, + 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( + `
    + + + +
`, + 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( + `
    + + + +
`, + 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( + ` + + + + + + + + + + + + + + + + + + + + + + + + +
vegetables
cucumber
carrot
mercedes
BMW
Audi
people
+ +
    +
  • Item 1 +
      +
    • Item 1A
    • +
    • Item 1B
    • +
    +
  • +
  • Item 2 +
      +
    • Item 2A
    • +
    • Item 2B
    • +
    +
  • + + +
    +
    Item 1
    +
    +
  • Item 1A
  • +
  • Item 1B
  • +
    +
    Item 2
    +
    +
    Item 2A
    +
    Item 2B
    +
    +
    `, + 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( + ` + + + + + + + + +
    cell1cell2
    cell3cell4
    `, + 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( + `
    +
    +
    cell1
    +
    cell2
    +
    +
    +
    cell1
    +
    cell2
    +
    +
    +
    cell1
    +
    cell2
    +
    +
    `, + 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( + `
    +

    heading1

    +

    heading2

    +

    heading3

    +

    heading4

    +
    heading5
    +
    heading6
    +
    ariaHeadingNoLevel
    +
    `, + 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( + `
      Password +
    • Xyzzy
    • +
    • Plughs
    • +
    • Shazaam
    • +
    • JoeSentMe
    • +
    `, + 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( + `
    +
    +
    cell
    +
    +
    `, + 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( + `
    +
    +
    +
    cell content
    +
    +
    +
    +
    +
    cell content
    +
    +
    +
    `, + 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( + `
    +
    Apples
    +
    Oranges
    +
    +
    Bananas
    `, + 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( + ` +
    + + +
    + + +
    + + + +
    `, + 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( + `
    Item 1
    Item 2
    `, + 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( + `
    +
    +
    treeitem 1
    +
    +
    +
    treeitem 2
    +
    +
    +
    +
    +
    option 1
    +
    +
    +
    option 2
    +
    +
    +
    +
    +
    listitem 1
    +
    +
    +
    listitem 2
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +`, + 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( + `
    +
    +
    first
    +
    +
    +
    second
    +
    +
    `, + 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( + ` + +
    +
    +
    + +
    + + `, + 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( + `
    `, + 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( + `
    `, + 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( + ` +
    +
    + +
    + +
    `, + 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( + ` +
    +
    +
    +
    +
    + `, + 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( + ` + + + `, + 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 = ` + + + + `; + +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( + ` +
    `, + 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, + + + + Inner Iframe + + + `; + +addAccessibleTask( + ` + `, + 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('hello'); + 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('');`; + 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,` + ); + 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( + ` + +
    +
    text
    `, + 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( + '
    ', + 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( + ``, + 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( + ``, + 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( + `${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( + ``, + 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( + ``, + 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( + ``, + 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( + ``, + 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( + ` +
      +
    • item1
    • +
    `, + 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( + '
      ', + 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( + '', + 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( + ` + + `, + 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( + ` +
      + +
      +
      + `, + 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( + '', + 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 = ` + +`; + +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( + ` + + + + + +
      cell1cell2
      `, + 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( + ` +

      text

      +
      text
      `, + 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 @@ + + + + Tree Update ARIA Dialog Test + + + + + 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 @@ + + + + Tree Update ARIA Owns Test + + +
      +
      + +
      +
      +
      + +
      +
      +
      +
      + +
      + +
      + +
      +
      + + +
      + +
      +
      + + +
      + +
      + hey +
      +
      + + 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 @@ + + + + Tree Update Imagemap Test + + + + b + + +
      +
      + + 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 @@ + + + + Tree Update Removal Test + + + +
      + + + 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 @@ + + + + Tree Update Visibility Test + + + +
      +
      +
      text
      +
      +
      + + +
      +
      +
      +
      text
      +
      text
      +
      +
      +
      + + +
      +
      +
      text
      +
      +
      +
      text
      +
      +
      + + +
      + + + + +
      + +
      +
      + + +
      +
      + + + + +
      +
      text
      +
      +
      +
      + + +
      +
      + + + + +
      + + + + +
      +
      text
      +
      +
      +
      text
      +
      +
      + + 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 @@ + + + + Whitespace text accessible creation/destruction + + +
      +
      + + 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]); + } +} -- cgit v1.2.3