tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

helper_fission_scroll_oopif.html (7734B)


      1 <!DOCTYPE HTML>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <meta name="viewport" content="width=device-width,initial-scale=1">
      6  <title>Test for async-scrolling an OOPIF and ensuring hit-testing still works</title>
      7  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      8  <script src="/tests/SimpleTest/paint_listener.js"></script>
      9  <script src="helper_fission_utils.js"></script>
     10  <script src="apz_test_utils.js"></script>
     11  <script src="apz_test_native_event_utils.js"></script>
     12  <script>
     13    const { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
     14      "resource://gre/modules/AppConstants.sys.mjs"
     15    );
     16 
     17    async function getWindowProtocol() {
     18      return await SpecialPowers.spawnChrome([], () => {
     19        try {
     20          return Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo)
     21            .windowProtocol;
     22        } catch {
     23          return "";
     24        }
     25      });
     26    }
     27 
     28    function getEventPromise(eventName) {
     29      let eventPromise = new Promise(resolve => {
     30        const listener = event => {
     31          if (event.data === eventName) {
     32            window.removeEventListener("message", listener);
     33            resolve();
     34          }
     35        }
     36        window.addEventListener("message", listener);
     37      });
     38      return eventPromise;
     39    }
     40 
     41    function getClickPromise() {
     42      let clickPromise = new Promise(resolve => {
     43        window.addEventListener("message", event => {
     44          let data = JSON.parse(event.data);
     45          if ("type" in data && data.type == "clicked") {
     46            let coordinates = {
     47              offsetX: data.coords.offsetX,
     48              offsetY: data.coords.offsetY
     49            };
     50            resolve(coordinates);
     51          }
     52        }, { once: true });
     53      });
     54      return clickPromise;
     55    }
     56 
     57    function getScrollYPromise() {
     58      let scrollPromise = new Promise(resolve => {
     59        window.addEventListener("message", event => {
     60          let eventData = JSON.parse(event.data);
     61          if ("type" in eventData && eventData.type == "scrolled") {
     62            resolve(eventData.scrollY);
     63          }
     64        }, { once: true });
     65      });
     66      return scrollPromise;
     67    }
     68 
     69    async function clickOnIframe(x, y) {
     70      let clickPromise = getClickPromise();
     71      await synthesizeNativeMouseEventWithAPZ(
     72        { type: "click", target: document.body, offsetX: x, offsetY: y },
     73        () => dump("Finished synthesizing click, waiting for OOPIF message...\n")
     74      );
     75      return clickPromise;
     76    }
     77 
     78    async function makeIframeScrollable(iframe) {
     79      let parentScrollListenerReady = getEventPromise("scrollableReady");
     80      let iframeScrollMaxY = await SpecialPowers.spawn(iframe, [], async () => {
     81        // Ensure the oopif is scrollable, and wait for the paint so that the
     82        // compositor also knows it's scrollable.
     83        content.document.body.style.height = "200vh";
     84        await content.wrappedJSObject.promiseApzFlushedRepaints();
     85        let scrollMaxY = content.window.scrollMaxY;
     86 
     87        // Also register a scroll listener for when it actually gets scrolled.
     88        content.window.addEventListener("scroll", function () {
     89          dump(`OOPIF got scroll event, now at ${content.window.scrollY}\n`);
     90          let data = JSON.stringify({
     91            type: "scrolled",
     92            scrollY: content.window.scrollY
     93          });
     94          content.window.parent.postMessage(data, "*");
     95        }, { once: true });
     96        content.window.parent.postMessage("scrollableReady", "*");
     97        return scrollMaxY;
     98      });
     99      await parentScrollListenerReady;
    100      return iframeScrollMaxY;
    101    }
    102 
    103    async function registerIframeClickListener(iframe) {
    104      let readyPromise = getEventPromise("listenerReady");
    105      let iframeClickReady = await SpecialPowers.spawn(iframe, [], async () => {
    106        content.document.addEventListener("click", (e) => {
    107          let data = JSON.stringify({
    108            type: "clicked",
    109            coords: {
    110              offsetX: e.clientX,
    111              offsetY: e.clientY
    112            }
    113          });
    114          content.window.parent.postMessage(data, "*");
    115        });
    116        content.window.parent.postMessage("listenerReady", "*");
    117        return true;
    118      });
    119      await readyPromise;
    120      return iframeClickReady;
    121    }
    122 
    123    /*------------------------------------------------------------------------*/
    124 
    125    async function test() {
    126      // Skip on Wayland
    127      const protocol = await getWindowProtocol();
    128      if (AppConstants.platform === "linux" && protocol === "wayland") {
    129        ok(true, "Skipping this test on Linux with Wayland");
    130        return;
    131      }
    132 
    133      ok(SpecialPowers.getBoolPref("apz.paint_skipping.enabled"),
    134        "Paint-skipping is expected to be enabled for this test to be meaningful");
    135 
    136      // Set up iframe
    137      let iframe = document.getElementById("testframe");
    138      await setupCrossOriginIFrame(iframe, "helper_fission_plain.html");
    139 
    140      // Register click listener in iframe
    141      let iframeClickReady = await registerIframeClickListener(iframe);
    142      ok(iframeClickReady, "Successfully installed click listener in OOP iframe");
    143 
    144      // hit-test into the iframe before scrolling
    145      let oldClickPoint = await clickOnIframe(50, 250);
    146 
    147      // Ensure parent window is at 0 scroll position before scrolling
    148      is(window.scrollY, 0, "window is at 0 scroll position");
    149      // do an APZ scroll and wait for the main-thread to get the repaint request,
    150      // and queue up a paint-skip scroll notification back to APZ.
    151      let scrollend = promiseScrollend(window);
    152      await promiseMoveMouseAndScrollWheelOver(document.body, 10, 10);
    153      await scrollend;
    154      await promiseOnlyApzControllerFlushed();
    155      await promiseAllPaintsDone();
    156      ok(window.scrollY > 5, "window has scrolled by " + window.scrollY + " pixels");
    157 
    158      // hit-test into the iframe after scrolling. The coordinates here are the
    159      // same relative to the body as before, but get computed to be different
    160      // relative to the window/screen.
    161      let newClickPoint = await clickOnIframe(50, 250);
    162      is(newClickPoint.x, oldClickPoint.x, "x-coord of old and new match");
    163      is(newClickPoint.y, oldClickPoint.y, "y-coord of old and new match");
    164 
    165      // Also check that we can send scroll events to the OOPIF. Any wheel events
    166      // delivered to this page after this point should result in a failure.
    167      addEventListener("wheel", function () {
    168        ok(false, "Got unwanted wheel event in parent document");
    169      });
    170 
    171      // Ensure iframe is at 0 scroll position
    172      const iframeY = await SpecialPowers.spawn(iframe, [], () => {
    173        return content.window.scrollY;
    174      });
    175      is(iframeY, 0, "scrollY of iframe should be 0 initially");
    176 
    177      // Ensure the OOPIF is scrollable.
    178      let iframeScrollMaxY = await makeIframeScrollable(iframe);
    179      ok(iframeScrollMaxY > 0, "iframe successfully made scrollable");
    180 
    181      // Now scroll over the OOP-iframe (we know it must be under the 50,250 point
    182      // because we just checked that above). Note that listening for wheel/scroll
    183      // events is trickier because they will fire in the OOPIF, so we can't just
    184      // use promiseMoveMouseAndScrollWheelOver directly.
    185      let scrolledPromise = getScrollYPromise();
    186      await synthesizeNativeWheel(document.body, 50, 250, 0, -10);
    187      let iframeScrollY = await scrolledPromise;
    188      ok(iframeScrollY > 0, "Confirmed that oopif is scrollable");
    189    }
    190 
    191    waitUntilApzStable()
    192      .then(test)
    193      .then(subtestDone, subtestFailed);
    194 
    195  </script>
    196 </head>
    197 <body>
    198  <iframe style="margin-top: 200px" id="testframe"></iframe>
    199  <div style="height: 5000px">tall div to make the page scrollable</div>
    200 </body>
    201 </html>