diff options
Diffstat (limited to 'accessible/tests/browser/states')
6 files changed, 542 insertions, 0 deletions
diff --git a/accessible/tests/browser/states/browser.ini b/accessible/tests/browser/states/browser.ini new file mode 100644 index 0000000000..f625d7aeb5 --- /dev/null +++ b/accessible/tests/browser/states/browser.ini @@ -0,0 +1,19 @@ +[DEFAULT] +subsuite = a11y +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_test_link.js] +https_first_disabled = true +skip-if = verify +[browser_test_select_visibility.js] +https_first_disabled = true +[browser_test_visibility.js] +https_first_disabled = true +[browser_test_visibility_2.js] +https_first_disabled = true diff --git a/accessible/tests/browser/states/browser_test_link.js b/accessible/tests/browser/states/browser_test_link.js new file mode 100644 index 0000000000..0a3e8a9975 --- /dev/null +++ b/accessible/tests/browser/states/browser_test_link.js @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +async function runTests(browser, accDoc) { + let getAcc = id => findAccessibleChildByID(accDoc, id); + + // a: no traversed state + testStates(getAcc("link_traversed"), 0, 0, STATE_TRAVERSED); + + let onStateChanged = waitForEvent(EVENT_STATE_CHANGE, "link_traversed"); + let newTabOpened = BrowserTestUtils.waitForNewTab(gBrowser); + + await BrowserTestUtils.synthesizeMouse( + "#link_traversed", + 1, + 1, + { ctrlKey: !MAC, metaKey: MAC }, + browser + ); + + await onStateChanged; + testStates(getAcc("link_traversed"), STATE_TRAVERSED); + + let newTab = await newTabOpened; + gBrowser.removeTab(newTab); +} + +/** + * Test caching of accessible object states + */ +addAccessibleTask( + // The URL doesn't really matter, just the fact that it isn't in the history + // initially. We append ms since epoch to the URL so it will never be visited + // initially, regardless of other tests (even this one) that ran before. + ` + <a id="link_traversed" + href="https://www.example.com/${Date.now()}" target="_top"> + example.com + </a>`, + runTests +); diff --git a/accessible/tests/browser/states/browser_test_select_visibility.js b/accessible/tests/browser/states/browser_test_select_visibility.js new file mode 100644 index 0000000000..89b4df67f7 --- /dev/null +++ b/accessible/tests/browser/states/browser_test_select_visibility.js @@ -0,0 +1,76 @@ +/* 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 selects and options +addAccessibleTask( + `<select id="select"> + <option id="o1">hello</option> + <option id="o2">world</option> + </select>`, + async function (browser, accDoc) { + const select = findAccessibleChildByID(accDoc, "select"); + ok( + isAccessible(select.firstChild, [nsIAccessibleSelectable]), + "No selectable accessible for combobox" + ); + await untilCacheOk( + () => testVisibility(select, false, false), + "select should be on screen and visible" + ); + + if (!browser.isRemoteBrowser) { + await untilCacheOk( + () => testVisibility(select.firstChild, false, true), + "combobox list should be on screen and invisible" + ); + } else { + // XXX: When the cache is used, states::INVISIBLE is + // incorrect. Test OFFSCREEN anyway. + await untilCacheOk(() => { + const [states] = getStates(select.firstChild); + return (states & STATE_OFFSCREEN) == 0; + }, "combobox list should be on screen"); + } + + const o1 = findAccessibleChildByID(accDoc, "o1"); + const o2 = findAccessibleChildByID(accDoc, "o2"); + + await untilCacheOk( + () => testVisibility(o1, false, false), + "option one should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(o2, true, false), + "option two should be off screen and visible" + ); + + // Select the second option (drop-down collapsed). + const p = waitForEvents({ + expected: [ + [EVENT_SELECTION, "o2"], + [EVENT_TEXT_VALUE_CHANGE, "select"], + ], + unexpected: [ + stateChangeEventArgs("o2", EXT_STATE_ACTIVE, true, true), + stateChangeEventArgs("o1", EXT_STATE_ACTIVE, false, true), + ], + }); + await invokeContentTask(browser, [], () => { + content.document.getElementById("select").selectedIndex = 1; + }); + await p; + + await untilCacheOk(() => { + const [states] = getStates(o1); + return (states & STATE_OFFSCREEN) != 0; + }, "option 1 should be off screen"); + await untilCacheOk(() => { + const [states] = getStates(o2); + return (states & STATE_OFFSCREEN) == 0; + }, "option 2 should be on screen"); + }, + { chrome: true, iframe: true, remoteIframe: true } +); diff --git a/accessible/tests/browser/states/browser_test_visibility.js b/accessible/tests/browser/states/browser_test_visibility.js new file mode 100644 index 0000000000..25bd903ed4 --- /dev/null +++ b/accessible/tests/browser/states/browser_test_visibility.js @@ -0,0 +1,181 @@ +/* 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 runTest(browser, accDoc) { + let getAcc = id => findAccessibleChildByID(accDoc, id); + + await untilCacheOk( + () => testVisibility(getAcc("div"), false, false), + "Div should be on screen" + ); + + let input = getAcc("input_scrolledoff"); + await untilCacheOk( + () => testVisibility(input, true, false), + "Input should be offscreen" + ); + + // scrolled off item (twice) + let lastLi = getAcc("li_last"); + await untilCacheOk( + () => testVisibility(lastLi, true, false), + "Last list item should be offscreen" + ); + + // scroll into view the item + await invokeContentTask(browser, [], () => { + content.document.getElementById("li_last").scrollIntoView(true); + }); + await untilCacheOk( + () => testVisibility(lastLi, false, false), + "Last list item should no longer be offscreen" + ); + + // first item is scrolled off now (testcase for bug 768786) + let firstLi = getAcc("li_first"); + await untilCacheOk( + () => testVisibility(firstLi, true, false), + "First listitem should now be offscreen" + ); + + await untilCacheOk( + () => testVisibility(getAcc("frame"), false, false), + "iframe should initially be onscreen" + ); + + let loaded = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, "iframeDoc"); + await invokeContentTask(browser, [], () => { + content.document.querySelector("iframe").src = + 'data:text/html,<body id="iframeDoc"><p id="p">hi</p></body>'; + }); + + const iframeDoc = (await loaded).accessible; + await untilCacheOk( + () => testVisibility(getAcc("frame"), false, false), + "iframe outer doc should now be on screen" + ); + await untilCacheOk( + () => testVisibility(iframeDoc, false, false), + "iframe inner doc should be on screen" + ); + const iframeP = findAccessibleChildByID(iframeDoc, "p"); + await untilCacheOk( + () => testVisibility(iframeP, false, false), + "iframe content should also be on screen" + ); + + // scroll into view the div + await invokeContentTask(browser, [], () => { + content.document.getElementById("div").scrollIntoView(true); + }); + + await untilCacheOk( + () => testVisibility(getAcc("frame"), true, false), + "iframe outer doc should now be off screen" + ); + await untilCacheOk( + () => testVisibility(iframeDoc, true, false), + "iframe inner doc should now be off screen" + ); + await untilCacheOk( + () => testVisibility(iframeP, true, false), + "iframe content should now be off screen" + ); + + let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + // Accessibles in background tab should have offscreen state and no + // invisible state. + await untilCacheOk( + () => testVisibility(getAcc("div"), true, false), + "Accs in background tab should be offscreen but not invisible." + ); + + await untilCacheOk( + () => testVisibility(getAcc("frame"), true, false), + "iframe outer doc should still be off screen" + ); + await untilCacheOk( + () => testVisibility(iframeDoc, true, false), + "iframe inner doc should still be off screen" + ); + await untilCacheOk( + () => testVisibility(iframeP, true, false), + "iframe content should still be off screen" + ); + + BrowserTestUtils.removeTab(newTab); +} + +addAccessibleTask( + ` + <div id="div" style="border:2px solid blue; width: 500px; height: 110vh;"></div> + <input id="input_scrolledoff"> + <ul style="border:2px solid red; width: 100px; height: 50px; overflow: auto;"> + <li id="li_first">item1</li><li>item2</li><li>item3</li> + <li>item4</li><li>item5</li><li id="li_last">item6</li> + </ul> + <iframe id="frame"></iframe> + `, + runTest, + { iframe: true, remoteIframe: true } +); + +/** + * Test div containers are reported as onscreen, even if some of their contents are + * offscreen. + */ +addAccessibleTask( + ` + <div id="outer" style="width:200vw; background: green; overflow:scroll;"><div id="inner"><div style="display:inline-block; width:100vw; background:red;" id="on">on screen</div><div style="background:blue; display:inline;" id="off">offscreen</div></div></div> + `, + async function (browser, accDoc) { + const outer = findAccessibleChildByID(accDoc, "outer"); + const inner = findAccessibleChildByID(accDoc, "inner"); + const on = findAccessibleChildByID(accDoc, "on"); + const off = findAccessibleChildByID(accDoc, "off"); + + await untilCacheOk( + () => testVisibility(outer, false, false), + "outer should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(inner, false, false), + "inner should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(on, false, false), + "on should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(off, true, false), + "off should be off screen and visible" + ); + }, + { chrome: true, iframe: true, remoteIframe: true } +); + +// test dynamic translation +addAccessibleTask( + `<div id="container" style="position: absolute; left: -300px; top: 100px;">Hello</div><button id="b" onclick="container.style.transform = 'translateX(400px)'">Move</button>`, + async function (browser, accDoc) { + const container = findAccessibleChildByID(accDoc, "container"); + await untilCacheOk( + () => testVisibility(container, true, false), + "container should be off screen and visible" + ); + await invokeContentTask(browser, [], () => { + let b = content.document.getElementById("b"); + b.click(); + }); + + await waitForContentPaint(browser); + await untilCacheOk( + () => testVisibility(container, false, false), + "container should be on screen and visible" + ); + }, + { chrome: true, iframe: true, remoteIframe: true } +); diff --git a/accessible/tests/browser/states/browser_test_visibility_2.js b/accessible/tests/browser/states/browser_test_visibility_2.js new file mode 100644 index 0000000000..ead134069a --- /dev/null +++ b/accessible/tests/browser/states/browser_test_visibility_2.js @@ -0,0 +1,131 @@ +/* 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 tables, table rows are reported on screen, even if some cells of a given row are + * offscreen. + */ +addAccessibleTask( + ` + <table id="table" style="width:150vw;" border><tr id="row"><td id="one" style="width:50vw;">one</td><td style="width:50vw;" id="two">two</td><td id="three">three</td></tr></table> + `, + async function (browser, accDoc) { + const table = findAccessibleChildByID(accDoc, "table"); + const row = findAccessibleChildByID(accDoc, "row"); + const one = findAccessibleChildByID(accDoc, "one"); + const two = findAccessibleChildByID(accDoc, "two"); + const three = findAccessibleChildByID(accDoc, "three"); + + await untilCacheOk( + () => testVisibility(table, false, false), + "table should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(row, false, false), + "row should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(one, false, false), + "one should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(two, false, false), + "two should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(three, true, false), + "three should be off screen and visible" + ); + }, + { chrome: true, iframe: true, remoteIframe: true } +); + +/** + * Test rows and cells outside of the viewport are reported as offscreen. + */ +addAccessibleTask( + ` + <table id="table" style="height:150vh;" border><tr style="height:100vh;" id="rowA"><td id="one">one</td></tr><tr id="rowB"><td id="two">two</td></tr></table> + `, + async function (browser, accDoc) { + const table = findAccessibleChildByID(accDoc, "table"); + const rowA = findAccessibleChildByID(accDoc, "rowA"); + const one = findAccessibleChildByID(accDoc, "one"); + const rowB = findAccessibleChildByID(accDoc, "rowB"); + const two = findAccessibleChildByID(accDoc, "two"); + + await untilCacheOk( + () => testVisibility(table, false, false), + "table should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(rowA, false, false), + "rowA should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(one, false, false), + "one should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(rowB, true, false), + "rowB should be off screen and visible" + ); + await untilCacheOk( + () => testVisibility(two, true, false), + "two should be off screen and visible" + ); + }, + { chrome: true, iframe: true, remoteIframe: true } +); + +addAccessibleTask( + ` + <div id="div">hello</div> + `, + async function (browser, accDoc) { + let textLeaf = findAccessibleChildByID(accDoc, "div").firstChild; + await untilCacheOk( + () => testVisibility(textLeaf, false, false), + "text should be on screen and visible" + ); + let p = waitForEvent(EVENT_TEXT_INSERTED, "div"); + await invokeContentTask(browser, [], () => { + content.document.getElementById("div").textContent = "goodbye"; + }); + await p; + textLeaf = findAccessibleChildByID(accDoc, "div").firstChild; + await untilCacheOk( + () => testVisibility(textLeaf, false, false), + "text should be on screen and visible" + ); + }, + { chrome: true, iframe: true, remoteIframe: true } +); + +/** + * Overlapping, opaque divs with the same bounds should not be considered + * offscreen. + */ +addAccessibleTask( + ` + <style>div { height: 5px; width: 5px; background: green; }</style> + <div id="outer" role="group"><div style="background:blue;" id="inner" role="group">hi</div></div> + `, + async function (browser, accDoc) { + const outer = findAccessibleChildByID(accDoc, "outer"); + const inner = findAccessibleChildByID(accDoc, "inner"); + + await untilCacheOk( + () => testVisibility(outer, false, false), + "outer should be on screen and visible" + ); + await untilCacheOk( + () => testVisibility(inner, false, false), + "inner should be on screen and visible" + ); + }, + { chrome: true, iframe: true, remoteIframe: true } +); diff --git a/accessible/tests/browser/states/head.js b/accessible/tests/browser/states/head.js new file mode 100644 index 0000000000..10c616cb80 --- /dev/null +++ b/accessible/tests/browser/states/head.js @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* exported waitForIFrameA11yReady, waitForIFrameUpdates, spawnTestStates, testVisibility */ + +// Load the shared-head file first. +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js", + this +); + +// Loading and common.js from accessible/tests/mochitest/ for all tests, as +// well as promisified-events.js. +/* import-globals-from ../../mochitest/states.js */ +/* import-globals-from ../../mochitest/role.js */ +loadScripts( + { name: "common.js", dir: MOCHITESTS_DIR }, + { name: "promisified-events.js", dir: MOCHITESTS_DIR }, + { name: "role.js", dir: MOCHITESTS_DIR }, + { name: "states.js", dir: MOCHITESTS_DIR } +); + +// This is another version of addA11yLoadEvent for fission. +async function waitForIFrameA11yReady(iFrameBrowsingContext) { + await SimpleTest.promiseFocus(window); + + await SpecialPowers.spawn(iFrameBrowsingContext, [], () => { + return new Promise(resolve => { + function waitForDocLoad() { + SpecialPowers.executeSoon(() => { + const acc = SpecialPowers.Cc[ + "@mozilla.org/accessibilityService;1" + ].getService(SpecialPowers.Ci.nsIAccessibilityService); + + const accDoc = acc.getAccessibleFor(content.document); + let state = {}; + accDoc.getState(state, {}); + if (state.value & SpecialPowers.Ci.nsIAccessibleStates.STATE_BUSY) { + SpecialPowers.executeSoon(waitForDocLoad); + return; + } + resolve(); + }, 0); + } + waitForDocLoad(); + }); + }); +} + +// A utility function to make sure the information of scroll position or visible +// area changes reach to out-of-process iframes. +async function waitForIFrameUpdates() { + // Wait for two frames since the information is notified via asynchronous IPC + // calls. + await new Promise(resolve => requestAnimationFrame(resolve)); + await new Promise(resolve => requestAnimationFrame(resolve)); +} + +// A utility function to test the state of |elementId| element in out-of-process +// |browsingContext|. +async function spawnTestStates(browsingContext, elementId, expectedStates) { + function testStates(id, expected, unexpected) { + const acc = SpecialPowers.Cc[ + "@mozilla.org/accessibilityService;1" + ].getService(SpecialPowers.Ci.nsIAccessibilityService); + const target = content.document.getElementById(id); + let state = {}; + acc.getAccessibleFor(target).getState(state, {}); + if (expected === 0) { + Assert.equal(state.value, expected); + } else { + Assert.ok(state.value & expected); + } + Assert.ok(!(state.value & unexpected)); + } + await SpecialPowers.spawn( + browsingContext, + [elementId, expectedStates], + testStates + ); +} + +function testVisibility(acc, shouldBeOffscreen, shouldBeInvisible) { + const [states] = getStates(acc); + let looksGood = shouldBeOffscreen == ((states & STATE_OFFSCREEN) != 0); + looksGood &= shouldBeInvisible == ((states & STATE_INVISIBLE) != 0); + return looksGood; +} |