Bug 1441709 - Support PaymentRequest.show() with an optional PaymentDetailsUpdate promise parameter. r=baku
authorEden Chuang <chenyu.chuang@gapp.nthu.edu.tw>
Mon, 02 Apr 2018 23:12:21 +0800
changeset 411350 767166c4bef998cbc5cc53101e2e24a45e8e57aa
parent 411349 53f006fc6afda343190b261833a087c95309cbd9
child 411351 c074e1397b87ca6661a3ae6754b6009081621dbf
push id33752
push userdluca@mozilla.com
push dateMon, 02 Apr 2018 22:00:32 +0000
treeherdermozilla-central@445255800255 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1441709
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1441709 - Support PaymentRequest.show() with an optional PaymentDetailsUpdate promise parameter. r=baku 1. Add "optional Promise<PaymentDetailsUpdate> detailsPromise" as a parameter of PaymentRequest.show() in PaymentRequest.webidl. 2. Let PaymentRequest inherit from PromiseNativeHandler, and implement the ResolvedCallback() and RejectedCallback() to handle the PaymentDetailsUpdate promise. 3. Update PaymentRequest.show() implementation. If PaymentDetailsUpdate Promise is not nullptr, the show request would not be transferred to chrome process immediately until the promise is resolved/rejected. 4. Update selectedShippingOption when requestShipping is true. 5. Change the PaymentMethod id validation sequence according to the spec.
dom/payments/PaymentRequest.cpp
dom/payments/PaymentRequest.h
dom/payments/PaymentRequestManager.cpp
dom/payments/PaymentRequestService.cpp
dom/payments/PaymentRequestUpdateEvent.cpp
dom/webidl/PaymentRequest.webidl
dom/webidl/PaymentRequestUpdateEvent.webidl
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -386,40 +386,40 @@ PaymentRequest::IsValidCurrency(const ns
 
 nsresult
 PaymentRequest::IsValidCurrencyAmount(const nsAString& aItem,
                                       const PaymentCurrencyAmount& aAmount,
                                       const bool aIsTotalItem,
                                       nsAString& aErrorMsg)
 {
   nsresult rv;
-  if (aIsTotalItem) {
-    rv = IsNonNegativeNumber(aItem, aAmount.mValue, aErrorMsg);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-  } else {
-    rv = IsValidNumber(aItem, aAmount.mValue, aErrorMsg);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-  }
   // currencySystem must equal urn:iso:std:iso:4217
   if (!aAmount.mCurrencySystem.EqualsASCII("urn:iso:std:iso:4217")) {
     aErrorMsg.AssignLiteral("The amount.currencySystem of \"");
     aErrorMsg.Append(aItem);
     aErrorMsg.AppendLiteral("\"(");
     aErrorMsg.Append(aAmount.mCurrencySystem);
     aErrorMsg.AppendLiteral(") must equal urn:iso:std:iso:4217.");
     return NS_ERROR_RANGE_ERR;
   }
   rv = IsValidCurrency(aItem, aAmount.mCurrency, aErrorMsg);
   if (NS_FAILED(rv)) {
     return rv;
   }
+  if (aIsTotalItem) {
+    rv = IsNonNegativeNumber(aItem, aAmount.mValue, aErrorMsg);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  } else {
+    rv = IsValidNumber(aItem, aAmount.mValue, aErrorMsg);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+  }
   return NS_OK;
 }
 
 nsresult
 PaymentRequest::IsValidDetailsInit(const PaymentDetailsInit& aDetails,
                                    const bool aRequestShipping,
                                    nsAString& aErrorMsg)
 {
@@ -676,17 +676,18 @@ void
 PaymentRequest::RespondCanMakePayment(bool aResult)
 {
   MOZ_ASSERT(mResultPromise);
   mResultPromise->MaybeResolve(aResult);
   mResultPromise = nullptr;
 }
 
 already_AddRefed<Promise>
-PaymentRequest::Show(ErrorResult& aRv)
+PaymentRequest::Show(const Optional<OwningNonNull<Promise>>& aDetailsPromise,
+                     ErrorResult& aRv)
 {
   if (mState != eCreated) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   if (!EventStateManager::IsHandlingUserInput()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
@@ -703,16 +704,22 @@ PaymentRequest::Show(ErrorResult& aRv)
   }
 
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   if (NS_WARN_IF(!manager)) {
     mState = eClosed;
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
+
+  if (aDetailsPromise.WasPassed()) {
+    aDetailsPromise.Value().AppendNativeHandler(this);
+    mUpdating = true;
+  }
+
   nsresult rv = manager->ShowPayment(mInternalId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     if (rv == NS_ERROR_ABORT) {
       promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
     }
     mState = eClosed;
@@ -994,16 +1001,53 @@ PaymentRequest::SetShippingType(const Nu
 }
 
 Nullable<PaymentShippingType>
 PaymentRequest::GetShippingType() const
 {
   return mShippingType;
 }
 
+void
+PaymentRequest::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  MOZ_ASSERT(aCx);
+  mUpdating = false;
+  if (NS_WARN_IF(!aValue.isObject())) {
+    return;
+  }
+
+  // Converting value to a PaymentDetailsUpdate dictionary
+  PaymentDetailsUpdate details;
+  if (!details.Init(aCx, aValue)) {
+    AbortUpdate(NS_ERROR_DOM_TYPE_ERR);
+    JS_ClearPendingException(aCx);
+    return;
+  }
+
+  nsresult rv = IsValidDetailsUpdate(details, mRequestShipping);
+  if (NS_FAILED(rv)) {
+    AbortUpdate(rv);
+    return;
+  }
+
+  // Update the PaymentRequest with the new details
+  if (NS_FAILED(UpdatePayment(aCx, details))) {
+    AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
+    return;
+  }
+}
+
+void
+PaymentRequest::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
+{
+  mUpdating = false;
+  AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
+}
+
 PaymentRequest::~PaymentRequest()
 {
 }
 
 JSObject*
 PaymentRequest::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PaymentRequestBinding::Wrap(aCx, this, aGivenProto);
--- a/dom/payments/PaymentRequest.h
+++ b/dom/payments/PaymentRequest.h
@@ -5,28 +5,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PaymentRequest_h
 #define mozilla_dom_PaymentRequest_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/PaymentRequestBinding.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/ErrorResult.h"
 #include "nsWrapperCache.h"
 #include "PaymentRequestUpdateEvent.h"
 
 namespace mozilla {
 namespace dom {
 
 class EventHandlerNonNull;
 class PaymentAddress;
 class PaymentResponse;
 
 class PaymentRequest final : public DOMEventTargetHelper
+                           , public PromiseNativeHandler
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PaymentRequest, DOMEventTargetHelper)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
@@ -85,17 +87,18 @@ public:
               const Sequence<PaymentMethodData>& aMethodData,
               const PaymentDetailsInit& aDetails,
               const PaymentOptions& aOptions,
               ErrorResult& aRv);
 
   already_AddRefed<Promise> CanMakePayment(ErrorResult& aRv);
   void RespondCanMakePayment(bool aResult);
 
-  already_AddRefed<Promise> Show(ErrorResult& aRv);
+  already_AddRefed<Promise> Show(const Optional<OwningNonNull<Promise>>& detailsPromise,
+                                 ErrorResult& aRv);
   void RespondShowPayment(const nsAString& aMethodName,
                           const nsAString& aDetails,
                           const nsAString& aPayerName,
                           const nsAString& aPayerEmail,
                           const nsAString& aPayerPhone,
                           nsresult aRv);
   void RejectShowPayment(nsresult aRejectReason);
   void RespondComplete();
@@ -105,16 +108,17 @@ public:
 
   void GetId(nsAString& aRetVal) const;
   void GetInternalId(nsAString& aRetVal);
   void SetId(const nsAString& aId);
 
   bool Equals(const nsAString& aInternalId) const;
 
   bool ReadyForUpdate();
+  bool IsUpdating() const { return mUpdating; }
   void SetUpdating(bool aUpdating);
 
   already_AddRefed<PaymentAddress> GetShippingAddress() const;
   // Update mShippingAddress and fire shippingaddresschange event
   nsresult UpdateShippingAddress(const nsAString& aCountry,
                                  const nsTArray<nsString>& aAddressLine,
                                  const nsAString& aRegion,
                                  const nsAString& aCity,
@@ -137,16 +141,21 @@ public:
   void SetShippingType(const Nullable<PaymentShippingType>& aShippingType);
   Nullable<PaymentShippingType> GetShippingType() const;
 
   inline void ShippingWasRequested()
   {
     mRequestShipping = true;
   }
 
+  void
+  ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+  void
+  RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
   IMPL_EVENT_HANDLER(shippingaddresschange);
   IMPL_EVENT_HANDLER(shippingoptionchange);
 
 protected:
   ~PaymentRequest();
 
   nsresult DispatchUpdateEvent(const nsAString& aType);
 
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -369,17 +369,17 @@ PaymentRequestManager::GetPaymentRequest
       RefPtr<PaymentRequest> paymentRequest = request;
       return paymentRequest.forget();
     }
   }
   return nullptr;
 }
 
 void
-GetSelectedShippingOption(const PaymentDetailsInit& aDetails,
+GetSelectedShippingOption(const PaymentDetailsBase& aDetails,
                           nsAString& aOption)
 {
   SetDOMStringToNull(aOption);
   if (!aDetails.mShippingOptions.WasPassed()) {
     return;
   }
 
   const Sequence<PaymentShippingOption>& shippingOptions =
@@ -497,20 +497,22 @@ PaymentRequestManager::ShowPayment(const
 {
   if (mShowingRequest) {
     return NS_ERROR_ABORT;
   }
   RefPtr<PaymentRequest> request = GetPaymentRequestById(aRequestId);
   if (!request) {
     return NS_ERROR_FAILURE;
   }
-
-  nsAutoString requestId(aRequestId);
-  IPCPaymentShowActionRequest action(requestId);
-  nsresult rv = SendRequestPayment(request, action);
+  nsresult rv = NS_OK;
+  if (!request->IsUpdating()) {
+    nsAutoString requestId(aRequestId);
+    IPCPaymentShowActionRequest action(requestId);
+    rv = SendRequestPayment(request, action);
+  }
   mShowingRequest = request;
   return rv;
 }
 
 nsresult
 PaymentRequestManager::AbortPayment(const nsAString& aRequestId)
 {
   RefPtr<PaymentRequest> request = GetPaymentRequestById(aRequestId);
@@ -552,29 +554,29 @@ PaymentRequestManager::UpdatePayment(JSC
                                      const PaymentDetailsUpdate& aDetails,
                                      bool aRequestShipping)
 {
   NS_ENSURE_ARG_POINTER(aCx);
   RefPtr<PaymentRequest> request = GetPaymentRequestById(aRequestId);
   if (!request) {
     return NS_ERROR_UNEXPECTED;
   }
-
-  // [TODO] Process details.shippingOptions if presented.
-  //        1) Check if there are duplicate IDs in details.shippingOptions,
-  //           if so, reset details.shippingOptions to an empty sequence.
-  //        2) Set request's selectedShippingOption to the ID of last selected
-  //           option.
-
   IPCPaymentDetails details;
   nsresult rv = ConvertDetailsUpdate(aCx, aDetails, details, aRequestShipping);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  nsAutoString shippingOption;
+  SetDOMStringToNull(shippingOption);
+  if (aRequestShipping) {
+    GetSelectedShippingOption(aDetails, shippingOption);
+    request->SetShippingOption(shippingOption);
+  }
+
   nsAutoString requestId(aRequestId);
   IPCPaymentUpdateActionRequest action(requestId, details);
   return SendRequestPayment(request, action);
 }
 
 nsresult
 PaymentRequestManager::RespondPayment(const IPCPaymentActionResponse& aResponse)
 {
@@ -638,22 +640,22 @@ PaymentRequestManager::RespondPayment(co
       const IPCPaymentAbortActionResponse& response = aResponse;
       RefPtr<PaymentRequest> request = GetPaymentRequestById(response.requestId());
       if (NS_WARN_IF(!request)) {
         return NS_ERROR_FAILURE;
       }
       request->RespondAbortPayment(response.isSucceeded());
       if (response.isSucceeded()) {
         MOZ_ASSERT(mShowingRequest == request);
-        mShowingRequest = nullptr;
         mRequestQueue.RemoveElement(request);
-        nsresult rv = ReleasePaymentChild(request);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
+      }
+      mShowingRequest = nullptr;
+      nsresult rv = ReleasePaymentChild(request);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
       }
       break;
     }
     case IPCPaymentActionResponse::TIPCPaymentCompleteActionResponse: {
       const IPCPaymentCompleteActionResponse& response = aResponse;
       RefPtr<PaymentRequest> request = GetPaymentRequestById(response.requestId());
       if (NS_WARN_IF(!request)) {
         return NS_ERROR_FAILURE;
--- a/dom/payments/PaymentRequestService.cpp
+++ b/dom/payments/PaymentRequestService.cpp
@@ -363,17 +363,23 @@ PaymentRequestService::RequestPayment(ns
       rv = GetPaymentRequestById(requestId, getter_AddRefs(payment));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       rv = payment->UpdatePaymentDetails(details);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-      rv = LaunchUIAction(requestId, type);
+      if (mShowingRequest) {
+        MOZ_ASSERT(mShowingRequest == payment);
+        rv = LaunchUIAction(requestId, type);
+      } else {
+        mShowingRequest = payment;
+        rv = LaunchUIAction(requestId, nsIPaymentActionRequest::SHOW_ACTION);
+      }
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return NS_ERROR_FAILURE;
       }
       break;
     }
     default: {
       return NS_ERROR_FAILURE;
     }
@@ -415,18 +421,18 @@ PaymentRequestService::RespondPayment(ns
   switch (type) {
     case nsIPaymentActionResponse::ABORT_ACTION: {
       nsCOMPtr<nsIPaymentAbortActionResponse> response =
         do_QueryInterface(aResponse);
       MOZ_ASSERT(response);
       bool isSucceeded;
       rv = response->IsSucceeded(&isSucceeded);
       NS_ENSURE_SUCCESS(rv, rv);
+      mShowingRequest = nullptr;
       if (isSucceeded) {
-        mShowingRequest = nullptr;
         mRequestQueue.RemoveElement(request);
       }
       break;
     }
     case nsIPaymentActionResponse::SHOW_ACTION: {
       nsCOMPtr<nsIPaymentShowActionResponse> response =
         do_QueryInterface(aResponse);
       MOZ_ASSERT(response);
--- a/dom/payments/PaymentRequestUpdateEvent.cpp
+++ b/dom/payments/PaymentRequestUpdateEvent.cpp
@@ -59,16 +59,18 @@ PaymentRequestUpdateEvent::ResolvedCallb
 
   if (NS_WARN_IF(!aValue.isObject()) || !mWaitForUpdate) {
     return;
   }
 
   // Converting value to a PaymentDetailsUpdate dictionary
   PaymentDetailsUpdate details;
   if (!details.Init(aCx, aValue)) {
+    mRequest->AbortUpdate(NS_ERROR_TYPE_ERR);
+    JS_ClearPendingException(aCx);
     return;
   }
 
   // Validate and canonicalize the details
   // requestShipping must be true here. PaymentRequestUpdateEvent is only
   // dispatched when shippingAddress/shippingOption is changed, and it also means
   // Options.RequestShipping must be true while creating the corresponding
   // PaymentRequest.
--- a/dom/webidl/PaymentRequest.webidl
+++ b/dom/webidl/PaymentRequest.webidl
@@ -49,16 +49,21 @@ dictionary PaymentDetailsBase {
   sequence<PaymentDetailsModifier> modifiers;
 };
 
 dictionary PaymentDetailsInit : PaymentDetailsBase {
            DOMString   id;
   required PaymentItem total;
 };
 
+dictionary PaymentDetailsUpdate : PaymentDetailsBase {
+  DOMString   error;
+  PaymentItem total;
+};
+
 enum PaymentShippingType {
   "shipping",
   "delivery",
   "pickup"
 };
 
 dictionary PaymentOptions {
   boolean             requestPayerName = false;
@@ -69,17 +74,17 @@ dictionary PaymentOptions {
 };
 
 [Constructor(sequence<PaymentMethodData> methodData, PaymentDetailsInit details,
              optional PaymentOptions options),
  SecureContext,
  Func="mozilla::dom::PaymentRequest::PrefEnabled"]
 interface PaymentRequest : EventTarget {
   [NewObject]
-  Promise<PaymentResponse> show();
+  Promise<PaymentResponse> show(optional Promise<PaymentDetailsUpdate> detailsPromise);
   [NewObject]
   Promise<void>            abort();
   [NewObject]
   Promise<boolean>         canMakePayment();
 
   readonly attribute DOMString            id;
   readonly attribute PaymentAddress?      shippingAddress;
   readonly attribute DOMString?           shippingOption;
--- a/dom/webidl/PaymentRequestUpdateEvent.webidl
+++ b/dom/webidl/PaymentRequestUpdateEvent.webidl
@@ -2,21 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this WebIDL file is
  *   https://www.w3.org/TR/payment-request/#paymentrequestupdateevent-interface
  */
 
-dictionary PaymentDetailsUpdate : PaymentDetailsBase {
-  DOMString   error;
-  PaymentItem total;
-};
-
 [Constructor(DOMString type,
              optional PaymentRequestUpdateEventInit eventInitDict),
  SecureContext,
  Func="mozilla::dom::PaymentRequest::PrefEnabled"]
 interface PaymentRequestUpdateEvent : Event {
   [Throws]
   void updateWith(Promise<PaymentDetailsUpdate> detailsPromise);
 };