summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /accessible/tests/browser
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/tests/browser')
-rw-r--r--accessible/tests/browser/.eslintrc.js2
-rw-r--r--accessible/tests/browser/bounds/browser.toml1
-rw-r--r--accessible/tests/browser/e10s/browser.toml1
-rw-r--r--accessible/tests/browser/e10s/browser_aria_activedescendant.js10
-rw-r--r--accessible/tests/browser/e10s/browser_caching_value.js10
-rw-r--r--accessible/tests/browser/events/browser.toml3
-rw-r--r--accessible/tests/browser/events/browser_content_prompt.js48
-rw-r--r--accessible/tests/browser/fission/browser.toml1
-rw-r--r--accessible/tests/browser/hittest/browser.toml1
-rw-r--r--accessible/tests/browser/mac/browser.toml3
-rw-r--r--accessible/tests/browser/mac/browser_aria_placeholder.js70
-rw-r--r--accessible/tests/browser/mac/browser_roles_elements.js4
-rw-r--r--accessible/tests/browser/mac/browser_selectables.js23
-rw-r--r--accessible/tests/browser/role/browser_computedARIARole.js4
-rw-r--r--accessible/tests/browser/scroll/browser.toml1
-rw-r--r--accessible/tests/browser/selectable/browser.toml1
-rw-r--r--accessible/tests/browser/selectable/browser_test_aria_select.js26
-rw-r--r--accessible/tests/browser/states/browser.toml1
-rw-r--r--accessible/tests/browser/text/browser.toml1
-rw-r--r--accessible/tests/browser/tree/browser.toml1
-rw-r--r--accessible/tests/browser/windows/a11y_setup.py154
-rw-r--r--accessible/tests/browser/windows/uia/browser.toml5
-rw-r--r--accessible/tests/browser/windows/uia/browser_focus.js61
-rw-r--r--accessible/tests/browser/windows/uia/browser_generalProps.js105
-rw-r--r--accessible/tests/browser/windows/uia/browser_simplePatterns.js445
-rw-r--r--accessible/tests/browser/windows/uia/browser_tree.js20
-rw-r--r--accessible/tests/browser/windows/uia/head.js75
27 files changed, 977 insertions, 100 deletions
diff --git a/accessible/tests/browser/.eslintrc.js b/accessible/tests/browser/.eslintrc.js
index 528797cb91..96c0859468 100644
--- a/accessible/tests/browser/.eslintrc.js
+++ b/accessible/tests/browser/.eslintrc.js
@@ -18,7 +18,7 @@ module.exports = {
"no-proto": "error",
"no-return-assign": "error",
"no-shadow": "error",
- "no-unused-vars": ["error", { vars: "all", args: "none" }],
+ "no-unused-vars": ["error", { vars: "all", argsIgnorePattern: "^_" }],
"one-var": ["error", "never"],
radix: "error",
strict: ["error", "global"],
diff --git a/accessible/tests/browser/bounds/browser.toml b/accessible/tests/browser/bounds/browser.toml
index da1fe37a1e..c4c421cdeb 100644
--- a/accessible/tests/browser/bounds/browser.toml
+++ b/accessible/tests/browser/bounds/browser.toml
@@ -3,7 +3,6 @@ subsuite = "a11y"
support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
- "!/accessible/tests/browser/*.jsm",
"!/accessible/tests/mochitest/*.js",
"!/accessible/tests/mochitest/letters.gif",
]
diff --git a/accessible/tests/browser/e10s/browser.toml b/accessible/tests/browser/e10s/browser.toml
index 914f839993..dff9b1c712 100644
--- a/accessible/tests/browser/e10s/browser.toml
+++ b/accessible/tests/browser/e10s/browser.toml
@@ -10,7 +10,6 @@ support-files = [
"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",
diff --git a/accessible/tests/browser/e10s/browser_aria_activedescendant.js b/accessible/tests/browser/e10s/browser_aria_activedescendant.js
index f58c5aab39..fadda0f964 100644
--- a/accessible/tests/browser/e10s/browser_aria_activedescendant.js
+++ b/accessible/tests/browser/e10s/browser_aria_activedescendant.js
@@ -277,7 +277,7 @@ async function basicListboxTest(browser, elementReflection) {
addAccessibleTask(
LISTBOX_MARKUP,
- async function (browser, docAcc) {
+ async function (browser) {
info("Test aria-activedescendant content attribute");
await basicListboxTest(browser, false);
@@ -303,7 +303,7 @@ addAccessibleTask(
addAccessibleTask(
LISTBOX_MARKUP,
- async function (browser, docAcc) {
+ async function (browser) {
info("Test ariaActiveDescendantElement element reflection");
await basicListboxTest(browser, true);
},
@@ -316,7 +316,7 @@ addAccessibleTask(
<div role="listbox">
<div role="option" id="activedesc_nondesc_option">option</div>
</div>`,
- async function (browser, docAcc) {
+ async function (browser) {
info("Test aria-activedescendant non-descendant");
await synthFocus(
browser,
@@ -348,7 +348,7 @@ addAccessibleTask(
item.setAttribute("role", "option");
listbox.appendChild(item);
</script>`,
- async function (browser, docAcc) {
+ async function (browser) {
info("Test aria-activedescendant in shadow root");
// We want to retrieve elements using their IDs inside the shadow root, so
// we define a custom get element by ID method that our utility functions
@@ -448,7 +448,7 @@ customElements.define("custom-listbox",
}
);
</script>`,
- async function (browser, docAcc) {
+ async function (browser) {
await synthFocus(browser, "custom-listbox1", "l1_3");
let evtProm = Promise.all([
diff --git a/accessible/tests/browser/e10s/browser_caching_value.js b/accessible/tests/browser/e10s/browser_caching_value.js
index 2b968b5948..926110199e 100644
--- a/accessible/tests/browser/e10s/browser_caching_value.js
+++ b/accessible/tests/browser/e10s/browser_caching_value.js
@@ -46,16 +46,6 @@ const valueTests = [
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: [
diff --git a/accessible/tests/browser/events/browser.toml b/accessible/tests/browser/events/browser.toml
index 7ec3c3621a..ffbb96cfbb 100644
--- a/accessible/tests/browser/events/browser.toml
+++ b/accessible/tests/browser/events/browser.toml
@@ -4,12 +4,13 @@ support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
"!/accessible/tests/mochitest/*.js",
- "!/accessible/tests/browser/*.jsm",
]
prefs = ["javascript.options.asyncstack_capture_debuggee_only=false"]
["browser_alert.js"]
+["browser_content_prompt.js"]
+
["browser_test_A11yUtils_announce.js"]
["browser_test_caret_move_granularity.js"]
diff --git a/accessible/tests/browser/events/browser_content_prompt.js b/accessible/tests/browser/events/browser_content_prompt.js
new file mode 100644
index 0000000000..7677c0258a
--- /dev/null
+++ b/accessible/tests/browser/events/browser_content_prompt.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Want to test relations.
+/* import-globals-from ../../mochitest/name.js */
+/* import-globals-from ../../mochitest/relations.js */
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "relations.js", dir: MOCHITESTS_DIR },
+ { name: "name.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR }
+);
+
+addAccessibleTask(``, async function (browser) {
+ info("Showing alert");
+ let shown = waitForEvent(
+ EVENT_SHOW,
+ evt => evt.accessible.role == ROLE_INTERNAL_FRAME
+ );
+ // Let's make sure the dialog content gets focus.
+ // On macOS, we unfortunately focus the label. We focus the OK button on
+ // all other platforms. See https://phabricator.services.mozilla.com/D204908
+ // for more discussion.
+ let expectedRole =
+ AppConstants.platform == "macosx" ? ROLE_LABEL : ROLE_PUSHBUTTON;
+ let focused = waitForEvent(EVENT_FOCUS, evt => {
+ return evt.accessible.role == expectedRole;
+ });
+ await invokeContentTask(browser, [], () => {
+ // Use setTimeout to avoid blocking the return of the content task
+ // on the alert, which is otherwise synchronous.
+ content.setTimeout(() => content.alert("test"), 0);
+ });
+ const frame = (await shown).accessible;
+ const focusedEl = (await focused).accessible;
+ ok(true, "Dialog shown and something got focused");
+ let dialog = getAccessible(focusedEl.DOMNode.ownerDocument);
+ testRole(dialog, ROLE_DIALOG);
+ let infoBody = focusedEl.DOMNode.ownerDocument.getElementById("infoBody");
+ testRelation(dialog, RELATION_DESCRIBED_BY, infoBody);
+ testDescr(dialog, "test ");
+ info("Dismissing alert");
+ let hidden = waitForEvent(EVENT_HIDE, frame);
+ EventUtils.synthesizeKey("KEY_Escape", {}, frame.DOMNode.contentWindow);
+ await hidden;
+});
diff --git a/accessible/tests/browser/fission/browser.toml b/accessible/tests/browser/fission/browser.toml
index 0332573db9..f795ec6b3d 100644
--- a/accessible/tests/browser/fission/browser.toml
+++ b/accessible/tests/browser/fission/browser.toml
@@ -3,7 +3,6 @@ subsuite = "a11y"
support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
- "!/accessible/tests/browser/*.jsm",
"!/accessible/tests/mochitest/*.js",
]
prefs = ["javascript.options.asyncstack_capture_debuggee_only=false"]
diff --git a/accessible/tests/browser/hittest/browser.toml b/accessible/tests/browser/hittest/browser.toml
index 7f82a6ff49..894bf05c39 100644
--- a/accessible/tests/browser/hittest/browser.toml
+++ b/accessible/tests/browser/hittest/browser.toml
@@ -3,7 +3,6 @@ subsuite = "a11y"
support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
- "!/accessible/tests/browser/*.jsm",
"!/accessible/tests/mochitest/*.js",
"!/accessible/tests/mochitest/letters.gif",
]
diff --git a/accessible/tests/browser/mac/browser.toml b/accessible/tests/browser/mac/browser.toml
index b180a42ab7..227575fb4d 100644
--- a/accessible/tests/browser/mac/browser.toml
+++ b/accessible/tests/browser/mac/browser.toml
@@ -9,7 +9,6 @@ support-files = [
"doc_menulist.xhtml",
"doc_tree.xhtml",
"!/accessible/tests/browser/shared-head.js",
- "!/accessible/tests/browser/*.jsm",
"!/accessible/tests/mochitest/*.js",
"!/accessible/tests/mochitest/letters.gif",
"!/accessible/tests/mochitest/moz.png",
@@ -29,6 +28,8 @@ https_first_disabled = true
["browser_aria_haspopup.js"]
+["browser_aria_placeholder.js"]
+
["browser_aria_setsize.js"]
["browser_attributed_text.js"]
diff --git a/accessible/tests/browser/mac/browser_aria_placeholder.js b/accessible/tests/browser/mac/browser_aria_placeholder.js
new file mode 100644
index 0000000000..e5102fd093
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_aria_placeholder.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Test that inputs with placeholder text expose it via AXPlaceholderValue and
+ * correctly _avoid_ exposing it via AXTItle, AXValue, or AXDescription. Verify AXPlaceholderValue is not exposed when the placeholder is used as the name.
+ */
+addAccessibleTask(
+ `
+ <input id="input" placeholder="Name"><br>
+ <label for="input2">Enter Name</label><input id="input2" placeholder="Name" value="Elmer Fudd">
+ `,
+ (browser, accDoc) => {
+ let input = getNativeInterface(accDoc, "input");
+ let input2 = getNativeInterface(accDoc, "input2");
+
+ is(
+ input.getAttributeValue("AXPlaceholderValue"),
+ null,
+ "Placeholder is used as name, so no AXPlaceholderValue is stored."
+ );
+ is(input.getAttributeValue("AXDescription"), "Name");
+ is(input.getAttributeValue("AXTitle"), "", "Correct title");
+ is(input.getAttributeValue("AXValue"), "", "Correct value");
+
+ is(
+ input2.getAttributeValue("AXPlaceholderValue"),
+ "Name",
+ "Correct placeholder value in presence of value"
+ );
+ is(
+ input2.getAttributeValue("AXDescription"),
+ "Enter Name",
+ "Correct label"
+ );
+ is(input2.getAttributeValue("AXTitle"), "", "Correct title");
+ is(input2.getAttributeValue("AXValue"), "Elmer Fudd", "Correct value");
+ }
+);
+
+/**
+ * Test that aria-placeholder gets exposed via AXPlaceholderValue and correctly
+ * contributes to AXValue (but not title or description).
+ */
+addAccessibleTask(
+ `
+ <span id="date-of-birth">Birthday</span>
+ <div
+ id="bday"
+ contenteditable
+ role="textbox"
+ aria-labelledby="date-of-birth"
+ aria-placeholder="MM-DD-YYYY">MM-DD-YYYY</div>
+ `,
+ (browser, accDoc) => {
+ let bday = getNativeInterface(accDoc, "bday");
+
+ is(
+ bday.getAttributeValue("AXPlaceholderValue"),
+ "MM-DD-YYYY",
+ "Correct placeholder value"
+ );
+ is(bday.getAttributeValue("AXDescription"), "Birthday", "Correct label");
+ is(bday.getAttributeValue("AXTitle"), "", "Correct title");
+ is(bday.getAttributeValue("AXValue"), "MM-DD-YYYY", "Correct value");
+ }
+);
diff --git a/accessible/tests/browser/mac/browser_roles_elements.js b/accessible/tests/browser/mac/browser_roles_elements.js
index b6049e7afd..65caf308c4 100644
--- a/accessible/tests/browser/mac/browser_roles_elements.js
+++ b/accessible/tests/browser/mac/browser_roles_elements.js
@@ -73,8 +73,8 @@ addAccessibleTask(
<div id="switch" role="switch"></div>
<div id="timer" role="timer"></div>
<div id="tooltip" role="tooltip"></div>
- <input type="radio" role="menuitemradio" id="menuitemradio">
- <input type="checkbox" role="menuitemcheckbox" id="menuitemcheckbox">
+ <div role="menu"><input type="radio" role="menuitemradio" id="menuitemradio"></div>
+ <div role="menu"><input type="checkbox" role="menuitemcheckbox" id="menuitemcheckbox"></div>
<input type="datetime-local" id="datetime">
<!-- text entries -->
diff --git a/accessible/tests/browser/mac/browser_selectables.js b/accessible/tests/browser/mac/browser_selectables.js
index af88c2e136..cc489e566d 100644
--- a/accessible/tests/browser/mac/browser_selectables.js
+++ b/accessible/tests/browser/mac/browser_selectables.js
@@ -300,9 +300,6 @@ addAccessibleTask(
async (browser, accDoc) => {
let select = getNativeInterface(accDoc, "select");
let one = getNativeInterface(accDoc, "one");
- let two = getNativeInterface(accDoc, "two");
- let three = getNativeInterface(accDoc, "three");
- let four = getNativeInterface(accDoc, "four");
is(
select.getAttributeValue("AXTitle"),
@@ -339,25 +336,5 @@ addAccessibleTask(
});
await evt;
is(select.getAttributeValue("AXSelectedChildren").length, 0);
- evt = waitForMacEvent("AXSelectedChildrenChanged");
- three.setAttributeValue("AXSelected", true);
- await evt;
- is(select.getAttributeValue("AXSelectedChildren").length, 1);
- ok(getSelectedIds(select).includes("three"), "'three' is selected");
- evt = waitForMacEvent("AXSelectedChildrenChanged");
- select.setAttributeValue("AXSelectedChildren", [one, two]);
- await evt;
- await untilCacheOk(() => {
- let ids = getSelectedIds(select);
- return ids[0] == "one" && ids[1] == "two";
- }, "Got correct selected children");
-
- evt = waitForMacEvent("AXSelectedChildrenChanged");
- select.setAttributeValue("AXSelectedChildren", [three, two, four]);
- await evt;
- await untilCacheOk(() => {
- let ids = getSelectedIds(select);
- return ids[0] == "two" && ids[1] == "three";
- }, "Got correct selected children");
}
);
diff --git a/accessible/tests/browser/role/browser_computedARIARole.js b/accessible/tests/browser/role/browser_computedARIARole.js
index 50cfe43c98..a87d0a0eae 100644
--- a/accessible/tests/browser/role/browser_computedARIARole.js
+++ b/accessible/tests/browser/role/browser_computedARIARole.js
@@ -14,7 +14,9 @@ addAccessibleTask(
<div id="ariaDirectory" role="directory">ARIA directory</div>
<div id="ariaAlertdialog" role="alertdialog">ARIA alertdialog</div>
<div id="ariaFeed" role="feed">ARIA feed</div>
-<div id="ariaRowgroup" role="rowgroup">ARIA rowgroup</div>
+<div role="table">
+ <div id="ariaRowgroup" role="rowgroup">ARIA rowgroup</div>
+</div>
<div id="ariaSearchbox" role="searchbox">ARIA searchbox</div>
<div id="ariaUnknown" role="unknown">unknown ARIA role</div>
<button id="htmlButton">HTML button</button>
diff --git a/accessible/tests/browser/scroll/browser.toml b/accessible/tests/browser/scroll/browser.toml
index ef637fe9a5..390e07db8e 100644
--- a/accessible/tests/browser/scroll/browser.toml
+++ b/accessible/tests/browser/scroll/browser.toml
@@ -3,7 +3,6 @@ subsuite = "a11y"
support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
- "!/accessible/tests/browser/*.jsm",
"!/accessible/tests/mochitest/*.js",
]
prefs = ["javascript.options.asyncstack_capture_debuggee_only=false"]
diff --git a/accessible/tests/browser/selectable/browser.toml b/accessible/tests/browser/selectable/browser.toml
index 38a13c25f4..144555b6a0 100644
--- a/accessible/tests/browser/selectable/browser.toml
+++ b/accessible/tests/browser/selectable/browser.toml
@@ -3,7 +3,6 @@ subsuite = "a11y"
support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
- "!/accessible/tests/browser/*.jsm",
"!/accessible/tests/mochitest/*.js",
]
prefs = ["javascript.options.asyncstack_capture_debuggee_only=false"]
diff --git a/accessible/tests/browser/selectable/browser_test_aria_select.js b/accessible/tests/browser/selectable/browser_test_aria_select.js
index f52603d1cb..dbc36956f8 100644
--- a/accessible/tests/browser/selectable/browser_test_aria_select.js
+++ b/accessible/tests/browser/selectable/browser_test_aria_select.js
@@ -72,16 +72,16 @@ addAccessibleTask(
// role="tablist" aria-multiselectable
addAccessibleTask(
`<div role="tablist" id="tablist" aria-multiselectable="true">
- <div role="tab" id="tab_multi1">tab1</div>
+ <div role="tab" id="tab_multi1" aria-selected="true">tab1</div>
<div role="tab" id="tab_multi2">tab2</div>
+ <div role="tab" id="tab_multi3" aria-selected="true">tab3</div>
</div>`,
async function (browser, docAcc) {
info('role="tablist" aria-multiselectable');
let tablist = findAccessibleChildByID(docAcc, "tablist", [
nsIAccessibleSelectable,
]);
-
- await testMultiSelectable(tablist, ["tab_multi1", "tab_multi2"]);
+ testSelectableSelection(tablist, ["tab_multi1", "tab_multi3"]);
},
{
chrome: true,
@@ -95,16 +95,16 @@ addAccessibleTask(
// role="listbox" aria-multiselectable
addAccessibleTask(
`<div role="listbox" id="listbox" aria-multiselectable="true">
- <div role="option" id="listbox2_item1">item1</div>
+ <div role="option" id="listbox2_item1" aria-selected="true">item1</div>
<div role="option" id="listbox2_item2">item2</div>
+ <div role="option" id="listbox2_item3" aria-selected="true">item2</div>
</div>`,
async function (browser, docAcc) {
info('role="listbox" aria-multiselectable');
let listbox = findAccessibleChildByID(docAcc, "listbox", [
nsIAccessibleSelectable,
]);
-
- await testMultiSelectable(listbox, ["listbox2_item1", "listbox2_item2"]);
+ testSelectableSelection(listbox, ["listbox2_item1", "listbox2_item3"]);
},
{
chrome: true,
@@ -122,7 +122,7 @@ addAccessibleTask(
<thead>
<tr>
<th tabindex="-1" role="columnheader" id="grid_colhead1"
- style="width:6em">Entry #</th>
+ style="width:6em" aria-selected="true">Entry #</th>
<th tabindex="-1" role="columnheader" id="grid_colhead2"
style="width:10em">Date</th>
<th tabindex="-1" role="columnheader" id="grid_colhead3"
@@ -134,7 +134,7 @@ addAccessibleTask(
<td tabindex="-1" role="rowheader" id="grid_rowhead"
aria-readonly="true">1</td>
<td tabindex="-1" role="gridcell" id="grid_cell1"
- aria-selected="false">03/14/05</td>
+ aria-selected="true">03/14/05</td>
<td tabindex="-1" role="gridcell" id="grid_cell2"
aria-selected="false">Conference Fee</td>
</tr>
@@ -145,15 +145,7 @@ addAccessibleTask(
let grid = findAccessibleChildByID(docAcc, "grid", [
nsIAccessibleSelectable,
]);
-
- await testMultiSelectable(grid, [
- "grid_colhead1",
- "grid_colhead2",
- "grid_colhead3",
- "grid_rowhead",
- "grid_cell1",
- "grid_cell2",
- ]);
+ testSelectableSelection(grid, ["grid_colhead1", "grid_cell1"]);
},
{
chrome: true,
diff --git a/accessible/tests/browser/states/browser.toml b/accessible/tests/browser/states/browser.toml
index fe29597d27..6b2d20ae14 100644
--- a/accessible/tests/browser/states/browser.toml
+++ b/accessible/tests/browser/states/browser.toml
@@ -4,7 +4,6 @@ support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
"!/accessible/tests/mochitest/*.js",
- "!/accessible/tests/browser/*.jsm",
]
prefs = ["javascript.options.asyncstack_capture_debuggee_only=false"]
diff --git a/accessible/tests/browser/text/browser.toml b/accessible/tests/browser/text/browser.toml
index c04531b126..4bc64866fc 100644
--- a/accessible/tests/browser/text/browser.toml
+++ b/accessible/tests/browser/text/browser.toml
@@ -3,7 +3,6 @@ subsuite = "a11y"
support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
- "!/accessible/tests/browser/*.jsm",
"!/accessible/tests/mochitest/*.js",
]
prefs = ["javascript.options.asyncstack_capture_debuggee_only=false"]
diff --git a/accessible/tests/browser/tree/browser.toml b/accessible/tests/browser/tree/browser.toml
index 64be6853d1..a7d9894fcb 100644
--- a/accessible/tests/browser/tree/browser.toml
+++ b/accessible/tests/browser/tree/browser.toml
@@ -4,7 +4,6 @@ support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
"!/accessible/tests/mochitest/*.js",
- "!/accessible/tests/browser/*.jsm",
]
prefs = ["javascript.options.asyncstack_capture_debuggee_only=false"]
diff --git a/accessible/tests/browser/windows/a11y_setup.py b/accessible/tests/browser/windows/a11y_setup.py
index 726eea07a4..860364d99b 100644
--- a/accessible/tests/browser/windows/a11y_setup.py
+++ b/accessible/tests/browser/windows/a11y_setup.py
@@ -61,9 +61,6 @@ uiaClient = comtypes.CoCreateInstance(
interface=uiaMod.IUIAutomation,
clsctx=comtypes.CLSCTX_INPROC_SERVER,
)
-TreeScope_Descendants = uiaMod.TreeScope_Descendants
-UIA_AutomationIdPropertyId = uiaMod.UIA_AutomationIdPropertyId
-del uiaMod
def AccessibleObjectFromWindow(hwnd, objectID=OBJID_CLIENT):
@@ -173,7 +170,7 @@ class WaitForWinEvent:
"""
def __init__(self, eventId, match):
- """event is the event id to wait for.
+ """eventId is the event id to wait for.
match is either None to match any object, an str containing the DOM id
of the desired object, or a function taking a WinEvent which should
return True if this is the requested event.
@@ -235,6 +232,7 @@ class WaitForWinEvent:
raise
finally:
user32.UnhookWinEvent(self._hook)
+ ctypes.windll.kernel32.CloseHandle(self._signal)
self._proc = None
if isinstance(self._matched, Exception):
raise self._matched from self._matched
@@ -243,17 +241,153 @@ class WaitForWinEvent:
def getDocUia():
"""Get the IUIAutomationElement for the document being tested."""
- # We start with IAccessible2 because there's no efficient way to
- # find the document we want with UIA.
- ia2 = getDocIa2()
- return uiaClient.ElementFromIAccessible(ia2, CHILDID_SELF)
+ # There's no efficient way to find the document we want with UIA. We can't
+ # get the IA2 and then get UIA from that because that will always use the
+ # IA2 -> UIA proxy, but we don't want that if we're trying to test our
+ # native implementation. For now, we just search the tree. In future, we
+ # could perhaps implement a custom property.
+ hwnd = getFirefoxHwnd()
+ root = uiaClient.ElementFromHandle(hwnd)
+ doc = findUiaByDomId(root, "body")
+ if not doc:
+ # Sometimes, when UIA is disabled, we can't find the document for some
+ # unknown reason. Since this only happens when UIA is disabled, we want
+ # the IA2 -> UIA proxy anyway, so we can start with IA2 in this case.
+ info("getUiaDoc: Falling back to IA2") # noqa: F821
+ ia2 = getDocIa2()
+ return uiaClient.ElementFromIAccessible(ia2, CHILDID_SELF)
+ child = uiaClient.RawViewWalker.GetFirstChildElement(doc)
+ if child and child.CurrentAutomationId == "default-iframe-id":
+ # This is an iframe or remoteIframe test.
+ doc = uiaClient.RawViewWalker.GetFirstChildElement(child)
+ return doc
def findUiaByDomId(root, id):
- cond = uiaClient.CreatePropertyCondition(UIA_AutomationIdPropertyId, id)
+ cond = uiaClient.CreatePropertyCondition(uiaMod.UIA_AutomationIdPropertyId, id)
# FindFirst ignores elements in the raw tree, so we have to use
# FindFirstBuildCache to override that, even though we don't want to cache
# anything.
request = uiaClient.CreateCacheRequest()
request.TreeFilter = uiaClient.RawViewCondition
- return root.FindFirstBuildCache(TreeScope_Descendants, cond, request)
+ el = root.FindFirstBuildCache(uiaMod.TreeScope_Descendants, cond, request)
+ if not el:
+ return None
+ # We need to test things that were introduced after UIA was initially
+ # introduced in Windows 7.
+ return el.QueryInterface(uiaMod.IUIAutomationElement9)
+
+
+class WaitForUiaEvent(comtypes.COMObject):
+ """Wait for a UIA event.
+ This should be used as follows:
+ 1. Create an instance to wait for the desired event.
+ 2. Perform the action that should fire the event.
+ 3. Call wait() on the instance you created in 1) to wait for the event.
+ """
+
+ # This tells comtypes which COM interfaces we implement. It will then call
+ # either `ISomeInterface_SomeMethod` or just `SomeMethod` on this instance
+ # when that method is called using COM. We use the shorter convention, since
+ # we don't anticipate method name conflicts with UIA interfaces.
+ _com_interfaces_ = [
+ uiaMod.IUIAutomationFocusChangedEventHandler,
+ uiaMod.IUIAutomationPropertyChangedEventHandler,
+ uiaMod.IUIAutomationEventHandler,
+ ]
+
+ def __init__(self, *, eventId=None, property=None, match=None):
+ """eventId is the event id to wait for. Alternatively, you can pass
+ property to wait for a particular property to change.
+ match is either None to match any object, an str containing the DOM id
+ of the desired object, or a function taking a IUIAutomationElement which
+ should return True if this is the requested event.
+ """
+ self._match = match
+ self._matched = None
+ # A kernel event used to signal when we get the desired event.
+ self._signal = ctypes.windll.kernel32.CreateEventW(None, True, False, None)
+ if eventId == uiaMod.UIA_AutomationFocusChangedEventId:
+ uiaClient.AddFocusChangedEventHandler(None, self)
+ elif eventId:
+ # Generic automation event.
+ uiaClient.AddAutomationEventHandler(
+ eventId,
+ uiaClient.GetRootElement(),
+ uiaMod.TreeScope_Subtree,
+ None,
+ self,
+ )
+ elif property:
+ uiaClient.AddPropertyChangedEventHandler(
+ uiaClient.GetRootElement(),
+ uiaMod.TreeScope_Subtree,
+ None,
+ self,
+ [property],
+ )
+ else:
+ raise ValueError("No supported event specified")
+
+ def _checkMatch(self, sender):
+ if isinstance(self._match, str):
+ try:
+ if sender.CurrentAutomationId == self._match:
+ self._matched = sender
+ except comtypes.COMError:
+ pass
+ elif callable(self._match):
+ try:
+ if self._match(sender):
+ self._matched = sender
+ except Exception as e:
+ self._matched = e
+ else:
+ self._matched = sender
+ if self._matched:
+ ctypes.windll.kernel32.SetEvent(self._signal)
+
+ def HandleFocusChangedEvent(self, sender):
+ self._checkMatch(sender)
+
+ def HandlePropertyChangedEvent(self, sender, propertyId, newValue):
+ self._checkMatch(sender)
+
+ def HandleAutomationEvent(self, sender, eventId):
+ self._checkMatch(sender)
+
+ def wait(self):
+ """Wait for and return the IUIAutomationElement which sent the desired
+ event."""
+ # Pump Windows messages until we get the desired event, which will be
+ # signalled using a kernel event.
+ handles = (ctypes.c_void_p * 1)(self._signal)
+ index = ctypes.wintypes.DWORD()
+ TIMEOUT = 10000
+ try:
+ ctypes.oledll.ole32.CoWaitForMultipleHandles(
+ COWAIT_DEFAULT, TIMEOUT, 1, handles, ctypes.byref(index)
+ )
+ except WindowsError as e:
+ if e.winerror == RPC_S_CALLPENDING:
+ raise TimeoutError("Timeout before desired event received")
+ raise
+ finally:
+ uiaClient.RemoveAllEventHandlers()
+ ctypes.windll.kernel32.CloseHandle(self._signal)
+ if isinstance(self._matched, Exception):
+ raise self._matched from self._matched
+ return self._matched
+
+
+def getUiaPattern(element, patternName):
+ """Get a control pattern interface from an IUIAutomationElement."""
+ patternId = getattr(uiaMod, f"UIA_{patternName}PatternId")
+ unknown = element.GetCurrentPattern(patternId)
+ if not unknown:
+ return None
+ # GetCurrentPattern returns an IUnknown. We have to QI to the real
+ # interface.
+ # Get the comtypes interface object.
+ interface = getattr(uiaMod, f"IUIAutomation{patternName}Pattern")
+ return unknown.QueryInterface(interface)
diff --git a/accessible/tests/browser/windows/uia/browser.toml b/accessible/tests/browser/windows/uia/browser.toml
index d1513c1822..75728f56d7 100644
--- a/accessible/tests/browser/windows/uia/browser.toml
+++ b/accessible/tests/browser/windows/uia/browser.toml
@@ -10,4 +10,9 @@ support-files = ["head.js"]
["browser_elementFromPoint.js"]
+["browser_focus.js"]
+["browser_generalProps.js"]
+
+["browser_simplePatterns.js"]
+
["browser_tree.js"]
diff --git a/accessible/tests/browser/windows/uia/browser_focus.js b/accessible/tests/browser/windows/uia/browser_focus.js
new file mode 100644
index 0000000000..f26c9e1d1b
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_focus.js
@@ -0,0 +1,61 @@
+/* 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";
+
+async function testIsFocusable(pyVar, isFocusable) {
+ const result = await runPython(`${pyVar}.CurrentIsKeyboardFocusable`);
+ if (isFocusable) {
+ ok(result, `${pyVar} is focusable`);
+ } else {
+ ok(!result, `${pyVar} isn't focusable`);
+ }
+}
+
+async function testHasFocus(pyVar, hasFocus) {
+ const result = await runPython(`${pyVar}.CurrentHasKeyboardFocus`);
+ if (hasFocus) {
+ ok(result, `${pyVar} has focus`);
+ } else {
+ ok(!result, `${pyVar} doesn't have focus`);
+ }
+}
+
+addUiaTask(
+ `
+<button id="button1">button1</button>
+<p id="p">p</p>
+<button id="button2">button2</button>
+ `,
+ async function (browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await testIsFocusable("doc", true);
+ await testHasFocus("doc", true);
+
+ await assignPyVarToUiaWithId("button1");
+ await testIsFocusable("button1", true);
+ await testHasFocus("button1", false);
+ info("Focusing button1");
+ await setUpWaitForUiaEvent("AutomationFocusChanged", "button1");
+ await invokeFocus(browser, "button1");
+ await waitForUiaEvent();
+ ok(true, "Got AutomationFocusChanged event on button1");
+ await testHasFocus("button1", true);
+
+ await assignPyVarToUiaWithId("p");
+ await testIsFocusable("p", false);
+ await testHasFocus("p", false);
+
+ await assignPyVarToUiaWithId("button2");
+ await testIsFocusable("button2", true);
+ await testHasFocus("button2", false);
+ info("Focusing button2");
+ await setUpWaitForUiaEvent("AutomationFocusChanged", "button2");
+ await invokeFocus(browser, "button2");
+ await waitForUiaEvent();
+ ok(true, "Got AutomationFocusChanged event on button2");
+ await testHasFocus("button2", true);
+ await testHasFocus("button1", false);
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_generalProps.js b/accessible/tests/browser/windows/uia/browser_generalProps.js
new file mode 100644
index 0000000000..5cfda226d0
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_generalProps.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";
+
+/**
+ * Test the Name property.
+ */
+addUiaTask(
+ `
+<button id="button">before</button>
+<div id="div">div</div>
+ `,
+ async function testName(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("button");
+ is(
+ await runPython(`button.CurrentName`),
+ "before",
+ "button has correct name"
+ );
+ await assignPyVarToUiaWithId("div");
+ is(await runPython(`div.CurrentName`), "", "div has no name");
+
+ info("Setting aria-label on button");
+ await setUpWaitForUiaPropEvent("Name", "button");
+ await invokeSetAttribute(browser, "button", "aria-label", "after");
+ await waitForUiaEvent();
+ ok(true, "Got Name prop change event on button");
+ is(
+ await runPython(`button.CurrentName`),
+ "after",
+ "button has correct name"
+ );
+ }
+);
+
+/**
+ * Test the FullDescription property.
+ */
+addUiaTask(
+ `
+<button id="button" aria-description="before">button</button>
+<div id="div">div</div>
+ `,
+ async function testFullDescription(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("button");
+ is(
+ await runPython(`button.CurrentFullDescription`),
+ "before",
+ "button has correct FullDescription"
+ );
+ await assignPyVarToUiaWithId("div");
+ is(
+ await runPython(`div.CurrentFullDescription`),
+ "",
+ "div has no FullDescription"
+ );
+
+ info("Setting aria-description on button");
+ await setUpWaitForUiaPropEvent("FullDescription", "button");
+ await invokeSetAttribute(browser, "button", "aria-description", "after");
+ await waitForUiaEvent();
+ ok(true, "Got FullDescription prop change event on button");
+ is(
+ await runPython(`button.CurrentFullDescription`),
+ "after",
+ "button has correct FullDescription"
+ );
+ },
+ // The IA2 -> UIA proxy doesn't support FullDescription.
+ { uiaEnabled: true, uiaDisabled: false }
+);
+
+/**
+ * Test the IsEnabled property.
+ */
+addUiaTask(
+ `
+<button id="button">button</button>
+<p id="p">p</p>
+ `,
+ async function testIsEnabled(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("button");
+ ok(await runPython(`button.CurrentIsEnabled`), "button has IsEnabled true");
+ // The IA2 -> UIA proxy doesn't fire IsEnabled prop change events.
+ if (gIsUiaEnabled) {
+ info("Setting disabled on button");
+ await setUpWaitForUiaPropEvent("IsEnabled", "button");
+ await invokeSetAttribute(browser, "button", "disabled", true);
+ await waitForUiaEvent();
+ ok(true, "Got IsEnabled prop change event on button");
+ ok(
+ !(await runPython(`button.CurrentIsEnabled`)),
+ "button has IsEnabled false"
+ );
+ }
+
+ await assignPyVarToUiaWithId("p");
+ ok(await runPython(`p.CurrentIsEnabled`), "p has IsEnabled true");
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_simplePatterns.js b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
new file mode 100644
index 0000000000..f464db0e13
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
@@ -0,0 +1,445 @@
+/* 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 }
+);
+
+/* eslint-disable camelcase */
+const ExpandCollapseState_Collapsed = 0;
+const ExpandCollapseState_Expanded = 1;
+const ToggleState_Off = 0;
+const ToggleState_On = 1;
+const ToggleState_Indeterminate = 2;
+/* eslint-enable camelcase */
+
+/**
+ * Test the Invoke pattern.
+ */
+addUiaTask(
+ `
+<button id="button">button</button>
+<p id="p">p</p>
+<input id="checkbox" type="checkbox">
+ `,
+ async function testInvoke() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("button");
+ await definePyVar("pattern", `getUiaPattern(button, "Invoke")`);
+ ok(await runPython(`bool(pattern)`), "button has Invoke pattern");
+ info("Calling Invoke on button");
+ // The button will get focus when it is clicked.
+ let focused = waitForEvent(EVENT_FOCUS, "button");
+ // The UIA -> IA2 proxy doesn't fire the Invoked event.
+ if (gIsUiaEnabled) {
+ await setUpWaitForUiaEvent("Invoke_Invoked", "button");
+ }
+ await runPython(`pattern.Invoke()`);
+ await focused;
+ ok(true, "button got focus");
+ if (gIsUiaEnabled) {
+ await waitForUiaEvent();
+ ok(true, "button got Invoked event");
+ }
+
+ await testPatternAbsent("p", "Invoke");
+ // The Microsoft IA2 -> UIA proxy doesn't follow Microsoft's own rules.
+ if (gIsUiaEnabled) {
+ // Check boxes expose the Toggle pattern, so they should not expose the
+ // Invoke pattern.
+ await testPatternAbsent("checkbox", "Invoke");
+ }
+ }
+);
+
+/**
+ * Test the Toggle pattern.
+ */
+addUiaTask(
+ `
+<input id="checkbox" type="checkbox" checked>
+<button id="toggleButton" aria-pressed="false">toggle</button>
+<button id="button">button</button>
+<p id="p">p</p>
+
+<script>
+ // When checkbox is clicked and it is not checked, make it indeterminate.
+ document.getElementById("checkbox").addEventListener("click", evt => {
+ // Within the event listener, .checked is reversed and you can't set
+ // .indeterminate. Work around this by deferring and handling the changes
+ // ourselves.
+ evt.preventDefault();
+ const target = evt.target;
+ setTimeout(() => {
+ if (target.checked) {
+ target.checked = false;
+ } else {
+ target.indeterminate = true;
+ }
+ }, 0);
+ });
+
+ // When toggleButton is clicked, set aria-pressed to true.
+ document.getElementById("toggleButton").addEventListener("click", evt => {
+ evt.target.ariaPressed = "true";
+ });
+</script>
+ `,
+ async function testToggle() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("checkbox");
+ await definePyVar("pattern", `getUiaPattern(checkbox, "Toggle")`);
+ ok(await runPython(`bool(pattern)`), "checkbox has Toggle pattern");
+ is(
+ await runPython(`pattern.CurrentToggleState`),
+ ToggleState_On,
+ "checkbox has ToggleState_On"
+ );
+ // The IA2 -> UIA proxy doesn't fire ToggleState prop change events.
+ if (gIsUiaEnabled) {
+ info("Calling Toggle on checkbox");
+ await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
+ await runPython(`pattern.Toggle()`);
+ await waitForUiaEvent();
+ ok(true, "Got ToggleState prop change event on checkbox");
+ is(
+ await runPython(`pattern.CurrentToggleState`),
+ ToggleState_Off,
+ "checkbox has ToggleState_Off"
+ );
+ info("Calling Toggle on checkbox");
+ await setUpWaitForUiaPropEvent("ToggleToggleState", "checkbox");
+ await runPython(`pattern.Toggle()`);
+ await waitForUiaEvent();
+ ok(true, "Got ToggleState prop change event on checkbox");
+ is(
+ await runPython(`pattern.CurrentToggleState`),
+ ToggleState_Indeterminate,
+ "checkbox has ToggleState_Indeterminate"
+ );
+ }
+
+ await assignPyVarToUiaWithId("toggleButton");
+ await definePyVar("pattern", `getUiaPattern(toggleButton, "Toggle")`);
+ ok(await runPython(`bool(pattern)`), "toggleButton has Toggle pattern");
+ is(
+ await runPython(`pattern.CurrentToggleState`),
+ ToggleState_Off,
+ "toggleButton has ToggleState_Off"
+ );
+ if (gIsUiaEnabled) {
+ info("Calling Toggle on toggleButton");
+ await setUpWaitForUiaPropEvent("ToggleToggleState", "toggleButton");
+ await runPython(`pattern.Toggle()`);
+ await waitForUiaEvent();
+ ok(true, "Got ToggleState prop change event on toggleButton");
+ is(
+ await runPython(`pattern.CurrentToggleState`),
+ ToggleState_On,
+ "toggleButton has ToggleState_Off"
+ );
+ }
+
+ await testPatternAbsent("button", "Toggle");
+ await testPatternAbsent("p", "Toggle");
+ }
+);
+
+/**
+ * Test the ExpandCollapse pattern.
+ */
+addUiaTask(
+ `
+<details>
+ <summary id="summary">summary</summary>
+ details
+</details>
+<button id="popup" aria-haspopup="true">popup</button>
+<button id="button">button</button>
+<script>
+ // When popup is clicked, set aria-expanded to true.
+ document.getElementById("popup").addEventListener("click", evt => {
+ evt.target.ariaExpanded = "true";
+ });
+</script>
+ `,
+ async function testExpandCollapse() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("summary");
+ await definePyVar("pattern", `getUiaPattern(summary, "ExpandCollapse")`);
+ ok(await runPython(`bool(pattern)`), "summary has ExpandCollapse pattern");
+ is(
+ await runPython(`pattern.CurrentExpandCollapseState`),
+ ExpandCollapseState_Collapsed,
+ "summary has ExpandCollapseState_Collapsed"
+ );
+ // The IA2 -> UIA proxy doesn't fire ToggleState prop change events, nor
+ // does it fail when Expand/Collapse is called on a control which is
+ // already in the desired state.
+ if (gIsUiaEnabled) {
+ info("Calling Expand on summary");
+ await setUpWaitForUiaPropEvent(
+ "ExpandCollapseExpandCollapseState",
+ "summary"
+ );
+ await runPython(`pattern.Expand()`);
+ await waitForUiaEvent();
+ ok(
+ true,
+ "Got ExpandCollapseExpandCollapseState prop change event on summary"
+ );
+ is(
+ await runPython(`pattern.CurrentExpandCollapseState`),
+ ExpandCollapseState_Expanded,
+ "summary has ExpandCollapseState_Expanded"
+ );
+ info("Calling Expand on summary");
+ await testPythonRaises(`pattern.Expand()`, "Expand on summary failed");
+ info("Calling Collapse on summary");
+ await setUpWaitForUiaPropEvent(
+ "ExpandCollapseExpandCollapseState",
+ "summary"
+ );
+ await runPython(`pattern.Collapse()`);
+ await waitForUiaEvent();
+ ok(
+ true,
+ "Got ExpandCollapseExpandCollapseState prop change event on summary"
+ );
+ is(
+ await runPython(`pattern.CurrentExpandCollapseState`),
+ ExpandCollapseState_Collapsed,
+ "summary has ExpandCollapseState_Collapsed"
+ );
+ info("Calling Collapse on summary");
+ await testPythonRaises(
+ `pattern.Collapse()`,
+ "Collapse on summary failed"
+ );
+ }
+
+ await assignPyVarToUiaWithId("popup");
+ // Initially, popup has aria-haspopup but not aria-expanded. That should
+ // be exposed as collapsed.
+ await definePyVar("pattern", `getUiaPattern(popup, "ExpandCollapse")`);
+ ok(await runPython(`bool(pattern)`), "popup has ExpandCollapse pattern");
+ // The IA2 -> UIA proxy doesn't expose ExpandCollapseState_Collapsed for
+ // aria-haspopup without aria-expanded.
+ if (gIsUiaEnabled) {
+ is(
+ await runPython(`pattern.CurrentExpandCollapseState`),
+ ExpandCollapseState_Collapsed,
+ "popup has ExpandCollapseState_Collapsed"
+ );
+ info("Calling Expand on popup");
+ await setUpWaitForUiaPropEvent(
+ "ExpandCollapseExpandCollapseState",
+ "popup"
+ );
+ await runPython(`pattern.Expand()`);
+ await waitForUiaEvent();
+ ok(
+ true,
+ "Got ExpandCollapseExpandCollapseState prop change event on popup"
+ );
+ is(
+ await runPython(`pattern.CurrentExpandCollapseState`),
+ ExpandCollapseState_Expanded,
+ "popup has ExpandCollapseState_Expanded"
+ );
+ }
+
+ await testPatternAbsent("button", "ExpandCollapse");
+ }
+);
+
+/**
+ * Test the ScrollItem pattern.
+ */
+addUiaTask(
+ `
+<hr style="height: 100vh;">
+<button id="button">button</button>
+ `,
+ async function testScrollItem(browser, docAcc) {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("button");
+ await definePyVar("pattern", `getUiaPattern(button, "ScrollItem")`);
+ ok(await runPython(`bool(pattern)`), "button has ScrollItem pattern");
+ const button = findAccessibleChildByID(docAcc, "button");
+ testStates(button, STATE_OFFSCREEN);
+ info("Calling ScrollIntoView on button");
+ // UIA doesn't have an event for this.
+ let scrolled = waitForEvent(EVENT_SCROLLING_END, docAcc);
+ await runPython(`pattern.ScrollIntoView()`);
+ await scrolled;
+ ok(true, "Document scrolled");
+ testStates(button, 0, 0, STATE_OFFSCREEN);
+ }
+);
+
+/**
+ * Test the Value pattern.
+ */
+addUiaTask(
+ `
+<input id="text" value="before">
+<input id="textRo" readonly value="textRo">
+<input id="textDis" disabled value="textDis">
+<select id="select"><option selected>a</option><option>b</option></select>
+<progress id="progress" value="0.5"></progress>
+<input id="range" type="range" aria-valuetext="02:00:00">
+<a id="link" href="https://example.com/">Link</a>
+<div id="ariaTextbox" contenteditable role="textbox">before</div>
+<button id="button">button</button>
+ `,
+ async function testValue() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("text");
+ await definePyVar("pattern", `getUiaPattern(text, "Value")`);
+ ok(await runPython(`bool(pattern)`), "text has Value pattern");
+ ok(
+ !(await runPython(`pattern.CurrentIsReadOnly`)),
+ "text has IsReadOnly false"
+ );
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "before",
+ "text has correct Value"
+ );
+ info("SetValue on text");
+ await setUpWaitForUiaPropEvent("ValueValue", "text");
+ await runPython(`pattern.SetValue("after")`);
+ await waitForUiaEvent();
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "after",
+ "text has correct Value"
+ );
+
+ await assignPyVarToUiaWithId("textRo");
+ await definePyVar("pattern", `getUiaPattern(textRo, "Value")`);
+ ok(await runPython(`bool(pattern)`), "textRo has Value pattern");
+ ok(
+ await runPython(`pattern.CurrentIsReadOnly`),
+ "textRo has IsReadOnly true"
+ );
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "textRo",
+ "textRo has correct Value"
+ );
+ info("SetValue on textRo");
+ await testPythonRaises(
+ `pattern.SetValue("after")`,
+ "SetValue on textRo failed"
+ );
+
+ await assignPyVarToUiaWithId("textDis");
+ await definePyVar("pattern", `getUiaPattern(textDis, "Value")`);
+ ok(await runPython(`bool(pattern)`), "textDis has Value pattern");
+ ok(
+ !(await runPython(`pattern.CurrentIsReadOnly`)),
+ "textDis has IsReadOnly false"
+ );
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "textDis",
+ "textDis has correct Value"
+ );
+ // The IA2 -> UIA proxy doesn't fail SetValue for a disabled element.
+ if (gIsUiaEnabled) {
+ info("SetValue on textDis");
+ await testPythonRaises(
+ `pattern.SetValue("after")`,
+ "SetValue on textDis failed"
+ );
+ }
+
+ await assignPyVarToUiaWithId("select");
+ await definePyVar("pattern", `getUiaPattern(select, "Value")`);
+ ok(await runPython(`bool(pattern)`), "select has Value pattern");
+ ok(
+ !(await runPython(`pattern.CurrentIsReadOnly`)),
+ "select has IsReadOnly false"
+ );
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "a",
+ "select has correct Value"
+ );
+ info("SetValue on select");
+ await testPythonRaises(
+ `pattern.SetValue("b")`,
+ "SetValue on select failed"
+ );
+
+ await assignPyVarToUiaWithId("progress");
+ await definePyVar("pattern", `getUiaPattern(progress, "Value")`);
+ ok(await runPython(`bool(pattern)`), "progress has Value pattern");
+ // Gecko a11y doesn't treat progress bars as read only, but it probably
+ // should.
+ todo(
+ await runPython(`pattern.CurrentIsReadOnly`),
+ "progress has IsReadOnly true"
+ );
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "50%",
+ "progress has correct Value"
+ );
+ info("SetValue on progress");
+ await testPythonRaises(
+ `pattern.SetValue("60%")`,
+ "SetValue on progress failed"
+ );
+
+ await assignPyVarToUiaWithId("range");
+ await definePyVar("pattern", `getUiaPattern(range, "Value")`);
+ ok(await runPython(`bool(pattern)`), "range has Value pattern");
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "02:00:00",
+ "range has correct Value"
+ );
+
+ await assignPyVarToUiaWithId("link");
+ await definePyVar("pattern", `getUiaPattern(link, "Value")`);
+ ok(await runPython(`bool(pattern)`), "link has Value pattern");
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "https://example.com/",
+ "link has correct Value"
+ );
+
+ await assignPyVarToUiaWithId("ariaTextbox");
+ await definePyVar("pattern", `getUiaPattern(ariaTextbox, "Value")`);
+ ok(await runPython(`bool(pattern)`), "ariaTextbox has Value pattern");
+ ok(
+ !(await runPython(`pattern.CurrentIsReadOnly`)),
+ "ariaTextbox has IsReadOnly false"
+ );
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "before",
+ "ariaTextbox has correct Value"
+ );
+ info("SetValue on ariaTextbox");
+ await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox");
+ await runPython(`pattern.SetValue("after")`);
+ await waitForUiaEvent();
+ is(
+ await runPython(`pattern.CurrentValue`),
+ "after",
+ "ariaTextbox has correct Value"
+ );
+
+ await testPatternAbsent("button", "Value");
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_tree.js b/accessible/tests/browser/windows/uia/browser_tree.js
index 778609bedb..c63045b6d1 100644
--- a/accessible/tests/browser/windows/uia/browser_tree.js
+++ b/accessible/tests/browser/windows/uia/browser_tree.js
@@ -13,24 +13,6 @@ async function testIsControl(pyVar, isControl) {
}
}
-/**
- * Define a global Python variable and assign it to a given Python expression.
- */
-function definePyVar(varName, expression) {
- return runPython(`
- global ${varName}
- ${varName} = ${expression}
- `);
-}
-
-/**
- * Get the UIA element with the given id and assign it to a global Python
- * variable using the id as the variable name.
- */
-function assignPyVarToUiaWithId(id) {
- return definePyVar(id, `findUiaByDomId(doc, "${id}")`);
-}
-
addUiaTask(
`
<p id="p">paragraph</p>
@@ -46,7 +28,7 @@ addUiaTask(
<div id="editable" contenteditable>editable</div>
<table id="table"><tr><th>th</th></tr></table>
`,
- async function (browser, docAcc) {
+ async function () {
await definePyVar("doc", `getDocUia()`);
await assignPyVarToUiaWithId("p");
await testIsControl("p", false);
diff --git a/accessible/tests/browser/windows/uia/head.js b/accessible/tests/browser/windows/uia/head.js
index e659354c7c..5b453ce6fe 100644
--- a/accessible/tests/browser/windows/uia/head.js
+++ b/accessible/tests/browser/windows/uia/head.js
@@ -4,7 +4,7 @@
"use strict";
-/* exported gIsUiaEnabled, addUiaTask */
+/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent, testPythonRaises */
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
@@ -53,3 +53,76 @@ function addUiaTask(doc, task, options = {}) {
addTask(false);
}
}
+
+/**
+ * Define a global Python variable and assign it to a given Python expression.
+ */
+function definePyVar(varName, expression) {
+ return runPython(`
+ global ${varName}
+ ${varName} = ${expression}
+ `);
+}
+
+/**
+ * Get the UIA element with the given id and assign it to a global Python
+ * variable using the id as the variable name.
+ */
+function assignPyVarToUiaWithId(id) {
+ return definePyVar(id, `findUiaByDomId(doc, "${id}")`);
+}
+
+/**
+ * Set up to wait for a UIA event. You must await this before performing the
+ * action which fires the event.
+ */
+function setUpWaitForUiaEvent(eventName, id) {
+ return definePyVar(
+ "onEvent",
+ `WaitForUiaEvent(eventId=UIA_${eventName}EventId, match="${id}")`
+ );
+}
+
+/**
+ * Set up to wait for a UIA property change event. You must await this before
+ * performing the action which fires the event.
+ */
+function setUpWaitForUiaPropEvent(propName, id) {
+ return definePyVar(
+ "onEvent",
+ `WaitForUiaEvent(property=UIA_${propName}PropertyId, match="${id}")`
+ );
+}
+
+/**
+ * Wait for the event requested in setUpWaitForUia*Event.
+ */
+function waitForUiaEvent() {
+ return runPython(`
+ onEvent.wait()
+ `);
+}
+
+/**
+ * Verify that a UIA element does *not* support the given control pattern.
+ */
+async function testPatternAbsent(id, patternName) {
+ const hasPattern = await runPython(`
+ el = findUiaByDomId(doc, "${id}")
+ return bool(getUiaPattern(el, "${patternName}"))
+ `);
+ ok(!hasPattern, `${id} doesn't have ${patternName} pattern`);
+}
+
+/**
+ * Verify that a Python expression raises an exception.
+ */
+async function testPythonRaises(expression, message) {
+ let failed = false;
+ try {
+ await runPython(expression);
+ } catch {
+ failed = true;
+ }
+ ok(failed, message);
+}