Bug 1646978 [wpt PR 24251] - [Portals] Disallow activation during unload, a=testonly
authorDavid Bokan <bokan@chromium.org>
Mon, 22 Jun 2020 10:45:34 +0000
changeset 600944 1e94519c3c73dc46f61080e6f93bcccbd3fc5ec1
parent 600943 2beded47c0dcc3ca69cdc16bfa970aa0c61cb8d3
child 600945 0e973f98258515a9640d3bd9569d7e672c7120ff
push id13310
push userffxbld-merge
push dateMon, 29 Jun 2020 14:50:06 +0000
treeherdermozilla-beta@15a59a0afa5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1646978, 24251, 1043764, 2251104, 780470
milestone79.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 1646978 [wpt PR 24251] - [Portals] Disallow activation during unload, a=testonly Automatic update from web-platform-tests [Portals] Disallow activation during unload This CL makes it so that portal activation is blocked when the document enters a beforeunload handler. If the unload proceeds, portal activation will continue to be blocked This must be implemented inside the renderer as the renderer can initiate navigations which the browser won't hear about until after beforeunload is confirmed. We do this by looking at the Document's LoadEventProgress state. Unfortunately, the current behavior is that, following a beforeunload, this state would progress unconditionally to kBeforeUnloadEventCompleted. This prevents us from distinguishing the case where the navigation is canceled, which should once again allow portal activation. This CL updates this state to only progress to the (renamed for consistency and clarity) kBeforeUnloadEventCompleted state only after we confirm the unload will proceed. Bug: 1043764 Change-Id: I80363ccf2228e3dc5434486c95c62c17e00743aa Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2251104 Commit-Queue: David Bokan <bokan@chromium.org> Reviewed-by: Jeremy Roman <jbroman@chromium.org> Cr-Commit-Position: refs/heads/master@{#780470} -- wpt-commits: 4877e40ba4bc993de5ccbd206845b63ceb07a892 wpt-pr: 24251
testing/web-platform/tests/portals/portals-activate-while-unloading.html
testing/web-platform/tests/portals/resources/portal-activate-in-handler.html
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/portals/portals-activate-while-unloading.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <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>
+      function childReady() {
+        return new Promise((resolve) => {
+          window.onmessage = resolve;
+        });
+      }
+
+      const handlers = ['beforeunload', 'pagehide', 'unload'];
+      for (let handler of handlers) {
+        promise_test(async (test) => {
+          let popup;
+
+          // Open a popup that has a portal, wait for both to be loaded.
+          {
+            await test_driver.bless('Open a popup', () => {
+              popup = open(`resources/portal-activate-in-handler.html?${handler}`,
+                           '_blank');
+            });
+            await childReady();
+          }
+
+          // We need the exception type below to ensure the activate() call
+          // throws but the popup global may be gone by then so stash it here.
+          const exception_type = popup.DOMException;
+
+          // Navigate the popup away.
+          const cur_path = popup.location.pathname;
+          popup.location = 'resources/blank-host.html';
+
+          // We need to wait until the handler is called but because of the
+          // nature of these handlers, we can't reliably communicate with the
+          // popup while they're running so we use a promise established
+          // earlier to wait until a time we know the portal has been activated
+          // and the returned promise stored on this global.
+          await window.handler_called_promise;
+          assert_not_equals(typeof(window.portal_promise), 'undefined',
+                            'Portal.activate() must be called');
+
+          // The popup should have called activate from the handler, and placed
+          // the promise returned from that call into this window in the
+          // |portal_promise| variable. We expect that this call should reject,
+          // however, if it does activate, it's timing dependent whether the
+          // handler will be run to completion so we may never fulfil the
+          // promise. In that case timeout and fail the test.
+          {
+            test.step_timeout(() => {
+              assert_unreached('Activation didn\'t fulfil.');
+            }, 3000);
+
+            await promise_rejects_dom(test,
+                                      "InvalidStateError",
+                                      exception_type,
+                                      window.portal_promise,
+                                      "Portal activation must fail.");
+          }
+          popup.close();
+        }, `cannot activate portal from ${handler}`);
+      }
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/portals/resources/portal-activate-in-handler.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <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>
+  </head>
+  <body>
+  </body>
+  <script>
+    // This page is reused with a different query parameter indicating which
+    // handler to register and activate a portal from.
+    const handler_name = window.location.search.substring(1);
+
+    const portal_element = document.createElement('portal');
+    portal_element.src = 'simple-portal.html';
+    document.body.appendChild(portal_element);
+
+    let page_loaded = false;
+    let portal_loaded = false;
+
+    function notifyReady() {
+      if (page_loaded && portal_loaded) {
+        window.opener.postMessage('done', '*');
+      }
+    }
+
+    portal_element.addEventListener('load', () => {
+      portal_loaded = true;
+      notifyReady();
+    });
+
+    window.addEventListener('load', () => {
+      page_loaded = true;
+      notifyReady();
+    });
+
+    // This will be used to let the parent page know the handler has run and
+    // |portal_promise| is now valid.
+    window.opener.handler_called_promise = new Promise((resolve) => {
+      window.addEventListener(handler_name, () => {
+        window.opener.portal_promise = portal_element.activate();
+
+        // Let the parent page know it can now look at |portal_promise|.
+        resolve();
+      }, {once: true});
+    });
+
+  </script>
+</html>