From a90a5cba08fdf6c0ceb95101c275108a152a3aed Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:35:37 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- accessible/tests/browser/e10s/browser.toml | 1 + .../tests/browser/e10s/browser_caching_states.js | 13 + accessible/tests/browser/e10s/head.js | 5 + accessible/tests/browser/mac/browser_range.js | 54 ++++ accessible/tests/browser/python_runner_wsh.py | 4 + .../browser/selectable/browser_test_aria_select.js | 23 +- accessible/tests/browser/windows/uia/browser.toml | 7 + .../browser/windows/uia/browser_generalProps.js | 350 +++++++++++++++++++++ .../browser/windows/uia/browser_gridPatterns.js | 161 ++++++++++ .../browser/windows/uia/browser_relationProps.js | 143 +++++++++ .../windows/uia/browser_selectionPatterns.js | 226 +++++++++++++ .../browser/windows/uia/browser_simplePatterns.js | 97 ++++++ accessible/tests/browser/windows/uia/head.js | 14 +- 13 files changed, 1091 insertions(+), 7 deletions(-) create mode 100644 accessible/tests/browser/windows/uia/browser_gridPatterns.js create mode 100644 accessible/tests/browser/windows/uia/browser_relationProps.js create mode 100644 accessible/tests/browser/windows/uia/browser_selectionPatterns.js (limited to 'accessible/tests/browser') diff --git a/accessible/tests/browser/e10s/browser.toml b/accessible/tests/browser/e10s/browser.toml index dff9b1c712..a4205d38e6 100644 --- a/accessible/tests/browser/e10s/browser.toml +++ b/accessible/tests/browser/e10s/browser.toml @@ -31,6 +31,7 @@ prefs = [ ["browser_caching_description.js"] ["browser_caching_document_props.js"] +https_first_disabled = true ["browser_caching_domnodeid.js"] diff --git a/accessible/tests/browser/e10s/browser_caching_states.js b/accessible/tests/browser/e10s/browser_caching_states.js index 7292228f25..6f674f8c48 100644 --- a/accessible/tests/browser/e10s/browser_caching_states.js +++ b/accessible/tests/browser/e10s/browser_caching_states.js @@ -424,6 +424,11 @@ addAccessibleTask(
multiNoSel
+
+
+
+
gridcell
+
`, async function (browser, docAcc) { @@ -450,6 +455,14 @@ addAccessibleTask( multiNoSel.takeFocus(); await focused; testStates(multiNoSel, STATE_FOCUSED, 0, STATE_SELECTED, 0); + + const gridcell = findAccessibleChildByID(docAcc, "gridcell"); + testStates(gridcell, 0, 0, STATE_FOCUSED | STATE_SELECTED, 0); + info("Focusing gridcell"); + focused = waitForEvent(EVENT_FOCUS, gridcell); + gridcell.takeFocus(); + await focused; + testStates(gridcell, STATE_FOCUSED, 0, STATE_SELECTED, 0); }, { topLevel: true, iframe: true, remoteIframe: true, chrome: true } ); diff --git a/accessible/tests/browser/e10s/head.js b/accessible/tests/browser/e10s/head.js index bdbcb7445f..e72af914d4 100644 --- a/accessible/tests/browser/e10s/head.js +++ b/accessible/tests/browser/e10s/head.js @@ -169,6 +169,11 @@ async function testRelated( attrs: [{ key: attr, value: "dependant2" }], expected: [null, host, dependant2], }, + { + desc: "Change attribute to multiple targets", + attrs: [{ key: attr, value: "dependant1 dependant2" }], + expected: [host, host, [dependant1, dependant2]], + }, { desc: "Remove attribute", attrs: [{ key: attr }], diff --git a/accessible/tests/browser/mac/browser_range.js b/accessible/tests/browser/mac/browser_range.js index 430e41d6ea..8a5bafba50 100644 --- a/accessible/tests/browser/mac/browser_range.js +++ b/accessible/tests/browser/mac/browser_range.js @@ -188,3 +188,57 @@ addAccessibleTask( is(slider.getAttributeValue("AXMaxValue"), 5, "Correct max value"); } ); + +/** + * Verify progress HTML elements expose their min, max, and value to VO. + * Progress elements should not expose a value description, and should not + * expose increment/decrement actions. + */ +addAccessibleTask( + ``, + async (browser, accDoc) => { + const progress = getNativeInterface(accDoc, "progress"); + is(progress.getAttributeValue("AXValue"), 70, "Correct value"); + is(progress.getAttributeValue("AXMaxValue"), 100, "Correct max value"); + is(progress.getAttributeValue("AXMinValue"), 0, "Correct min value"); + is( + progress.getAttributeValue("AXValueDescription"), + null, + "Progress elements should not expose a value description" + ); + for (let action of progress.actionNames) { + isnot( + action, + "AXIncrement", + "Progress elements should not expose increment action" + ); + isnot( + action, + "AXDecrement", + "Progress elements should not expose decrement action" + ); + } + } +); + +/** + * Verify progress HTML elements expose changes to their value. + */ +addAccessibleTask( + ``, + async (browser, accDoc) => { + const progress = getNativeInterface(accDoc, "progress"); + is(progress.getAttributeValue("AXValue"), 70, "Correct value"); + is(progress.getAttributeValue("AXMaxValue"), 100, "Correct max value"); + is(progress.getAttributeValue("AXMinValue"), 0, "Correct min value"); + + const evt = waitForMacEvent("AXValueChanged"); + await invokeContentTask(browser, [], () => { + const p = content.document.getElementById("progress"); + p.setAttribute("value", "90"); + }); + await evt; + + is(progress.getAttributeValue("AXValue"), 90, "Correct updated value"); + } +); diff --git a/accessible/tests/browser/python_runner_wsh.py b/accessible/tests/browser/python_runner_wsh.py index 488051240f..2686967cfb 100644 --- a/accessible/tests/browser/python_runner_wsh.py +++ b/accessible/tests/browser/python_runner_wsh.py @@ -9,6 +9,7 @@ It is intended to be called from JS browser tests. """ import json +import math import os import sys import traceback @@ -83,6 +84,9 @@ def web_socket_transfer_data(request): exec(code, namespace) # Run the function we just defined. ret = namespace["run"]() + if isinstance(ret, float) and math.isnan(ret): + # NaN can't be serialized by JSON. + ret = None send("return", ret) except Exception: send("exception", traceback.format_exc()) diff --git a/accessible/tests/browser/selectable/browser_test_aria_select.js b/accessible/tests/browser/selectable/browser_test_aria_select.js index dbc36956f8..2c3a80586e 100644 --- a/accessible/tests/browser/selectable/browser_test_aria_select.js +++ b/accessible/tests/browser/selectable/browser_test_aria_select.js @@ -117,7 +117,8 @@ addAccessibleTask( // //////////////////////////////////////////////////////////////////////// // role="grid" aria-multiselectable, selectable children in subtree addAccessibleTask( - ` @@ -133,19 +134,29 @@ addAccessibleTask( - - - -
103/14/05Conference Fee
`, + + + + +
ab
+ `, async function (browser, docAcc) { info('role="grid" aria-multiselectable, selectable children in subtree'); - let grid = findAccessibleChildByID(docAcc, "grid", [ + const grid = findAccessibleChildByID(docAcc, "grid", [ nsIAccessibleSelectable, ]); + // grid_cell1 is a with an implicit role of gridcell. testSelectableSelection(grid, ["grid_colhead1", "grid_cell1"]); + info("Verify aria-selected doesn't apply to that isn't gridcell"); + // We can't use testSelectableSelection here because table (rightly) isn't a + // selectable container. + const tableB = findAccessibleChildByID(docAcc, "tableB"); + testStates(tableB, 0, 0, STATE_SELECTED, 0); }, { chrome: true, diff --git a/accessible/tests/browser/windows/uia/browser.toml b/accessible/tests/browser/windows/uia/browser.toml index 75728f56d7..13b1c12cc0 100644 --- a/accessible/tests/browser/windows/uia/browser.toml +++ b/accessible/tests/browser/windows/uia/browser.toml @@ -11,8 +11,15 @@ support-files = ["head.js"] ["browser_elementFromPoint.js"] ["browser_focus.js"] + ["browser_generalProps.js"] +["browser_gridPatterns.js"] + +["browser_relationProps.js"] + +["browser_selectionPatterns.js"] + ["browser_simplePatterns.js"] ["browser_tree.js"] diff --git a/accessible/tests/browser/windows/uia/browser_generalProps.js b/accessible/tests/browser/windows/uia/browser_generalProps.js index 5cfda226d0..244c9e4b1b 100644 --- a/accessible/tests/browser/windows/uia/browser_generalProps.js +++ b/accessible/tests/browser/windows/uia/browser_generalProps.js @@ -4,6 +4,12 @@ "use strict"; +/* eslint-disable camelcase */ +// From https://learn.microsoft.com/en-us/windows/win32/winauto/landmark-type-identifiers +const UIA_CustomLandmarkTypeId = 80000; +const UIA_MainLandmarkTypeId = 80002; +/* eslint-enable camelcase */ + /** * Test the Name property. */ @@ -103,3 +109,347 @@ addUiaTask( ok(await runPython(`p.CurrentIsEnabled`), "p has IsEnabled true"); } ); + +async function testGroupPos(id, level, pos, size) { + await assignPyVarToUiaWithId(id); + is(await runPython(`${id}.CurrentLevel`), level, `${id} Level correct`); + is( + await runPython(`${id}.CurrentPositionInSet`), + pos, + `${id} PositionInSet correct` + ); + is( + await runPython(`${id}.CurrentSizeOfSet`), + size, + `${id} SizeOfSet correct` + ); +} + +/** + * Test the Level, PositionInSet and SizeOfSet properties. + */ +addUiaTask( + ` + +

h2

+ + `, + async function testGroupPosProps(browser) { + await definePyVar("doc", `getDocUia()`); + await testGroupPos("li1", 1, 1, 1); + await testGroupPos("li2a", 2, 1, 2); + await testGroupPos("li2c", 2, 2, 2); + info("Showing li2b"); + // There aren't events in any API for a change to group position properties + // because this would be too spammy and isn't particularly useful given + // how frequently these can change. + let shown = waitForEvent(EVENT_SHOW, "li2b"); + await invokeContentTask(browser, [], () => { + content.document.getElementById("li2b").hidden = false; + }); + await shown; + await testGroupPos("li2a", 2, 1, 3); + await testGroupPos("li2b", 2, 2, 3); + await testGroupPos("li2c", 2, 3, 3); + + // The IA2 -> UIA proxy doesn't map heading level to the Level property. + if (gIsUiaEnabled) { + await testGroupPos("h2", 2, 0, 0); + } + await testGroupPos("button", 0, 0, 0); + } +); + +/** + * Test the FrameworkId property. + */ +addUiaTask( + ``, + async function testFrameworkId() { + await definePyVar("doc", `getDocUia()`); + is( + await runPython(`doc.CurrentFrameworkId`), + "Gecko", + "doc FrameworkId is correct" + ); + await assignPyVarToUiaWithId("button"); + is( + await runPython(`button.CurrentFrameworkId`), + "Gecko", + "button FrameworkId is correct" + ); + } +); + +/** + * Test the ClassName property. + */ +addUiaTask( + ` +

p

+ + `, + async function testClassName(browser, docAcc) { + await definePyVar("doc", `getDocUia()`); + await assignPyVarToUiaWithId("p"); + ok(!(await runPython(`p.CurrentClassName`)), "p has no ClassName"); + + await assignPyVarToUiaWithId("button"); + is( + await runPython(`button.CurrentClassName`), + "c1", + "button has correct ClassName" + ); + info("Changing button class"); + await invokeSetAttribute(browser, "button", "class", "c2 c3"); + // Gecko doesn't fire an event for class changes, as this isn't useful for + // clients. + const button = findAccessibleChildByID(docAcc, "button"); + await untilCacheIs( + () => button.attributes.getStringProperty("class"), + "c2 c3", + "button class updated" + ); + is( + await runPython(`button.CurrentClassName`), + "c2 c3", + "button has correct ClassName" + ); + }, + // The IA2 -> UIA proxy doesn't support ClassName. + { uiaEnabled: true, uiaDisabled: false } +); + +/** + * Test the AriaRole property. + */ +addUiaTask( + ` +
button
+
main
+
unknown
+ +

computedHeading

+
computedMain
+
generic
+ `, + async function testAriaRole() { + await definePyVar("doc", `getDocUia()`); + is( + await runPython(`findUiaByDomId(doc, "button").CurrentAriaRole`), + "button", + "button has correct AriaRole" + ); + is( + await runPython(`findUiaByDomId(doc, "main").CurrentAriaRole`), + "main", + "main has correct AriaRole" + ); + is( + await runPython(`findUiaByDomId(doc, "unknown").CurrentAriaRole`), + "unknown", + "unknown has correct AriaRole" + ); + // The IA2 -> UIA proxy doesn't compute ARIA roles. + if (gIsUiaEnabled) { + is( + await runPython( + `findUiaByDomId(doc, "computedButton").CurrentAriaRole` + ), + "button", + "computedButton has correct AriaRole" + ); + is( + await runPython(`findUiaByDomId(doc, "computedMain").CurrentAriaRole`), + "main", + "computedMain has correct AriaRole" + ); + is( + await runPython( + `findUiaByDomId(doc, "computedHeading").CurrentAriaRole` + ), + "heading", + "computedHeading has correct AriaRole" + ); + is( + await runPython(`findUiaByDomId(doc, "generic").CurrentAriaRole`), + "generic", + "generic has correct AriaRole" + ); + } + } +); + +/** + * Test the LocalizedControlType property. We don't support this ourselves, but + * the system provides it based on ControlType and AriaRole. + */ +addUiaTask( + ` + +

h1

+
main
+ `, + async function testLocalizedControlType() { + await definePyVar("doc", `getDocUia()`); + is( + await runPython( + `findUiaByDomId(doc, "button").CurrentLocalizedControlType` + ), + "button", + "button has correct LocalizedControlType" + ); + // The IA2 -> UIA proxy doesn't compute ARIA roles, so it can't compute the + // correct LocalizedControlType for these either. + if (gIsUiaEnabled) { + is( + await runPython( + `findUiaByDomId(doc, "h1").CurrentLocalizedControlType` + ), + "heading", + "h1 has correct LocalizedControlType" + ); + is( + await runPython( + `findUiaByDomId(doc, "main").CurrentLocalizedControlType` + ), + "main", + "main has correct LocalizedControlType" + ); + } + } +); + +/** + * Test the LandmarkType property. + */ +addUiaTask( + ` +
main
+
htmlMain
+ + +
region
+
unnamedRegion
+
mainBanner
+
none
+ `, + async function testLandmarkType() { + await definePyVar("doc", `getDocUia()`); + is( + await runPython(`findUiaByDomId(doc, "main").CurrentLandmarkType`), + UIA_MainLandmarkTypeId, + "main has correct LandmarkType" + ); + is( + await runPython(`findUiaByDomId(doc, "htmlMain").CurrentLandmarkType`), + UIA_MainLandmarkTypeId, + "htmlMain has correct LandmarkType" + ); + is( + await runPython(`findUiaByDomId(doc, "banner").CurrentLandmarkType`), + UIA_CustomLandmarkTypeId, + "banner has correct LandmarkType" + ); + is( + await runPython(`findUiaByDomId(doc, "header").CurrentLandmarkType`), + UIA_CustomLandmarkTypeId, + "header has correct LandmarkType" + ); + is( + await runPython(`findUiaByDomId(doc, "region").CurrentLandmarkType`), + UIA_CustomLandmarkTypeId, + "region has correct LandmarkType" + ); + is( + await runPython( + `findUiaByDomId(doc, "unnamedRegion").CurrentLandmarkType` + ), + 0, + "unnamedRegion has correct LandmarkType" + ); + // ARIA role takes precedence. + is( + await runPython(`findUiaByDomId(doc, "mainBanner").CurrentLandmarkType`), + UIA_CustomLandmarkTypeId, + "mainBanner has correct LandmarkType" + ); + is( + await runPython(`findUiaByDomId(doc, "none").CurrentLandmarkType`), + 0, + "none has correct LandmarkType" + ); + } +); + +/** + * Test the LocalizedLandmarkType property. + */ +addUiaTask( + ` +
main
+ +
region
+
unnamedRegion
+
mainBanner
+
none
+ `, + async function testLocalizedLandmarkType() { + await definePyVar("doc", `getDocUia()`); + // Provided by the system. + is( + await runPython( + `findUiaByDomId(doc, "main").CurrentLocalizedLandmarkType` + ), + "main", + "main has correct LocalizedLandmarkType" + ); + // The IA2 -> UIA proxy doesn't follow the Core AAM spec for this role. + if (gIsUiaEnabled) { + // Provided by us. + is( + await runPython( + `findUiaByDomId(doc, "contentinfo").CurrentLocalizedLandmarkType` + ), + "content information", + "contentinfo has correct LocalizedLandmarkType" + ); + } + is( + await runPython( + `findUiaByDomId(doc, "region").CurrentLocalizedLandmarkType` + ), + "region", + "region has correct LocalizedLandmarkType" + ); + // Invalid landmark. + is( + await runPython( + `findUiaByDomId(doc, "unnamedRegion").CurrentLocalizedLandmarkType` + ), + "", + "unnamedRegion has correct LocalizedLandmarkType" + ); + // ARIA role takes precedence. + is( + await runPython( + `findUiaByDomId(doc, "mainBanner").CurrentLocalizedLandmarkType` + ), + "banner", + "mainBanner has correct LocalizedLandmarkType" + ); + is( + await runPython( + `findUiaByDomId(doc, "none").CurrentLocalizedLandmarkType` + ), + "", + "none has correct LocalizedLandmarkType" + ); + } +); diff --git a/accessible/tests/browser/windows/uia/browser_gridPatterns.js b/accessible/tests/browser/windows/uia/browser_gridPatterns.js new file mode 100644 index 0000000000..24c80a6340 --- /dev/null +++ b/accessible/tests/browser/windows/uia/browser_gridPatterns.js @@ -0,0 +1,161 @@ +/* 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"; + +/* eslint-disable camelcase */ +const RowOrColumnMajor_RowMajor = 0; +/* eslint-enable camelcase */ + +const SNIPPET = ` + + + + + +
abc
dgef
hi
jkl
+ +`; + +async function testGridGetItem(row, col, cellId) { + is( + await runPython(`pattern.GetItem(${row}, ${col}).CurrentAutomationId`), + cellId, + `GetItem with row ${row} and col ${col} returned ${cellId}` + ); +} + +async function testGridItemProps(id, row, col, rowSpan, colSpan, gridId) { + await assignPyVarToUiaWithId(id); + await definePyVar("pattern", `getUiaPattern(${id}, "GridItem")`); + ok(await runPython(`bool(pattern)`), `${id} has GridItem pattern`); + is(await runPython(`pattern.CurrentRow`), row, `${id} has correct Row`); + is(await runPython(`pattern.CurrentColumn`), col, `${id} has correct Column`); + is( + await runPython(`pattern.CurrentRowSpan`), + rowSpan, + `${id} has correct RowSpan` + ); + is( + await runPython(`pattern.CurrentColumnSpan`), + colSpan, + `${id} has correct ColumnSpan` + ); + is( + await runPython(`pattern.CurrentContainingGrid.CurrentAutomationId`), + gridId, + `${id} ContainingGridItem is ${gridId}` + ); +} + +async function testTableItemProps(id, rowHeaders, colHeaders) { + await assignPyVarToUiaWithId(id); + await definePyVar("pattern", `getUiaPattern(${id}, "TableItem")`); + ok(await runPython(`bool(pattern)`), `${id} has TableItem pattern`); + await isUiaElementArray( + `pattern.GetCurrentRowHeaderItems()`, + rowHeaders, + `${id} has correct RowHeaderItems` + ); + await isUiaElementArray( + `pattern.GetCurrentColumnHeaderItems()`, + colHeaders, + `${id} has correct ColumnHeaderItems` + ); +} + +/** + * Test the Grid pattern. + */ +addUiaTask(SNIPPET, async function testGrid() { + await definePyVar("doc", `getDocUia()`); + await assignPyVarToUiaWithId("table"); + await definePyVar("pattern", `getUiaPattern(table, "Grid")`); + ok(await runPython(`bool(pattern)`), "table has Grid pattern"); + is( + await runPython(`pattern.CurrentRowCount`), + 4, + "table has correct RowCount" + ); + is( + await runPython(`pattern.CurrentColumnCount`), + 3, + "table has correct ColumnCount" + ); + await testGridGetItem(0, 0, "a"); + await testGridGetItem(0, 1, "b"); + await testGridGetItem(0, 2, "c"); + await testGridGetItem(1, 0, "dg"); + await testGridGetItem(1, 1, "ef"); + await testGridGetItem(1, 2, "ef"); + await testGridGetItem(2, 0, "dg"); + await testGridGetItem(2, 1, "h"); + await testGridGetItem(2, 2, "i"); + + await testPatternAbsent("button", "Grid"); +}); + +/** + * Test the GridItem pattern. + */ +addUiaTask(SNIPPET, async function testGridItem() { + await definePyVar("doc", `getDocUia()`); + await testGridItemProps("a", 0, 0, 1, 1, "table"); + await testGridItemProps("b", 0, 1, 1, 1, "table"); + await testGridItemProps("c", 0, 2, 1, 1, "table"); + await testGridItemProps("dg", 1, 0, 2, 1, "table"); + await testGridItemProps("ef", 1, 1, 1, 2, "table"); + await testGridItemProps("jkl", 3, 0, 1, 3, "table"); + + await testPatternAbsent("button", "GridItem"); +}); + +/** + * Test the Table pattern. + */ +addUiaTask( + SNIPPET, + async function testTable() { + await definePyVar("doc", `getDocUia()`); + await assignPyVarToUiaWithId("table"); + await definePyVar("pattern", `getUiaPattern(table, "Table")`); + ok(await runPython(`bool(pattern)`), "table has Table pattern"); + await isUiaElementArray( + `pattern.GetCurrentRowHeaders()`, + ["dg", "h"], + "table has correct RowHeaders" + ); + await isUiaElementArray( + `pattern.GetCurrentColumnHeaders()`, + ["a", "b", "c"], + "table has correct ColumnHeaders" + ); + is( + await runPython(`pattern.CurrentRowOrColumnMajor`), + RowOrColumnMajor_RowMajor, + "table has correct RowOrColumnMajor" + ); + + await testPatternAbsent("button", "Table"); + }, + // The IA2 -> UIA proxy doesn't support the Row/ColumnHeaders properties. + { uiaEnabled: true, uiaDisabled: false } +); + +/** + * Test the TableItem pattern. + */ +addUiaTask(SNIPPET, async function testTableItem() { + await definePyVar("doc", `getDocUia()`); + await testTableItemProps("a", [], []); + await testTableItemProps("b", [], []); + await testTableItemProps("c", [], []); + await testTableItemProps("dg", [], ["a"]); + await testTableItemProps("ef", ["dg"], ["b", "c"]); + await testTableItemProps("h", ["dg"], ["b"]); + await testTableItemProps("i", ["dg", "h"], ["c"]); + await testTableItemProps("jkl", [], ["a", "b", "c"]); + + await testPatternAbsent("button", "TableItem"); +}); diff --git a/accessible/tests/browser/windows/uia/browser_relationProps.js b/accessible/tests/browser/windows/uia/browser_relationProps.js new file mode 100644 index 0000000000..ff4059f99e --- /dev/null +++ b/accessible/tests/browser/windows/uia/browser_relationProps.js @@ -0,0 +1,143 @@ +/* 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 testUiaRelationArray(id, prop, targets) { + return isUiaElementArray( + `findUiaByDomId(doc, "${id}").Current${prop}`, + targets, + `${id} has correct ${prop} targets` + ); +} + +/** + * Test the ControllerFor property. + */ +addUiaTask( + ` + + + +
t1
+
t2
+
t3
+
t4
+ + `, + async function testControllerFor() { + await definePyVar("doc", `getDocUia()`); + await testUiaRelationArray("controls", "ControllerFor", ["t1", "t2"]); + // The IA2 -> UIA proxy doesn't support IA2_RELATION_ERROR. + if (gIsUiaEnabled) { + await testUiaRelationArray("error", "ControllerFor", ["t3", "t4"]); + await testUiaRelationArray("controlsError", "ControllerFor", [ + "t1", + "t2", + "t3", + "t4", + ]); + } + await testUiaRelationArray("none", "ControllerFor", []); + } +); + +/** + * Test the DescribedBy property. + */ +addUiaTask( + ` + + + +
t1
+
t2
+
t3
+
t4
+ + `, + async function testDescribedBy() { + await definePyVar("doc", `getDocUia()`); + await testUiaRelationArray("describedby", "DescribedBy", ["t1", "t2"]); + // The IA2 -> UIA proxy doesn't support IA2_RELATION_DETAILS. + if (gIsUiaEnabled) { + await testUiaRelationArray("details", "DescribedBy", ["t3", "t4"]); + await testUiaRelationArray("describedbyDetails", "DescribedBy", [ + "t1", + "t2", + "t3", + "t4", + ]); + } + await testUiaRelationArray("none", "DescribedBy", []); + } +); + +/** + * Test the FlowsFrom and FlowsTo properties. + */ +addUiaTask( + ` +
t1
+
t2
+ + `, + async function testFlows() { + await definePyVar("doc", `getDocUia()`); + await testUiaRelationArray("t1", "FlowsTo", ["t2"]); + await testUiaRelationArray("t2", "FlowsFrom", ["t1"]); + await testUiaRelationArray("none", "FlowsFrom", []); + await testUiaRelationArray("none", "FlowsTo", []); + } +); + +/** + * Test the LabeledBy property. + */ +addUiaTask( + ` + + + + + + `, + async function testLabeledBy() { + await definePyVar("doc", `getDocUia()`); + // input's LabeledBy should be label's text leaf. + let result = await runPython(` + input = findUiaByDomId(doc, "input") + label = findUiaByDomId(doc, "label") + labelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(label) + return uiaClient.CompareElements(input.CurrentLabeledBy, labelLeaf) + `); + ok(result, "input has correct LabeledBy"); + // wrappedInput's LabeledBy should be wrappingLabelP's text leaf. + result = await runPython(` + wrappedInput = findUiaByDomId(doc, "wrappedInput") + wrappingLabelP = findUiaByDomId(doc, "wrappingLabelP") + wrappingLabelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(wrappingLabelP) + return uiaClient.CompareElements(wrappedInput.CurrentLabeledBy, wrappingLabelLeaf) + `); + ok(result, "wrappedInput has correct LabeledBy"); + // button has aria-labelledby, but UIA prohibits LabeledBy on buttons. + ok( + !(await runPython( + `bool(findUiaByDomId(doc, "button").CurrentLabeledBy)` + )), + "button has no LabeledBy" + ); + ok( + !(await runPython( + `bool(findUiaByDomId(doc, "noLabel").CurrentLabeledBy)` + )), + "noLabel has no LabeledBy" + ); + }, + // The IA2 -> UIA proxy doesn't expose LabeledBy properly. + { uiaEnabled: true, uiaDisabled: false } +); diff --git a/accessible/tests/browser/windows/uia/browser_selectionPatterns.js b/accessible/tests/browser/windows/uia/browser_selectionPatterns.js new file mode 100644 index 0000000000..a1f70b886a --- /dev/null +++ b/accessible/tests/browser/windows/uia/browser_selectionPatterns.js @@ -0,0 +1,226 @@ +/* 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 SNIPPET = ` + + + + +
+
al1
+
al2
+
+
+ + +
+ + + + + + + + + +
g1g2
g3g4
+
+ + +
+ + +`; + +async function testSelectionProps(id, selection, multiple, required) { + await assignPyVarToUiaWithId(id); + await definePyVar("pattern", `getUiaPattern(${id}, "Selection")`); + ok(await runPython(`bool(pattern)`), `${id} has Selection pattern`); + await isUiaElementArray( + `pattern.GetCurrentSelection()`, + selection, + `${id} has correct Selection` + ); + is( + !!(await runPython(`pattern.CurrentCanSelectMultiple`)), + multiple, + `${id} has correct CanSelectMultiple` + ); + // The IA2 -> UIA proxy doesn't reflect the required state correctly. + if (gIsUiaEnabled) { + is( + !!(await runPython(`pattern.CurrentIsSelectionRequired`)), + required, + `${id} has correct IsSelectionRequired` + ); + } +} + +async function testSelectionItemProps(id, selected, container) { + await assignPyVarToUiaWithId(id); + await definePyVar("pattern", `getUiaPattern(${id}, "SelectionItem")`); + ok(await runPython(`bool(pattern)`), `${id} has SelectionItem pattern`); + is( + !!(await runPython(`pattern.CurrentIsSelected`)), + selected, + `${id} has correct IsSelected` + ); + if (container) { + is( + await runPython(`pattern.CurrentSelectionContainer.CurrentAutomationId`), + container, + `${id} has correct SelectionContainer` + ); + } else { + ok( + !(await runPython(`bool(pattern.CurrentSelectionContainer)`)), + `${id} has no SelectionContainer` + ); + } +} + +/** + * Test the Selection pattern. + */ +addUiaTask(SNIPPET, async function testSelection(browser) { + await definePyVar("doc", `getDocUia()`); + await testSelectionProps("selectList", ["sl1"], false, false); + await testSelectionProps("selectRequired", [], false, true); + + await testSelectionProps("selectMulti", ["sm1", "sm3"], true, false); + // The Selection pattern only has an event for a complete invalidation of the + // container's selection, which only happens when there are too many selection + // events. Smaller selection changes fire events in the SelectionItem pattern. + info("Changing selectMulti selection"); + await setUpWaitForUiaEvent("Selection_Invalidated", "selectMulti"); + await invokeContentTask(browser, [], () => { + const multi = content.document.getElementById("selectMulti"); + multi[0].selected = false; + multi[1].selected = true; + multi[2].selected = false; + multi[3].selected = true; + multi[4].selected = true; + multi[5].selected = true; + }); + await waitForUiaEvent(); + ok(true, "select got Invalidated event"); + await testSelectionProps( + "selectMulti", + ["sm2", "sm4", "sm5", "sm6"], + true, + false + ); + + await testPatternAbsent("selectCombo", "Selection"); + + await testSelectionProps("ariaListbox", ["al1"], false, false); + await testSelectionProps("tablist", ["t2"], false, false); + // The IA2 -> UIA proxy doesn't expose the Selection pattern on grids. + if (gIsUiaEnabled) { + await testSelectionProps("grid", ["g2", "g4"], true, false); + } + + // radio gets the SelectionItem pattern, but radiogroup doesn't get the + // Selection pattern for now. Same for menu/menuitemradio. + await testPatternAbsent("radiogroup", "Selection"); + await testPatternAbsent("menu", "Selection"); + + await testPatternAbsent("button", "Selection"); +}); + +/** + * Test the SelectionItem pattern. + */ +addUiaTask(SNIPPET, async function testSelection() { + await definePyVar("doc", `getDocUia()`); + await testPatternAbsent("selectList", "SelectionItem"); + await testSelectionItemProps("sl1", true, "selectList"); + await testSelectionItemProps("sl2", false, "selectList"); + info("Calling Select on sl2"); + await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "sl2"); + await runPython(`pattern.Select()`); + await waitForUiaEvent(); + ok(true, "sl2 got ElementSelected event"); + await testSelectionItemProps("sl1", false, "selectList"); + await testSelectionItemProps("sl2", true, "selectList"); + + await testSelectionItemProps("sr1", false, "selectRequired"); + + await testSelectionItemProps("sm1", true, "selectMulti"); + await testSelectionItemProps("sm2", false, "selectMulti"); + info("Calling AddToSelection on sm2"); + await setUpWaitForUiaEvent("SelectionItem_ElementAddedToSelection", "sm2"); + await runPython(`pattern.AddToSelection()`); + await waitForUiaEvent(); + ok(true, "sm2 got ElementAddedToSelection event"); + await testSelectionItemProps("sm2", true, "selectMulti"); + await testSelectionItemProps("sm3", true, "selectMulti"); + info("Calling RemoveFromSelection on sm3"); + await setUpWaitForUiaEvent( + "SelectionItem_ElementRemovedFromSelection", + "sm3" + ); + await runPython(`pattern.RemoveFromSelection()`); + await waitForUiaEvent(); + ok(true, "sm3 got ElementRemovedFromSelection event"); + await testSelectionItemProps("sm3", false, "selectMulti"); + + await testSelectionItemProps("t1", false, "tablist"); + await testSelectionItemProps("t2", true, "tablist"); + + // The IA2 -> UIA proxy doesn't expose the SelectionItem pattern on grid + // cells. + if (gIsUiaEnabled) { + await testSelectionItemProps("g1", false, "grid"); + await testSelectionItemProps("g2", true, "grid"); + } + + await testSelectionItemProps("r1", true, null); + await testSelectionItemProps("r2", false, null); + // The IA2 -> UIA proxy doesn't fire correct events for radio buttons. + if (gIsUiaEnabled) { + info("Calling Select on r2"); + await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "r2"); + await runPython(`pattern.Select()`); + await waitForUiaEvent(); + ok(true, "r2 got ElementSelected event"); + await testSelectionItemProps("r1", false, null); + await testSelectionItemProps("r2", true, null); + info("Calling RemoveFromSelection on r2"); + await testPythonRaises( + `pattern.RemoveFromSelection()`, + "RemoveFromSelection failed on r2" + ); + } + + await testPatternAbsent("m1", "SelectionItem"); + // The IA2 -> UIA proxy doesn't expose the SelectionItem pattern for radio + // menu items. + if (gIsUiaEnabled) { + await testSelectionItemProps("m2", false, null); + await testSelectionItemProps("m3", true, null); + } + + await testPatternAbsent("button", "SelectionItem"); +}); diff --git a/accessible/tests/browser/windows/uia/browser_simplePatterns.js b/accessible/tests/browser/windows/uia/browser_simplePatterns.js index f464db0e13..484d217af2 100644 --- a/accessible/tests/browser/windows/uia/browser_simplePatterns.js +++ b/accessible/tests/browser/windows/uia/browser_simplePatterns.js @@ -27,6 +27,7 @@ addUiaTask(

p

+ `, async function testInvoke() { await definePyVar("doc", `getDocUia()`); @@ -54,6 +55,8 @@ addUiaTask( // Check boxes expose the Toggle pattern, so they should not expose the // Invoke pattern. await testPatternAbsent("checkbox", "Invoke"); + // Ditto for radio buttons. + await testPatternAbsent("radio", "Invoke"); } } ); @@ -317,6 +320,7 @@ addUiaTask( await setUpWaitForUiaPropEvent("ValueValue", "text"); await runPython(`pattern.SetValue("after")`); await waitForUiaEvent(); + ok(true, "Got ValueValue prop change event on text"); is( await runPython(`pattern.CurrentValue`), "after", @@ -434,6 +438,7 @@ addUiaTask( await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox"); await runPython(`pattern.SetValue("after")`); await waitForUiaEvent(); + ok(true, "Got ValueValue prop change event on ariaTextbox"); is( await runPython(`pattern.CurrentValue`), "after", @@ -443,3 +448,95 @@ addUiaTask( await testPatternAbsent("button", "Value"); } ); + +async function testRangeValueProps(id, ro, val, min, max, small, large) { + await assignPyVarToUiaWithId(id); + await definePyVar("pattern", `getUiaPattern(${id}, "RangeValue")`); + ok(await runPython(`bool(pattern)`), `${id} has RangeValue pattern`); + is( + !!(await runPython(`pattern.CurrentIsReadOnly`)), + ro, + `${id} has IsReadOnly ${ro}` + ); + is(await runPython(`pattern.CurrentValue`), val, `${id} has correct Value`); + is( + await runPython(`pattern.CurrentMinimum`), + min, + `${id} has correct Minimum` + ); + is( + await runPython(`pattern.CurrentMaximum`), + max, + `${id} has correct Maximum` + ); + // IA2 doesn't support small/large change, so the IA2 -> UIA proxy can't + // either. + if (gIsUiaEnabled) { + is( + await runPython(`pattern.CurrentSmallChange`), + small, + `${id} has correct SmallChange` + ); + is( + await runPython(`pattern.CurrentLargeChange`), + large, + `${id} has correct LargeChange` + ); + } +} + +/** + * Test the RangeValue pattern. + */ +addUiaTask( + ` + + + + +
slider
+ + `, + async function testRangeValue(browser) { + await definePyVar("doc", `getDocUia()`); + await testRangeValueProps("range", false, 50, 0, 100, 1, 10); + info("SetValue on range"); + await setUpWaitForUiaPropEvent("RangeValueValue", "range"); + await runPython(`pattern.SetValue(20)`); + await waitForUiaEvent(); + ok(true, "Got RangeValueValue prop change event on range"); + is(await runPython(`pattern.CurrentValue`), 20, "range has correct Value"); + + await testRangeValueProps("rangeBig", false, 500, 0, 1000, 1, 100); + + // Gecko a11y doesn't expose progress bars as read only, but it probably + // should. + await testRangeValueProps("progress", false, 0.5, 0, 1, 0, 0.1); + info("Calling SetValue on progress"); + await testPythonRaises( + `pattern.SetValue(0.6)`, + "SetValue on progress failed" + ); + + await testRangeValueProps("numberRo", true, 5, 0, 10, 1, 1); + info("Calling SetValue on numberRo"); + await testPythonRaises( + `pattern.SetValue(6)`, + "SetValue on numberRo failed" + ); + + await testRangeValueProps("ariaSlider", false, 50, 0, 100, null, null); + info("Setting aria-valuenow on ariaSlider"); + await setUpWaitForUiaPropEvent("RangeValueValue", "ariaSlider"); + await invokeSetAttribute(browser, "ariaSlider", "aria-valuenow", "60"); + await waitForUiaEvent(); + ok(true, "Got RangeValueValue prop change event on ariaSlider"); + is( + await runPython(`pattern.CurrentValue`), + 60, + "ariaSlider has correct Value" + ); + + await testPatternAbsent("button", "RangeValue"); + } +); diff --git a/accessible/tests/browser/windows/uia/head.js b/accessible/tests/browser/windows/uia/head.js index 5b453ce6fe..217b8cb844 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, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent, testPythonRaises */ +/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent, testPythonRaises, isUiaElementArray */ // Load the shared-head file first. Services.scriptloader.loadSubScript( @@ -126,3 +126,15 @@ async function testPythonRaises(expression, message) { } ok(failed, message); } + +/** + * Verify that an array of UIA elements contains (only) elements with the given + * DOM ids. + */ +async function isUiaElementArray(pyExpr, ids, message) { + const result = await runPython(` + uias = (${pyExpr}) + return [uias.GetElement(i).CurrentAutomationId for i in range(uias.Length)] + `); + SimpleTest.isDeeply(result, ids, message); +} -- cgit v1.2.3