Diff
Modified: trunk/LayoutTests/ChangeLog (237596 => 237597)
--- trunk/LayoutTests/ChangeLog 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/ChangeLog 2018-10-30 19:07:40 UTC (rev 237597)
@@ -1,5 +1,29 @@
2018-10-30 Andy Estes <[email protected]>
+ [Payment Request] Implement PaymentResponse.retry()
+ https://bugs.webkit.org/show_bug.cgi?id=190985
+
+ Reviewed by Daniel Bates
+
+ * http/tests/paymentrequest/payment-address-attributes-and-toJSON-method.https.html:
+ * http/tests/paymentrequest/payment-response-complete-method.https.html:
+ * http/tests/paymentrequest/payment-response-methodName-attribute.https.html:
+ * http/tests/paymentrequest/payment-response-payerEmail-attribute.https.html:
+ * http/tests/paymentrequest/payment-response-payerName-attribute.https.html:
+ * http/tests/paymentrequest/payment-response-payerPhone-attribute.https.html:
+ * http/tests/paymentrequest/payment-response-rejects-if-not-active.https-expected.txt: Added.
+ * http/tests/paymentrequest/payment-response-rejects-if-not-active.https.html: Copied from
+ imported/w3c/web-platform-tests/payment-request/payment-response/rejects_if_not_active-manual.https.html
+ and automated using internals.mockPaymentCoordinator.
+ * http/tests/paymentrequest/payment-response-retry-method.https-expected.txt: Added.
+ * http/tests/paymentrequest/payment-response-retry-method.https.html: Copied from
+ imported/w3c/web-platform-tests/payment-request/payment-response/retry-method-manual.https.html
+ and automated using internals.mockPaymentCoordinator.
+ * http/tests/paymentrequest/resources/helpers.js:
+ (setUpAndSmokeTest):
+
+2018-10-30 Andy Estes <[email protected]>
+
[Apple Pay] PaymentRequest.canMakePayment() should resolve to true whenever Apple Pay is available
https://bugs.webkit.org/show_bug.cgi?id=191039
Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-address-attributes-and-toJSON-method.https.html (237596 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-address-attributes-and-toJSON-method.https.html 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-address-attributes-and-toJSON-method.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -10,6 +10,7 @@
<script src=""
<script src=""
<script>
+setUpAndSmokeTest({ explicit_done: true, explicit_timeout: true });
const options = { requestShipping: true };
function runTest(button, expected = {}) {
button.disabled = true;
Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-response-complete-method.https.html (237596 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-complete-method.https.html 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-complete-method.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -10,6 +10,7 @@
<script src=""
<script src=""
<script>
+setUpAndSmokeTest({ explicit_done: true, explicit_timeout: true });
async function runTest({ completeWith: result }, button) {
button.disabled = true;
const { response, request } = await getPaymentRequestResponse();
Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-response-methodName-attribute.https.html (237596 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-methodName-attribute.https.html 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-methodName-attribute.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -9,6 +9,9 @@
<script src=""
<script src=""
<script src=""
+<script>
+ setUpAndSmokeTest({ explicit_done: true, explicit_timeout: true });
+</script>
<ol>
<li>
<button id="button">
Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerEmail-attribute.https.html (237596 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerEmail-attribute.https.html 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerEmail-attribute.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -9,6 +9,9 @@
<script src=""
<script src=""
<script src=""
+<script>
+ setUpAndSmokeTest({ explicit_done: true, explicit_timeout: true });
+</script>
<ol>
<li>
<button id="button1">
Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerName-attribute.https.html (237596 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerName-attribute.https.html 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerName-attribute.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -9,6 +9,9 @@
<script src=""
<script src=""
<script src=""
+<script>
+ setUpAndSmokeTest({ explicit_done: true, explicit_timeout: true });
+</script>
<ol>
<li>
<button id="button1">
Modified: trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerPhone-attribute.https.html (237596 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerPhone-attribute.https.html 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-payerPhone-attribute.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -9,6 +9,9 @@
<script src=""
<script src=""
<script src=""
+<script>
+ setUpAndSmokeTest({ explicit_done: true, explicit_timeout: true });
+</script>
<ol>
<li>
<button id="button1">
Added: trunk/LayoutTests/http/tests/paymentrequest/payment-response-rejects-if-not-active.https-expected.txt (0 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-rejects-if-not-active.https-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-rejects-if-not-active.https-expected.txt 2018-10-30 19:07:40 UTC (rev 237597)
@@ -0,0 +1,5 @@
+
+PASS retry()'s retryPromise rejects if document is not fully active.
+PASS retry()'s retryPromise rejects if the document becomes not fully active.
+PASS complete()'s completePromise rejects if document is not fully active.
+
Added: trunk/LayoutTests/http/tests/paymentrequest/payment-response-rejects-if-not-active.https.html (0 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-rejects-if-not-active.https.html (rev 0)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-rejects-if-not-active.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<link rel="help" href=""
+<title>PaymentResponse retry() rejects if doc is not fully active</title>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<link rel="help" href=""
+<body>
+<script>
+setup({ explicit_done: true, explicit_timeout: true });
+const validMethods = Object.freeze([validPaymentMethod()]);
+const validAmount = Object.freeze({
+ currency: "USD",
+ value: "5.00",
+});
+const validTotal = Object.freeze({
+ label: "Total due",
+ amount: validAmount,
+});
+const validDetails = Object.freeze({
+ total: validTotal,
+});
+
+function getLoadedPaymentResponse(iframe, url) {
+ return new Promise(resolve => {
+ iframe.addEventListener(
+ "load",
+ () => {
+ const { PaymentRequest } = iframe.contentWindow;
+ activateThen(async () => {
+ const request = new PaymentRequest(validMethods, validDetails);
+ const acceptPromise = request.show();
+ internals.mockPaymentCoordinator.acceptPayment();
+ const response = await acceptPromise;
+ resolve(response);
+ });
+ },
+ { once: true }
+ );
+ iframe.src = ""
+ });
+}
+
+function methodNotFullyActive(text, methodName, ...args) {
+ promise_test(async t => {
+ const iframe = document.createElement("iframe");
+ iframe.allowPaymentRequest = true;
+ document.body.appendChild(iframe);
+
+ // We first got to page1.html, grab a PaymentResponse instance.
+ const response = await getLoadedPaymentResponse(
+ iframe,
+ "/payment-request/resources/page1.html"
+ );
+ // We navigate the iframe again, putting response's document into an inactive state.
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve);
+ iframe.src = ""
+ });
+ // Now, response's relevant global object's document is no longer active.
+ // So, promise needs to reject appropriately.
+ const promise = response[methodName](...args);
+ await promise_rejects(
+ t,
+ "AbortError",
+ promise,
+ "Inactive document, so must throw AbortError"
+ );
+ // We are done, so clean up.
+ iframe.remove();
+ }, text);
+}
+
+function methodBecomesNotFullyActive(text, methodName, ...args) {
+ promise_test(async t => {
+ const iframe = document.createElement("iframe");
+ iframe.allowPaymentRequest = true;
+ document.body.appendChild(iframe);
+
+ // We first got to page1.html, grab a PaymentResponse instance.
+ const response = await getLoadedPaymentResponse(
+ iframe,
+ "/payment-request/resources/page1.html"
+ );
+
+ // we get the promise from page1.html, while it's active!
+ const promise = response[methodName](...args);
+
+ // We navigate the iframe again, putting response's document into an inactive state.
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve);
+ iframe.src = ""
+ });
+
+ // Now, response's relevant global object's document is no longer active.
+ // So, promise needs to reject appropriately.
+ await promise_rejects(
+ t,
+ "AbortError",
+ promise,
+ "Inactive document, so must throw AbortError"
+ );
+ // We are done, so clean up.
+ iframe.remove();
+ }, text);
+}
+
+window.addEventListener("load", async () => {
+ await methodNotFullyActive("retry()'s retryPromise rejects if document is not fully active.", 'retry', {});
+ await methodBecomesNotFullyActive("retry()'s retryPromise rejects if the document becomes not fully active.", 'retry', {});
+ await methodNotFullyActive("complete()'s completePromise rejects if document is not fully active.", 'complete', 'success');
+ done();
+});
+</script>
Added: trunk/LayoutTests/http/tests/paymentrequest/payment-response-retry-method.https-expected.txt (0 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-retry-method.https-expected.txt (rev 0)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-retry-method.https-expected.txt 2018-10-30 19:07:40 UTC (rev 237597)
@@ -0,0 +1,13 @@
+
+PASS Can construct a payment request (smoke test).
+PASS PaymentResponse.prototype must have a retry() function (smoke test).
+PASS A completed payment request cannot be retried.
+PASS Calling retry() more than once yield a rejected promise, but the retryPromise resolves independently.
+PASS Calling complete() while a retry() is in progress results in an InvalidStateError.
+PASS Trying to abort() via PaymentRequest is not possible.
+PASS It's possible to retry() multiple times and eventually complete(). After complete(), however, retry() rejects with an InvalidStateError.
+PASS The user aborting retrying a payment causes the retryPromise to reject with AbortError. Aborting a payment is causes it complete.
+PASS When retrying, the user is shown error fields to fix.
+PASS When "abort the update" occurs because of an update error, the `retryPromise` is rejected and response.[[complete]] becomes true.
+PASS Calling retry() multiple times is always a new object.
+
Added: trunk/LayoutTests/http/tests/paymentrequest/payment-response-retry-method.https.html (0 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/payment-response-retry-method.https.html (rev 0)
+++ trunk/LayoutTests/http/tests/paymentrequest/payment-response-retry-method.https.html 2018-10-30 19:07:40 UTC (rev 237597)
@@ -0,0 +1,188 @@
+<!doctype html>
+<meta charset="utf8">
+<link rel="help" href=""
+<title>
+ PaymentResponse.prototype.retry() method
+</title>
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script src=""
+<script>
+ setUpAndSmokeTest({ explicit_timeout: true });
+</script>
+<body>
+<script>
+test(() => {
+ assert_true(
+ "retry" in PaymentResponse.prototype,
+ "retry must be in prototype"
+ );
+ assert_true(
+ PaymentResponse.prototype.retry instanceof Function,
+ "retry must be a function"
+ );
+}, "PaymentResponse.prototype must have a retry() function (smoke test).");
+
+promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ // sets response.[[complete]] to true.
+ await response.complete("success");
+ return promise_rejects(
+ t,
+ "InvalidStateError",
+ response.retry({}),
+ "response.[[complete]] is true, so rejects with InvalidStateError."
+ );
+}, "A completed payment request cannot be retried.");
+
+promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ const retryPromise = response.retry({});
+ await promise_rejects(
+ t,
+ "InvalidStateError",
+ response.retry({}),
+ "Calling retry() again rejects with an InvalidStateError"
+ );
+ internals.mockPaymentCoordinator.acceptPayment();
+ await retryPromise;
+ await response.complete("success");
+}, "Calling retry() more than once yield a rejected promise, but the retryPromise resolves independently.");
+
+promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ const retryPromise = response.retry({});
+ await promise_rejects(
+ t,
+ "InvalidStateError",
+ response.complete("success"),
+ "Calling complete() while retrying rejects with an InvalidStateError"
+ );
+ internals.mockPaymentCoordinator.acceptPayment();
+ await retryPromise;
+ await response.complete("success");
+}, "Calling complete() while a retry() is in progress results in an InvalidStateError.");
+
+promise_test(async t => {
+ const { response, request } = await getPaymentRequestResponse();
+ const retryPromise = response.retry({});
+ await promise_rejects(
+ t,
+ "InvalidStateError",
+ request.abort(),
+ "Calling request.abort() while retrying rejects with an InvalidStateError"
+ );
+ internals.mockPaymentCoordinator.acceptPayment();
+ await retryPromise;
+ await response.complete("success");
+}, "Trying to abort() via PaymentRequest is not possible.");
+
+promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ var promise = response.retry({});
+ internals.mockPaymentCoordinator.acceptPayment();
+ assert_equals(
+ await promise,
+ undefined,
+ "Expected undefined as the resolve value"
+ );
+ promise = response.retry({});
+ internals.mockPaymentCoordinator.acceptPayment();
+ assert_equals(
+ await promise,
+ undefined,
+ "Expected undefined as the resolve value"
+ );
+ await response.complete("success");
+ await promise_rejects(
+ t,
+ "InvalidStateError",
+ response.retry({}),
+ "Calling retry() after complete() rejects with a InvalidStateError"
+ );
+}, "It's possible to retry() multiple times and eventually complete(). After complete(), however, retry() rejects with an InvalidStateError.");
+
+promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ var promise = response.retry({});
+ internals.mockPaymentCoordinator.cancelPayment();
+ await promise_rejects(
+ t,
+ "AbortError",
+ promise,
+ "The user aborting a retry rejects with a AbortError"
+ );
+ await promise_rejects(
+ t,
+ "InvalidStateError",
+ response.retry({}),
+ "After the user aborts, response [[complete]] is true so retry() must reject with InvalidStateError"
+ );
+ await promise_rejects(
+ t,
+ "InvalidStateError",
+ response.complete("success"),
+ "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
+ );
+}, "The user aborting retrying a payment causes the retryPromise to reject with AbortError. Aborting a payment is causes it complete.");
+
+promise_test(async t => {
+ const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
+ const retryPromise = response.retry({
+ shippingAddress: { city: "Invalid city", addressLine: "Invalid address line" },
+ });
+ const errors = internals.mockPaymentCoordinator.errors;
+ assert_equals(errors.length, 2, "Must have two errors");
+ assert_equals(errors[0].code, "shippingContactInvalid", "Must be a 'shippingContactInvalid' error");
+ assert_equals(errors[0].message, "Invalid address line", "Error message must match");
+ assert_equals(errors[0].contactField, "addressLines", "Contact field must match");
+ assert_equals(errors[1].code, "shippingContactInvalid", "Must be a 'shippingContactInvalid' error");
+ assert_equals(errors[1].message, "Invalid city", "Error message must match");
+ assert_equals(errors[1].contactField, "locality", "Contact field must match");
+ internals.mockPaymentCoordinator.acceptPayment();
+ await retryPromise;
+ await response.complete("success");
+}, "When retrying, the user is shown error fields to fix.");
+
+promise_test(async t => {
+ const { response, request } = await getPaymentRequestResponse({ requestShipping: true });
+ // causes "abort the update" to run
+ const shippingChangedPromise = new Promise(resolve => {
+ request._onshippingoptionchange_ = () => {
+ event.updateWith({ total: { amount: { currency: "USD", value: "INVALID" } }});
+ resolve();
+ };
+ });
+ const retryPromise = response.retry({});
+ internals.mockPaymentCoordinator.changeShippingOption("option");
+ await shippingChangedPromise;
+ await promise_rejects(
+ t,
+ new TypeError(),
+ retryPromise,
+ "retry() aborts with a TypeError, because totals' value is invalid"
+ );
+ await promise_rejects(
+ t,
+ "InvalidStateError",
+ response.complete("success"),
+ "After the user aborts, response [[complete]] is true, so complete() rejects with a InvalidStateError"
+ );
+}, 'When "abort the update" occurs because of an update error, the `retryPromise` is rejected and response.[[complete]] becomes true.');
+
+promise_test(async t => {
+ const { response } = await getPaymentRequestResponse();
+ const retryPromise = response.retry({});
+ const promises = new Set([
+ retryPromise,
+ response.retry({}).catch(() => {}),
+ response.retry({}).catch(() => {}),
+ ]);
+ assert_equals(promises.size, 3, "Must have three unique objects");
+ internals.mockPaymentCoordinator.acceptPayment();
+ await retryPromise;
+ await response.complete();
+}, "Calling retry() multiple times is always a new object.");
+</script>
Modified: trunk/LayoutTests/http/tests/paymentrequest/resources/helpers.js (237596 => 237597)
--- trunk/LayoutTests/http/tests/paymentrequest/resources/helpers.js 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/LayoutTests/http/tests/paymentrequest/resources/helpers.js 2018-10-30 19:07:40 UTC (rev 237597)
@@ -1,5 +1,3 @@
-setup({ explicit_done: true, explicit_timeout: true });
-
const validMethod = Object.freeze({
supportedMethods: "https://apple.com/apple-pay",
data: {
@@ -22,18 +20,23 @@
label: "Valid total",
amount: validAmount,
});
+
const validDetails = {
total: validTotal,
};
-test(() => {
- try {
- new PaymentRequest(validMethods, validDetails);
- } catch (err) {
- done();
- throw err;
- }
-}, "Can construct a payment request (smoke test).");
+function setUpAndSmokeTest(options)
+{
+ setup(options);
+ test(() => {
+ try {
+ new PaymentRequest(validMethods, validDetails);
+ } catch (err) {
+ done();
+ throw err;
+ }
+ }, "Can construct a payment request (smoke test).");
+}
/**
* Pops up a payment sheet, allowing options to be
Modified: trunk/Source/WebCore/ChangeLog (237596 => 237597)
--- trunk/Source/WebCore/ChangeLog 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/ChangeLog 2018-10-30 19:07:40 UTC (rev 237597)
@@ -1,3 +1,98 @@
+2018-10-30 Andy Estes <[email protected]>
+
+ [Payment Request] Implement PaymentResponse.retry()
+ https://bugs.webkit.org/show_bug.cgi?id=190985
+
+ Reviewed by Daniel Bates.
+
+ Implemented the retry() method on PaymentResponse as specified in the Payment Request API
+ W3C Editor's Draft of 24 October 2018.
+
+ See https://w3c.github.io/payment-request/#retry-method for details.
+
+ Tests: http/tests/paymentrequest/payment-response-rejects-if-not-active.https.html
+ http/tests/paymentrequest/payment-response-retry-method.https.html
+
+ * Modules/applepay/PaymentCoordinator.h:
+ (WebCore::PaymentCoordinator::client): Added. Returns m_client.
+ * Modules/applepay/PaymentCoordinatorClient.h:
+ (WebCore::PaymentCoordinatorClient::isMockPaymentCoordinator const): Added. Used to downcast
+ a PaymentCoordinatorClient to a MockPaymentCoordinator.
+ * Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp:
+ (WebCore::ApplePayPaymentHandler::computeTotalAndLineItems const): Made const.
+ (WebCore::ApplePayPaymentHandler::computeErrors const): Broke this function into
+ computeAddressErrors, computePayerErrors, and computePaymentMethodErrors, then modified this
+ function to call those functions. Exceptions thrown by computePaymentMethodErrors are ignored.
+ (WebCore::ApplePayPaymentHandler::computeAddressErrors const): Added.
+ (WebCore::ApplePayPaymentHandler::computePayerErrors const): Added.
+ (WebCore::ApplePayPaymentHandler::computePaymentMethodErrors const): Added.
+ (WebCore::ApplePayPaymentHandler::complete): Added ASSERTs to verify whether result is a
+ final result.
+ (WebCore::ApplePayPaymentHandler::retry): Computed PaymentErrors from PaymentValidationErrors,
+ ensured the PaymentAuthorizationResult was non-final by adding an unknown error if necessary,
+ and called PaymentCoordinator::completePaymentSession.
+ * Modules/applepay/paymentrequest/ApplePayPaymentHandler.h:
+ * Modules/paymentrequest/PaymentHandler.h:
+ * Modules/paymentrequest/PaymentRequest.cpp:
+ (WebCore::PaymentRequest::show): Changed to call settleShowPromise.
+ (WebCore::PaymentRequest::abortWithException): Changed to abort PaymentResponse's retry
+ promise, if present, instead of PaymentResponse's show promise.
+ (WebCore::PaymentRequest::settleShowPromise): Added. Settles m_showPromise then sets it to
+ std::nullopt.
+ (WebCore::PaymentRequest::closeActivePaymentHandler): Added. Hides the active payment
+ handler then sets it to std::nullopt.
+ (WebCore::PaymentRequest::stop): Stopped calling abortWithException, since that function
+ might settle PaymentResponse's retry promise. PaymentResponse is now an ActiveDOMObject and
+ will settle its own promise in its implementation of stop.
+ (WebCore::PaymentRequest::abort): Changed to throw an InvalidStateError if there is a
+ pending retry promise and to call abortWithException instead of stop.
+ (WebCore::PaymentRequest::completeMerchantValidation): Changed to call abortWithException
+ instead of stop.
+ (WebCore::PaymentRequest::settleDetailsPromise): Ditto.
+ (WebCore::PaymentRequest::accept): Updated the existing PaymentResponse, if present, rather
+ than creating a new one. Settled the existing PaymentResponse's retry promise, if present,
+ rather than the show promise.
+ (WebCore::PaymentRequest::complete): Changed to throw an AbortError if there is no longer an
+ active payment handler.
+ (WebCore::PaymentRequest::retry): Changed to throw an AbortError if there is no longer an
+ active payment handler, and to call PaymentHandler::retry if there is.
+ (WebCore::PaymentRequest::cancel): Changed to call abortWithException instead of stop.
+ * Modules/paymentrequest/PaymentRequest.h:
+ * Modules/paymentrequest/PaymentRequest.idl:
+ * Modules/paymentrequest/PaymentResponse.cpp:
+ (WebCore::PaymentResponse::PaymentResponse):
+ (WebCore::PaymentResponse::finishConstruction): Pending activities create strong references
+ to |this|, so they cannot be created in constructors without relaxing adoption requirements.
+ Added this function so that the pending activity can be created after the PaymentResponse is
+ created and adopted.
+ (WebCore::PaymentResponse::~PaymentResponse):
+ (WebCore::PaymentResponse::complete): Updated to throw an AbortError or InvalidStateError
+ when necessary.
+ (WebCore::PaymentResponse::retry): Implemented. Throws an AbortError or InvalidStateError
+ when necessary, otherwise calls PaymentRequest::retry and stores the retry promise in
+ m_retryPromise.
+ (WebCore::PaymentResponse::abortWithException): Added. Rejects the retry promise with
+ |exception|, clears the pending activity, and sets m_state to Completed.
+ (WebCore::PaymentResponse::settleRetryPromise): Added. Settles the retry promise and sets it
+ to std::nullopt.
+ (WebCore::PaymentResponse::canSuspendForDocumentSuspension const): Added. Returns true if
+ there is no pending activity.
+ (WebCore::PaymentResponse::stop): Added. Rejects the retry promise with AbortError, clears
+ the pending activity, and sets m_state to Stopped.
+ * Modules/paymentrequest/PaymentResponse.h: Changed create to call finishConstruction and
+ made PaymentResponse an ActiveDOMObject instead of a ContextDestructionObserver.
+ * testing/Internals.cpp:
+ (WebCore::Internals::Internals): Changed to only create a MockPaymentCoordinator for main frames.
+ (WebCore::Internals::mockPaymentCoordinator): Changed to get the MockPaymentCoordinator by
+ downcasting the page's payment coordinator client.
+ * testing/Internals.h:
+ * testing/Internals.idl:
+ * testing/MockPaymentCoordinator.cpp:
+ (WebCore::MockPaymentCoordinator::completePaymentSession): Changed to only increment
+ hideCount for final results.
+ * testing/MockPaymentCoordinator.h:
+ (isType): Added so that PaymentCoordinatorClients can be downcasted to MockPaymentCoordinators.
+
2018-10-30 Zalan Bujtas <[email protected]>
[iOS] Use the mainframe view size to compute window.outerWidth/height.
Modified: trunk/Source/WebCore/Modules/applepay/PaymentCoordinator.h (237596 => 237597)
--- trunk/Source/WebCore/Modules/applepay/PaymentCoordinator.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/applepay/PaymentCoordinator.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -50,6 +50,8 @@
WEBCORE_EXPORT explicit PaymentCoordinator(PaymentCoordinatorClient&);
WEBCORE_EXPORT ~PaymentCoordinator();
+ PaymentCoordinatorClient& client() { return m_client; }
+
bool supportsVersion(unsigned version) const;
bool canMakePayments();
void canMakePaymentsWithActiveCard(const String& merchantIdentifier, const String& domainName, WTF::Function<void (bool)>&& completionHandler);
Modified: trunk/Source/WebCore/Modules/applepay/PaymentCoordinatorClient.h (237596 => 237597)
--- trunk/Source/WebCore/Modules/applepay/PaymentCoordinatorClient.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/applepay/PaymentCoordinatorClient.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -58,6 +58,8 @@
virtual void cancelPaymentSession() = 0;
virtual void paymentCoordinatorDestroyed() = 0;
+ virtual bool isMockPaymentCoordinator() const { return false; }
+
protected:
virtual ~PaymentCoordinatorClient() = default;
};
Modified: trunk/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp (237596 => 237597)
--- trunk/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.cpp 2018-10-30 19:07:40 UTC (rev 237597)
@@ -53,6 +53,7 @@
#include "PaymentMethod.h"
#include "PaymentRequestValidator.h"
#include "PaymentResponse.h"
+#include "PaymentValidationErrors.h"
#include "Settings.h"
namespace WebCore {
@@ -249,7 +250,7 @@
return WTFMove(shippingMethods);
}
-ExceptionOr<ApplePaySessionPaymentRequest::TotalAndLineItems> ApplePayPaymentHandler::computeTotalAndLineItems()
+ExceptionOr<ApplePaySessionPaymentRequest::TotalAndLineItems> ApplePayPaymentHandler::computeTotalAndLineItems() const
{
auto& details = m_paymentRequest->paymentDetails();
String currency = details.total.amount.currency;
@@ -322,21 +323,43 @@
{
Vector<PaymentError> errors;
+ if (m_paymentRequest->paymentDetails().shippingOptions.isEmpty())
+ computeAddressErrors(WTFMove(error), WTFMove(addressErrors), errors);
+
+ computePayerErrors(WTFMove(payerErrors), errors);
+
+ auto scope = DECLARE_CATCH_SCOPE(scriptExecutionContext()->vm());
+ auto exception = computePaymentMethodErrors(paymentMethodErrors, errors);
+ if (exception.hasException()) {
+ ASSERT(scope.exception());
+ scope.clearException();
+ }
+
+ return errors;
+}
+
+void ApplePayPaymentHandler::computeAddressErrors(String&& error, AddressErrors&& addressErrors, Vector<PaymentError>& errors) const
+{
+ if (!m_paymentRequest->paymentOptions().requestShipping)
+ return;
+
+ using ContactField = PaymentError::ContactField;
+ appendShippingContactInvalidError(WTFMove(error), std::nullopt, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.addressLine), ContactField::AddressLines, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.city), ContactField::Locality, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.country), ContactField::Country, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.dependentLocality), ContactField::SubLocality, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.phone), ContactField::PhoneNumber, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.postalCode), ContactField::PostalCode, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.recipient), ContactField::Name, errors);
+ appendShippingContactInvalidError(WTFMove(addressErrors.region), ContactField::AdministrativeArea, errors);
+}
+
+void ApplePayPaymentHandler::computePayerErrors(PayerErrorFields&& payerErrors, Vector<PaymentError>& errors) const
+{
auto& options = m_paymentRequest->paymentOptions();
using ContactField = PaymentError::ContactField;
- if (options.requestShipping && m_paymentRequest->paymentDetails().shippingOptions.isEmpty()) {
- appendShippingContactInvalidError(WTFMove(error), std::nullopt, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.addressLine), ContactField::AddressLines, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.city), ContactField::Locality, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.country), ContactField::Country, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.dependentLocality), ContactField::SubLocality, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.phone), ContactField::PhoneNumber, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.postalCode), ContactField::PostalCode, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.recipient), ContactField::Name, errors);
- appendShippingContactInvalidError(WTFMove(addressErrors.region), ContactField::AdministrativeArea, errors);
- }
-
if (options.requestPayerName)
appendShippingContactInvalidError(WTFMove(payerErrors.name), ContactField::Name, errors);
@@ -345,25 +368,29 @@
if (options.requestPayerPhone)
appendShippingContactInvalidError(WTFMove(payerErrors.phone), ContactField::PhoneNumber, errors);
+}
+ExceptionOr<void> ApplePayPaymentHandler::computePaymentMethodErrors(JSC::JSObject* paymentMethodErrors, Vector<PaymentError>& errors) const
+{
+ if (!paymentMethodErrors)
+ return { };
+
#if ENABLE(APPLE_PAY_SESSION_V3)
- if (paymentMethodErrors) {
- auto& context = *scriptExecutionContext();
- auto catchScope = DECLARE_CATCH_SCOPE(context.vm());
- auto applePayErrors = convert<IDLSequence<IDLInterface<ApplePayError>>>(*context.execState(), paymentMethodErrors);
- if (!catchScope.exception()) {
- for (auto& applePayError : applePayErrors) {
- if (applePayError)
- errors.append({ applePayError->code(), applePayError->message(), applePayError->contactField() });
- }
- } else
- catchScope.clearException();
+ auto& context = *scriptExecutionContext();
+ auto throwScope = DECLARE_THROW_SCOPE(context.vm());
+ auto applePayErrors = convert<IDLSequence<IDLInterface<ApplePayError>>>(*context.execState(), paymentMethodErrors);
+ if (throwScope.exception())
+ return Exception { ExistingExceptionError };
+
+ for (auto& applePayError : applePayErrors) {
+ if (applePayError)
+ errors.append({ applePayError->code(), applePayError->message(), applePayError->contactField() });
}
#else
- UNUSED_PARAM(paymentMethodErrors);
+ UNUSED_PARAM(errors);
#endif
- return errors;
+ return { };
}
ExceptionOr<void> ApplePayPaymentHandler::detailsUpdated(PaymentRequest::UpdateReason reason, String&& error, AddressErrors&& addressErrors, PayerErrorFields&& payerErrors, JSC::JSObject* paymentMethodErrors)
@@ -458,6 +485,7 @@
void ApplePayPaymentHandler::complete(std::optional<PaymentComplete>&& result)
{
if (!result) {
+ ASSERT(isFinalStateResult(std::nullopt));
paymentCoordinator().completePaymentSession(std::nullopt);
return;
}
@@ -473,9 +501,31 @@
break;
}
+ ASSERT(isFinalStateResult(authorizationResult));
paymentCoordinator().completePaymentSession(WTFMove(authorizationResult));
}
+ExceptionOr<void> ApplePayPaymentHandler::retry(PaymentValidationErrors&& validationErrors)
+{
+ Vector<PaymentError> errors;
+
+ computeAddressErrors(WTFMove(validationErrors.error), WTFMove(validationErrors.shippingAddress), errors);
+ computePayerErrors(WTFMove(validationErrors.payer), errors);
+
+ auto exception = computePaymentMethodErrors(validationErrors.paymentMethod.get(), errors);
+ if (exception.hasException())
+ return exception.releaseException();
+
+ // Ensure there is always at least one error to avoid having a final result.
+ if (errors.isEmpty())
+ errors.append({ PaymentError::Code::Unknown, { }, std::nullopt });
+
+ PaymentAuthorizationResult authorizationResult { PaymentAuthorizationStatus::Failure, WTFMove(errors) };
+ ASSERT(!isFinalStateResult(authorizationResult));
+ paymentCoordinator().completePaymentSession(WTFMove(authorizationResult));
+ return { };
+}
+
unsigned ApplePayPaymentHandler::version() const
{
if (!m_applePayRequest)
Modified: trunk/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.h (237596 => 237597)
--- trunk/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/applepay/paymentrequest/ApplePayPaymentHandler.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -53,8 +53,11 @@
PaymentCoordinator& paymentCoordinator() const;
ExceptionOr<Vector<ApplePaySessionPaymentRequest::ShippingMethod>> computeShippingMethods();
- ExceptionOr<ApplePaySessionPaymentRequest::TotalAndLineItems> computeTotalAndLineItems();
+ ExceptionOr<ApplePaySessionPaymentRequest::TotalAndLineItems> computeTotalAndLineItems() const;
Vector<PaymentError> computeErrors(String&& error, AddressErrors&&, PayerErrorFields&&, JSC::JSObject* paymentMethodErrors) const;
+ void computeAddressErrors(String&& error, AddressErrors&&, Vector<PaymentError>&) const;
+ void computePayerErrors(PayerErrorFields&&, Vector<PaymentError>&) const;
+ ExceptionOr<void> computePaymentMethodErrors(JSC::JSObject* paymentMethodErrors, Vector<PaymentError>&) const;
ExceptionOr<void> shippingAddressUpdated(Vector<PaymentError>&& errors);
ExceptionOr<void> shippingOptionUpdated();
@@ -68,6 +71,7 @@
ExceptionOr<void> detailsUpdated(PaymentRequest::UpdateReason, String&& error, AddressErrors&&, PayerErrorFields&&, JSC::JSObject* paymentMethodErrors) final;
ExceptionOr<void> merchantValidationCompleted(JSC::JSValue&&) final;
void complete(std::optional<PaymentComplete>&&) final;
+ ExceptionOr<void> retry(PaymentValidationErrors&&) final;
// PaymentSession
unsigned version() const final;
Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentHandler.h (237596 => 237597)
--- trunk/Source/WebCore/Modules/paymentrequest/PaymentHandler.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentHandler.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -40,6 +40,7 @@
class Document;
struct AddressErrors;
struct PayerErrorFields;
+struct PaymentValidationErrors;
class PaymentHandler : public virtual PaymentSessionBase {
public:
@@ -54,6 +55,7 @@
virtual ExceptionOr<void> detailsUpdated(PaymentRequest::UpdateReason, String&& error, AddressErrors&&, PayerErrorFields&&, JSC::JSObject* paymentMethodErrors) = 0;
virtual ExceptionOr<void> merchantValidationCompleted(JSC::JSValue&&) = 0;
virtual void complete(std::optional<PaymentComplete>&&) = 0;
+ virtual ExceptionOr<void> retry(PaymentValidationErrors&&) = 0;
};
} // namespace WebCore
Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp (237596 => 237597)
--- trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.cpp 2018-10-30 19:07:40 UTC (rev 237597)
@@ -42,6 +42,7 @@
#include "PaymentMethodData.h"
#include "PaymentOptions.h"
#include "PaymentRequestUpdateEvent.h"
+#include "PaymentValidationErrors.h"
#include "ScriptController.h"
#include <_javascript_Core/JSONObject.h>
#include <_javascript_Core/ThrowScope.h>
@@ -404,7 +405,7 @@
for (auto& paymentMethod : m_serializedMethodData) {
auto data = "" paymentMethod.serializedData);
if (data.hasException()) {
- m_showPromise->reject(data.releaseException());
+ settleShowPromise(data.releaseException());
return;
}
@@ -414,7 +415,7 @@
auto result = handler->convertData(data.releaseReturnValue());
if (result.hasException()) {
- m_showPromise->reject(result.releaseException());
+ settleShowPromise(result.releaseException());
return;
}
@@ -423,13 +424,13 @@
}
if (!selectedPaymentHandler) {
- m_showPromise->reject(Exception { NotSupportedError });
+ settleShowPromise(Exception { NotSupportedError });
return;
}
auto exception = selectedPaymentHandler->show();
if (exception.hasException()) {
- m_showPromise->reject(exception.releaseException());
+ settleShowPromise(exception.releaseException());
return;
}
@@ -445,32 +446,51 @@
void PaymentRequest::abortWithException(Exception&& exception)
{
- if (m_state != State::Interactive)
- return;
+ ASSERT(m_state == State::Interactive);
+ closeActivePaymentHandler();
- if (auto paymentHandler = activePaymentHandler())
- paymentHandler->hide();
- m_activePaymentHandler = std::nullopt;
+ if (m_response)
+ m_response->abortWithException(WTFMove(exception));
+ else
+ settleShowPromise(WTFMove(exception));
+}
- ASSERT(m_state == State::Interactive);
+void PaymentRequest::settleShowPromise(ExceptionOr<PaymentResponse&>&& result)
+{
+ if (auto showPromise = std::exchange(m_showPromise, std::nullopt))
+ showPromise->settle(WTFMove(result));
+}
+
+void PaymentRequest::closeActivePaymentHandler()
+{
+ if (auto activePaymentHandler = std::exchange(m_activePaymentHandler, std::nullopt))
+ activePaymentHandler->paymentHandler->hide();
+
+ m_isUpdating = false;
m_state = State::Closed;
- m_showPromise->reject(WTFMove(exception));
}
void PaymentRequest::stop()
{
- abortWithException(Exception { AbortError });
+ closeActivePaymentHandler();
+ settleShowPromise(Exception { AbortError });
}
// https://www.w3.org/TR/payment-request/#abort()-method
-ExceptionOr<void> PaymentRequest::abort(AbortPromise&& promise)
+void PaymentRequest::abort(AbortPromise&& promise)
{
- if (m_state != State::Interactive)
- return Exception { InvalidStateError };
+ if (m_response && m_response->hasRetryPromise()) {
+ promise.reject(Exception { InvalidStateError });
+ return;
+ }
- stop();
+ if (m_state != State::Interactive) {
+ promise.reject(Exception { InvalidStateError });
+ return;
+ }
+
+ abortWithException(Exception { AbortError });
promise.resolve();
- return { };
}
// https://www.w3.org/TR/payment-request/#canmakepayment()-method
@@ -587,7 +607,7 @@
return;
if (m_merchantSessionPromise->status() == DOMPromise::Status::Rejected) {
- stop();
+ abortWithException(Exception { AbortError });
return;
}
@@ -613,7 +633,7 @@
return;
if (m_isCancelPending || m_detailsPromise->status() == DOMPromise::Status::Rejected) {
- stop();
+ abortWithException(Exception { AbortError });
return;
}
@@ -679,42 +699,55 @@
void PaymentRequest::accept(const String& methodName, PaymentResponse::DetailsFunction&& detailsFunction, Ref<PaymentAddress>&& shippingAddress, const String& payerName, const String& payerEmail, const String& payerPhone)
{
+ ASSERT(!m_isUpdating);
ASSERT(m_state == State::Interactive);
- auto response = PaymentResponse::create(scriptExecutionContext(), *this, WTFMove(detailsFunction));
- response->setRequestId(m_details.id);
- response->setMethodName(methodName);
-
- if (m_options.requestShipping) {
- response->setShippingAddress(shippingAddress.ptr());
- response->setShippingOption(m_shippingOption);
+ bool isRetry = m_response;
+ if (!isRetry) {
+ m_response = PaymentResponse::create(scriptExecutionContext(), *this, WTFMove(detailsFunction));
+ m_response->setRequestId(m_details.id);
}
- if (m_options.requestPayerName)
- response->setPayerName(payerName);
+ m_response->setMethodName(methodName);
+ m_response->setShippingAddress(m_options.requestShipping ? shippingAddress.ptr() : nullptr);
+ m_response->setShippingOption(m_options.requestShipping ? m_shippingOption : String { });
+ m_response->setPayerName(m_options.requestPayerName ? payerName : String { });
+ m_response->setPayerEmail(m_options.requestPayerEmail ? payerEmail : String { });
+ m_response->setPayerPhone(m_options.requestPayerPhone ? payerPhone : String { });
- if (m_options.requestPayerEmail)
- response->setPayerEmail(payerEmail);
+ if (!isRetry)
+ settleShowPromise(*m_response);
+ else {
+ ASSERT(m_response->hasRetryPromise());
+ m_response->settleRetryPromise();
+ }
- if (m_options.requestPayerPhone)
- response->setPayerPhone(payerPhone);
-
- m_showPromise->resolve(response.get());
m_state = State::Closed;
}
-void PaymentRequest::complete(std::optional<PaymentComplete>&& result)
+ExceptionOr<void> PaymentRequest::complete(std::optional<PaymentComplete>&& result)
{
ASSERT(m_state == State::Closed);
+ if (!m_activePaymentHandler)
+ return Exception { AbortError };
+
activePaymentHandler()->complete(WTFMove(result));
m_activePaymentHandler = std::nullopt;
+ return { };
}
+ExceptionOr<void> PaymentRequest::retry(PaymentValidationErrors&& errors)
+{
+ ASSERT(m_state == State::Closed);
+ if (!m_activePaymentHandler)
+ return Exception { AbortError };
+
+ m_state = State::Interactive;
+ return activePaymentHandler()->retry(WTFMove(errors));
+}
+
void PaymentRequest::cancel()
{
- if (m_state != State::Interactive)
- return;
-
m_activePaymentHandler = std::nullopt;
if (m_isUpdating) {
@@ -722,7 +755,7 @@
return;
}
- stop();
+ abortWithException(Exception { AbortError });
}
} // namespace WebCore
Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.h (237596 => 237597)
--- trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -50,7 +50,7 @@
struct PaymentDetailsUpdate;
struct PaymentMethodData;
-class PaymentRequest final : public RefCounted<PaymentRequest>, public ActiveDOMObject, public EventTargetWithInlineData {
+class PaymentRequest final : public ActiveDOMObject, public CanMakeWeakPtr<PaymentRequest>, public EventTargetWithInlineData, public RefCounted<PaymentRequest> {
public:
using AbortPromise = DOMPromiseDeferred<void>;
using CanMakePaymentPromise = DOMPromiseDeferred<IDLBoolean>;
@@ -60,7 +60,7 @@
~PaymentRequest();
void show(Document&, RefPtr<DOMPromise>&& detailsPromise, ShowPromise&&);
- ExceptionOr<void> abort(AbortPromise&&);
+ void abort(AbortPromise&&);
void canMakePayment(Document&, CanMakePaymentPromise&&);
const String& id() const;
@@ -93,7 +93,8 @@
ExceptionOr<void> updateWith(UpdateReason, Ref<DOMPromise>&&);
ExceptionOr<void> completeMerchantValidation(Event&, Ref<DOMPromise>&&);
void accept(const String& methodName, PaymentResponse::DetailsFunction&&, Ref<PaymentAddress>&& shippingAddress, const String& payerName, const String& payerEmail, const String& payerPhone);
- void complete(std::optional<PaymentComplete>&&);
+ ExceptionOr<void> complete(std::optional<PaymentComplete>&&);
+ ExceptionOr<void> retry(PaymentValidationErrors&&);
void cancel();
using MethodIdentifier = Variant<String, URL>;
@@ -117,6 +118,8 @@
void whenDetailsSettled(std::function<void()>&&);
void abortWithException(Exception&&);
PaymentHandler* activePaymentHandler() { return m_activePaymentHandler ? m_activePaymentHandler->paymentHandler.ptr() : nullptr; }
+ void settleShowPromise(ExceptionOr<PaymentResponse&>&&);
+ void closeActivePaymentHandler();
// ActiveDOMObject
const char* activeDOMObjectName() const final { return "PaymentRequest"; }
@@ -141,6 +144,7 @@
std::optional<PaymentHandlerWithPendingActivity> m_activePaymentHandler;
RefPtr<DOMPromise> m_detailsPromise;
RefPtr<DOMPromise> m_merchantSessionPromise;
+ RefPtr<PaymentResponse> m_response;
bool m_isUpdating { false };
bool m_isCancelPending { false };
};
Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.idl (237596 => 237597)
--- trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.idl 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentRequest.idl 2018-10-30 19:07:40 UTC (rev 237597)
@@ -33,7 +33,7 @@
SecureContext,
] interface PaymentRequest : EventTarget {
[CallWith=Document] Promise<PaymentResponse> show(optional Promise<PaymentDetailsUpdate> detailsPromise);
- [MayThrowException] Promise<void> abort();
+ Promise<void> abort();
[CallWith=Document] Promise<boolean> canMakePayment();
readonly attribute DOMString id;
Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentResponse.cpp (237596 => 237597)
--- trunk/Source/WebCore/Modules/paymentrequest/PaymentResponse.cpp 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentResponse.cpp 2018-10-30 19:07:40 UTC (rev 237597)
@@ -35,33 +35,100 @@
namespace WebCore {
PaymentResponse::PaymentResponse(ScriptExecutionContext* context, PaymentRequest& request, DetailsFunction&& detailsFunction)
- : ContextDestructionObserver { context }
- , m_request { request }
+ : ActiveDOMObject { context }
+ , m_request { makeWeakPtr(request) }
, m_detailsFunction { WTFMove(detailsFunction) }
{
ASSERT(m_detailsFunction);
+ suspendIfNeeded();
}
-PaymentResponse::~PaymentResponse() = default;
+void PaymentResponse::finishConstruction()
+{
+ ASSERT(!hasPendingActivity());
+ m_pendingActivity = makePendingActivity(*this);
+}
+PaymentResponse::~PaymentResponse()
+{
+ ASSERT(!hasPendingActivity());
+ ASSERT(!hasRetryPromise());
+}
+
void PaymentResponse::complete(std::optional<PaymentComplete>&& result, DOMPromiseDeferred<void>&& promise)
{
- if (m_completeCalled) {
+ if (m_state == State::Stopped || !m_request) {
+ promise.reject(Exception { AbortError });
+ return;
+ }
+
+ if (m_state == State::Completed || m_retryPromise) {
promise.reject(Exception { InvalidStateError });
return;
}
- m_completeCalled = true;
- m_request->complete(WTFMove(result));
- promise.resolve();
+ ASSERT(hasPendingActivity());
+ ASSERT(m_state == State::Created);
+ m_pendingActivity = nullptr;
+ m_state = State::Completed;
+
+ promise.settle(m_request->complete(WTFMove(result)));
}
-void PaymentResponse::retry(PaymentValidationErrors&&, DOMPromiseDeferred<void>&& promise)
+void PaymentResponse::retry(PaymentValidationErrors&& errors, DOMPromiseDeferred<void>&& promise)
{
- notImplemented();
- promise.reject(Exception { NotSupportedError });
+ if (m_state == State::Stopped || !m_request) {
+ promise.reject(Exception { AbortError });
+ return;
+ }
+
+ if (m_state == State::Completed || m_retryPromise) {
+ promise.reject(Exception { InvalidStateError });
+ return;
+ }
+
+ ASSERT(hasPendingActivity());
+ ASSERT(m_state == State::Created);
+
+ auto exception = m_request->retry(WTFMove(errors));
+ if (exception.hasException()) {
+ promise.reject(exception.releaseException());
+ return;
+ }
+
+ m_retryPromise = WTFMove(promise);
}
+void PaymentResponse::abortWithException(Exception&& exception)
+{
+ settleRetryPromise(WTFMove(exception));
+ m_pendingActivity = nullptr;
+ m_state = State::Completed;
+}
+
+void PaymentResponse::settleRetryPromise(ExceptionOr<void>&& result)
+{
+ if (!m_retryPromise)
+ return;
+
+ ASSERT(hasPendingActivity());
+ ASSERT(m_state == State::Created);
+ std::exchange(m_retryPromise, std::nullopt)->settle(WTFMove(result));
+}
+
+bool PaymentResponse::canSuspendForDocumentSuspension() const
+{
+ ASSERT(m_state != State::Stopped);
+ return !hasPendingActivity();
+}
+
+void PaymentResponse::stop()
+{
+ settleRetryPromise(Exception { AbortError });
+ m_pendingActivity = nullptr;
+ m_state = State::Stopped;
+}
+
} // namespace WebCore
#endif // ENABLE(PAYMENT_REQUEST)
Modified: trunk/Source/WebCore/Modules/paymentrequest/PaymentResponse.h (237596 => 237597)
--- trunk/Source/WebCore/Modules/paymentrequest/PaymentResponse.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/Modules/paymentrequest/PaymentResponse.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -27,6 +27,7 @@
#if ENABLE(PAYMENT_REQUEST)
+#include "ActiveDOMObject.h"
#include "ContextDestructionObserver.h"
#include "EventTarget.h"
#include "JSDOMPromiseDeferred.h"
@@ -33,6 +34,7 @@
#include "JSValueInWrappedObject.h"
#include "PaymentAddress.h"
#include "PaymentComplete.h"
+#include <wtf/WeakPtr.h>
namespace WebCore {
@@ -40,13 +42,15 @@
class PaymentRequest;
struct PaymentValidationErrors;
-class PaymentResponse final : public ContextDestructionObserver, public EventTargetWithInlineData, public RefCounted<PaymentResponse> {
+class PaymentResponse final : public ActiveDOMObject, public EventTargetWithInlineData, public RefCounted<PaymentResponse> {
public:
using DetailsFunction = Function<JSC::Strong<JSC::JSObject>(JSC::ExecState&)>;
static Ref<PaymentResponse> create(ScriptExecutionContext* context, PaymentRequest& request, DetailsFunction&& detailsFunction)
{
- return adoptRef(*new PaymentResponse(context, request, WTFMove(detailsFunction)));
+ auto response = adoptRef(*new PaymentResponse(context, request, WTFMove(detailsFunction)));
+ response->finishConstruction();
+ return response;
}
~PaymentResponse();
@@ -77,6 +81,9 @@
void complete(std::optional<PaymentComplete>&&, DOMPromiseDeferred<void>&&);
void retry(PaymentValidationErrors&&, DOMPromiseDeferred<void>&&);
+ void abortWithException(Exception&&);
+ bool hasRetryPromise() const { return !!m_retryPromise; }
+ void settleRetryPromise(ExceptionOr<void>&& = { });
using RefCounted<PaymentResponse>::ref;
using RefCounted<PaymentResponse>::deref;
@@ -83,14 +90,26 @@
private:
PaymentResponse(ScriptExecutionContext*, PaymentRequest&, DetailsFunction&&);
+ void finishConstruction();
+ // ActiveDOMObject
+ const char* activeDOMObjectName() const final { return "PaymentResponse"; }
+ bool canSuspendForDocumentSuspension() const final;
+ void stop() final;
+
// EventTarget
EventTargetInterface eventTargetInterface() const final { return PaymentResponseEventTargetInterfaceType; }
- ScriptExecutionContext* scriptExecutionContext() const final { return ContextDestructionObserver::scriptExecutionContext(); }
+ ScriptExecutionContext* scriptExecutionContext() const final { return ActiveDOMObject::scriptExecutionContext(); }
void refEventTarget() final { ref(); }
void derefEventTarget() final { deref(); }
- Ref<PaymentRequest> m_request;
+ enum class State {
+ Created,
+ Completed,
+ Stopped,
+ };
+
+ WeakPtr<PaymentRequest> m_request;
String m_requestId;
String m_methodName;
DetailsFunction m_detailsFunction;
@@ -100,7 +119,9 @@
String m_payerName;
String m_payerEmail;
String m_payerPhone;
- bool m_completeCalled { false };
+ State m_state { State::Created };
+ std::optional<DOMPromiseDeferred<void>> m_retryPromise;
+ RefPtr<PendingActivity<PaymentResponse>> m_pendingActivity;
};
} // namespace WebCore
Modified: trunk/Source/WebCore/testing/Internals.cpp (237596 => 237597)
--- trunk/Source/WebCore/testing/Internals.cpp 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/testing/Internals.cpp 2018-10-30 19:07:40 UTC (rev 237597)
@@ -547,9 +547,9 @@
#if ENABLE(APPLE_PAY)
auto* frame = document.frame();
- if (frame && frame->page()) {
- m_mockPaymentCoordinator = new MockPaymentCoordinator(*frame->page());
- frame->page()->setPaymentCoordinator(std::make_unique<PaymentCoordinator>(*m_mockPaymentCoordinator));
+ if (frame && frame->page() && frame->isMainFrame()) {
+ auto mockPaymentCoordinator = new MockPaymentCoordinator(*frame->page());
+ frame->page()->setPaymentCoordinator(std::make_unique<PaymentCoordinator>(*mockPaymentCoordinator));
}
#endif
}
@@ -4625,9 +4625,9 @@
#endif
#if ENABLE(APPLE_PAY)
-MockPaymentCoordinator& Internals::mockPaymentCoordinator() const
+MockPaymentCoordinator& Internals::mockPaymentCoordinator(Document& document)
{
- return *m_mockPaymentCoordinator;
+ return downcast<MockPaymentCoordinator>(document.frame()->page()->paymentCoordinator().client());
}
#endif
Modified: trunk/Source/WebCore/testing/Internals.h (237596 => 237597)
--- trunk/Source/WebCore/testing/Internals.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/testing/Internals.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -698,7 +698,7 @@
#endif
#if ENABLE(APPLE_PAY)
- MockPaymentCoordinator& mockPaymentCoordinator() const;
+ MockPaymentCoordinator& mockPaymentCoordinator(Document&);
#endif
bool isSystemPreviewLink(Element&) const;
@@ -795,10 +795,6 @@
std::unique_ptr<InspectorStubFrontend> m_inspectorFrontend;
RefPtr<CacheStorageConnection> m_cacheStorageConnection;
-
-#if ENABLE(APPLE_PAY)
- MockPaymentCoordinator* m_mockPaymentCoordinator { nullptr };
-#endif
};
} // namespace WebCore
Modified: trunk/Source/WebCore/testing/Internals.idl (237596 => 237597)
--- trunk/Source/WebCore/testing/Internals.idl 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/testing/Internals.idl 2018-10-30 19:07:40 UTC (rev 237597)
@@ -682,7 +682,7 @@
[Conditional=SERVICE_WORKER] void terminateServiceWorker(ServiceWorker worker);
[Conditional=SERVICE_WORKER] boolean hasServiceWorkerConnection();
- [Conditional=APPLE_PAY] readonly attribute MockPaymentCoordinator mockPaymentCoordinator;
+ [CallWith=Document, Conditional=APPLE_PAY] readonly attribute MockPaymentCoordinator mockPaymentCoordinator;
boolean isSystemPreviewLink(Element element);
boolean isSystemPreviewImage(Element element);
Modified: trunk/Source/WebCore/testing/MockPaymentCoordinator.cpp (237596 => 237597)
--- trunk/Source/WebCore/testing/MockPaymentCoordinator.cpp 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/testing/MockPaymentCoordinator.cpp 2018-10-30 19:07:40 UTC (rev 237597)
@@ -28,6 +28,7 @@
#if ENABLE(APPLE_PAY)
+#include "ApplePaySessionPaymentRequest.h"
#include "MockPayment.h"
#include "MockPaymentContact.h"
#include "MockPaymentMethod.h"
@@ -209,7 +210,10 @@
void MockPaymentCoordinator::completePaymentSession(std::optional<PaymentAuthorizationResult>&& result)
{
- if (!isFinalStateResult(result))
+ auto isFinalState = isFinalStateResult(result);
+ m_errors = WTFMove(result->errors);
+
+ if (!isFinalState)
return;
++hideCount;
Modified: trunk/Source/WebCore/testing/MockPaymentCoordinator.h (237596 => 237597)
--- trunk/Source/WebCore/testing/MockPaymentCoordinator.h 2018-10-30 18:33:01 UTC (rev 237596)
+++ trunk/Source/WebCore/testing/MockPaymentCoordinator.h 2018-10-30 19:07:40 UTC (rev 237597)
@@ -76,6 +76,8 @@
void cancelPaymentSession() final;
void paymentCoordinatorDestroyed() final;
+ bool isMockPaymentCoordinator() const final { return true; }
+
void updateTotalAndLineItems(const ApplePaySessionPaymentRequest::TotalAndLineItems&);
Page& m_page;
@@ -91,4 +93,8 @@
} // namespace WebCore
+SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::MockPaymentCoordinator)
+ static bool isType(const WebCore::PaymentCoordinatorClient& paymentCoordinatorClient) { return paymentCoordinatorClient.isMockPaymentCoordinator(); }
+SPECIALIZE_TYPE_TRAITS_END()
+
#endif // ENABLE(APPLE_PAY)