Bug 1497219 - Rejecting PaymentRequest::API calls when the PaymentRequest is not in fully active document. r=baku
authorEden Chuang <echuang@mozilla.com>
Wed, 05 Dec 2018 15:01:45 -0500
changeset 450775 2738bb9f6d54d34aa1742c50da7f6f120d9577a9
parent 450774 610a04b74c8742efd24a4b8cc96da25ee482748a
child 450776 7ba89c2d426df93c36e8c5550f65f69d4db1dc1d
push id35208
push usercsabou@mozilla.com
push dateSat, 15 Dec 2018 02:48:07 +0000
treeherdermozilla-central@d86d184dc7d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1497219
milestone66.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 1497219 - Rejecting PaymentRequest::API calls when the PaymentRequest is not in fully active document. r=baku PaymentRequest API is not supported when the PaymentRequest is not in the fully active document. Adding bool a PaymentRequest::InFullyActiveDocument() method to check if the PaymentRequest is in fully active document. This method should be called at the start of any PaymentRequest APIs. If the PaymentRequest is not in fully active document, ignoring the API call and throw the NS_ERROR_DOM_ABORT_ERR if needed.
dom/payments/PaymentRequest.cpp
dom/payments/PaymentRequest.h
dom/payments/PaymentRequestUpdateEvent.cpp
dom/payments/PaymentResponse.cpp
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -640,16 +640,21 @@ PaymentRequest::PaymentRequest(nsPIDOMWi
       mUpdateError(NS_OK),
       mState(eCreated),
       mIPC(nullptr) {
   MOZ_ASSERT(aWindow);
   RegisterActivityObserver();
 }
 
 already_AddRefed<Promise> PaymentRequest::CanMakePayment(ErrorResult& aRv) {
+  if (!InFullyActiveDocument()) {
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
+  }
+
   if (mState != eCreated) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   if (mResultPromise) {
     // XXX This doesn't match the spec but does match Chromium.
     aRv.Throw(NS_ERROR_DOM_NOT_ALLOWED_ERR);
@@ -678,37 +683,36 @@ already_AddRefed<Promise> PaymentRequest
 void PaymentRequest::RespondCanMakePayment(bool aResult) {
   MOZ_ASSERT(mResultPromise);
   mResultPromise->MaybeResolve(aResult);
   mResultPromise = nullptr;
 }
 
 already_AddRefed<Promise> PaymentRequest::Show(
     const Optional<OwningNonNull<Promise>>& aDetailsPromise, ErrorResult& aRv) {
+  if (!InFullyActiveDocument()) {
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
+  }
+
   nsIGlobalObject* global = GetOwnerGlobal();
   nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
-  MOZ_ASSERT(win);
   nsIDocument* doc = win->GetExtantDoc();
 
   if (!EventStateManager::IsHandlingUserInput()) {
     nsString msg = NS_LITERAL_STRING(
         "User activation is now required to call PaymentRequest.show()");
     nsContentUtils::ReportToConsoleNonLocalized(
         msg, nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Security"), doc);
     if (StaticPrefs::dom_payments_request_user_interaction_required()) {
       aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
       return nullptr;
     }
   }
 
-  if (!doc || !doc->IsCurrentActiveDocument()) {
-    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
-    return nullptr;
-  }
-
   if (mState != eCreated) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   ErrorResult result;
   RefPtr<Promise> promise = Promise::Create(global, result);
   if (result.Failed()) {
@@ -787,16 +791,21 @@ void PaymentRequest::RespondShowPayment(
 }
 
 void PaymentRequest::RespondComplete() {
   MOZ_ASSERT(mResponse);
   mResponse->RespondComplete();
 }
 
 already_AddRefed<Promise> PaymentRequest::Abort(ErrorResult& aRv) {
+  if (!InFullyActiveDocument()) {
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
+  }
+
   if (mState != eInteractive) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   if (mAbortPromise) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
@@ -867,16 +876,21 @@ nsresult PaymentRequest::UpdatePayment(J
   nsresult rv = manager->UpdatePayment(aCx, this, aDetails, mRequestShipping);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 void PaymentRequest::AbortUpdate(nsresult aRv) {
+  // perfect ignoring when the document is not fully active.
+  if (!InFullyActiveDocument()) {
+    return;
+  }
+
   MOZ_ASSERT(NS_FAILED(aRv));
 
   if (mState != eInteractive) {
     return;
   }
   // Close down any remaining user interface.
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   MOZ_ASSERT(manager);
@@ -1050,16 +1064,20 @@ void PaymentRequest::GetOptions(PaymentO
 }
 
 void PaymentRequest::SetOptions(const PaymentOptions& aOptions) {
   mOptions = aOptions;
 }
 
 void PaymentRequest::ResolvedCallback(JSContext* aCx,
                                       JS::Handle<JS::Value> aValue) {
+  if (!InFullyActiveDocument()) {
+    return;
+  }
+
   MOZ_ASSERT(aCx);
   mUpdating = false;
   if (NS_WARN_IF(!aValue.isObject())) {
     return;
   }
 
   // Converting value to a PaymentDetailsUpdate dictionary
   PaymentDetailsUpdate details;
@@ -1079,20 +1097,48 @@ void PaymentRequest::ResolvedCallback(JS
   if (NS_FAILED(UpdatePayment(aCx, details))) {
     AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
     return;
   }
 }
 
 void PaymentRequest::RejectedCallback(JSContext* aCx,
                                       JS::Handle<JS::Value> aValue) {
+  if (!InFullyActiveDocument()) {
+    return;
+  }
+
   mUpdating = false;
   AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
 }
 
+bool PaymentRequest::InFullyActiveDocument() {
+  nsIGlobalObject* global = GetOwnerGlobal();
+  if (!global) {
+    return false;
+  }
+
+  nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
+  nsIDocument* doc = win->GetExtantDoc();
+  if (!doc || !doc->IsCurrentActiveDocument()) {
+    return false;
+  }
+
+  // According to the definition of the fully active document, recursive
+  // checking the parent document are all IsCurrentActiveDocument
+  nsIDocument* parentDoc = doc->GetParentDocument();
+  while (parentDoc) {
+    if (parentDoc && !parentDoc->IsCurrentActiveDocument()) {
+      return false;
+    }
+    parentDoc = parentDoc->GetParentDocument();
+  }
+  return true;
+}
+
 void PaymentRequest::RegisterActivityObserver() {
   if (nsPIDOMWindowInner* window = GetOwner()) {
     nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
     if (doc) {
       doc->RegisterActivityObserver(
           NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
     }
   }
@@ -1109,17 +1155,36 @@ void PaymentRequest::UnregisterActivityO
 }
 
 void PaymentRequest::NotifyOwnerDocumentActivityChanged() {
   nsPIDOMWindowInner* window = GetOwner();
   NS_ENSURE_TRUE_VOID(window);
   nsIDocument* doc = window->GetExtantDoc();
   NS_ENSURE_TRUE_VOID(doc);
 
-  if (!doc->IsCurrentActiveDocument()) {
+  if (!InFullyActiveDocument()) {
+    if (mState == eInteractive) {
+      if (mAcceptPromise) {
+        mAcceptPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+        mAcceptPromise = nullptr;
+      }
+      if (mResponse) {
+        mResponse->RejectRetry(NS_ERROR_DOM_ABORT_ERR);
+      }
+      if (mAbortPromise) {
+        mAbortPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+        mAbortPromise = nullptr;
+      }
+    }
+    if (mState == eCreated) {
+      if (mResultPromise) {
+        mResultPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+        mResultPromise = nullptr;
+      }
+    }
     RefPtr<PaymentRequestManager> mgr = PaymentRequestManager::GetSingleton();
     mgr->ClosePayment(this);
   }
 }
 
 PaymentRequest::~PaymentRequest() {
   if (mIPC) {
     // If we're being destroyed, the PaymentRequestManager isn't holding any
--- a/dom/payments/PaymentRequest.h
+++ b/dom/payments/PaymentRequest.h
@@ -198,16 +198,18 @@ class PaymentRequest final : public DOME
   inline void ShippingWasRequested() { mRequestShipping = true; }
 
   nsresult UpdatePaymentMethod(const nsAString& aMethodName,
                                const ChangeDetails& aMethodDetails);
 
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
+  bool InFullyActiveDocument();
+
   IMPL_EVENT_HANDLER(merchantvalidation);
   IMPL_EVENT_HANDLER(shippingaddresschange);
   IMPL_EVENT_HANDLER(shippingoptionchange);
   IMPL_EVENT_HANDLER(paymentmethodchange);
 
   void SetIPC(PaymentRequestChild* aChild) { mIPC = aChild; }
 
   PaymentRequestChild* GetIPC() const { return mIPC; }
--- a/dom/payments/PaymentRequestUpdateEvent.cpp
+++ b/dom/payments/PaymentRequestUpdateEvent.cpp
@@ -47,16 +47,19 @@ PaymentRequestUpdateEvent::PaymentReques
       mRequest(nullptr) {
   MOZ_ASSERT(aOwner);
 }
 
 void PaymentRequestUpdateEvent::ResolvedCallback(JSContext* aCx,
                                                  JS::Handle<JS::Value> aValue) {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(mRequest);
+  if (!mRequest->InFullyActiveDocument()) {
+    return;
+  }
 
   if (NS_WARN_IF(!aValue.isObject()) || !mWaitForUpdate) {
     return;
   }
 
   // Converting value to a PaymentDetailsUpdate dictionary
   PaymentDetailsUpdate details;
   if (!details.Init(aCx, aValue)) {
@@ -84,30 +87,36 @@ void PaymentRequestUpdateEvent::Resolved
   }
   mWaitForUpdate = false;
   mRequest->SetUpdating(false);
 }
 
 void PaymentRequestUpdateEvent::RejectedCallback(JSContext* aCx,
                                                  JS::Handle<JS::Value> aValue) {
   MOZ_ASSERT(mRequest);
+  if (!mRequest->InFullyActiveDocument()) {
+    return;
+  }
 
   mRequest->AbortUpdate(NS_ERROR_DOM_ABORT_ERR);
   mWaitForUpdate = false;
   mRequest->SetUpdating(false);
 }
 
 void PaymentRequestUpdateEvent::UpdateWith(Promise& aPromise,
                                            ErrorResult& aRv) {
   if (!IsTrusted()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   MOZ_ASSERT(mRequest);
+  if (!mRequest->InFullyActiveDocument()) {
+    return;
+  }
 
   if (mWaitForUpdate || !mRequest->ReadyForUpdate()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   aPromise.AppendNativeHandler(this);
 
--- a/dom/payments/PaymentResponse.cpp
+++ b/dom/payments/PaymentResponse.cpp
@@ -171,16 +171,22 @@ void PaymentResponse::GetPayerPhone(nsSt
 // (the object should be kept alive by the callee).
 already_AddRefed<PaymentAddress> PaymentResponse::GetShippingAddress() const {
   RefPtr<PaymentAddress> address = mShippingAddress;
   return address.forget();
 }
 
 already_AddRefed<Promise> PaymentResponse::Complete(PaymentComplete result,
                                                     ErrorResult& aRv) {
+  MOZ_ASSERT(mRequest);
+  if (!mRequest->InFullyActiveDocument()) {
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
+  }
+
   if (mCompleteCalled) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   mCompleteCalled = true;
 
   if (mTimer) {
@@ -221,40 +227,35 @@ void PaymentResponse::RespondComplete() 
   if (mPromise) {
     mPromise->MaybeResolve(JS::UndefinedHandleValue);
     mPromise = nullptr;
   }
 }
 
 already_AddRefed<Promise> PaymentResponse::Retry(
     JSContext* aCx, const PaymentValidationErrors& aErrors, ErrorResult& aRv) {
+  MOZ_ASSERT(mRequest);
+  if (!mRequest->InFullyActiveDocument()) {
+    aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
+    return nullptr;
+  }
+
   nsIGlobalObject* global = GetOwner()->AsGlobal();
   ErrorResult errResult;
   RefPtr<Promise> promise = Promise::Create(global, errResult);
   if (errResult.Failed()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   if (mTimer) {
     mTimer->Cancel();
     mTimer = nullptr;
   }
 
-  if (NS_WARN_IF(!GetOwner())) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
-
-  nsIDocument* doc = GetOwner()->GetExtantDoc();
-  if (!doc || !doc->IsCurrentActiveDocument()) {
-    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-    return promise.forget();
-  }
-
   if (mCompleteCalled || mRetryPromise) {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
     return promise.forget();
   }
 
   nsresult rv = ValidatePaymentValidationErrors(aErrors);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     promise->MaybeReject(rv);
@@ -408,16 +409,21 @@ nsresult PaymentResponse::ValidatePaymen
     }
   }
   return NS_ERROR_DOM_ABORT_ERR;
 }
 
 NS_IMETHODIMP
 PaymentResponse::Notify(nsITimer* timer) {
   mTimer = nullptr;
+
+  if (!mRequest->InFullyActiveDocument()) {
+    return NS_OK;
+  }
+
   if (mCompleteCalled) {
     return NS_OK;
   }
 
   mCompleteCalled = true;
 
   RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
   if (NS_WARN_IF(!manager)) {