Title: [246863] trunk
Revision
246863
Author
[email protected]
Date
2019-06-26 17:39:29 -0700 (Wed, 26 Jun 2019)

Log Message

[Payment Request] Set state to Closed when show() is called during an active session
https://bugs.webkit.org/show_bug.cgi?id=199239

Reviewed by Alex Christensen.

Source/WebCore:

According to step 7 of https://w3c.github.io/payment-request/#show-method (as of 26 June
2019), when another payment request is showing, calling show() should set the [[state]]
internal slot to Closed in addition to rejecting the show promise with an AbortError. WebKit
was only doing the latter. Let's do both.

Test: http/tests/paymentrequest/payment-is-showing.https.html

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

LayoutTests:

Copied from web-platform-tests/payment-request/payment-is-showing.https.html and modified to
use UIHelper instead of test_driver.bless.

* http/tests/paymentrequest/payment-is-showing.https-expected.txt: Added.
* http/tests/paymentrequest/payment-is-showing.https.html: Added.
* http/tests/paymentrequest/resources/blank.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (246862 => 246863)


--- trunk/LayoutTests/ChangeLog	2019-06-27 00:36:51 UTC (rev 246862)
+++ trunk/LayoutTests/ChangeLog	2019-06-27 00:39:29 UTC (rev 246863)
@@ -1,3 +1,17 @@
+2019-06-26  Andy Estes  <[email protected]>
+
+        [Payment Request] Set state to Closed when show() is called during an active session
+        https://bugs.webkit.org/show_bug.cgi?id=199239
+
+        Reviewed by Alex Christensen.
+
+        Copied from web-platform-tests/payment-request/payment-is-showing.https.html and modified to
+        use UIHelper instead of test_driver.bless.
+
+        * http/tests/paymentrequest/payment-is-showing.https-expected.txt: Added.
+        * http/tests/paymentrequest/payment-is-showing.https.html: Added.
+        * http/tests/paymentrequest/resources/blank.html: Added.
+
 2019-06-26  Russell Epstein  <[email protected]>
 
         Layout Test http/tests/performance/performance-resource-timing-resourcetimingbufferfull-shrinking-buffer-crash.html is failing.

Added: trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https-expected.txt (0 => 246863)


--- trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https-expected.txt	2019-06-27 00:39:29 UTC (rev 246863)
@@ -0,0 +1,8 @@
+
+PASS The top browsing context can only show one payment sheet at a time. 
+PASS If an iframe shows a payment request, the top-level browsing context can't also show one. 
+PASS An iframe cannot show a payment request if the top-level window is already showing one. 
+PASS Given multiple nested browsing contexts, and window calls show() first, other nested browsing contexts can't show a request. 
+PASS Given multiple nested browsing contexts, and an iframe calls show() first, other nested browsing contexts can't show a request. 
+PASS Navigating an iframe as a nested browsing context sets 'payment request is showing boolean' to false. 
+

Added: trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https.html (0 => 246863)


--- trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-is-showing.https.html	2019-06-27 00:39:29 UTC (rev 246863)
@@ -0,0 +1,290 @@
+<!DOCTYPE html> <meta charset="utf-8" />
+<title>Test for PaymentRequest.show(optional promise) method</title>
+<link
+  rel="help"
+  href=""
+/>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<body>
+  <script>
+    'use strict';
+    const applePayMethod = {
+      supportedMethods: 'https://apple.com/apple-pay',
+      data: {
+        version: 3,
+        merchantIdentifier: 'merchant.com.example',
+        countryCode: 'US',
+        merchantCapabilities: ['supports3DS'],
+        supportedNetworks: ['visa'],
+      },
+    };
+    const methods = [{supportedMethods: 'basic-card'}, applePayMethod];
+    const details = {
+      total: {
+        label: 'Total',
+        amount: {
+          currency: 'USD',
+          value: '1.00',
+        },
+      },
+    };
+
+    /**
+     * Attaches an iframe to window.document.
+     *
+     * @param {String} src Optional resource URL to load.
+     * @returns {Promise} Resolves when the src loads.
+     */
+    async function attachIframe(src = '') {
+      const iframe = document.createElement('iframe');
+      iframe.allowPaymentRequest = true;
+      iframe.src = ""
+      document.body.appendChild(iframe);
+      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;
+    }
+
+    promise_test(async t => {
+      const request1 = new PaymentRequest(methods, details);
+      const request2 = new PaymentRequest(methods, details);
+
+      // 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()];
+        }
+      );
+      await promise_rejects(
+        t,
+        'AbortError',
+        showPromise2,
+        'Attempting to show a second payment request must reject.',
+      );
+
+      await request1.abort();
+      await promise_rejects(
+        t,
+        'AbortError',
+        showPromise1,
+        'request1 was aborted via .abort()',
+      );
+
+      // Finally, request2 should have been "closed", so trying to show
+      // it will again result in promise rejected with an InvalidStateError.
+      // See: https://github.com/w3c/payment-request/pull/821
+      const [rejectedPromise] = await activateThen(async () => { return [request2.show()] });
+      await promise_rejects(
+        t,
+        'InvalidStateError',
+        rejectedPromise,
+        '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',
+      );
+    }, 'The top browsing context can only show one payment sheet at a time.');
+
+    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);
+
+      // Let's get some blessed showPromises
+      const [iframeShowPromise, windowShowPromise] = await activateThen(
+        async () => {
+          // iframe sets "is showing boolean", ignore the returned promise.
+          // The top level window now tries to show() the payment request.
+          return [iframeRequest.show(), windowRequest.show()];
+        }
+      );
+
+      await promise_rejects(
+        t,
+        'AbortError',
+        windowShowPromise,
+        'iframe is already showing a payment request.',
+      );
+
+      // Cleanup
+      await iframeRequest.abort();
+      await promise_rejects(
+        t,
+        'AbortError',
+        iframeShowPromise,
+        'abort() was called');
+      iframe.remove();
+    }, "If an iframe shows a payment request, the top-level browsing context can't also show one.");
+
+    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 activateThen(
+        async () => {
+          return [windowRequest.show(), iframeRequest.show()];
+        }
+      );
+
+      await promise_rejects(
+        t,
+        'AbortError',
+        iframeShowPromise,
+        'The top window is already showing a payment request.',
+      );
+
+      // Cleanup
+      await windowRequest.abort();
+      await promise_rejects(
+        t,
+        'AbortError',
+        windowShowPromise,
+        'abort() was called');
+      iframe.remove();
+    }, 'An iframe cannot show a payment request if the top-level window is already showing one.');
+
+    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);
+
+      // 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(
+        async () => {
+          return [
+            windowRequest.show(),
+            iframeRequest.show(),
+          ];
+        }
+      );
+
+      await promise_rejects(
+        t,
+        'AbortError',
+        iframeShowPromise,
+        'Expected iframeShowPromise to reject, request is already showing.',
+      );
+
+      await windowRequest.abort();
+      await promise_rejects(
+        t,
+        'AbortError',
+        windowShowPromise,
+        'abort() was called');
+      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);
+
+      // 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(),
+          ];
+        }
+      );
+
+      // windowShowPromise and iframeRequest will both reject
+      await promise_rejects(
+        t,
+        'AbortError',
+        windowShowPromise,
+        'Expected windowShowPromise to reject, the popup is showing a payment request.',
+      );
+
+      await iframeRequest.abort();
+      await promise_rejects(
+        t,
+        'AbortError',
+        iframeShowPromise,
+        'abort() was called');
+      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] = await activateThen(
+        async () => { return [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(
+        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 activateThen(async () => { return [request.show()] });
+      await request.abort();
+      await promise_rejects(
+        t,
+        'AbortError',
+        showPromise,
+        'abort() was called');
+    }, "Navigating an iframe as a nested browsing context sets 'payment request is showing boolean' to false.");
+  </script>
+</body>

Added: trunk/LayoutTests/http/tests/paymentrequest/resources/blank.html (0 => 246863)


--- trunk/LayoutTests/http/tests/paymentrequest/resources/blank.html	                        (rev 0)
+++ trunk/LayoutTests/http/tests/paymentrequest/resources/blank.html	2019-06-27 00:39:29 UTC (rev 246863)
@@ -0,0 +1 @@
+<!DOCTYPE html> <meta charset="utf-8" />

Modified: trunk/Source/WebCore/ChangeLog (246862 => 246863)


--- trunk/Source/WebCore/ChangeLog	2019-06-27 00:36:51 UTC (rev 246862)
+++ trunk/Source/WebCore/ChangeLog	2019-06-27 00:39:29 UTC (rev 246863)
@@ -1,3 +1,20 @@
+2019-06-26  Andy Estes  <[email protected]>
+
+        [Payment Request] Set state to Closed when show() is called during an active session
+        https://bugs.webkit.org/show_bug.cgi?id=199239
+
+        Reviewed by Alex Christensen.
+
+        According to step 7 of https://w3c.github.io/payment-request/#show-method (as of 26 June
+        2019), when another payment request is showing, calling show() should set the [[state]]
+        internal slot to Closed in addition to rejecting the show promise with an AbortError. WebKit
+        was only doing the latter. Let's do both.
+
+        Test: http/tests/paymentrequest/payment-is-showing.https.html
+
+        * Modules/paymentrequest/PaymentRequest.cpp:
+        (WebCore::PaymentRequest::show):
+
 2019-06-26  Joseph Pecoraro  <[email protected]>
 
         Web Inspector: Implement console.countReset

Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp (246862 => 246863)


--- trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp	2019-06-27 00:36:51 UTC (rev 246862)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp	2019-06-27 00:39:29 UTC (rev 246863)
@@ -404,6 +404,7 @@
 
     if (PaymentHandler::hasActiveSession(document)) {
         promise.reject(Exception { AbortError });
+        m_state = State::Closed;
         return;
     }
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to