Bug 1498812 - Part 4: Add scroll position test that is specifically testing the Visual Viewport. r=mikedeboer
authorJan Henning <jh+bugzilla@buttercookie.de>
Fri, 11 Jan 2019 20:36:16 +0000
changeset 453548 7b00521b6f3176cb88698b4773d9be13f0e54b2e
parent 453547 bf744ce7867af3c2548b1ec10233692f11f1ecf9
child 453549 710922bc053aa9fbf9c54af538c8c005770c9a14
push id35360
push usernbeleuzu@mozilla.com
push dateSat, 12 Jan 2019 09:39:47 +0000
treeherdermozilla-central@cb35977ae7a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1498812, 1516056
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1498812 - Part 4: Add scroll position test that is specifically testing the Visual Viewport. r=mikedeboer The existing tests didn't catch this problem, because calling scrollTo(), which is both what a) the session store and session history itself are currently using to set the scroll position to be restored, as well as b) how the existing session store test is setting the page up to be scrolled ready for testing forces both the layout and visual viewport positions to the respective coordinates, even if the same configuration of visual and layout viewport offsets could never be achieved through manual scrolling (i.e. bug 1516056). This then triggers all the expected events and makes it so that reading the scroll position through the layout viewport returns the expected values, which is why the existing tests never noticed that something is off. Therefore, we introduce a test here that has a page where the layout viewport can never scroll (at least horizontally) and where we simulate scrolling by actually inputting fake touch events instead of simply calling scrollTo(). This will result in only the visual viewport scrolling, ensuring that we can properly test the new expected behaviour of the session store and session history. Because layout and visual viewport scroll positions aren't necessarily updated at exactly the same time and the session store is currently still relying on the conventional "scroll" events (relating to the layout viewport), which means the tests have to rely on the same events, too, we don't yet switch all session store tests to generally verify the current scroll position of the page using the visual viewport, and temporarily make this only opt-in via the corresponding helper function in head_scroll.js. I know that the proper way to reference "foreign" files in text manifests is to use !/absolute/path/to/file/helper.js, but as one of the files originally comes from a chrome mochitest and the other one (apz_test_utils.js) doesn't and this test itself is a chrome mochitest, this was the best way I found to get both files copied into the correct directory on the test device so the test could access them. Differential Revision: https://phabricator.services.mozilla.com/D15685
mobile/android/tests/browser/chrome/chrome.ini
mobile/android/tests/browser/chrome/head_scroll.js
mobile/android/tests/browser/chrome/test_session_scroll_visual_viewport.html
--- a/mobile/android/tests/browser/chrome/chrome.ini
+++ b/mobile/android/tests/browser/chrome/chrome.ini
@@ -46,15 +46,19 @@ skip-if = true # Bug 1241478
 [test_resource_substitutions.html]
 [test_restricted_profiles.html]
 [test_select_disabled.html]
 [test_selectoraddtab.html]
 [test_session_clear_history.html]
 [test_session_form_data.html]
 [test_session_parentid.html]
 [test_session_scroll_position.html]
+[test_session_scroll_visual_viewport.html]
+support-files =
+  ../../../../../gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+  ../../../../../gfx/layers/apz/test/mochitest/apz_test_utils.js
 [test_session_undo_close_tab.html]
 [test_session_zombification.html]
 [test_settings_fontinflation.html]
 [test_shared_preferences.html]
 [test_simple_discovery.html]
 [test_video_discovery.html]
 [test_web_channel.html]
--- a/mobile/android/tests/browser/chrome/head_scroll.js
+++ b/mobile/android/tests/browser/chrome/head_scroll.js
@@ -21,28 +21,55 @@ function setScrollPosition(browser,
   if (zoom) {
     browser.contentWindow.windowUtils.setResolutionAndScaleTo(zoom);
   }
   window.scrollTo(x, y);
 }
 
 function checkScroll(browser, data) {
   let {x, y, zoom} = data;
-  let utils = getFrame(browser, data).windowUtils;
-
-  let actualX = {}, actualY = {};
-  let actualZoom = utils.getResolution();
-  utils.getScrollXY(false, actualX, actualY);
+  let scrollPos = getScrollPosition(browser, data);
 
   if (data.hasOwnProperty("x")) {
-    is(actualX.value, x, "scrollX set correctly");
+    is(scrollPos.x, x, "scrollX set correctly");
   }
   if (data.hasOwnProperty("y")) {
-    is(actualY.value, y, "scrollY set correctly");
+    is(scrollPos.y, y, "scrollY set correctly");
   }
   if (zoom) {
-    ok(fuzzyEquals(actualZoom, zoom), "zoom set correctly");
+    ok(fuzzyEquals(scrollPos.zoom, zoom), "zoom set correctly");
   }
 }
 
+function getScrollPosition(browser, data = {}) {
+  let utils = getFrame(browser, data).windowUtils;
+  let visualScrollPos = data.visualScrollPos === true;
+  let x = {}, y = {};
+
+  let zoom = utils.getResolution();
+  if (visualScrollPos) {
+    utils.getVisualViewportOffset(x, y);
+  } else {
+    utils.getScrollXY(false, x, y);
+  }
+
+  return { x: x.value, y: y.value, zoom, visualScrollPos };
+}
+
 function getScrollString({ x = 0, y = 0 }) {
   return x + "," + y;
 }
+
+function presStateToCSSPx(presState) {
+  // App units aren't commonly exposed to JS, so we can't just call a helper function
+  // and have to convert them ourselves instead.
+  // Conversion factor taken from gfx/src/AppUnits.h.
+  const APP_UNITS_PER_CSS_PX = 60;
+
+  let state = {...presState};
+  if (state.scroll) {
+    let scroll = state.scroll.split(",").map(pos => parseInt(pos, 10) || 0);
+    scroll = scroll.map(appUnits => Math.round(appUnits / APP_UNITS_PER_CSS_PX));
+    state.scroll = getScrollString({ x: scroll[0], y: scroll[1] });
+  }
+
+  return state;
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/chrome/test_session_scroll_visual_viewport.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1498812
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Various scroll position tests for the mobile session store, dealing specifically with the Visual Viewport</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/AddTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="head_scroll.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript">
+  /* import-globals-from ../../../../../gfx/layers/apz/test/mochitest/apz_test_utils.js */
+  /* import-globals-from ../../../../../gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js */
+
+  /** Tests for Bug 1498812 **/
+
+  "use strict";
+
+  ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+  ChromeUtils.import("resource://gre/modules/Services.jsm");
+  ChromeUtils.import("resource://gre/modules/Messaging.jsm");
+
+  // The chrome window and friends.
+  let chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+  let BrowserApp = chromeWin.BrowserApp;
+
+  const BASE = "http://example.org/chrome/mobile/android/tests/browser/chrome/";
+  // This is a plain desktop page without any meta viewport tags,
+  // so the layout viewport will always fill the full page width.
+  const URL = BASE + "basic_article.html";
+  // A mobile page using width=device-width, which leads to the same result.
+  const URL2 = BASE + "basic_article_mobile.html";
+
+  async function scrollRight(window) {
+    // This listener will trigger the test to continue once APZ is done with
+    // processing the scroll.
+    let transformEnd = promiseNotification("APZ:TransformEnd");
+
+    let scroll = [
+        [ { x: 125, y: 100 } ],
+        [ { x: 120, y: 100 } ],
+        [ { x: 115, y: 100 } ],
+        [ { x: 110, y: 100 } ],
+        [ { x: 105, y: 100 } ],
+        [ { x: 100, y: 100 } ],
+    ];
+
+    let touchIds = [0];
+    let doScroll = synthesizeNativeTouchSequences(document.body, scroll, null, touchIds);
+    while (!doScroll.next().done);
+
+    await transformEnd;
+
+    await promiseApzRepaintsFlushed(window);
+  }
+
+  // Track the tabs where the tests are happening.
+  let tabScroll;
+
+  function cleanupTabs() {
+    if (tabScroll) {
+      BrowserApp.closeTab(tabScroll);
+      tabScroll = null;
+    }
+  }
+
+  SimpleTest.registerCleanupFunction(function() {
+    cleanupTabs();
+  });
+
+  let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+
+  add_task(async function test_sessionStoreScrollPositionVisualViewport() {
+    let zoomIn = {x: 0, y: 0, zoom: 4 };
+    let scrollPos1, scrollPos2;
+    // Creates a tab, sets a scroll position and zoom level and closes the tab.
+    async function createAndRemoveTab() {
+        // Create a new tab.
+        tabScroll = BrowserApp.addTab(URL);
+        let browser = tabScroll.browser;
+        await promiseBrowserEvent(browser, "pageshow");
+
+        // Zoom in, so we can scroll to the right.
+        let scrolled = promiseTabEvent(browser, "SSTabScrollCaptured");
+        setScrollPosition(browser, zoomIn);
+        await scrolled;
+
+        // Check that we've actually zoomed.
+        checkScroll(browser, zoomIn);
+
+        scrolled = promiseTabEvent(browser, "SSTabScrollCaptured");
+        await scrollRight(browser.contentWindow);
+
+        // The above scroll only scrolled the visual viewport, not the layout
+        // viewport. As long as we don't have a scroll event for the visual
+        // viewport, we need to fake a scroll event here in order to induce
+        // the session store to record the scroll position again (bug 1500554).
+        browser.dispatchEvent(new browser.contentWindow.UIEvent("scroll"));
+        await scrolled;
+
+        scrollPos1 = getScrollPosition(browser, {visualScrollPos: true});
+        isnot(scrollPos1.x, 0, "we should be scrolled to the right");
+        is(scrollPos1.y, 0, "we scrolled horizontally");
+
+        // Navigate to a different page and scroll/zoom there as well.
+        browser.loadURI(URL2);
+        await promiseBrowserEvent(browser, "pageshow");
+
+        scrolled = promiseTabEvent(browser, "SSTabScrollCaptured");
+        setScrollPosition(browser, zoomIn);
+        await scrolled;
+        checkScroll(browser, zoomIn);
+
+        scrolled = promiseTabEvent(browser, "SSTabScrollCaptured");
+        await scrollRight(browser.contentWindow);
+
+        // Fake a scroll event (see above).
+        browser.dispatchEvent(new browser.contentWindow.UIEvent("scroll"));
+        await scrolled;
+
+        scrollPos2 = getScrollPosition(browser, {visualScrollPos: true});
+        isnot(scrollPos2.x, 0, "we should be scrolled to the right");
+        is(scrollPos2.y, 0, "we scrolled horizontally");
+
+        // Remove the tab.
+        let closed = promiseTabEvent(browser, "SSTabCloseProcessed");
+        BrowserApp.closeTab(tabScroll);
+        await closed;
+    }
+
+    await createAndRemoveTab();
+
+    // Check the live scroll data for the current history entry...
+    let tabData = ss.getClosedTabs(chromeWin)[0];
+    let {scrolldata} = tabData;
+    todo_is(scrolldata.scroll, getScrollString(scrollPos2), "stored scroll position is correct");
+    ok(fuzzyEquals(scrolldata.zoom.resolution, scrollPos2.zoom), "stored zoom level is correct");
+
+    // ... and the presState from the previous history entry.
+    let {index} = tabData;
+    index -= 1; // session history uses 1-based index
+    let {entries} = tabData;
+    let prevPage = entries[index - 1];
+    todo(prevPage.presState, "presState exists");
+    if (prevPage.presState) {
+      let presState = prevPage.presState[0];
+      // The presState operates in app units, while all other scroll positions
+      // in JS-land use CSS pixels.
+      presState = presStateToCSSPx(presState);
+      todo_is(presState.scroll, getScrollString(scrollPos1), "stored scroll position for previous page is correct");
+      ok(fuzzyEquals(presState.res, scrollPos1.zoom), "stored zoom level for previous page is correct");
+    }
+  });
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1498812">Mozilla Bug 1498812</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>