[renderblocking] Add fullscreen-related WPT for render-blocking (2nd try)

This is a 2nd attempt of crrev.com/c/3546751, which got blocked because
it's flaky on browsers that have not implemented render-blocking. In this
attempt, we rewrite the test by putting everything in a subframe so that
we don't need to dynamically insert test elements by script. We also
remove two other tests because they are not really testing different
code paths, while the stylesheet test unnecessarily makes it harder to
write test since pending stylesheets are script-blocking.

===

Original description of the previous attempt:

There used to be WPT test cases asserting that certain fullscreen-
related events are not filed when a document is render-blocked. The
tests were removed due to causing memory leaks (crrev.com/c/3438371).

This patch re-adds the test cases with a different approach that, this
time we fullscreen a subframe instead of the main frame. This doesn't
cause a memory leak.

This patch also discovers a bug that 'resize' event can be fired on
child frames even when it's render-blocked.

Fixed: 1293987
Bug: 1309664
Change-Id: I4b7691e1174e1d9f38b9800939f7394a60fe3ca8
Cq-Include-Trybots: luci.chromium.try:layout_test_leak_detection
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3551474
Reviewed-by: Joey Arhar <jarhar@chromium.org>
Commit-Queue: Xiaocheng Hu <xiaochengh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#986243}
diff --git a/html/dom/render-blocking/render-blocked-apis-by-module-preload-link.tentative.html b/html/dom/render-blocking/render-blocked-apis-by-module-preload-link.tentative.html
deleted file mode 100644
index fd4884a..0000000
--- a/html/dom/render-blocking/render-blocked-apis-by-module-preload-link.tentative.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!doctype html>
-<title>Certain APIs should not trigger while rendering is blocked by a preload link</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="/preload/resources/preload_helper.js"></script>
-<script src="support/test-render-blocking.js"></script>
-
-<link id="module-preload" rel="modulepreload" blocking="render"
-      href="support/dummy-1.mjs?pipe=trickle(d1)">
-
-<div id="dummy">Lorem ipsum</div>
-
-<script>
-const preload = document.getElementById('module-preload');
-test_render_blocked_apis(
-    preload,
-    async () => {
-      // Attach the module script into document.
-      // It should not start a new load as it's already preloaded.
-      const script = document.createElement('script');
-      const scriptObserver = new LoadObserver(script);
-      script.type = 'module';
-      script.src = 'support/dummy-1.mjs?pipe=trickle(d1)';
-      document.body.appendChild(script);
-      await scriptObserver.load;
-      verifyLoadedAndNoDoubleDownload('support/dummy-1.mjs?pipe=trickle(d1)');
-      assert_equals(document.getElementById('dummy').textContent, '1',
-                    'preloadedmodule script should be executed');
-    },
-    'Render-blocking module script is preloaded and executed');
-</script>
diff --git a/html/dom/render-blocking/render-blocked-apis-by-preload-link.tentative.html b/html/dom/render-blocking/render-blocked-apis-by-preload-link.tentative.html
index 8219fc8..f716783 100644
--- a/html/dom/render-blocking/render-blocked-apis-by-preload-link.tentative.html
+++ b/html/dom/render-blocking/render-blocked-apis-by-preload-link.tentative.html
@@ -2,26 +2,121 @@
 <title>Certain APIs should not trigger while rendering is blocked by a preload link</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script src="support/test-render-blocking.js"></script>
 
-<link id="font-preload" rel="preload" as="font" blocking="render" crossorigin
-      href="/fonts/Ahem.ttf?pipe=trickle(d1)">
-<style>
-@font-face {
-  font-family: custom-font;
-  src: url('/fonts/Ahem.ttf?pipe=trickle(d1)');
-}
-</style>
-<span id="target" style="font: 20px/1 custom-font">Lorem ipsum</span>
+<iframe id="iframe" src="support/subframe-render-blocking-preload.html"></iframe>
+<button>fullscreen</button>
 
 <script>
-const preload = document.getElementById('font-preload');
-test_render_blocked_apis(
-    preload,
-    () => {
-      const target = document.getElementById('target');
-      assert_equals(target.offsetHeight, 20);
-      assert_equals(target.offsetWidth, 220);
-    },
-    'Render-blocking web font is applied');
+// Tests that certain steps of Update the rendering [1] are not reached when
+// the document is render-blocked and hence has no rendering opportunities.
+// This test file only tests those steps related to viewport changes. It uses a
+// subframe so that viewport-related events are easier to fire.
+// [1] https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering
+
+const iframe = document.querySelector('iframe');
+const button = document.querySelector('button');
+button.onclick = () => iframe.contentDocument.documentElement.requestFullscreen();
+
+let autofocusTest;
+let scrollTest;
+let animationTest;
+let resizeTest;
+let mediaChangeTest;
+let fullscreenChangeTest;
+let rafTest;
+let intersectionObserverTest;
+
+const setup = new Promise((resolve, reject) => {
+  if (!window.test_driver)
+    reject('This test require test_driver to emulate user actions');
+
+  iframe.contentWindow.addEventListener('DOMContentLoaded', () => {
+    const preloadLink = iframe.contentDocument.getElementById('font-preload');
+    const preloadObserver = new LoadObserver(preloadLink);
+
+    // Returns a Promise that rejects if certain events on `target` fire before
+    // the Promise `condition` fulfills.
+    function rejectIfEventsFired(condition, target, events) {
+      if (!Array.isArray(events))
+        events = [events];
+      return new Promise((resolve, reject) => {
+        for (let eventName of events) {
+          target.addEventListener(eventName,
+              () => reject(`'${eventName}' event is dispatched`));
+        }
+        condition.then(resolve);
+      });
+    }
+
+    autofocusTest = rejectIfEventsFired(
+        preloadObserver.load,
+        iframe.contentDocument.getElementById('autofocus-target'),
+        'focus');
+    scrollTest = rejectIfEventsFired(
+        preloadObserver.load,
+        iframe.contentDocument.getElementById('scroll-target'),
+        'scroll');
+    animationTest = rejectIfEventsFired(
+        preloadObserver.load,
+        iframe.contentDocument.getElementById('animation-target'),
+        ['animationstart', 'animationend']);
+    resizeTest = rejectIfEventsFired(
+        preloadObserver.load, iframe.contentWindow, 'resize');
+    mediaChangeTest = rejectIfEventsFired(
+        preloadObserver.load,
+        iframe.contentWindow.matchMedia('(display-mode: fullscreen)'), 'change');
+    fullscreenChangeTest = rejectIfEventsFired(
+        preloadObserver.load,
+        iframe.contentDocument, ['fullscreenchange', 'fullscreenerror']);
+    rafTest = new Promise((resolve, reject) => {
+        iframe.contentWindow.requestAnimationFrame(
+            () => reject('Animation frame callback is run'));
+        preloadObserver.load.then(resolve);
+    });
+    intersectionObserverTest = new Promise((resolve, reject) => {
+        new iframe.contentWindow.IntersectionObserver(
+            () => reject('IntersectionObserver callback is run'))
+            .observe(iframe.contentDocument.documentElement);
+        preloadObserver.load.then(resolve);
+    });
+
+    // These should trigger events on the subframe if it's not render-blocked.
+    iframe.contentDocument.getElementById('scroll-target').scrollTop = 100;
+    test_driver.click(button).then(resolve);
+  });
+});
+
+promise_setup(() => setup);
+promise_test(
+    () => autofocusTest,
+    'Should not flush autofocus candidates when render-blocked');
+promise_test(
+    () => scrollTest,
+    'Should not run the scroll steps when render-blocked');
+promise_test(
+    () => animationTest,
+    'Should not run the update animations and send events steps when render-blocked');
+promise_test(
+    () => resizeTest,
+    'Should not run the resize steps when render-blocked');
+promise_test(
+    () => mediaChangeTest,
+    'Should not run the evaluate media queries and report changes steps when render-blocked');
+promise_test(
+    () => fullscreenChangeTest,
+    'Should not run the fullscreen steps when render-blocked');
+promise_test(
+    () => rafTest,
+    'Should not run animation frame callbacks when render-blocked');
+promise_test(
+    () => intersectionObserverTest,
+    'Should not run the update intersection observers step when render-blocked');
+promise_test(async () => {
+  const target = iframe.contentDocument.getElementById('target');
+  assert_equals(target.offsetHeight, 20);
+  assert_equals(target.offsetWidth, 220);
+}, 'The render-blocking web font is applied');
 </script>
diff --git a/html/dom/render-blocking/render-blocked-apis-by-stylesheet-link.tentative.html b/html/dom/render-blocking/render-blocked-apis-by-stylesheet-link.tentative.html
deleted file mode 100644
index 30b60fc..0000000
--- a/html/dom/render-blocking/render-blocked-apis-by-stylesheet-link.tentative.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!doctype html>
-<title>Certain APIs should not trigger while rendering is blocked by a stylesheet link</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<script src="support/test-render-blocking.js"></script>
-<script>
-// Test case must be set up before the stylesheet, because the stylesheet is
-// script-blocking, which means we can't set it up while the stylesheet is
-// loading.
-test_render_blocked_apis(() => {
-  let color = getComputedStyle(document.querySelector('.target')).color;
-  assert_equals(color, 'rgb(255, 0, 0)');
-}, 'Render-blocking stylesheet is applied');
-</script>
-<link rel="stylesheet" href="support/target-red.css?pipe=trickle(d1)">
-<div class="target">
-  This should be red
-</div>
diff --git a/html/dom/render-blocking/support/subframe-render-blocking-preload.html b/html/dom/render-blocking/support/subframe-render-blocking-preload.html
new file mode 100644
index 0000000..80d8840
--- /dev/null
+++ b/html/dom/render-blocking/support/subframe-render-blocking-preload.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>This subframe preloads a render-blocking font</title>
+<link id="font-preload" rel="preload" as="font" blocking="render" crossorigin
+      href="/fonts/Ahem.ttf?pipe=trickle(d2)">
+
+<style>
+@font-face {
+  font-family: custom-font;
+  src: url('/fonts/Ahem.ttf?pipe=trickle(d2)');
+}
+</style>
+<span id="target" style="font: 20px/1 custom-font">Lorem ipsum</span>
+
+<textarea id="autofocus-target" autofocus></textarea>
+
+<div id="scroll-target" style="height: 100px; overflow: scroll">
+  <div style="height: 200px"></div>
+</div>
+
+<style>
+@keyframes anim {
+  from { height: 100px; }
+  to { height: 200px; }
+}
+</style>
+<div id="animation-target" style="height: 50px; animation: anim 100ms"></div>
+
+<!-- We should also verify that the context lost steps for canvas are not run,
+  -- but there's currently no way to reliably trigger a context lost in WPT.
+  -- See https://github.com/web-platform-tests/wpt/issues/30039
+-->
+
+
diff --git a/html/dom/render-blocking/support/test-render-blocking.js b/html/dom/render-blocking/support/test-render-blocking.js
index b3d09ab..73f860a 100644
--- a/html/dom/render-blocking/support/test-render-blocking.js
+++ b/html/dom/render-blocking/support/test-render-blocking.js
@@ -84,99 +84,3 @@
     return loadObserver.load.then(() => finalTest(test));
   }, finalTestTitle);
 }
-
-// Tests that certain steps of Update the rendering [1] are not reached when
-// the document is render-blocked and hence has no rendering opportunities.
-// [1] https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering
-function test_render_blocked_apis(optional_element, finalTest, finalTestTitle) {
-  // Ideally, we should observe the 'load' event on the specific render-blocking
-  // elements. However, this is not possible for script-blocking stylesheets, so
-  // we have to observe the 'load' event on 'window' instead.
-  if (!(optional_element instanceof HTMLElement)) {
-    finalTestTitle = finalTest;
-    finalTest = optional_element;
-    optional_element = undefined;
-  }
-  const loadObserver = new LoadObserver(optional_element || window);
-
-  function test_event_blocked(target, events, title, optional_action) {
-    if (!Array.isArray(events))
-      events = [events];
-    const promise = new Promise((resolve, reject) => {
-      for (let eventName of events) {
-        target.addEventListener(eventName,
-                                () => reject(`'${eventName}' event is dispatched`));
-      }
-      loadObserver.load.then(resolve);
-
-      if (optional_action)
-        optional_action();
-    });
-    promise_test(() => promise, title);
-  }
-
-  test_event_blocked(
-      createAutofocusTarget(), 'focus',
-      'Should not flush autofocus candidates when render-blocked');
-
-  const scrollTarget = createScrollTarget();
-  test_event_blocked(
-      scrollTarget, 'scroll',
-      'Should not run the scroll steps when render-blocked',
-      () => scrollTarget.scrollTop = 100);
-
-  test_event_blocked(
-      createAnimationTarget(), ['animationstart', 'animationend'],
-      'Should not run the update animations and send events steps when render-blocked');
-
-  /* TODO(xiaochengh): requestFullscreen() with test driver currently causes
-   * memory leak in Blink web test runner. Fix it and re-enable these tests.
-   * See https://crbug.com/1293987 for details
-   *
-  // requestFullscreen() below will trigger viewport resize.
-  test_event_blocked(
-      window, 'resize',
-      'Should not run the resize steps when render-blocked');
-
-  // requestFullscreen() below will change the matches state
-  test_event_blocked(
-      matchMedia('all and (display-mode: fullscreen)'), 'change',
-      'Should not run the evaluate media queries and report changes steps when render-blocked');
-
-  test_event_blocked(
-      document, ['fullscreenchange', 'fullscreenerror'],
-      'Should not run the fullscreen steps when render-blocked',
-      () => {
-        if (window.test_driver) {
-          test_driver.bless('Initiate fullscreen',
-              () => document.documentElement.requestFullscreen()
-              .then(() => document.exitFullscreen()));
-        }
-      });
-   */
-
-  // We should also verify that the context lost steps for canvas are not run,
-  // but there's currently no way to reliably trigger a context lost in WPT.
-  // See https://github.com/web-platform-tests/wpt/issues/30039
-
-  const raf = new Promise((resolve, reject) => {
-    requestAnimationFrame(() => reject('Animation frame callback is run'));
-    loadObserver.load.then(resolve);
-  });
-  promise_test(
-      () => raf,
-      'Should not run animation frame callbacks when render-blocked');
-
-  const intersection = new Promise((resolve, reject) => {
-    new IntersectionObserver(() => reject('IntersectionObserver callback is run'))
-        .observe(document.documentElement);
-    loadObserver.load.then(resolve);
-  });
-  promise_test(
-      () => intersection,
-      'Should not run the update intersection observers step when render-blocked');
-
-  promise_test(test => {
-    return loadObserver.load.then(() => finalTest(test));
-  }, finalTestTitle);
-}