From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- accessible/tests/browser/windows/uia/browser.toml | 5 + .../tests/browser/windows/uia/browser_focus.js | 61 +++ .../browser/windows/uia/browser_generalProps.js | 105 +++++ .../browser/windows/uia/browser_simplePatterns.js | 445 +++++++++++++++++++++ .../tests/browser/windows/uia/browser_tree.js | 20 +- accessible/tests/browser/windows/uia/head.js | 75 +++- 6 files changed, 691 insertions(+), 20 deletions(-) create mode 100644 accessible/tests/browser/windows/uia/browser_focus.js create mode 100644 accessible/tests/browser/windows/uia/browser_generalProps.js create mode 100644 accessible/tests/browser/windows/uia/browser_simplePatterns.js (limited to 'accessible/tests/browser/windows/uia') 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( + ` + +

p

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

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

p

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

p

+ + + `, + 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( + ` +
+ summary + details +
+ + + + `, + 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( + ` +
+ + `, + 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( + ` + + + + + + +Link +
before
+ + `, + 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( `

paragraph

@@ -46,7 +28,7 @@ addUiaTask(
editable
th
`, - 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); +} -- cgit v1.2.3