| <!DOCTYPE html> <meta charset="utf-8" /> | |
| <title>Test for PaymentRequest.show(optional promise) method</title> | |
| <link | |
| rel="help" | |
| href="https://w3c.github.io/browser-payment-api/#dfn-payment-request-is-showing" | |
| /> | |
| <script src="/js-test-resources/ui-helper.js"></script> | |
| <script src="/resources/payment-request.js"></script> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <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 = 'resources/blank.html') { | |
| const iframe = document.createElement('iframe'); | |
| iframe.allowPaymentRequest = true; | |
| iframe.src = 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 = 'resources/blank.html') { | |
| 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] = await activateThen( | |
| () => { | |
| return [request1.show()] | |
| } | |
| ); | |
| const [showPromise2] = await activateThen( | |
| () => { | |
| return [request2.show()] | |
| } | |
| ); | |
| await promise_rejects_dom( | |
| t, | |
| 'AbortError', | |
| showPromise2, | |
| 'Attempting to show a second payment request must reject.', | |
| ); | |
| await request1.abort(); | |
| await promise_rejects_dom( | |
| 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_dom( | |
| 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 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 [windowRequest.show()]; | |
| } | |
| ); | |
| await promise_rejects_dom( | |
| t, | |
| 'AbortError', | |
| windowShowPromise, | |
| 'iframe is already showing a payment request', | |
| ); | |
| // Cleanup | |
| await iframeRequest.abort(); | |
| await promise_rejects_dom( | |
| t, | |
| 'AbortError', | |
| iframeWindow.DOMException, | |
| 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] = await activateThen( | |
| () => { | |
| return [windowRequest.show()]; | |
| } | |
| ); | |
| const [iframeShowPromise] = await activateThen( | |
| () => { | |
| return [iframeRequest.show()]; | |
| } | |
| ); | |
| await promise_rejects_dom( | |
| t, | |
| 'AbortError', | |
| iframeWindow.DOMException, | |
| iframeShowPromise, | |
| 'The top window is already showing a payment request.', | |
| ); | |
| // Cleanup | |
| await windowRequest.abort(); | |
| await promise_rejects_dom( | |
| 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] = await activateThen( | |
| () => { | |
| return [windowRequest.show()]; | |
| } | |
| ); | |
| const [ iframeShowPromise ] = await activateThen( | |
| async () => { | |
| return [iframeRequest.show()]; | |
| } | |
| ); | |
| await promise_rejects_dom( | |
| t, | |
| 'AbortError', | |
| iframeWindow.DOMException, | |
| iframeShowPromise, | |
| 'Expected iframeShowPromise to reject, request is already showing.', | |
| ); | |
| await windowRequest.abort(); | |
| await promise_rejects_dom( | |
| 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] = await activateThen( | |
| () => { | |
| return [iframeRequest.show()]; | |
| } | |
| ); | |
| const [windowShowPromise] = await activateThen( | |
| () => { | |
| return [windowRequest.show()]; | |
| } | |
| ); | |
| // 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 iframeRequest.abort(); | |
| await promise_rejects_dom( | |
| t, | |
| 'AbortError', | |
| iframeWindow.DOMException, | |
| 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 = 'resources/blank.html?abc=123'; | |
| await new Promise(resolve => (iframe.onload = resolve)); | |
| 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_dom( | |
| t, | |
| 'AbortError', | |
| showPromise, | |
| 'abort() was called'); | |
| }, "Navigating an iframe as a nested browsing context sets 'payment request is showing boolean' to false."); | |
| </script> | |
| </body> |