summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/browser/scroll/browser_test_scroll_bounds.js')
-rw-r--r--accessible/tests/browser/scroll/browser_test_scroll_bounds.js606
1 files changed, 606 insertions, 0 deletions
diff --git a/accessible/tests/browser/scroll/browser_test_scroll_bounds.js b/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
new file mode 100644
index 0000000000..bd61340aa6
--- /dev/null
+++ b/accessible/tests/browser/scroll/browser_test_scroll_bounds.js
@@ -0,0 +1,606 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* import-globals-from ../../mochitest/role.js */
+loadScripts(
+ { name: "layout.js", dir: MOCHITESTS_DIR },
+ { name: "role.js", dir: MOCHITESTS_DIR }
+);
+requestLongerTimeout(2);
+
+const appUnitsPerDevPixel = 60;
+
+function testCachedScrollPosition(acc, expectedX, expectedY) {
+ let cachedPosition = "";
+ try {
+ cachedPosition = acc.cache.getStringProperty("scroll-position");
+ } catch (e) {
+ // If the key doesn't exist, this means 0, 0.
+ cachedPosition = "0, 0";
+ }
+
+ // The value we retrieve from the cache is in app units, but the values
+ // passed in are in pixels. Since the retrieved value is a string,
+ // and harder to modify, adjust our expected x and y values to match its units.
+ return (
+ cachedPosition ==
+ `${expectedX * appUnitsPerDevPixel}, ${expectedY * appUnitsPerDevPixel}`
+ );
+}
+
+function getCachedBounds(acc) {
+ let cachedBounds = "";
+ try {
+ cachedBounds = acc.cache.getStringProperty("relative-bounds");
+ } catch (e) {
+ ok(false, "Unable to fetch cached bounds from cache!");
+ }
+ return cachedBounds;
+}
+
+/**
+ * Test bounds of accessibles after scrolling
+ */
+addAccessibleTask(
+ `
+ <div id='square' style='height:100px; width:100px; background:green; margin-top:3000px; margin-bottom:4000px;'>
+ </div>
+
+ <div id='rect' style='height:40px; width:200px; background:blue; margin-bottom:3400px'>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ ok(docAcc, "iframe document acc is present");
+ await testBoundsWithContent(docAcc, "square", browser);
+ await testBoundsWithContent(docAcc, "rect", browser);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("square").scrollIntoView();
+ });
+
+ await waitForContentPaint(browser);
+
+ await testBoundsWithContent(docAcc, "square", browser);
+ await testBoundsWithContent(docAcc, "rect", browser);
+
+ // Scroll rect into view, but also make it reflow so we can be sure the
+ // bounds are correct for reflowed frames.
+ await invokeContentTask(browser, [], () => {
+ const rect = content.document.getElementById("rect");
+ rect.scrollIntoView();
+ rect.style.width = "300px";
+ rect.offsetTop; // Flush layout.
+ rect.style.width = "200px";
+ rect.offsetTop; // Flush layout.
+ });
+
+ await waitForContentPaint(browser);
+ await testBoundsWithContent(docAcc, "square", browser);
+ await testBoundsWithContent(docAcc, "rect", browser);
+ },
+ { iframe: true, remoteIframe: true, chrome: true }
+);
+
+/**
+ * Test scroll offset on cached accessibles
+ */
+addAccessibleTask(
+ `
+ <div id='square' style='height:100px; width:100px; background:green; margin-top:3000px; margin-bottom:4000px;'>
+ </div>
+
+ <div id='rect' style='height:40px; width:200px; background:blue; margin-bottom:3400px'>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ ok(docAcc, "iframe document acc is present");
+ await untilCacheOk(
+ () => testCachedScrollPosition(docAcc, 0, 0),
+ "Correct initial scroll position."
+ );
+ const rectAcc = findAccessibleChildByID(docAcc, "rect");
+ const rectInitialBounds = getCachedBounds(rectAcc);
+
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("square").scrollIntoView();
+ });
+
+ await waitForContentPaint(browser);
+
+ // The only content to scroll over is `square`'s top margin
+ // so our scroll offset here should be 3000px
+ await untilCacheOk(
+ () => testCachedScrollPosition(docAcc, 0, 3000),
+ "Correct scroll position after first scroll."
+ );
+
+ // Scroll rect into view, but also make it reflow so we can be sure the
+ // bounds are correct for reflowed frames.
+ await invokeContentTask(browser, [], () => {
+ const rect = content.document.getElementById("rect");
+ rect.scrollIntoView();
+ rect.style.width = "300px";
+ rect.offsetTop;
+ rect.style.width = "200px";
+ });
+
+ await waitForContentPaint(browser);
+ // We have to scroll over `square`'s top margin (3000px),
+ // `square` itself (100px), and `square`'s bottom margin (4000px).
+ // This should give us a 7100px offset.
+ await untilCacheOk(
+ () => testCachedScrollPosition(docAcc, 0, 7100),
+ "Correct final scroll position."
+ );
+ await untilCacheIs(
+ () => getCachedBounds(rectAcc),
+ rectInitialBounds,
+ "Cached relative bounds don't change when scrolling"
+ );
+ },
+ { iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test scroll offset fixed-pos acc accs
+ */
+addAccessibleTask(
+ `
+ <div style="margin-top: 100px; margin-left: 75px; border: 1px solid;">
+ <div id="d" style="position:fixed;">
+ <button id="top">top</button>
+ </div>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ const origTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ const origDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ const e = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ for (let i = 0; i < 1000; ++i) {
+ const div = content.document.createElement("div");
+ div.innerHTML = "<button>${i}</button>";
+ content.document.body.append(div);
+ }
+ });
+ await e;
+
+ await invokeContentTask(browser, [], () => {
+ // scroll to the bottom of the page
+ content.window.scrollTo(0, content.document.body.scrollHeight);
+ });
+
+ await waitForContentPaint(browser);
+
+ let newTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ let newDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ is(
+ origTopBounds[0],
+ newTopBounds[0],
+ "x of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origTopBounds[1],
+ newTopBounds[1],
+ "y of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origTopBounds[2],
+ newTopBounds[2],
+ "width of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origTopBounds[3],
+ newTopBounds[3],
+ "height of fixed elem is unaffected by scrolling"
+ );
+ is(
+ origDBounds[0],
+ newTopBounds[0],
+ "x of fixed elem container is unaffected by scrolling"
+ );
+ is(
+ origDBounds[1],
+ newDBounds[1],
+ "y of fixed elem container is unaffected by scrolling"
+ );
+ is(
+ origDBounds[2],
+ newDBounds[2],
+ "width of fixed container elem is unaffected by scrolling"
+ );
+ is(
+ origDBounds[3],
+ newDBounds[3],
+ "height of fixed container elem is unaffected by scrolling"
+ );
+
+ await invokeContentTask(browser, [], () => {
+ // remove position styling
+ content.document.getElementById("d").style = "";
+ });
+
+ await waitForContentPaint(browser);
+
+ newTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ newDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ is(
+ origTopBounds[0],
+ newTopBounds[0],
+ "x of non-fixed element remains accurate."
+ );
+ ok(newTopBounds[1] < 0, "y coordinate shows item scrolled off page");
+ is(
+ origTopBounds[2],
+ newTopBounds[2],
+ "width of non-fixed element remains accurate."
+ );
+ is(
+ origTopBounds[3],
+ newTopBounds[3],
+ "height of non-fixed element remains accurate."
+ );
+ is(
+ origDBounds[0],
+ newDBounds[0],
+ "x of non-fixed container element remains accurate."
+ );
+ ok(newDBounds[1] < 0, "y coordinate shows container scrolled off page");
+ // Removing the position styling on this acc causes it to be bound by
+ // its parent's bounding box, which alters its width as a block element.
+ // We don't particularly care about width in this test, so skip it.
+ is(
+ origDBounds[3],
+ newDBounds[3],
+ "height of non-fixed container element remains accurate."
+ );
+
+ await invokeContentTask(browser, [], () => {
+ // re-add position styling
+ content.document.getElementById("d").style = "position:fixed;";
+ });
+
+ await waitForContentPaint(browser);
+
+ newTopBounds = await testBoundsWithContent(docAcc, "top", browser);
+ newDBounds = await testBoundsWithContent(docAcc, "d", browser);
+ is(
+ origTopBounds[0],
+ newTopBounds[0],
+ "x correct when position:fixed is added."
+ );
+ is(
+ origTopBounds[1],
+ newTopBounds[1],
+ "y correct when position:fixed is added."
+ );
+ is(
+ origTopBounds[2],
+ newTopBounds[2],
+ "width correct when position:fixed is added."
+ );
+ is(
+ origTopBounds[3],
+ newTopBounds[3],
+ "height correct when position:fixed is added."
+ );
+ is(
+ origDBounds[0],
+ newDBounds[0],
+ "x of container correct when position:fixed is added."
+ );
+ is(
+ origDBounds[1],
+ newDBounds[1],
+ "y of container correct when position:fixed is added."
+ );
+ is(
+ origDBounds[2],
+ newDBounds[2],
+ "width of container correct when position:fixed is added."
+ );
+ is(
+ origDBounds[3],
+ newDBounds[3],
+ "height of container correct when position:fixed is added."
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test position: fixed for containers that would otherwise be pruned from the
+ * a11y tree.
+ */
+addAccessibleTask(
+ `
+<table id="fixed" role="presentation" style="position: fixed;">
+ <tr><th>fixed</th></tr>
+</table>
+<div id="mutate" role="presentation">mutate</div>
+<hr style="height: 200vh;">
+<p>bottom</p>
+ `,
+ async function (browser, docAcc) {
+ const fixed = findAccessibleChildByID(docAcc, "fixed");
+ ok(fixed, "fixed is accessible");
+ isnot(fixed.role, ROLE_TABLE, "fixed doesn't have ROLE_TABLE");
+ ok(!findAccessibleChildByID(docAcc, "mutate"), "mutate inaccessible");
+ info("Setting position: fixed on mutate");
+ let shown = waitForEvent(EVENT_SHOW, "mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").style.position = "fixed";
+ });
+ await shown;
+ const origFixedBounds = await testBoundsWithContent(
+ docAcc,
+ "fixed",
+ browser
+ );
+ const origMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ info("Scrolling to bottom of page");
+ await invokeContentTask(browser, [], () => {
+ content.window.scrollTo(0, content.document.body.scrollHeight);
+ });
+ await waitForContentPaint(browser);
+ const newFixedBounds = await testBoundsWithContent(
+ docAcc,
+ "fixed",
+ browser
+ );
+ Assert.deepEqual(
+ newFixedBounds,
+ origFixedBounds,
+ "fixed bounds are unchanged"
+ );
+ const newMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ Assert.deepEqual(
+ newMutateBounds,
+ origMutateBounds,
+ "mutate bounds are unchanged"
+ );
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);
+
+/**
+ * Test scroll offset on sticky-pos acc
+ */
+addAccessibleTask(
+ `
+ <div id="d" style="margin-top: 100px; margin-left: 75px; position:sticky; top:0px;">
+ <button id="top">top</button>
+ </div>
+ `,
+ async function (browser, docAcc) {
+ const containerBounds = await testBoundsWithContent(docAcc, "d", browser);
+ const e = waitForEvent(EVENT_REORDER, docAcc);
+ await invokeContentTask(browser, [], () => {
+ for (let i = 0; i < 1000; ++i) {
+ const div = content.document.createElement("div");
+ div.innerHTML = "<button>${i}</button>";
+ content.document.body.append(div);
+ }
+ });
+ await e;
+ for (let id of ["d", "top"]) {
+ info(`Verifying bounds for acc with ID ${id}`);
+ const origBounds = await testBoundsWithContent(docAcc, id, browser);
+
+ info("Scrolling partially");
+ await invokeContentTask(browser, [], () => {
+ // scroll some of the window
+ content.window.scrollTo(0, 50);
+ });
+
+ await waitForContentPaint(browser);
+
+ let newBounds = await testBoundsWithContent(docAcc, id, browser);
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of sticky element is unaffected by scrolling`
+ );
+ ok(
+ origBounds[1] > newBounds[1] && newBounds[1] >= 0,
+ "sticky element scrolled, but not off the page"
+ );
+ is(
+ origBounds[2],
+ newBounds[2],
+ `width of sticky element is unaffected by scrolling`
+ );
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of sticky element is unaffected by scrolling`
+ );
+
+ info("Scrolling to bottom");
+ await invokeContentTask(browser, [], () => {
+ // scroll to the bottom of the page
+ content.window.scrollTo(0, content.document.body.scrollHeight);
+ });
+
+ await waitForContentPaint(browser);
+
+ newBounds = await testBoundsWithContent(docAcc, id, browser);
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of sticky element is unaffected by scrolling`
+ );
+ // Subtract margin from container screen coords to get chrome height
+ // which is where our y pos should be
+ is(
+ newBounds[1],
+ containerBounds[1] - 100,
+ "Sticky element is top of screen"
+ );
+ is(
+ origBounds[2],
+ newBounds[2],
+ `width of sticky element is unaffected by scrolling`
+ );
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of sticky element is unaffected by scrolling`
+ );
+
+ info("Removing position style on container");
+ await invokeContentTask(browser, [], () => {
+ // remove position styling
+ content.document.getElementById("d").style =
+ "margin-top: 100px; margin-left: 75px;";
+ });
+
+ await waitForContentPaint(browser);
+
+ newBounds = await testBoundsWithContent(docAcc, id, browser);
+
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of non-sticky element remains accurate.`
+ );
+ ok(newBounds[1] < 0, "y coordinate shows item scrolled off page");
+
+ // Removing the position styling on this acc causes it to be bound by
+ // its parent's bounding box, which alters its width as a block element.
+ // We don't particularly care about width in this test, so skip it.
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of non-sticky element remains accurate.`
+ );
+
+ info("Adding position style on container");
+ await invokeContentTask(browser, [], () => {
+ // re-add position styling
+ content.document.getElementById("d").style =
+ "margin-top: 100px; margin-left: 75px; position:sticky; top:0px;";
+ });
+
+ await waitForContentPaint(browser);
+
+ newBounds = await testBoundsWithContent(docAcc, id, browser);
+ is(
+ origBounds[0],
+ newBounds[0],
+ `x coord of sticky element is unaffected by scrolling`
+ );
+ is(
+ newBounds[1],
+ containerBounds[1] - 100,
+ "Sticky element is top of screen"
+ );
+ is(
+ origBounds[2],
+ newBounds[2],
+ `width of sticky element is unaffected by scrolling`
+ );
+ is(
+ origBounds[3],
+ newBounds[3],
+ `height of sticky element is unaffected by scrolling`
+ );
+
+ info("Scrolling back up to test next ID");
+ await invokeContentTask(browser, [], () => {
+ // scroll some of the window
+ content.window.scrollTo(0, 0);
+ });
+ }
+ },
+ { chrome: false, iframe: false, remoteIframe: false }
+);
+
+/**
+ * Test position: sticky for containers that would otherwise be pruned from the
+ * a11y tree.
+ */
+addAccessibleTask(
+ `
+<hr style="height: 100vh;">
+<div id="stickyContainer">
+ <div id="sticky" role="presentation" style="position: sticky; top: 0px;">sticky</div>
+ <hr style="height: 100vh;">
+ <p id="stickyEnd">stickyEnd</p>
+</div>
+<div id="mutateContainer">
+ <div id="mutate" role="presentation" style="top: 0px;">mutate</div>
+ <hr style="height: 100vh;">
+ <p id="mutateEnd">mutateEnd</p>
+</div>
+ `,
+ async function (browser, docAcc) {
+ ok(findAccessibleChildByID(docAcc, "sticky"), "sticky is accessible");
+ info("Scrolling to sticky");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("sticky").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const origStickyBounds = await testBoundsWithContent(
+ docAcc,
+ "sticky",
+ browser
+ );
+ info("Scrolling to stickyEnd");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("stickyEnd").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const newStickyBounds = await testBoundsWithContent(
+ docAcc,
+ "sticky",
+ browser
+ );
+ Assert.deepEqual(
+ newStickyBounds,
+ origStickyBounds,
+ "sticky bounds are unchanged"
+ );
+
+ ok(!findAccessibleChildByID(docAcc, "mutate"), "mutate inaccessible");
+ info("Setting position: sticky on mutate");
+ let shown = waitForEvent(EVENT_SHOW, "mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").style.position = "sticky";
+ });
+ await shown;
+ info("Scrolling to mutate");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutate").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const origMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ info("Scrolling to mutateEnd");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("mutateEnd").scrollIntoView();
+ });
+ await waitForContentPaint(browser);
+ const newMutateBounds = await testBoundsWithContent(
+ docAcc,
+ "mutate",
+ browser
+ );
+ assertBoundsFuzzyEqual(newMutateBounds, origMutateBounds);
+ },
+ { chrome: true, iframe: true, remoteIframe: true }
+);