Title: [281830] trunk
Revision
281830
Author
[email protected]
Date
2021-08-31 16:07:44 -0700 (Tue, 31 Aug 2021)

Log Message

[Payment Request] Calling PaymentRequest's show() should consume user activation
https://bugs.webkit.org/show_bug.cgi?id=217365

Patch by Marcos Caceres <[email protected]> on 2021-08-31
Reviewed by Youenn Fablet and Devin Rousso.

LayoutTests/imported/w3c:

Re-import payment-request tests from WPT c0453ea15be63fc697bdbc141aa6837a1020b114.

* web-platform-tests/payment-request/payment-is-showing.https.html:
* web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt:
* web-platform-tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https-expected.txt:
* web-platform-tests/payment-request/payment-request-show-method.https-expected.txt: Added.
* web-platform-tests/payment-request/payment-request-show-method.https.html:
* web-platform-tests/payment-request/rejects_if_not_active.https.html:
* web-platform-tests/payment-request/show-method-optional-promise-rejects.https-expected.txt:

Source/WebCore:

Tested by existing WPT tests.

* Modules/paymentrequest/PaymentRequest.cpp:
(WebCore::PaymentRequest::show):

LayoutTests:

* http/tests/paymentrequest/payment-is-showing.https.html:
* http/tests/paymentrequest/payment-request-show-method.https.html:
* platform/ios-wk2/imported/w3c/web-platform-tests/payment-request/show-method-optional-promise-rejects.https-expected.txt: Removed.

Modified Paths

Added Paths

Removed Paths

  • trunk/LayoutTests/platform/ios-wk2/imported/w3c/web-platform-tests/payment-request/

Diff

Modified: trunk/LayoutTests/ChangeLog (281829 => 281830)


--- trunk/LayoutTests/ChangeLog	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/ChangeLog	2021-08-31 23:07:44 UTC (rev 281830)
@@ -1,3 +1,14 @@
+2021-08-31  Marcos Caceres  <[email protected]>
+
+        [Payment Request] Calling PaymentRequest's show() should consume user activation
+        https://bugs.webkit.org/show_bug.cgi?id=217365
+
+        Reviewed by Youenn Fablet and Devin Rousso.
+
+        * http/tests/paymentrequest/payment-is-showing.https.html:
+        * http/tests/paymentrequest/payment-request-show-method.https.html:
+        * platform/ios-wk2/imported/w3c/web-platform-tests/payment-request/show-method-optional-promise-rejects.https-expected.txt: Removed.
+
 2021-08-31  Eric Hutchison  <[email protected]>
 
         Update test expectations for iOS14 fast/ tests.

Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https.html (281829 => 281830)


--- trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https.html	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https.html	2021-08-31 23:07:44 UTC (rev 281830)
@@ -71,11 +71,16 @@
       // Sets the "payment-relevant browsing context's payment request is
       // showing boolean" to true and then try to show a second payment sheet in
       // the same window. The second show() should reject.
-      const [showPromise1, showPromise2] = await activateThen(
-        async () => {
-          return [request1.show(), request2.show()];
+      const [showPromise1] = await activateThen(
+        () => {
+          return [request1.show()]
         }
       );
+      const [showPromise2] = await activateThen(
+        () => {
+          return [request2.show()]
+        }
+      );
       await promise_rejects(
         t,
         'AbortError',
@@ -117,12 +122,18 @@
       const iframeRequest = new iframeWindow.PaymentRequest(methods, details);
       const windowRequest = new window.PaymentRequest(methods, details);
 
-      // Let's get some blessed showPromises
-      const [iframeShowPromise, windowShowPromise] = await activateThen(
-        async () => {
-          // iframe sets "is showing boolean", ignore the returned promise.
+      // Let's get blessed promise for the iframe
+      const [iframeShowPromise] = await activateThen(
+        () => {
+          // iframe sets "is showing boolean"
+          return [iframeRequest.show()];
+        }
+      );
+
+      const [windowShowPromise] = await activateThen(
+        () => {
           // The top level window now tries to show() the payment request.
-          return [iframeRequest.show(), windowRequest.show()];
+          return [windowRequest.show()];
         }
       );
 
@@ -130,7 +141,7 @@
         t,
         'AbortError',
         windowShowPromise,
-        'iframe is already showing a payment request.',
+        'iframe is already showing a payment request',
       );
 
       // Cleanup
@@ -154,12 +165,18 @@
       // We first show a payment request via the the top level browsing context,
       // windowRequest.show() sets "is showing boolean" to true. Then we try to
       // show a payment request in the iframe, which should reject.
-      const [windowShowPromise, iframeShowPromise] = await activateThen(
-        async () => {
-          return [windowRequest.show(), iframeRequest.show()];
+      const [windowShowPromise] = await activateThen(
+        () => {
+          return [windowRequest.show()];
         }
       );
 
+      const [iframeShowPromise] = await activateThen(
+        () => {
+          return [iframeRequest.show()];
+        }
+      );
+
       await promise_rejects(
         t,
         'AbortError',
@@ -188,15 +205,15 @@
       // Get the showPromise for each browsing context. Doing this in a separate
       // test_driver.bless() is important because the user gesture brings
       // |window| to the foreground, so that the payment sheet can show.
-      const [
-        windowShowPromise,
-        iframeShowPromise,
-      ] = await activateThen(
+      const [windowShowPromise] = await activateThen(
+        () => {
+          return [windowRequest.show()];
+        }
+      );
+
+      const [ iframeShowPromise ] = await activateThen(
         async () => {
-          return [
-            windowRequest.show(),
-            iframeRequest.show(),
-          ];
+          return [iframeRequest.show()];
         }
       );
 
@@ -227,17 +244,16 @@
       // Get the showPromise for each browsing context. Doing this in a separate
       // test_driver.bless() is important because the user gesture brings
       // |window| to the foreground, so that the payment sheet can show.
-      const [
-        iframeShowPromise,
-        windowShowPromise,
-      ] = await activateThen(
-        async () => {
-          return [
-            iframeRequest.show(),
-            windowRequest.show(),
-          ];
+      const [iframeShowPromise] = await activateThen(
+        () => {
+          return [iframeRequest.show()];
         }
       );
+      const [windowShowPromise] = await activateThen(
+        () => {
+          return [windowRequest.show()];
+        }
+      );
 
       // windowShowPromise and iframeRequest will both reject
       await promise_rejects(

Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-request-show-method.https.html (281829 => 281830)


--- trunk/LayoutTests/http/tests/paymentrequest/payment-request-show-method.https.html	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-request-show-method.https.html	2021-08-31 23:07:44 UTC (rev 281830)
@@ -46,7 +46,7 @@
 user_activation_test(async t => {
   const request = new PaymentRequest(defaultMethods, defaultDetails);
   const acceptPromise = request.show(); // Sets state to "interactive"
-  await promise_rejects(t, "InvalidStateError", request.show());
+  await promise_rejects(t, "SecurityError", request.show());
   await request.abort();
   await promise_rejects(t, "AbortError", acceptPromise);
 }, `Throws if the promise [[state]] is not "created"`);
@@ -56,7 +56,7 @@
   const request2 = new PaymentRequest(defaultMethods, defaultDetails);
   const acceptPromise1 = request1.show();
   const acceptPromise2 = request2.show();
-  await promise_rejects(t, "AbortError", acceptPromise2);
+  await promise_rejects(t, "SecurityError", acceptPromise2);
   await request1.abort();
   await promise_rejects(t, "AbortError", acceptPromise1);
 }, `If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException.`);

Modified: trunk/LayoutTests/imported/w3c/ChangeLog (281829 => 281830)


--- trunk/LayoutTests/imported/w3c/ChangeLog	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/imported/w3c/ChangeLog	2021-08-31 23:07:44 UTC (rev 281830)
@@ -1,3 +1,20 @@
+2021-08-31  Marcos Caceres  <[email protected]>
+
+        [Payment Request] Calling PaymentRequest's show() should consume user activation
+        https://bugs.webkit.org/show_bug.cgi?id=217365
+
+        Reviewed by Youenn Fablet and Devin Rousso.
+
+        Re-import payment-request tests from WPT c0453ea15be63fc697bdbc141aa6837a1020b114.
+
+        * web-platform-tests/payment-request/payment-is-showing.https.html:
+        * web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt:
+        * web-platform-tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https-expected.txt:
+        * web-platform-tests/payment-request/payment-request-show-method.https-expected.txt: Added.
+        * web-platform-tests/payment-request/payment-request-show-method.https.html:
+        * web-platform-tests/payment-request/rejects_if_not_active.https.html:
+        * web-platform-tests/payment-request/show-method-optional-promise-rejects.https-expected.txt:
+
 2021-08-31  Ryosuke Niwa  <[email protected]>
 
         Resync web-platform-tests/shadow-dom

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-is-showing.https.html (281829 => 281830)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-is-showing.https.html	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-is-showing.https.html	2021-08-31 23:07:44 UTC (rev 281830)
@@ -4,7 +4,7 @@
   rel="help"
   href=""
 />
-<meta name="timeout" content="long">
+<meta name="timeout" content="long" />
 <script src=""
 <script src=""
 <script src=""
@@ -22,7 +22,7 @@
         supportedNetworks: ["visa"],
       },
     };
-    const methods = [{supportedMethods: "basic-card"}, applePayMethod];
+    const methods = [{ supportedMethods: "basic-card" }, applePayMethod];
     const details = {
       total: {
         label: "Total",
@@ -44,28 +44,23 @@
       iframe.allow = "payment";
       iframe.src = ""
       document.body.appendChild(iframe);
-      await new Promise(resolve => {
-        iframe.addEventListener("load", resolve, {once: true});
+      await new Promise((resolve) => {
+        iframe.addEventListener("load", resolve, { once: true });
       });
       return iframe;
     }
 
-    /**
-     * Creates a popup window. The caller must be triggered with a user gesture.
-     *
-     * @param {String} src Optional resource URL to load.
-     * @returns {Promise} Resolves when the src loads.
-     */
-    async function loadPopupInsideUserGesture(src = "" {
-      const popupWindow = window.open(src, "", "width=400,height=400");
-      await new Promise(resolve => {
-        popupWindow.addEventListener("load", resolve, {once: true});
-      });
-      popupWindow.focus();
-      return popupWindow;
+    function getShowPromiseFromContext(paymentRequest, context = this) {
+      return test_driver.bless(
+        "payment request show()",
+        () => {
+          return [paymentRequest.show()];
+        },
+        context
+      );
     }
 
-    promise_test(async t => {
+    promise_test(async (t) => {
       const request1 = new PaymentRequest(methods, details);
       const request2 = new PaymentRequest(methods, details);
 
@@ -72,17 +67,17 @@
       // Sets the "payment-relevant browsing context's payment request is
       // showing boolean" to true and then try to show a second payment sheet in
       // the same window. The second show() should reject.
-      const [showPromise1, showPromise2] = await test_driver.bless(
-        "testing one payment sheet per window",
-        () => {
-          return [request1.show(), request2.show()];
-        },
-      );
+      await test_driver.bless("payment request show()");
+      const showPromise1 = request1.show();
+
+      await test_driver.bless("payment request show()");
+      const showPromise2 = request2.show();
+
       await promise_rejects_dom(
         t,
         "AbortError",
         showPromise2,
-        "Attempting to show a second payment request must reject.",
+        "Attempting to show a second payment request must reject."
       );
 
       await request1.abort();
@@ -90,7 +85,7 @@
         t,
         "AbortError",
         showPromise1,
-        "request1 was aborted via .abort()",
+        "request1 was aborted via .abort()"
       );
 
       // Finally, request2 should have been "closed", so trying to show
@@ -101,40 +96,40 @@
         t,
         "InvalidStateError",
         rejectedPromise,
-        "Attempting to show a second payment request must reject.",
+        "Attempting to show a second payment request must reject."
       );
       // Finally, we confirm that request2's returned promises are unique.
       assert_not_equals(
         showPromise2,
         rejectedPromise,
-        "Returned Promises be unique",
+        "Returned Promises be unique"
       );
     }, "The top browsing context can only show one payment sheet at a time.");
 
-    promise_test(async t => {
+    promise_test(async (t) => {
       const iframe = await attachIframe();
       const iframeWindow = iframe.contentWindow;
 
       // Payment requests
+      const windowRequest = new window.PaymentRequest(methods, details);
       const iframeRequest = new iframeWindow.PaymentRequest(methods, details);
-      const windowRequest = new window.PaymentRequest(methods, details);
 
       // Let's get some blessed showPromises
-      const [showPromise] = await test_driver.bless(
-        "testing top window show() blocked by payment sheet in iframe",
-        () => {
-          // iframe sets "is showing boolean", ignore the returned promise.
-          iframeRequest.show();
-          // The top level window now tries to show() the payment request.
-          return [windowRequest.show()];
-        },
+      // iframe sets "is showing boolean", ignore the returned promise.
+      const [iframePromise] = await getShowPromiseFromContext(
+        iframeRequest,
+        iframeWindow
       );
 
+      // The top level window now tries to show() the payment request.
+      await test_driver.bless("payment request show()");
+      const showPromise = windowRequest.show();
+
       await promise_rejects_dom(
         t,
         "AbortError",
         showPromise,
-        "iframe is already showing a payment request.",
+        "iframe is already showing a payment request."
       );
 
       // Cleanup
@@ -142,361 +137,34 @@
       iframe.remove();
     }, "If an iframe shows a payment request, the top-level browsing context can't also show one.");
 
-    promise_test(async t => {
+    promise_test(async (t) => {
       const iframe = await attachIframe();
       const iframeWindow = iframe.contentWindow;
-
-      // Payment requests
       const iframeRequest = new iframeWindow.PaymentRequest(methods, details);
-      const windowRequest = new window.PaymentRequest(methods, details);
-
-      // We first show a payment request via the the top level browsing context,
-      // windowRequest.show() sets "is showing boolean" to true. Then we try to
-      // show a payment request in the iframe, which should reject.
-      const [windowShowPromise, iframeShowPromise] = await test_driver.bless(
-        "testing iframe show() blocked by payment sheet in top window",
-        () => {
-          return [windowRequest.show(), iframeRequest.show()];
-        },
+      const [iframeShowPromise] = await getShowPromiseFromContext(
+        iframeRequest,
+        iframeWindow
       );
 
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        iframeShowPromise,
-        "The top window is already showing a payment request.",
-      );
-
-      // Cleanup
-      await windowRequest.abort();
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        windowShowPromise,
-        "The window payment request should be aborted by test.",
-        );
-      iframe.remove();
-    }, "An iframe cannot show a payment request if the top-level window is already showing one.");
-
-    promise_test(async t => {
-      // Create a PaymentReuqest in top-level window.
-      const windowRequest = new window.PaymentRequest(methods, details);
-
-      // Use a single user gesture to open a popup window with a PaymentRequest.
-      // Then trigger show() first on |popupRequest| then on |windowRequest|.
-      // The latter should reject.
-      const [
-        popupWindow,
-        popupRequest,
-        popupShowPromise,
-        windowShowPromise,
-      ] = await test_driver.bless(
-        "testing top-level show() blocked by payment sheet in popup",
-        async () => {
-          const popupWindow = await loadPopupInsideUserGesture();
-          const popupRequest = new popupWindow.PaymentRequest(methods, details);
-          const popupShowPromise = popupRequest.show();
-          const windowShowPromise = windowRequest.show();
-          return [
-            popupWindow,
-            popupRequest,
-            popupShowPromise,
-            windowShowPromise,
-          ];
-        },
-      );
-
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        windowShowPromise,
-        "Expected window's showPromise to reject, request is already showing",
-      );
-
-      await popupRequest.abort();
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        popupShowPromise,
-        "Expected popupShowPromise to be aborted by test.",
-      );
-      popupWindow.close();
-    }, "Using a popup window prevents the top-browsing context from showing a payment request");
-
-    promise_test(async t => {
-      const iframe = await attachIframe();
-      const iframeWindow = iframe.contentWindow;
-
-      // Create requests
-      const windowRequest = new window.PaymentRequest(methods, details);
-      const iframeRequest = new iframeWindow.PaymentRequest(methods, details);
-
-      // Open a popup window
-      const [popupWindow, popupRequest] =
-      await test_driver.bless(
-        "open popup to test multiple context and window calls show() first",
-        async () => {
-          const popupWindow = await loadPopupInsideUserGesture();
-          const popupRequest = new popupWindow.PaymentRequest(methods, details);
-          return [popupWindow, popupRequest];
-        },
-      );
-
-      // Get the showPromise for each browsing context. Doing this in a separate
-      // test_driver.bless() is important because the user gesture brings
-      // |window| to the foreground, so that the payment sheet can show.
-      const [
-        windowShowPromise,
-        popupShowPromise,
-        iframeShowPromise,
-      ] = await test_driver.bless(
-        "test multiple nested browsing context",
-        () => {
-          return [
-            windowRequest.show(),
-            popupRequest.show(),
-            iframeRequest.show(),
-          ];
-        },
-      );
-      // popupRequest and iframeRequest will both reject
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        popupShowPromise,
-        "Expected popupShowPromise to reject, request is already showing.",
-      );
-
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        iframeShowPromise,
-        "Expected iframeShowPromise to reject, request is already showing.",
-      );
-
-      await windowRequest.abort();
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        windowShowPromise,
-        "Expect window promise to be aborted by test."
-      );
-      popupWindow.close();
-      iframe.remove();
-    }, "Given multiple nested browsing contexts, and window calls show() first, other nested browsing contexts can't show a request.");
-
-    promise_test(async t => {
-      const iframe = await attachIframe();
-      const iframeWindow = iframe.contentWindow;
-
-      // Create requests
-      const windowRequest = new window.PaymentRequest(methods, details);
-      const iframeRequest = new iframeWindow.PaymentRequest(methods, details);
-
-      // Open a popup window
-      const [
-        popupWindow,
-        popupRequest,
-        popupShowPromise,
-        windowShowPromise,
-        iframeShowPromise
-      ] = await test_driver.bless(
-        "test multiple browsing context and iframe calls show() first",
-        async () => {
-          const popupWindow = await loadPopupInsideUserGesture();
-          const popupRequest = new popupWindow.PaymentRequest(methods, details);
-          const popupShowPromise = popupRequest.show();
-          const windowShowPromise = windowRequest.show();
-          const iframeShowPromise = iframeRequest.show();
-          return [popupWindow,
-                  popupRequest,
-                  popupShowPromise,
-                  windowShowPromise,
-                  iframeShowPromise];
+      // We navigate away, causing the payment sheet to close
+      // and the request is showing boolean to become false.
+      await new Promise((resolve) => {
+        iframe._onload_ = resolve;
+        iframe.src = ""
       });
 
-      // windowShowPromise and iframeRequest will both reject
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        windowShowPromise,
-        "Expected windowShowPromise to reject, the popup is showing a payment request.",
-      );
-
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        iframeShowPromise,
-        "Expected iframeShowPromise to reject, the popup is showing a payment request.",
-      );
-
-      await popupRequest.abort();
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        popupShowPromise,
-        "Expected popupShowPromise to be aborted by test.",
-      );
-      popupWindow.close();
       iframe.remove();
-    }, "Given multiple nested browsing contexts, and popup calls show() first, other nested browsing contexts can't show a request.");
 
-    promise_test(async t => {
-      const iframe = await attachIframe();
-      const iframeWindow = iframe.contentWindow;
-
-      // Create requests
-      const windowRequest = new window.PaymentRequest(methods, details);
-      const iframeRequest = new iframeWindow.PaymentRequest(methods, details);
-
-      const [popupWindow, popupRequest] = await test_driver.bless(
-        "open popup to test multiple context and iframe calls show() first",
-        async () => {
-          const w = await loadPopupInsideUserGesture();
-          const r = new w.PaymentRequest(methods, details);
-          return [w, r];
-        },
-      );
-
-      // Get the showPromise for each browsing context. Doing this in a separate
-      // test_driver.bless() is important because the user gesture brings
-      // |window| to the foreground, so that the payment sheet can show.
-      const [
-        iframeShowPromise,
-        popupShowPromise,
-        windowShowPromise,
-      ] = await test_driver.bless(
-        "test multiple browsing context and iframe calls show() first",
-        async () => {
-          return [
-            iframeRequest.show(),
-            popupRequest.show(),
-            windowRequest.show(),
-          ];
-        },
-      );
-
-      // windowShowPromise and iframeRequest will both reject
+      // Now we should be ok to spin up a new payment request
+      const request = new window.PaymentRequest(methods, details);
+      const [showPromise] = await getShowPromiseFromContext(request);
+      await request.abort();
       await promise_rejects_dom(
         t,
         "AbortError",
-        windowShowPromise,
-        "Expected windowShowPromise to reject, the popup is showing a payment request.",
+        showPromise,
+        "Normal abort."
       );
-
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        popupShowPromise,
-        "Expected popupShowPromise to reject, the popup is showing a payment request.",
-      );
-
-      await iframeRequest.abort();
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        iframeShowPromise,
-        "Expected iframeShowPromise to be aborted by test."
-      );
-      popupWindow.close();
-      iframe.remove();
-    }, "Given multiple nested browsing contexts, and an iframe calls show() first, other nested browsing contexts can't show a request.");
-
-    promise_test(async t => {
-      const iframe = await attachIframe();
-      const iframeWindow = iframe.contentWindow;
-      const iframeRequest = new iframeWindow.PaymentRequest(methods, details);
-      const iframeShowPromise = test_driver.bless(
-        "test navigating iframe after show()",
-        () => iframeRequest.show(),
-      );
-
-      // We navigate away, causing the payment sheet to close
-      // and the request is showing boolean to become false.
-      iframe.src = ""
-      await new Promise(resolve => (iframe._onload_ = resolve));
-      await promise_rejects_dom(
-        t,
-        "AbortError",
-        iframeShowPromise,
-        "Navigating iframe away must cause the iframeShowPromise to reject.",
-      );
-      iframe.remove();
-
-      // Now we should be ok to spin up a new payment request
-      const request = new window.PaymentRequest(methods, details);
-      const [showPromise] = await test_driver.bless(
-          "start a new payment request",
-          () => {
-            return [request.show()];
-          });
-
-      // If a payment sheet fails to show, it should reject immediately. If it
-      // hasn't rejected in 1 second, then the test has passed.
-      t.step_timeout(async () => {
-        // We're done. Clean up.
-        await request.abort();
-        t.done();
-      });
-
-      // If the navigation in iframe failed to close the original payment sheet
-      // there, |showPromise| should reject immediately and this indicates a
-      // failure of this test.
-      await showPromise.then(() => {
-        assert_true(false,
-          "Second payment sheet should be pending but is resolved.");
-      })
-      .catch(e => {
-        assert_true(false,
-          "Second payment sheet should be pending but is rejected." + e.message);
-      });
     }, "Navigating an iframe as a nested browsing context sets 'payment request is showing boolean' to false.");
-
-    promise_test(async t => {
-      const [popupWindow, popupRequest, popupShowPromise] =
-        await test_driver.bless(
-          "trigger payment in a popup window",
-          async () => {
-            const popupWindow = await loadPopupInsideUserGesture();
-            const popupRequest = new popupWindow.PaymentRequest(methods, details);
-            return [popupWindow, popupRequest, popupRequest.show()];
-          });
-
-      // We navigate away, causing the payment sheet to close
-      // and the request is showing boolean to become false.
-      popupWindow.location = "blank.html?abc=123";
-      await new Promise(resolve => (popupWindow._onload_ = resolve));
-
-      // Don't wait for |popupShowPromise| to reject because it may never do
-      // (see https://github.com/w3c/payment-request/issues/872). Instead, try
-      // to spin up a new payment request and make sure it succeeds.
-      const request = new window.PaymentRequest(methods, details);
-      const [showPromise] = await test_driver.bless(
-        "trigger payment in main window",
-        () => {
-          return [request.show()];
-        });
-
-      // If a payment sheet fails to show, it should reject immediately. If it
-      // hasn't rejected in 1 second, then the test has passed.
-      t.step_timeout(async () => {
-        // We're done. Clean up.
-        popupWindow.close();
-        await request.abort();
-        t.done();
-      }, 1000);
-
-      // If the navigation in popup window failed to close the original payment
-      // sheet there, |showPromise| should reject immediately and this indicates
-      // a failure of this test.
-      await showPromise.then(() => {
-        assert_true(false,
-          "Second payment sheet should be pending but is resolved.");
-      })
-      .catch(e => {
-        assert_true(false,
-          "Second payment sheet should be pending but is rejected.");
-      });
-    }, "Navigating a popup as a nested browsing context sets 'payment request is showing boolean' to false.");
   </script>
 </body>

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt (281829 => 281830)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-canmakepayment-method.https-expected.txt	2021-08-31 23:07:44 UTC (rev 281830)
@@ -4,6 +4,6 @@
 PASS If request.[[state]] is "created", then return a promise that resolves to true for known method.
 PASS All methods are unsupported
 PASS Mix of supported and unsupported methods, at least one method is supported.
-FAIL If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException. assert_unreached: Should have rejected: undefined Reached unreachable code
-FAIL If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "InvalidStateError: The object is in an invalid state."
+PASS If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException.
+PASS If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException.
 

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https-expected.txt (281829 => 281830)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https-expected.txt	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https-expected.txt	2021-08-31 23:07:44 UTC (rev 281830)
@@ -2,5 +2,5 @@
 
 FAIL hasEnrolledInstrument() resolves to false for unsupported payment methods. promise_test: Unhandled rejection with value: object "TypeError: request.hasEnrolledInstrument is not a function. (In 'request.hasEnrolledInstrument()', 'request.hasEnrolledInstrument' is undefined)"
 FAIL If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "TypeError: request.hasEnrolledInstrument is not a function. (In 'request.hasEnrolledInstrument()', 'request.hasEnrolledInstrument' is undefined)"
-FAIL If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. promise_test: Unhandled rejection with value: object "InvalidStateError: The object is in an invalid state."
+FAIL If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException. promise_rejects_dom: function "function () { throw e }" threw object "NotSupportedError: The operation is not supported." that is not a DOMException AbortError: property "code" is equal to 9, expected 20
 

Added: trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https-expected.txt (0 => 281830)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https-expected.txt	2021-08-31 23:07:44 UTC (rev 281830)
@@ -0,0 +1,8 @@
+If you find a buggy test, please file a bug and tag one of the suggested reviewers.
+
+PASS Calling show() without being triggered by user interaction throws
+PASS Throws if the promise [[state]] is not 'created'.
+PASS If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException.
+PASS If payment method consultation produces no supported method of payment, then return a promise rejected with a "NotSupportedError" DOMException.
+PASS Calling show() multiple times always returns a new promise.
+

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https.html (281829 => 281830)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https.html	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/payment-request-show-method.https.html	2021-08-31 23:07:44 UTC (rev 281830)
@@ -38,30 +38,20 @@
 promise_test(async (t) => {
   const request = new PaymentRequest(defaultMethods, defaultDetails);
   const acceptPromise = request.show();
-  // Abort the request after 2 seconds if it has not rejected. This allows the
-  // other tests in this file to run even if a non-compliant browser shows the
-  // payment sheet without user activation.
-  t.step_timeout(() => {
-    t.force_timeout();
-    request.abort();
-  }, 2000);
-
-  await promise_rejects_dom(t, "NotAllowedError", acceptPromise);
+  await promise_rejects_dom(t, "SecurityError", acceptPromise);
 }, `Calling show() without being triggered by user interaction throws`);
 
 promise_test(async (t) => {
   const request = new PaymentRequest(defaultMethods, defaultDetails);
-  await test_driver.bless(
-    "test: throws if the promise [[state]] is not 'created'"
-  );
-  const acceptPromise = request.show(); // Sets state to "interactive"
-  // No user activation...
-  await promise_rejects_dom(t, "NotAllowedError", request.show());
+  await promise_rejects_dom(t, "SecurityError", request.show());
 
-  // Get user activation
-  await test_driver.bless(
-    "test: throws if the promise [[state]] is not 'created'"
-  );
+  await test_driver.bless();
+  // Sets state to "interactive", but consumes user interaction.
+  const acceptPromise = request.show();
+  await promise_rejects_dom(t, "SecurityError", request.show());
+
+  // With user activation, but already showing the sheet...
+  await test_driver.bless();
   await promise_rejects_dom(t, "InvalidStateError", request.show());
 
   await request.abort();
@@ -70,16 +60,15 @@
 
 promise_test(async (t) => {
   const request1 = new PaymentRequest(defaultMethods, defaultDetails);
-  const request2 = new PaymentRequest(defaultMethods, defaultDetails);
-
-  await test_driver.bless("payment request");
+  await test_driver.bless();
   const acceptPromise1 = request1.show();
 
   // User activation consumed, so...
-  await promise_rejects_dom(t, "NotAllowedError", request2.show());
+  const request2 = new PaymentRequest(defaultMethods, defaultDetails);
+  await promise_rejects_dom(t, "SecurityError", request2.show());
 
   // Payment request already showing, so...
-  await test_driver.bless("payment request");
+  await test_driver.bless();
   await promise_rejects_dom(t, "AbortError", request2.show());
 
   await request1.abort();
@@ -91,7 +80,7 @@
     [{ supportedMethods: "this-is-not-supported" }],
     defaultDetails
   );
-  await test_driver.bless(`test: reject promise with "NotSupportedError"`);
+  await test_driver.bless();
   const acceptPromise = request.show();
   await promise_rejects_dom(t, "NotSupportedError", acceptPromise);
 }, `If payment method consultation produces no supported method of payment, then return a promise rejected with a "NotSupportedError" DOMException.`);
@@ -98,20 +87,20 @@
 
 promise_test(async (t) => {
   const request = new PaymentRequest(defaultMethods, defaultDetails);
-  await test_driver.bless("call show()");
+  await test_driver.bless();
   const p1 = request.show();
-  await test_driver.bless("call show()");
+  await test_driver.bless();
   const p2 = request.show();
-  await test_driver.bless("call show()");
+  await test_driver.bless();
   const p3 = request.show();
+  await request.abort();
+
   const promises = new Set([p1, p2, p3]);
   assert_equals(promises.size, 3, "Must have three unique objects");
 
+  await promise_rejects_dom(t, "AbortError", p1);
   await promise_rejects_dom(t, "InvalidStateError", p2);
   await promise_rejects_dom(t, "InvalidStateError", p3);
-
-  await request.abort();
-  await promise_rejects_dom(t, "AbortError", p1);
 }, "Calling show() multiple times always returns a new promise.");
 </script>
 <small>
@@ -120,5 +109,6 @@
   tag one of the
   <a
     href=""
-    >suggested reviewers</a>.
+    >suggested reviewers</a
+  >.
 </small>

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/rejects_if_not_active.https.html (281829 => 281830)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/rejects_if_not_active.https.html	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/rejects_if_not_active.https.html	2021-08-31 23:07:44 UTC (rev 281830)
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
-<meta charset=utf-8>
+<meta charset="utf-8" />
 <title>PaymentRequest show() rejects if doc is not fully active</title>
-<link rel="help" href=""
+<link rel="help" href="" />
 <script src=""
 <script src=""
 <script src=""
@@ -16,7 +16,7 @@
     countryCode: "US",
     merchantCapabilities: ["supports3DS"],
     supportedNetworks: ["visa"],
-  }
+  },
 });
 const validMethod = Object.freeze({
   supportedMethods: "basic-card",
@@ -34,64 +34,56 @@
   total: validTotal,
 });
 
-function getLoadedPaymentRequest(iframe, url) {
-  return new Promise(resolve => {
-    iframe.addEventListener(
-      "load",
-      () => {
-        const { PaymentRequest } = iframe.contentWindow;
-        const request = new PaymentRequest(validMethods, validDetails);
-        resolve(request);
-      },
-      { once: true }
-    );
+async function getLoadedPaymentRequest(iframe, url) {
+  await new Promise((resolve) => {
+    iframe.addEventListener("load", resolve, { once: true });
     iframe.src = ""
   });
+  const { PaymentRequest } = iframe.contentWindow;
+  const request = new PaymentRequest(validMethods, validDetails);
+  return request;
 }
 
-promise_test(async t => {
+promise_test(async (t) => {
   const iframe = document.createElement("iframe");
-  iframe.allow = "payment";
   document.body.appendChild(iframe);
   // Make a request in the iframe.
   const request = await getLoadedPaymentRequest(
     iframe,
-    "/payment-request/resources/page1.html"
+    "resources/page1.html"
   );
-  const [showPromise] = await test_driver.bless("show payment request", () => {
-    return [request.show()];
-  });
+  await test_driver.bless("show payment request");
+  const showPromise = request.show();
+  const firstCtor = iframe.contentWindow.DOMException;
+
   // Navigate the iframe to a new location. Wait for the load event to fire.
-  await new Promise(resolve => {
-    iframe.addEventListener("load", resolve);
-    iframe.src = ""
-    // An implementation may optionally reject |showPromise|.
-    showPromise.catch(e => {});
-  });
+  const request2 = await getLoadedPaymentRequest(
+    iframe,
+    "resources/page2.html"
+  );
 
-  // The navigaton should have dismissed the previous payment request so it
-  // should be possible to show another one now.
-  const request2 = new iframe.contentWindow.PaymentRequest(
-    validMethods, validDetails);
-  const [showPromise2] = await test_driver.bless(
-    "show 2nd payment request", () => {
-      return [request2.show()];
-    });
+  await promise_rejects_dom(
+    t,
+    "AbortError",
+    firstCtor,
+    showPromise,
+    "Show promise should abort."
+  );
 
-  // Stop the test in 1 second if it has not rejected, which means that a
-  // payment sheet is successfully shown.
-  t.step_timeout(async () => {
-    // We are done, so clean up.
-    iframe.remove();
-    t.done();
-  }, 1000);
-
-  // This should never settle because the payment sheet should be pending.
-  await showPromise2.then(() => {
-    assert_true(false, "Second payment should be pending but is resolved.");
-  })
-  .catch(e => {
-    assert_true(false, "Second payment should be pending but is rejected.");
-  });
+  // The navigation dismisses the previous payment request so it
+  // now possible to show another one.
+  await test_driver.bless("show 2nd payment request");
+  const showPromise2 = request2.show();
+  const secondCtor = iframe.contentWindow.DOMException;
+  // no longer fully active
+  iframe.remove();
+  await promise_rejects_dom(
+    t,
+    "AbortError",
+    secondCtor,
+    showPromise2,
+    "Show promise should abort."
+  );
 }, "If a payment request is showing, but its document is navigated away (so no longer fully active), the payment sheet is dismissed.");
 </script>
+</body>

Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/show-method-optional-promise-rejects.https-expected.txt (281829 => 281830)


--- trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/show-method-optional-promise-rejects.https-expected.txt	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/payment-request/show-method-optional-promise-rejects.https-expected.txt	2021-08-31 23:07:44 UTC (rev 281830)
@@ -1,30 +1,12 @@
 
-FAIL Rejection of detailsPromise must abort the update with an 'AbortError' DOMException. promise_rejects_dom: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." that is not a DOMException AbortError: property "code" is equal to 18, expected 20
-FAIL Total in the update is a string, so converting to IDL must abort the update with a TypeError. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function TypeError() {
-    [native code]
-}" ("TypeError")
-FAIL Total is recursive, so converting to IDL must abort the update with a TypeError. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function TypeError() {
-    [native code]
-}" ("TypeError")
-FAIL Updating with a negative total results in a TypeError. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function TypeError() {
-    [native code]
-}" ("TypeError")
-FAIL Updating with a displayItem with an invalid currency results in RangeError. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function RangeError() {
-    [native code]
-}" ("RangeError")
-FAIL Updating with duplicate shippingOptions (same IDs) results in a TypeError. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function TypeError() {
-    [native code]
-}" ("TypeError")
-FAIL Updating with a shippingOption with an invalid currency value results in a RangError. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function RangeError() {
-    [native code]
-}" ("RangeError")
-FAIL Must throw a RangeError when a modifier's total item has an invalid currency. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function RangeError() {
-    [native code]
-}" ("RangeError")
-FAIL Must throw a RangeError when a modifier display item has an invalid currency. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function RangeError() {
-    [native code]
-}" ("RangeError")
-FAIL Must throw as Modifier has a recursive dictionary. promise_rejects_js: badDetails must cause acceptPromise to reject with expectedError function "function () { throw e }" threw object "SecurityError: show() must be triggered by user activation." ("SecurityError") expected instance of function "function TypeError() {
-    [native code]
-}" ("TypeError")
+PASS Rejection of detailsPromise must abort the update with an 'AbortError' DOMException.
+PASS Total in the update is a string, so converting to IDL must abort the update with a TypeError.
+PASS Total is recursive, so converting to IDL must abort the update with a TypeError.
+PASS Updating with a negative total results in a TypeError.
+PASS Updating with a displayItem with an invalid currency results in RangeError.
+PASS Updating with duplicate shippingOptions (same IDs) results in a TypeError.
+PASS Updating with a shippingOption with an invalid currency value results in a RangError.
+PASS Must throw a RangeError when a modifier's total item has an invalid currency.
+PASS Must throw a RangeError when a modifier display item has an invalid currency.
+PASS Must throw as Modifier has a recursive dictionary.
 

Modified: trunk/Source/WebCore/ChangeLog (281829 => 281830)


--- trunk/Source/WebCore/ChangeLog	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/Source/WebCore/ChangeLog	2021-08-31 23:07:44 UTC (rev 281830)
@@ -1,3 +1,15 @@
+2021-08-31  Marcos Caceres  <[email protected]>
+
+        [Payment Request] Calling PaymentRequest's show() should consume user activation
+        https://bugs.webkit.org/show_bug.cgi?id=217365
+
+        Reviewed by Youenn Fablet and Devin Rousso.
+
+        Tested by existing WPT tests.
+
+        * Modules/paymentrequest/PaymentRequest.cpp:
+        (WebCore::PaymentRequest::show):
+
 2021-08-31  Thibault Saunier  <[email protected]>
 
         [GStreamer] Fix deadlock tearing down pipeline when using fallback sink

Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp (281829 => 281830)


--- trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp	2021-08-31 22:59:26 UTC (rev 281829)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp	2021-08-31 23:07:44 UTC (rev 281830)
@@ -363,7 +363,7 @@
     ASSERT(!m_activePaymentHandler);
 }
 
-// https://www.w3.org/TR/payment-request/#show()-method
+// https://www.w3.org/TR/payment-request/#show-method
 void PaymentRequest::show(Document& document, RefPtr<DOMPromise>&& detailsPromise, ShowPromise&& promise)
 {
     if (!document.frame()) {
@@ -371,7 +371,8 @@
         return;
     }
 
-    if (!UserGestureIndicator::processingUserGesture()) {
+    auto* window = document.frame()->window();
+    if (!window || !window->consumeTransientActivation()) {
         promise.reject(Exception { SecurityError, "show() must be triggered by user activation." });
         return;
     }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to