Bug 1442453 - Create a single IPC actor per PaymentRequest. r=baku draft
authorBlake Kaplan <mrbkap@gmail.com>
Thu, 31 May 2018 13:30:29 -0700
changeset 808272 2a427d066ab0cff8aeba151a6fd93af983397dd4
parent 808271 12f586dd6cf294f6c92178655f59be4f0845a8b2
child 808273 f2f58eb288a6387b939b7de08ef1a570722be602
push id113333
push userbmo:mrbkap@mozilla.com
push dateMon, 18 Jun 2018 22:37:33 +0000
reviewersbaku
bugs1442453
milestone62.0a1
Bug 1442453 - Create a single IPC actor per PaymentRequest. r=baku There isn't any need to create an actor per call to the parent. This patch lines up PaymentRequest with PPaymentRequestChild objects and links them together. It also simplifies the maps and arrays we use to keep track of these objects. There's one tricky bit to note in this patch: in the case that a promise is passed to paymentRequest.show(), we don't notify the parent process until the promise resolves (when we call either UpdatePayment or AbortUpdate). In that case, I needed to distinguish between an "update" because of the promise resolving or a call to updateWith on an shippingaddresschange event in order to get the bookkeeping right with the mActivePayments hashtable. In that case, the PaymentRequest is kept alive by mShowingRequest alone. In all other cases, mActivePayments keeps the PaymentRequest alive until we resolve or reject the correct promise. MozReview-Commit-ID: HoHjn8eqC4T
dom/ipc/TabChild.cpp
dom/payments/PaymentRequest.cpp
dom/payments/PaymentRequest.h
dom/payments/PaymentRequestManager.cpp
dom/payments/PaymentRequestManager.h
dom/payments/PaymentRequestUpdateEvent.cpp
dom/payments/ipc/PaymentRequestChild.cpp
dom/payments/ipc/PaymentRequestChild.h
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -3434,16 +3434,17 @@ TabChild::AllocPPaymentRequestChild()
 {
   MOZ_CRASH("We should never be manually allocating PPaymentRequestChild actors");
   return nullptr;
 }
 
 bool
 TabChild::DeallocPPaymentRequestChild(PPaymentRequestChild* actor)
 {
+  delete actor;
   return true;
 }
 
 ScreenIntSize
 TabChild::GetInnerSize()
 {
   LayoutDeviceIntSize innerSize =
     RoundedToInt(mUnscaledInnerSize * mPuppetWidget->GetDefaultScale());
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "BasicCardPayment.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/PaymentRequest.h"
+#include "mozilla/dom/PaymentRequestChild.h"
 #include "mozilla/dom/PaymentResponse.h"
 #include "mozilla/EventStateManager.h"
 #include "nsContentUtils.h"
 #include "nsIURLParser.h"
 #include "nsNetCID.h"
 #include "PaymentRequestManager.h"
 
 namespace mozilla {
@@ -626,18 +627,20 @@ PaymentRequest::CreatePaymentRequest(nsP
 }
 
 PaymentRequest::PaymentRequest(nsPIDOMWindowInner* aWindow, const nsAString& aInternalId)
   : DOMEventTargetHelper(aWindow)
   , mInternalId(aInternalId)
   , mShippingAddress(nullptr)
   , mUpdating(false)
   , mRequestShipping(false)
+  , mDeferredShow(false)
   , mUpdateError(NS_OK)
   , mState(eCreated)
+  , mIPC(nullptr)
 {
   MOZ_ASSERT(aWindow);
 }
 
 already_AddRefed<Promise>
 PaymentRequest::CanMakePayment(ErrorResult& aRv)
 {
   if (mState != eCreated) {
@@ -708,16 +711,17 @@ PaymentRequest::Show(const Optional<Owni
     mState = eClosed;
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   if (aDetailsPromise.WasPassed()) {
     aDetailsPromise.Value().AppendNativeHandler(this);
     mUpdating = true;
+    mDeferredShow = 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);
@@ -802,17 +806,20 @@ PaymentRequest::Abort(ErrorResult& aRv)
     return nullptr;
   }
 
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   if (NS_WARN_IF(!manager)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
-  nsresult rv = manager->AbortPayment(mInternalId);
+
+  // It's possible for to call this between show and its promise resolving.
+  nsresult rv = manager->AbortPayment(this, mDeferredShow);
+  mDeferredShow = false;
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   mAbortPromise = promise;
   return promise.forget();
 }
@@ -847,39 +854,41 @@ PaymentRequest::RespondAbortPayment(bool
     RejectShowPayment(NS_ERROR_DOM_ABORT_ERR);
   } else {
     mAbortPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     mAbortPromise = nullptr;
   }
 }
 
 nsresult
-PaymentRequest::UpdatePayment(JSContext* aCx, const PaymentDetailsUpdate& aDetails)
+PaymentRequest::UpdatePayment(JSContext* aCx, const PaymentDetailsUpdate& aDetails,
+                              bool aDeferredShow)
 {
   NS_ENSURE_ARG_POINTER(aCx);
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   if (NS_WARN_IF(!manager)) {
     return NS_ERROR_FAILURE;
   }
-  nsresult rv = manager->UpdatePayment(aCx, mInternalId, aDetails, mRequestShipping);
+  nsresult rv = manager->UpdatePayment(aCx, this, aDetails, mRequestShipping,
+                                       aDeferredShow);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 void
-PaymentRequest::AbortUpdate(nsresult aRv)
+PaymentRequest::AbortUpdate(nsresult aRv, bool aDeferredShow)
 {
   MOZ_ASSERT(NS_FAILED(aRv));
 
   // Close down any remaining user interface.
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   MOZ_ASSERT(manager);
-  nsresult rv = manager->AbortPayment(mInternalId);
+  nsresult rv = manager->AbortPayment(this, aDeferredShow);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   // Remember update error |aRv| and do the following steps in RespondShowPayment.
   // 1. Set target.state to closed
   // 2. Reject the promise target.acceptPromise with exception "aRv"
   // 3. Abort the algorithm with update error
@@ -1014,43 +1023,50 @@ PaymentRequest::ResolvedCallback(JSConte
   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);
+    AbortUpdate(NS_ERROR_DOM_TYPE_ERR, mDeferredShow);
     JS_ClearPendingException(aCx);
     return;
   }
 
   nsresult rv = IsValidDetailsUpdate(details, mRequestShipping);
   if (NS_FAILED(rv)) {
-    AbortUpdate(rv);
+    AbortUpdate(rv, mDeferredShow);
     return;
   }
 
   // Update the PaymentRequest with the new details
-  if (NS_FAILED(UpdatePayment(aCx, details))) {
-    AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
+  if (NS_FAILED(UpdatePayment(aCx, details, mDeferredShow))) {
+    AbortUpdate(NS_ERROR_DOM_ABORT_ERR, mDeferredShow);
     return;
   }
+
+  mDeferredShow = false;
 }
 
 void
 PaymentRequest::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
+  MOZ_ASSERT(mDeferredShow);
   mUpdating = false;
-  AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
+  AbortUpdate(NS_ERROR_DOM_ABORT_ERR, mDeferredShow);
+  mDeferredShow = false;
 }
 
 PaymentRequest::~PaymentRequest()
 {
+  if (mIPC) {
+    mIPC->MaybeDelete();
+  }
 }
 
 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
@@ -15,16 +15,17 @@
 #include "nsWrapperCache.h"
 #include "PaymentRequestUpdateEvent.h"
 
 namespace mozilla {
 namespace dom {
 
 class EventHandlerNonNull;
 class PaymentAddress;
+class PaymentRequestChild;
 class PaymentResponse;
 
 class PaymentRequest final : public DOMEventTargetHelper
                            , public PromiseNativeHandler
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
@@ -130,18 +131,19 @@ public:
                                  const nsAString& aRecipient,
                                  const nsAString& aPhone);
 
 
   void SetShippingOption(const nsAString& aShippingOption);
   void GetShippingOption(nsAString& aRetVal) const;
   nsresult UpdateShippingOption(const nsAString& aShippingOption);
 
-  nsresult UpdatePayment(JSContext* aCx, const PaymentDetailsUpdate& aDetails);
-  void AbortUpdate(nsresult aRv);
+  nsresult UpdatePayment(JSContext* aCx, const PaymentDetailsUpdate& aDetails,
+                         bool aDeferredShow);
+  void AbortUpdate(nsresult aRv, bool aDeferredShow);
 
   void SetShippingType(const Nullable<PaymentShippingType>& aShippingType);
   Nullable<PaymentShippingType> GetShippingType() const;
 
   inline void ShippingWasRequested()
   {
     mRequestShipping = true;
   }
@@ -149,16 +151,26 @@ public:
   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);
 
+  void SetIPC(PaymentRequestChild* aChild)
+  {
+    mIPC = aChild;
+  }
+
+  PaymentRequestChild* GetIPC()
+  {
+    return mIPC;
+  }
+
 protected:
   ~PaymentRequest();
 
   nsresult DispatchUpdateEvent(const nsAString& aType);
 
   PaymentRequest(nsPIDOMWindowInner* aWindow, const nsAString& aInternalId);
 
   // Id for internal identification
@@ -186,23 +198,30 @@ protected:
 
   // "true" when there is a pending updateWith() call to update the payment request
   // and "false" otherwise.
   bool mUpdating;
 
   // Whether shipping was requested. This models [[options]].requestShipping,
   // but we don't actually store the full [[options]] internal slot.
   bool mRequestShipping;
+
+  // True if the user passed a promise to show, causing us to defer telling the
+  // front end about it.
+  bool mDeferredShow;
+
   // The error is set in AbortUpdate(). The value is NS_OK by default.
   nsresult mUpdateError;
 
   enum {
     eUnknown,
     eCreated,
     eInteractive,
     eClosed
   } mState;
+
+  PaymentRequestChild* mIPC;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PaymentRequest_h
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -247,125 +247,101 @@ ConvertOptions(const PaymentOptions& aOp
                                  shippingType);
 }
 } // end of namespace
 
 /* PaymentRequestManager */
 
 StaticRefPtr<PaymentRequestManager> gPaymentManager;
 
-nsresult
-PaymentRequestManager::GetPaymentChild(PaymentRequest* aRequest,
-                                       PaymentRequestChild** aChild)
+PaymentRequestChild*
+PaymentRequestManager::GetPaymentChild(PaymentRequest* aRequest)
 {
-  NS_ENSURE_ARG_POINTER(aRequest);
-  NS_ENSURE_ARG_POINTER(aChild);
-  *aChild = nullptr;
+  MOZ_ASSERT(aRequest);
 
-  RefPtr<PaymentRequestChild> paymentChild;
-  if (mPaymentChildHash.Get(aRequest, getter_AddRefs(paymentChild))) {
-    paymentChild.forget(aChild);
-    return NS_OK;
+  if (PaymentRequestChild* child = aRequest->GetIPC()) {
+    return child;
   }
 
   nsPIDOMWindowInner* win = aRequest->GetOwner();
-  NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(win, nullptr);
   TabChild* tabChild = TabChild::GetFrom(win->GetDocShell());
-  NS_ENSURE_TRUE(tabChild, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(tabChild, nullptr);
   nsAutoString requestId;
   aRequest->GetInternalId(requestId);
 
-  paymentChild = new PaymentRequestChild();
+  PaymentRequestChild* paymentChild = new PaymentRequestChild(aRequest);
   tabChild->SendPPaymentRequestConstructor(paymentChild);
-  if (!mPaymentChildHash.Put(aRequest, paymentChild, mozilla::fallible) ) {
-    return NS_ERROR_OUT_OF_MEMORY;
+  if (!mPaymentChildHash.Put(requestId, aRequest, mozilla::fallible) ) {
+    paymentChild->MaybeDelete();
+    return nullptr;
   }
-  paymentChild.forget(aChild);
-  return NS_OK;
+
+  return paymentChild;
 }
 
 nsresult
-PaymentRequestManager::ReleasePaymentChild(PaymentRequestChild* aPaymentChild)
+PaymentRequestManager::ReleasePaymentChild(const nsAString& aId)
 {
-  NS_ENSURE_ARG_POINTER(aPaymentChild);
-  for (auto iter = mPaymentChildHash.Iter(); !iter.Done(); iter.Next()) {
-    RefPtr<PaymentRequestChild> child = iter.Data();
-    if (NS_WARN_IF(!child)) {
-      return NS_ERROR_FAILURE;
-    }
-    if (child == aPaymentChild) {
-      iter.Remove();
-      return NS_OK;
-    }
-  }
-  return NS_OK;
-}
-
-nsresult
-PaymentRequestManager::ReleasePaymentChild(PaymentRequest* aRequest)
-{
-  NS_ENSURE_ARG_POINTER(aRequest);
-
-  RefPtr<PaymentRequestChild> paymentChild;
-  if(!mPaymentChildHash.Remove(aRequest, getter_AddRefs(paymentChild))) {
-    return NS_ERROR_FAILURE;
-  }
-  if (NS_WARN_IF(!paymentChild)) {
-    return NS_ERROR_FAILURE;
-  }
-  paymentChild->MaybeDelete();
+  mPaymentChildHash.Remove(aId);
   return NS_OK;
 }
 
 nsresult
 PaymentRequestManager::SendRequestPayment(PaymentRequest* aRequest,
                                           const IPCPaymentActionRequest& aAction,
-                                          bool aReleaseAfterSend)
+                                          bool aResponseExpected)
 {
-  RefPtr<PaymentRequestChild> requestChild;
-  nsresult rv = GetPaymentChild(aRequest, getter_AddRefs(requestChild));
+  PaymentRequestChild* requestChild = GetPaymentChild(aRequest);
+  nsresult rv = requestChild->RequestPayment(aAction);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  rv = requestChild->RequestPayment(aAction);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  if (aReleaseAfterSend) {
-    rv = ReleasePaymentChild(aRequest);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
+  if (aResponseExpected) {
+    auto count = mActivePayments.LookupForAdd(aRequest);
+    if (count) {
+      count.Data()++;
+    } else {
+      count.OrInsert([]() { return 1; });
     }
   }
   return NS_OK;
 }
 
+void
+PaymentRequestManager::NotifyRequestDone(PaymentRequest* aRequest)
+{
+  auto entry = mActivePayments.Lookup(aRequest);
+  MOZ_ASSERT(entry);
+  MOZ_ASSERT(entry.Data() > 0);
+
+  uint32_t count = --entry.Data();
+  if (count == 0) {
+    entry.Remove();
+  }
+}
+
 already_AddRefed<PaymentRequestManager>
 PaymentRequestManager::GetSingleton()
 {
   if (!gPaymentManager) {
     gPaymentManager = new PaymentRequestManager();
     ClearOnShutdown(&gPaymentManager);
   }
   RefPtr<PaymentRequestManager> manager = gPaymentManager;
   return manager.forget();
 }
 
 already_AddRefed<PaymentRequest>
 PaymentRequestManager::GetPaymentRequestById(const nsAString& aRequestId)
 {
-  for (const RefPtr<PaymentRequest>& request : mRequestQueue) {
-    if (request->Equals(aRequestId)) {
-      RefPtr<PaymentRequest> paymentRequest = request;
-      return paymentRequest.forget();
-    }
-  }
-  return nullptr;
+  // TODO Pass PaymentRequestChild objects around instead of strings.
+  RefPtr<PaymentRequest> request = mPaymentChildHash.Get(aRequestId);
+  return request.forget();
 }
 
 void
 GetSelectedShippingOption(const PaymentDetailsBase& aDetails,
                           nsAString& aOption)
 {
   SetDOMStringToNull(aOption);
   if (!aDetails.mShippingOptions.WasPassed()) {
@@ -455,21 +431,20 @@ PaymentRequestManager::CreatePayment(JSC
 
   IPCPaymentCreateActionRequest action(internalId,
                                        IPC::Principal(aTopLevelPrincipal),
                                        methodData,
                                        details,
                                        options,
                                        shippingOption);
 
-  rv = SendRequestPayment(request, action, true);
+  rv = SendRequestPayment(request, action, false);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  mRequestQueue.AppendElement(request);
   request.forget(aRequest);
   return NS_OK;
 }
 
 nsresult
 PaymentRequestManager::CanMakePayment(const nsAString& aRequestId)
 {
   RefPtr<PaymentRequest> request = GetPaymentRequestById(aRequestId);
@@ -499,27 +474,30 @@ PaymentRequestManager::ShowPayment(const
     IPCPaymentShowActionRequest action(requestId);
     rv = SendRequestPayment(request, action);
   }
   mShowingRequest = request;
   return rv;
 }
 
 nsresult
-PaymentRequestManager::AbortPayment(const nsAString& aRequestId)
+PaymentRequestManager::AbortPayment(PaymentRequest* aRequest, bool aDeferredShow)
 {
   RefPtr<PaymentRequest> request = GetPaymentRequestById(aRequestId);
   if (!request) {
     return NS_ERROR_FAILURE;
   }
+  MOZ_ASSERT(request == mShowingRequest);
 
   nsAutoString requestId(aRequestId);
   IPCPaymentAbortActionRequest action(requestId);
 
-  return SendRequestPayment(request, action);
+  // If aDeferredShow is true, then show was called with a promise that was
+  // rejected. In that case, we need to remember that we called show earlier.
+  return SendRequestPayment(aRequest, action, aDeferredShow);
 }
 
 nsresult
 PaymentRequestManager::CompletePayment(const nsAString& aRequestId,
                                        const PaymentComplete& aComplete)
 {
   RefPtr<PaymentRequest> request = GetPaymentRequestById(aRequestId);
   if (!request) {
@@ -531,24 +509,25 @@ PaymentRequestManager::CompletePayment(c
   if (completeIndex < ArrayLength(PaymentCompleteValues::strings)) {
     completeStatusString.AssignASCII(
       PaymentCompleteValues::strings[completeIndex].value);
   }
 
   nsAutoString requestId(aRequestId);
   IPCPaymentCompleteActionRequest action(requestId, completeStatusString);
 
-  return SendRequestPayment(request, action);
+  return SendRequestPayment(request, action, false);
 }
 
 nsresult
 PaymentRequestManager::UpdatePayment(JSContext* aCx,
                                      const nsAString& aRequestId,
                                      const PaymentDetailsUpdate& aDetails,
-                                     bool aRequestShipping)
+                                     bool aRequestShipping,
+                                     bool aDeferredShow)
 {
   NS_ENSURE_ARG_POINTER(aCx);
   RefPtr<PaymentRequest> request = GetPaymentRequestById(aRequestId);
   if (!request) {
     return NS_ERROR_UNEXPECTED;
   }
   IPCPaymentDetails details;
   nsresult rv = ConvertDetailsUpdate(aCx, aDetails, details, aRequestShipping);
@@ -560,34 +539,34 @@ PaymentRequestManager::UpdatePayment(JSC
   SetDOMStringToNull(shippingOption);
   if (aRequestShipping) {
     GetSelectedShippingOption(aDetails, shippingOption);
     request->SetShippingOption(shippingOption);
   }
 
   nsAutoString requestId(aRequestId);
   IPCPaymentUpdateActionRequest action(requestId, details, shippingOption);
-  return SendRequestPayment(request, action);
+
+  // If aDeferredShow is true, then this call serves as the ShowUpdate call for
+  // this request.
+  return SendRequestPayment(aRequest, action, aDeferredShow);
 }
 
 nsresult
 PaymentRequestManager::RespondPayment(const IPCPaymentActionResponse& aResponse)
 {
   switch (aResponse.type()) {
     case IPCPaymentActionResponse::TIPCPaymentCanMakeActionResponse: {
       const IPCPaymentCanMakeActionResponse& response = aResponse;
       RefPtr<PaymentRequest> request = GetPaymentRequestById(response.requestId());
       if (NS_WARN_IF(!request)) {
         return NS_ERROR_FAILURE;
       }
       request->RespondCanMakePayment(response.result());
-      nsresult rv = ReleasePaymentChild(request);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+      NotifyRequestDone(request);
       break;
     }
     case IPCPaymentActionResponse::TIPCPaymentShowActionResponse: {
       const IPCPaymentShowActionResponse& response = aResponse;
       RefPtr<PaymentRequest> request = GetPaymentRequestById(response.requestId());
       if (NS_WARN_IF(!request)) {
         return NS_ERROR_FAILURE;
       }
@@ -614,56 +593,44 @@ PaymentRequestManager::RespondPayment(co
                                   response.data(),
                                   response.payerName(),
                                   response.payerEmail(),
                                   response.payerPhone(),
                                   rejectedReason);
       if (NS_FAILED(rejectedReason)) {
         MOZ_ASSERT(mShowingRequest == request);
         mShowingRequest = nullptr;
-        mRequestQueue.RemoveElement(request);
-        nsresult rv = ReleasePaymentChild(request);
-        if (NS_WARN_IF(NS_FAILED(rv))) {
-          return rv;
-        }
+        NotifyRequestDone(aRequest);
       }
       break;
     }
     case IPCPaymentActionResponse::TIPCPaymentAbortActionResponse: {
       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);
-        mRequestQueue.RemoveElement(request);
       }
       mShowingRequest = nullptr;
-      nsresult rv = ReleasePaymentChild(request);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+      NotifyRequestDone(request);
       break;
     }
     case IPCPaymentActionResponse::TIPCPaymentCompleteActionResponse: {
       const IPCPaymentCompleteActionResponse& response = aResponse;
       RefPtr<PaymentRequest> request = GetPaymentRequestById(response.requestId());
       if (NS_WARN_IF(!request)) {
         return NS_ERROR_FAILURE;
       }
       request->RespondComplete();
       MOZ_ASSERT(mShowingRequest == request);
       mShowingRequest = nullptr;
-      mRequestQueue.RemoveElement(request);
-      nsresult rv = ReleasePaymentChild(request);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+      NotifyRequestDone(request);
       break;
     }
     default: {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
--- a/dom/payments/PaymentRequestManager.h
+++ b/dom/payments/PaymentRequestManager.h
@@ -44,49 +44,54 @@ public:
   CreatePayment(JSContext* aCx,
                 nsPIDOMWindowInner* aWindow,
                 nsIPrincipal* aTopLevelPrincipal,
                 const Sequence<PaymentMethodData>& aMethodData,
                 const PaymentDetailsInit& aDetails,
                 const PaymentOptions& aOptions,
                 PaymentRequest** aRequest);
 
-  nsresult CanMakePayment(const nsAString& aRequestId);
-  nsresult ShowPayment(const nsAString& aRequestId);
-  nsresult AbortPayment(const nsAString& aRequestId);
-  nsresult CompletePayment(const nsAString& aRequestId,
+  nsresult CanMakePayment(PaymentRequest* aRequest);
+  nsresult ShowPayment(PaymentRequest* aRequest);
+  nsresult AbortPayment(PaymentRequest* aRequest, bool aDeferredShow);
+  nsresult CompletePayment(PaymentRequest* aRequest,
                            const PaymentComplete& aComplete);
   nsresult UpdatePayment(JSContext* aCx,
                          const nsAString& aRequestId,
                          const PaymentDetailsUpdate& aDetails,
-                         bool aRequestShipping);
+                         bool aRequestShipping,
+                         bool aDeferredShow);
 
   nsresult RespondPayment(const IPCPaymentActionResponse& aResponse);
   nsresult ChangeShippingAddress(const nsAString& aRequestId,
                                  const IPCPaymentAddress& aAddress);
   nsresult ChangeShippingOption(const nsAString& aRequestId,
                                 const nsAString& aOption);
 
   nsresult
-  ReleasePaymentChild(PaymentRequestChild* aPaymentChild);
+  ReleasePaymentChild(const nsAString& aId);
 
 private:
   PaymentRequestManager() = default;
-  ~PaymentRequestManager() = default;
+  ~PaymentRequestManager()
+  {
+    MOZ_ASSERT(mActivePayments.Count() == 0);
+  }
 
-  nsresult GetPaymentChild(PaymentRequest* aRequest,
-                           PaymentRequestChild** aPaymentChild);
-  nsresult ReleasePaymentChild(PaymentRequest* aRequest);
+  PaymentRequestChild* GetPaymentChild(PaymentRequest* aRequest);
 
   nsresult SendRequestPayment(PaymentRequest* aRequest,
                               const IPCPaymentActionRequest& action,
-                              bool aReleaseAfterSend = false);
+                              bool aResponseExpected = true);
+
+  void NotifyRequestDone(PaymentRequest* aRequest);
 
   // The container for the created PaymentRequests
-  nsTArray<RefPtr<PaymentRequest>> mRequestQueue;
-  nsRefPtrHashtable<nsRefPtrHashKey<PaymentRequest>, PaymentRequestChild> mPaymentChildHash;
+  nsDataHashtable<nsStringHashKey, PaymentRequest*> mPaymentChildHash;
+  // Strong pointer to requests with ongoing IPC messages to the parent.
+  nsDataHashtable<nsRefPtrHashKey<PaymentRequest>, uint32_t> mActivePayments;
   RefPtr<PaymentRequest> mShowingRequest;
 };
 
 } // end of namespace dom
 } // end of namespace mozilla
 
 #endif
--- a/dom/payments/PaymentRequestUpdateEvent.cpp
+++ b/dom/payments/PaymentRequestUpdateEvent.cpp
@@ -59,47 +59,47 @@ 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);
+    mRequest->AbortUpdate(NS_ERROR_TYPE_ERR, false);
     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.
   nsresult rv = mRequest->IsValidDetailsUpdate(details, true/*aRequestShipping*/);
   if (NS_FAILED(rv)) {
-    mRequest->AbortUpdate(rv);
+    mRequest->AbortUpdate(rv, false);
     return;
   }
 
   // Update the PaymentRequest with the new details
-  if (NS_FAILED(mRequest->UpdatePayment(aCx, details))) {
-    mRequest->AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
+  if (NS_FAILED(mRequest->UpdatePayment(aCx, details, false))) {
+    mRequest->AbortUpdate(NS_ERROR_DOM_ABORT_ERR, false);
     return;
   }
   mWaitForUpdate = false;
   mRequest->SetUpdating(false);
 }
 
 void
 PaymentRequestUpdateEvent::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   MOZ_ASSERT(mRequest);
 
-  mRequest->AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
+  mRequest->AbortUpdate(NS_ERROR_DOM_ABORT_ERR, false);
   mWaitForUpdate = false;
   mRequest->SetUpdating(false);
 }
 
 void
 PaymentRequestUpdateEvent::UpdateWith(Promise& aPromise, ErrorResult& aRv)
 {
   if (!IsTrusted()) {
--- a/dom/payments/ipc/PaymentRequestChild.cpp
+++ b/dom/payments/ipc/PaymentRequestChild.cpp
@@ -1,106 +1,122 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "PaymentRequestChild.h"
+#include "mozilla/dom/PaymentRequest.h"
 #include "mozilla/dom/PaymentRequestManager.h"
 
 namespace mozilla {
 namespace dom {
 
-PaymentRequestChild::PaymentRequestChild()
-  : mActorAlive(true)
+PaymentRequestChild::PaymentRequestChild(PaymentRequest* aRequest)
+  : mRequest(aRequest)
 {
+  mRequest->SetIPC(this);
 }
 
 nsresult
 PaymentRequestChild::RequestPayment(const IPCPaymentActionRequest& aAction)
 {
-  if (!mActorAlive) {
+  if (!mRequest) {
     return NS_ERROR_FAILURE;
   }
   if (!SendRequestPayment(aAction)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
 PaymentRequestChild::RecvRespondPayment(const IPCPaymentActionResponse& aResponse)
 {
-  if (!mActorAlive) {
+  if (!mRequest) {
     return IPC_FAIL_NO_REASON(this);
   }
   const IPCPaymentActionResponse& response = aResponse;
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   MOZ_ASSERT(manager);
   nsresult rv = manager->RespondPayment(response);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 PaymentRequestChild::RecvChangeShippingAddress(const nsString& aRequestId,
                                                const IPCPaymentAddress& aAddress)
 {
-  if (!mActorAlive) {
+  if (!mRequest) {
     return IPC_FAIL_NO_REASON(this);
   }
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   MOZ_ASSERT(manager);
   nsresult rv = manager->ChangeShippingAddress(aRequestId, aAddress);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 PaymentRequestChild::RecvChangeShippingOption(const nsString& aRequestId,
                                               const nsString& aOption)
 {
-  if (!mActorAlive) {
+  if (!mRequest) {
     return IPC_FAIL_NO_REASON(this);
   }
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   MOZ_ASSERT(manager);
   nsresult rv = manager->ChangeShippingOption(aRequestId, aOption);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 void
 PaymentRequestChild::ActorDestroy(ActorDestroyReason aWhy)
 {
-  mActorAlive = false;
-  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
-  MOZ_ASSERT(manager);
-  nsresult rv = manager->ReleasePaymentChild(this);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    MOZ_ASSERT(false);
+  if (mRequest) {
+    DetachFromRequest();
   }
 }
 
 void
 PaymentRequestChild::MaybeDelete()
 {
-  if (mActorAlive) {
-    mActorAlive = false;
+  if (mRequest) {
+    DetachFromRequest();
     Send__delete__(this);
   }
 }
 
 bool
 PaymentRequestChild::SendRequestPayment(const IPCPaymentActionRequest& aAction)
 {
   return PPaymentRequestChild::SendRequestPayment(aAction);
 }
 
+void
+PaymentRequestChild::DetachFromRequest()
+{
+  MOZ_ASSERT(mRequest);
+  nsAutoString id;
+  mRequest->GetInternalId(id);
+
+  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
+  MOZ_ASSERT(manager);
+  nsresult rv = manager->ReleasePaymentChild(id);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    MOZ_ASSERT(false);
+  }
+
+  mRequest->SetIPC(nullptr);
+  mRequest = nullptr;
+}
+
 } // end of namespace dom
 } // end of namespace mozilla
--- a/dom/payments/ipc/PaymentRequestChild.h
+++ b/dom/payments/ipc/PaymentRequestChild.h
@@ -7,21 +7,22 @@
 #ifndef mozilla_dom_PaymentRequestChild_h
 #define mozilla_dom_PaymentRequestChild_h
 
 #include "mozilla/dom/PPaymentRequestChild.h"
 
 namespace mozilla {
 namespace dom {
 
+class PaymentRequest;
+
 class PaymentRequestChild final : public PPaymentRequestChild
 {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PaymentRequestChild);
 public:
-  PaymentRequestChild();
+  explicit PaymentRequestChild(PaymentRequest* aRequest);
 
   void MaybeDelete();
 
   nsresult RequestPayment(const IPCPaymentActionRequest& aAction);
 
 protected:
   mozilla::ipc::IPCResult
   RecvRespondPayment(const IPCPaymentActionResponse& aResponse) override;
@@ -35,16 +36,16 @@ protected:
                            const nsString& aOption) override;
 
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
 private:
   ~PaymentRequestChild() = default;
 
   bool SendRequestPayment(const IPCPaymentActionRequest& aAction);
-
-  bool mActorAlive;
+  void DetachFromRequest();
+  PaymentRequest* MOZ_NON_OWNING_REF mRequest;
 };
 
 } // end of namespace dom
 } // end of namespace mozilla
 
 #endif