зеркало из https://github.com/mozilla/gecko-dev.git
1244 строки
40 KiB
C++
1244 строки
40 KiB
C++
/* -*- 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 "BasicCardPayment.h"
|
||
#include "mozilla/dom/Document.h"
|
||
#include "mozilla/dom/Element.h"
|
||
#include "mozilla/dom/FeaturePolicyUtils.h"
|
||
#include "mozilla/dom/PaymentMethodChangeEvent.h"
|
||
#include "mozilla/dom/PaymentRequest.h"
|
||
#include "mozilla/dom/PaymentRequestChild.h"
|
||
#include "mozilla/dom/PaymentRequestManager.h"
|
||
#include "mozilla/dom/RootedDictionary.h"
|
||
#include "mozilla/dom/UserActivation.h"
|
||
#include "mozilla/intl/LocaleService.h"
|
||
#include "mozilla/intl/MozLocale.h"
|
||
#include "mozilla/StaticPrefs_dom.h"
|
||
#include "nsContentUtils.h"
|
||
#include "nsImportModule.h"
|
||
#include "nsIRegion.h"
|
||
#include "nsIScriptError.h"
|
||
#include "nsIURLParser.h"
|
||
#include "nsNetCID.h"
|
||
#include "nsServiceManagerUtils.h"
|
||
#include "mozilla/dom/MerchantValidationEvent.h"
|
||
#include "PaymentResponse.h"
|
||
|
||
using mozilla::intl::LocaleService;
|
||
|
||
namespace mozilla::dom {
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentRequest)
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentRequest,
|
||
DOMEventTargetHelper)
|
||
// Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
|
||
// DOMEventTargetHelper does it for us.
|
||
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentRequest,
|
||
DOMEventTargetHelper)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultPromise)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAcceptPromise)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortPromise)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponse)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFullShippingAddress)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
|
||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentRequest,
|
||
DOMEventTargetHelper)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResultPromise)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAcceptPromise)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortPromise)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponse)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFullShippingAddress)
|
||
tmp->UnregisterActivityObserver();
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
|
||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||
|
||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentRequest)
|
||
NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
|
||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||
|
||
NS_IMPL_ADDREF_INHERITED(PaymentRequest, DOMEventTargetHelper)
|
||
NS_IMPL_RELEASE_INHERITED(PaymentRequest, DOMEventTargetHelper)
|
||
|
||
bool PaymentRequest::PrefEnabled(JSContext* aCx, JSObject* aObj) {
|
||
#if defined(NIGHTLY_BUILD)
|
||
if (!XRE_IsContentProcess()) {
|
||
return false;
|
||
}
|
||
if (!StaticPrefs::dom_payments_request_enabled()) {
|
||
return false;
|
||
}
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
MOZ_ASSERT(manager);
|
||
|
||
nsCOMPtr<nsIRegion> regionJsm =
|
||
do_ImportModule("resource://gre/modules/Region.jsm", "Region");
|
||
nsAutoString region;
|
||
nsresult rv = regionJsm->GetHome(region);
|
||
if (NS_FAILED(rv)) {
|
||
return false;
|
||
}
|
||
|
||
if (!manager->IsRegionSupported(region)) {
|
||
return false;
|
||
}
|
||
nsAutoCString locale;
|
||
LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
|
||
mozilla::intl::Locale loc = mozilla::intl::Locale(locale);
|
||
if (!(loc.GetLanguage() == "en" && loc.GetRegion() == "US")) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
#else
|
||
return false;
|
||
#endif
|
||
}
|
||
|
||
void PaymentRequest::IsValidStandardizedPMI(const nsAString& aIdentifier,
|
||
ErrorResult& aRv) {
|
||
/*
|
||
* The syntax of a standardized payment method identifier is given by the
|
||
* following [ABNF]:
|
||
*
|
||
* stdpmi = part *( "-" part )
|
||
* part = 1loweralpha *( DIGIT / loweralpha )
|
||
* loweralpha = %x61-7A
|
||
*/
|
||
const char16_t* start = aIdentifier.BeginReading();
|
||
const char16_t* end = aIdentifier.EndReading();
|
||
while (start != end) {
|
||
// the first char must be in the range %x61-7A
|
||
if ((*start < 'a' || *start > 'z')) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("' is not valid. The character '");
|
||
error.Append(NS_ConvertUTF16toUTF8(start, 1));
|
||
error.AppendLiteral(
|
||
"' at the beginning or after the '-' must be in the range [a-z].");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
++start;
|
||
// the rest can be in the range %x61-7A + DIGITs
|
||
while (start != end && *start != '-' &&
|
||
((*start >= 'a' && *start <= 'z') ||
|
||
(*start >= '0' && *start <= '9'))) {
|
||
++start;
|
||
}
|
||
// if the char is not in the range %x61-7A + DIGITs, it must be '-'
|
||
if (start != end && *start != '-') {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("' is not valid. The character '");
|
||
error.Append(NS_ConvertUTF16toUTF8(start, 1));
|
||
error.AppendLiteral("' must be in the range [a-zA-z0-9-].");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
if (*start == '-') {
|
||
++start;
|
||
// the last char can not be '-'
|
||
if (start == end) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("' is not valid. The last character '");
|
||
error.Append(NS_ConvertUTF16toUTF8(start, 1));
|
||
error.AppendLiteral("' must be in the range [a-z0-9].");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::IsValidPaymentMethodIdentifier(
|
||
const nsAString& aIdentifier, ErrorResult& aRv) {
|
||
if (aIdentifier.IsEmpty()) {
|
||
aRv.ThrowTypeError("Payment method identifier is required.");
|
||
return;
|
||
}
|
||
/*
|
||
* URL-based payment method identifier
|
||
*
|
||
* 1. If url's scheme is not "https", return false.
|
||
* 2. If url's username or password is not the empty string, return false.
|
||
* 3. Otherwise, return true.
|
||
*/
|
||
nsCOMPtr<nsIURLParser> urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID);
|
||
MOZ_ASSERT(urlParser);
|
||
uint32_t schemePos = 0;
|
||
int32_t schemeLen = 0;
|
||
uint32_t authorityPos = 0;
|
||
int32_t authorityLen = 0;
|
||
NS_ConvertUTF16toUTF8 url(aIdentifier);
|
||
nsresult rv =
|
||
urlParser->ParseURL(url.get(), url.Length(), &schemePos, &schemeLen,
|
||
&authorityPos, &authorityLen, nullptr, nullptr);
|
||
if (NS_FAILED(rv)) {
|
||
nsAutoCString error;
|
||
error.AppendLiteral("Error parsing payment method identifier '");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("'as a URL.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
|
||
if (schemeLen == -1) {
|
||
// The PMI is not a URL-based PMI, check if it is a standardized PMI
|
||
IsValidStandardizedPMI(aIdentifier, aRv);
|
||
return;
|
||
}
|
||
if (!Substring(aIdentifier, schemePos, schemeLen).EqualsASCII("https")) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("' is not valid. The scheme must be 'https'.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
if (Substring(aIdentifier, authorityPos, authorityLen).IsEmpty()) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("' is not valid. hostname can not be empty.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
|
||
uint32_t usernamePos = 0;
|
||
int32_t usernameLen = 0;
|
||
uint32_t passwordPos = 0;
|
||
int32_t passwordLen = 0;
|
||
uint32_t hostnamePos = 0;
|
||
int32_t hostnameLen = 0;
|
||
int32_t port = 0;
|
||
|
||
NS_ConvertUTF16toUTF8 authority(
|
||
Substring(aIdentifier, authorityPos, authorityLen));
|
||
rv = urlParser->ParseAuthority(
|
||
authority.get(), authority.Length(), &usernamePos, &usernameLen,
|
||
&passwordPos, &passwordLen, &hostnamePos, &hostnameLen, &port);
|
||
if (NS_FAILED(rv)) {
|
||
// Handle the special cases that URLParser treats it as an invalid URL, but
|
||
// are used in web-platform-test
|
||
// For exmaple:
|
||
// https://:@example.com // should be considered as valid
|
||
// https://:password@example.com. // should be considered as invalid
|
||
int32_t atPos = authority.FindChar('@');
|
||
if (atPos >= 0) {
|
||
// only accept the case https://:@xxx
|
||
if (atPos == 1 && authority.CharAt(0) == ':') {
|
||
usernamePos = 0;
|
||
usernameLen = 0;
|
||
passwordPos = 0;
|
||
passwordLen = 0;
|
||
} else {
|
||
// for the fail cases, don't care about what the actual length is.
|
||
usernamePos = 0;
|
||
usernameLen = INT32_MAX;
|
||
passwordPos = 0;
|
||
passwordLen = INT32_MAX;
|
||
}
|
||
} else {
|
||
usernamePos = 0;
|
||
usernameLen = -1;
|
||
passwordPos = 0;
|
||
passwordLen = -1;
|
||
}
|
||
// Parse server information when both username and password are empty or do
|
||
// not exist.
|
||
if ((usernameLen <= 0) && (passwordLen <= 0)) {
|
||
if (authority.Length() - atPos - 1 == 0) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("' is not valid. hostname can not be empty.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
// Re-using nsIURLParser::ParseServerInfo to extract the hostname and port
|
||
// information. This can help us to handle complicated IPv6 cases.
|
||
nsAutoCString serverInfo(
|
||
Substring(authority, atPos + 1, authority.Length() - atPos - 1));
|
||
rv = urlParser->ParseServerInfo(serverInfo.get(), serverInfo.Length(),
|
||
&hostnamePos, &hostnameLen, &port);
|
||
if (NS_FAILED(rv)) {
|
||
// ParseServerInfo returns NS_ERROR_MALFORMED_URI in all fail cases, we
|
||
// probably need a followup bug to figure out the fail reason.
|
||
nsAutoCString error;
|
||
error.AssignLiteral("Error extracting hostname from '");
|
||
error.Append(serverInfo);
|
||
error.AppendLiteral("'.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
// PMI is valid when usernameLen/passwordLen equals to -1 or 0.
|
||
if (usernameLen > 0 || passwordLen > 0) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AssignLiteral("' is not valid. Username and password must be empty.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
|
||
// PMI is valid when hostnameLen is larger than 0
|
||
if (hostnameLen <= 0) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("'");
|
||
error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
|
||
error.AppendLiteral("' is not valid. hostname can not be empty.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::IsValidMethodData(
|
||
JSContext* aCx, const Sequence<PaymentMethodData>& aMethodData,
|
||
ErrorResult& aRv) {
|
||
if (!aMethodData.Length()) {
|
||
aRv.ThrowTypeError("At least one payment method is required.");
|
||
return;
|
||
}
|
||
|
||
nsTArray<nsString> methods;
|
||
for (const PaymentMethodData& methodData : aMethodData) {
|
||
IsValidPaymentMethodIdentifier(methodData.mSupportedMethods, aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
|
||
RefPtr<BasicCardService> service = BasicCardService::GetService();
|
||
MOZ_ASSERT(service);
|
||
if (service->IsBasicCardPayment(methodData.mSupportedMethods)) {
|
||
if (!methodData.mData.WasPassed()) {
|
||
continue;
|
||
}
|
||
MOZ_ASSERT(aCx);
|
||
nsAutoString error;
|
||
if (!service->IsValidBasicCardRequest(aCx, methodData.mData.Value(),
|
||
error)) {
|
||
aRv.ThrowTypeError(NS_ConvertUTF16toUTF8(error));
|
||
return;
|
||
}
|
||
}
|
||
if (!methods.Contains(methodData.mSupportedMethods)) {
|
||
methods.AppendElement(methodData.mSupportedMethods);
|
||
} else {
|
||
aRv.ThrowRangeError(nsPrintfCString(
|
||
"Duplicate payment method '%s'",
|
||
NS_ConvertUTF16toUTF8(methodData.mSupportedMethods).get()));
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::IsValidNumber(const nsAString& aItem,
|
||
const nsAString& aStr, ErrorResult& aRv) {
|
||
nsresult error = NS_ERROR_FAILURE;
|
||
|
||
if (!aStr.IsEmpty()) {
|
||
nsAutoString aValue(aStr);
|
||
|
||
// If the beginning character is '-', we will check the second one.
|
||
int beginningIndex = (aValue.First() == '-') ? 1 : 0;
|
||
|
||
// Ensure
|
||
// - the beginning character is a digit in [0-9], and
|
||
// - the last character is not '.'
|
||
// to follow spec:
|
||
// https://w3c.github.io/browser-payment-api/#dfn-valid-decimal-monetary-value
|
||
//
|
||
// For example, ".1" is not valid for '.' is not in [0-9],
|
||
// and " 0.1" either for beginning with ' '
|
||
if (aValue.Last() != '.' && aValue.CharAt(beginningIndex) >= '0' &&
|
||
aValue.CharAt(beginningIndex) <= '9') {
|
||
aValue.ToFloat(&error);
|
||
}
|
||
}
|
||
|
||
if (NS_FAILED(error)) {
|
||
nsAutoCString errorMsg;
|
||
errorMsg.AssignLiteral("The amount.value of \"");
|
||
errorMsg.Append(NS_ConvertUTF16toUTF8(aItem));
|
||
errorMsg.AppendLiteral("\"(");
|
||
errorMsg.Append(NS_ConvertUTF16toUTF8(aStr));
|
||
errorMsg.AppendLiteral(") must be a valid decimal monetary value.");
|
||
aRv.ThrowTypeError(errorMsg);
|
||
return;
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::IsNonNegativeNumber(const nsAString& aItem,
|
||
const nsAString& aStr,
|
||
ErrorResult& aRv) {
|
||
nsresult error = NS_ERROR_FAILURE;
|
||
|
||
if (!aStr.IsEmpty()) {
|
||
nsAutoString aValue(aStr);
|
||
// Ensure
|
||
// - the beginning character is a digit in [0-9], and
|
||
// - the last character is not '.'
|
||
if (aValue.Last() != '.' && aValue.First() >= '0' &&
|
||
aValue.First() <= '9') {
|
||
aValue.ToFloat(&error);
|
||
}
|
||
}
|
||
|
||
if (NS_FAILED(error)) {
|
||
nsAutoCString errorMsg;
|
||
errorMsg.AssignLiteral("The amount.value of \"");
|
||
errorMsg.Append(NS_ConvertUTF16toUTF8(aItem));
|
||
errorMsg.AppendLiteral("\"(");
|
||
errorMsg.Append(NS_ConvertUTF16toUTF8(aStr));
|
||
errorMsg.AppendLiteral(
|
||
") must be a valid and non-negative decimal monetary value.");
|
||
aRv.ThrowTypeError(errorMsg);
|
||
return;
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::IsValidCurrency(const nsAString& aItem,
|
||
const nsAString& aCurrency,
|
||
ErrorResult& aRv) {
|
||
/*
|
||
* According to spec in
|
||
* https://w3c.github.io/payment-request/#validity-checkers, perform currency
|
||
* validation with following criteria
|
||
* 1. The currency length must be 3.
|
||
* 2. The currency contains any character that must be in the range "A" to
|
||
* "Z" (U+0041 to U+005A) or the range "a" to "z" (U+0061 to U+007A)
|
||
*/
|
||
if (aCurrency.Length() != 3) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("The length amount.currency of \"");
|
||
error.Append(NS_ConvertUTF16toUTF8(aItem));
|
||
error.AppendLiteral("\"(");
|
||
error.Append(NS_ConvertUTF16toUTF8(aCurrency));
|
||
error.AppendLiteral(") must be 3.");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
// Don't use nsUnicharUtils::ToUpperCase, it converts the invalid "ınr" PMI to
|
||
// to the valid one "INR".
|
||
for (uint32_t idx = 0; idx < aCurrency.Length(); ++idx) {
|
||
if ((aCurrency.CharAt(idx) >= 'A' && aCurrency.CharAt(idx) <= 'Z') ||
|
||
(aCurrency.CharAt(idx) >= 'a' && aCurrency.CharAt(idx) <= 'z')) {
|
||
continue;
|
||
}
|
||
nsAutoCString error;
|
||
error.AssignLiteral("The character amount.currency of \"");
|
||
error.Append(NS_ConvertUTF16toUTF8(aItem));
|
||
error.AppendLiteral("\"(");
|
||
error.Append(NS_ConvertUTF16toUTF8(aCurrency));
|
||
error.AppendLiteral(
|
||
") must be in the range 'A' to 'Z'(U+0041 to U+005A) or 'a' to "
|
||
"'z'(U+0061 to U+007A).");
|
||
aRv.ThrowRangeError(error);
|
||
return;
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::IsValidCurrencyAmount(const nsAString& aItem,
|
||
const PaymentCurrencyAmount& aAmount,
|
||
const bool aIsTotalItem,
|
||
ErrorResult& aRv) {
|
||
IsValidCurrency(aItem, aAmount.mCurrency, aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
if (aIsTotalItem) {
|
||
IsNonNegativeNumber(aItem, aAmount.mValue, aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
} else {
|
||
IsValidNumber(aItem, aAmount.mValue, aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::IsValidDetailsInit(const PaymentDetailsInit& aDetails,
|
||
const bool aRequestShipping,
|
||
ErrorResult& aRv) {
|
||
// Check the amount.value and amount.currency of detail.total
|
||
IsValidCurrencyAmount(u"details.total"_ns, aDetails.mTotal.mAmount,
|
||
true, // isTotalItem
|
||
aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
return IsValidDetailsBase(aDetails, aRequestShipping, aRv);
|
||
}
|
||
|
||
void PaymentRequest::IsValidDetailsUpdate(const PaymentDetailsUpdate& aDetails,
|
||
const bool aRequestShipping,
|
||
ErrorResult& aRv) {
|
||
// Check the amount.value and amount.currency of detail.total
|
||
if (aDetails.mTotal.WasPassed()) {
|
||
IsValidCurrencyAmount(u"details.total"_ns, aDetails.mTotal.Value().mAmount,
|
||
true, // isTotalItem
|
||
aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
}
|
||
IsValidDetailsBase(aDetails, aRequestShipping, aRv);
|
||
}
|
||
|
||
void PaymentRequest::IsValidDetailsBase(const PaymentDetailsBase& aDetails,
|
||
const bool aRequestShipping,
|
||
ErrorResult& aRv) {
|
||
// Check the amount.value of each item in the display items
|
||
if (aDetails.mDisplayItems.WasPassed()) {
|
||
const Sequence<PaymentItem>& displayItems = aDetails.mDisplayItems.Value();
|
||
for (const PaymentItem& displayItem : displayItems) {
|
||
IsValidCurrencyAmount(displayItem.mLabel, displayItem.mAmount,
|
||
false, // isTotalItem
|
||
aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Check the shipping option
|
||
if (aDetails.mShippingOptions.WasPassed() && aRequestShipping) {
|
||
const Sequence<PaymentShippingOption>& shippingOptions =
|
||
aDetails.mShippingOptions.Value();
|
||
nsTArray<nsString> seenIDs;
|
||
for (const PaymentShippingOption& shippingOption : shippingOptions) {
|
||
IsValidCurrencyAmount(u"details.shippingOptions"_ns,
|
||
shippingOption.mAmount,
|
||
false, // isTotalItem
|
||
aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
if (seenIDs.Contains(shippingOption.mId)) {
|
||
nsAutoCString error;
|
||
error.AssignLiteral("Duplicate shippingOption id '");
|
||
error.Append(NS_ConvertUTF16toUTF8(shippingOption.mId));
|
||
error.AppendLiteral("'");
|
||
aRv.ThrowTypeError(error);
|
||
return;
|
||
}
|
||
seenIDs.AppendElement(shippingOption.mId);
|
||
}
|
||
}
|
||
|
||
// Check payment details modifiers
|
||
if (aDetails.mModifiers.WasPassed()) {
|
||
const Sequence<PaymentDetailsModifier>& modifiers =
|
||
aDetails.mModifiers.Value();
|
||
for (const PaymentDetailsModifier& modifier : modifiers) {
|
||
IsValidPaymentMethodIdentifier(modifier.mSupportedMethods, aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
if (modifier.mTotal.WasPassed()) {
|
||
IsValidCurrencyAmount(u"details.modifiers.total"_ns,
|
||
modifier.mTotal.Value().mAmount,
|
||
true, // isTotalItem
|
||
aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
}
|
||
if (modifier.mAdditionalDisplayItems.WasPassed()) {
|
||
const Sequence<PaymentItem>& displayItems =
|
||
modifier.mAdditionalDisplayItems.Value();
|
||
for (const PaymentItem& displayItem : displayItems) {
|
||
IsValidCurrencyAmount(displayItem.mLabel, displayItem.mAmount,
|
||
false, // isTotalItem
|
||
aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
already_AddRefed<PaymentRequest> PaymentRequest::Constructor(
|
||
const GlobalObject& aGlobal, const Sequence<PaymentMethodData>& aMethodData,
|
||
const PaymentDetailsInit& aDetails, const PaymentOptions& aOptions,
|
||
ErrorResult& aRv) {
|
||
nsCOMPtr<nsPIDOMWindowInner> window =
|
||
do_QueryInterface(aGlobal.GetAsSupports());
|
||
if (!window) {
|
||
aRv.ThrowAbortError("No global object for creating PaymentRequest");
|
||
return nullptr;
|
||
}
|
||
|
||
nsCOMPtr<Document> doc = window->GetExtantDoc();
|
||
if (!doc) {
|
||
aRv.ThrowAbortError("No document for creating PaymentRequest");
|
||
return nullptr;
|
||
}
|
||
|
||
// the feature can only be used in an active document
|
||
if (!doc->IsCurrentActiveDocument()) {
|
||
aRv.ThrowSecurityError(
|
||
"Can't create a PaymentRequest for an inactive document");
|
||
return nullptr;
|
||
}
|
||
|
||
if (!FeaturePolicyUtils::IsFeatureAllowed(doc, u"payment"_ns)) {
|
||
aRv.ThrowSecurityError(
|
||
"Document's Feature Policy does not allow to create a PaymentRequest");
|
||
return nullptr;
|
||
}
|
||
|
||
// Get the top level principal
|
||
nsCOMPtr<Document> topLevelDoc = doc->GetTopLevelContentDocument();
|
||
MOZ_ASSERT(topLevelDoc);
|
||
nsCOMPtr<nsIPrincipal> topLevelPrincipal = topLevelDoc->NodePrincipal();
|
||
|
||
// Check payment methods and details
|
||
IsValidMethodData(aGlobal.Context(), aMethodData, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
IsValidDetailsInit(aDetails, aOptions.mRequestShipping, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
if (NS_WARN_IF(!manager)) {
|
||
return nullptr;
|
||
}
|
||
|
||
// Create PaymentRequest and set its |mId|
|
||
RefPtr<PaymentRequest> request;
|
||
manager->CreatePayment(aGlobal.Context(), window, topLevelPrincipal,
|
||
aMethodData, aDetails, aOptions,
|
||
getter_AddRefs(request), aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
return request.forget();
|
||
}
|
||
|
||
already_AddRefed<PaymentRequest> PaymentRequest::CreatePaymentRequest(
|
||
nsPIDOMWindowInner* aWindow, ErrorResult& aRv) {
|
||
// Generate a unique id for identification
|
||
nsID uuid;
|
||
if (NS_WARN_IF(NS_FAILED(nsContentUtils::GenerateUUIDInPlace(uuid)))) {
|
||
aRv.ThrowAbortError(
|
||
"Failed to create an internal UUID for the PaymentRequest");
|
||
return nullptr;
|
||
}
|
||
|
||
// Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
|
||
char buffer[NSID_LENGTH];
|
||
uuid.ToProvidedString(buffer);
|
||
|
||
// Remove {} and the null terminator
|
||
nsAutoString id;
|
||
id.AssignASCII(&buffer[1], NSID_LENGTH - 3);
|
||
|
||
// Create payment request with generated id
|
||
RefPtr<PaymentRequest> request = new PaymentRequest(aWindow, id);
|
||
return request.forget();
|
||
}
|
||
|
||
PaymentRequest::PaymentRequest(nsPIDOMWindowInner* aWindow,
|
||
const nsAString& aInternalId)
|
||
: DOMEventTargetHelper(aWindow),
|
||
mInternalId(aInternalId),
|
||
mShippingAddress(nullptr),
|
||
mUpdating(false),
|
||
mRequestShipping(false),
|
||
mState(eCreated),
|
||
mIPC(nullptr) {
|
||
MOZ_ASSERT(aWindow);
|
||
RegisterActivityObserver();
|
||
}
|
||
|
||
already_AddRefed<Promise> PaymentRequest::CanMakePayment(ErrorResult& aRv) {
|
||
if (!InFullyActiveDocument()) {
|
||
aRv.ThrowAbortError("The owner document is not fully active");
|
||
return nullptr;
|
||
}
|
||
|
||
if (mState != eCreated) {
|
||
aRv.ThrowInvalidStateError(
|
||
"The PaymentRequest's state should be 'Created'");
|
||
return nullptr;
|
||
}
|
||
|
||
if (mResultPromise) {
|
||
// XXX This doesn't match the spec but does match Chromium.
|
||
aRv.ThrowNotAllowedError(
|
||
"PaymentRequest.CanMakePayment() has already been called");
|
||
return nullptr;
|
||
}
|
||
|
||
nsIGlobalObject* global = GetOwnerGlobal();
|
||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
MOZ_ASSERT(manager);
|
||
manager->CanMakePayment(this, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
mResultPromise = promise;
|
||
return promise.forget();
|
||
}
|
||
|
||
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.ThrowAbortError("The owner document is not fully active");
|
||
return nullptr;
|
||
}
|
||
|
||
nsIGlobalObject* global = GetOwnerGlobal();
|
||
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
|
||
Document* doc = win->GetExtantDoc();
|
||
|
||
if (!UserActivation::IsHandlingUserInput()) {
|
||
nsString msg = nsLiteralString(
|
||
u"User activation is now required to call PaymentRequest.show()");
|
||
nsContentUtils::ReportToConsoleNonLocalized(
|
||
msg, nsIScriptError::warningFlag, "Security"_ns, doc);
|
||
if (StaticPrefs::dom_payments_request_user_interaction_required()) {
|
||
aRv.ThrowSecurityError(NS_ConvertUTF16toUTF8(msg));
|
||
return nullptr;
|
||
}
|
||
}
|
||
|
||
if (mState != eCreated) {
|
||
aRv.ThrowInvalidStateError(
|
||
"The PaymentRequest's state should be 'Created'");
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||
if (aRv.Failed()) {
|
||
mState = eClosed;
|
||
return nullptr;
|
||
}
|
||
|
||
if (aDetailsPromise.WasPassed()) {
|
||
aDetailsPromise.Value().AppendNativeHandler(this);
|
||
mUpdating = true;
|
||
}
|
||
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
MOZ_ASSERT(manager);
|
||
manager->ShowPayment(this, aRv);
|
||
if (aRv.Failed()) {
|
||
mState = eClosed;
|
||
return nullptr;
|
||
}
|
||
|
||
mAcceptPromise = promise;
|
||
mState = eInteractive;
|
||
return promise.forget();
|
||
}
|
||
|
||
void PaymentRequest::RejectShowPayment(ErrorResult&& aRejectReason) {
|
||
MOZ_ASSERT(mAcceptPromise || mResponse);
|
||
MOZ_ASSERT(mState == eInteractive);
|
||
|
||
if (mResponse) {
|
||
mResponse->RejectRetry(std::move(aRejectReason));
|
||
} else {
|
||
mAcceptPromise->MaybeReject(std::move(aRejectReason));
|
||
}
|
||
mState = eClosed;
|
||
mAcceptPromise = nullptr;
|
||
}
|
||
|
||
void PaymentRequest::RespondShowPayment(const nsAString& aMethodName,
|
||
const ResponseData& aDetails,
|
||
const nsAString& aPayerName,
|
||
const nsAString& aPayerEmail,
|
||
const nsAString& aPayerPhone,
|
||
ErrorResult&& aResult) {
|
||
MOZ_ASSERT(mAcceptPromise || mResponse);
|
||
MOZ_ASSERT(mState == eInteractive);
|
||
|
||
if (aResult.Failed()) {
|
||
RejectShowPayment(std::move(aResult));
|
||
return;
|
||
}
|
||
|
||
// https://github.com/w3c/payment-request/issues/692
|
||
mShippingAddress.swap(mFullShippingAddress);
|
||
mFullShippingAddress = nullptr;
|
||
|
||
if (mResponse) {
|
||
mResponse->RespondRetry(aMethodName, mShippingOption, mShippingAddress,
|
||
aDetails, aPayerName, aPayerEmail, aPayerPhone);
|
||
} else {
|
||
RefPtr<PaymentResponse> paymentResponse = new PaymentResponse(
|
||
GetOwner(), this, mId, aMethodName, mShippingOption, mShippingAddress,
|
||
aDetails, aPayerName, aPayerEmail, aPayerPhone);
|
||
mResponse = paymentResponse;
|
||
mAcceptPromise->MaybeResolve(paymentResponse);
|
||
}
|
||
|
||
mState = eClosed;
|
||
mAcceptPromise = nullptr;
|
||
}
|
||
|
||
void PaymentRequest::RespondComplete() {
|
||
MOZ_ASSERT(mResponse);
|
||
mResponse->RespondComplete();
|
||
}
|
||
|
||
already_AddRefed<Promise> PaymentRequest::Abort(ErrorResult& aRv) {
|
||
if (!InFullyActiveDocument()) {
|
||
aRv.ThrowAbortError("The owner document is not fully active");
|
||
return nullptr;
|
||
}
|
||
|
||
if (mState != eInteractive) {
|
||
aRv.ThrowSecurityError(
|
||
"The PaymentRequest's state should be 'Interactive'");
|
||
return nullptr;
|
||
}
|
||
|
||
if (mAbortPromise) {
|
||
aRv.ThrowInvalidStateError(
|
||
"PaymentRequest.abort() has already been called");
|
||
return nullptr;
|
||
}
|
||
|
||
nsIGlobalObject* global = GetOwnerGlobal();
|
||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
MOZ_ASSERT(manager);
|
||
manager->AbortPayment(this, aRv);
|
||
if (aRv.Failed()) {
|
||
return nullptr;
|
||
}
|
||
|
||
mAbortPromise = promise;
|
||
return promise.forget();
|
||
}
|
||
|
||
void PaymentRequest::RespondAbortPayment(bool aSuccess) {
|
||
// Check whether we are aborting the update:
|
||
//
|
||
// - If |mUpdateError| is failed, we are aborting the update as
|
||
// |mUpdateError| was set in method |AbortUpdate|.
|
||
// => Reject |mAcceptPromise| and reset |mUpdateError| to complete
|
||
// the action, regardless of |aSuccess|.
|
||
//
|
||
// - Otherwise, we are handling |Abort| method call from merchant.
|
||
// => Resolve/Reject |mAbortPromise| based on |aSuccess|.
|
||
if (mUpdateError.Failed()) {
|
||
// Respond show with mUpdateError, set mUpdating to false.
|
||
mUpdating = false;
|
||
RespondShowPayment(u""_ns, ResponseData(), u""_ns, u""_ns, u""_ns,
|
||
std::move(mUpdateError));
|
||
return;
|
||
}
|
||
|
||
MOZ_ASSERT(mAbortPromise);
|
||
MOZ_ASSERT(mState == eInteractive);
|
||
|
||
if (aSuccess) {
|
||
mAbortPromise->MaybeResolve(JS::UndefinedHandleValue);
|
||
mAbortPromise = nullptr;
|
||
ErrorResult abortResult;
|
||
abortResult.ThrowAbortError("The PaymentRequest is aborted");
|
||
RejectShowPayment(std::move(abortResult));
|
||
} else {
|
||
mAbortPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||
mAbortPromise = nullptr;
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::UpdatePayment(JSContext* aCx,
|
||
const PaymentDetailsUpdate& aDetails,
|
||
ErrorResult& aRv) {
|
||
MOZ_ASSERT(aCx);
|
||
if (mState != eInteractive) {
|
||
aRv.ThrowInvalidStateError(
|
||
"The PaymentRequest state should be 'Interactive'");
|
||
return;
|
||
}
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
MOZ_ASSERT(manager);
|
||
manager->UpdatePayment(aCx, this, aDetails, mRequestShipping, aRv);
|
||
}
|
||
|
||
void PaymentRequest::AbortUpdate(ErrorResult& aReason) {
|
||
// AbortUpdate has the responsiblity to call aReason.SuppressException() when
|
||
// fail to update.
|
||
|
||
MOZ_ASSERT(aReason.Failed());
|
||
|
||
// Completely ignoring the call when the owner document is not fully active.
|
||
if (!InFullyActiveDocument()) {
|
||
aReason.SuppressException();
|
||
return;
|
||
}
|
||
|
||
// Completely ignoring the call when the PaymentRequest state is not
|
||
// eInteractive.
|
||
if (mState != eInteractive) {
|
||
aReason.SuppressException();
|
||
return;
|
||
}
|
||
// Try to close down any remaining user interface. Should recevie
|
||
// RespondAbortPayment from chrome process.
|
||
// Completely ignoring the call when failed to send action to chrome process.
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
MOZ_ASSERT(manager);
|
||
IgnoredErrorResult result;
|
||
manager->AbortPayment(this, result);
|
||
if (result.Failed()) {
|
||
aReason.SuppressException();
|
||
return;
|
||
}
|
||
|
||
// Remember update error |aReason| 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
|
||
mUpdateError = std::move(aReason);
|
||
}
|
||
|
||
void PaymentRequest::RetryPayment(JSContext* aCx,
|
||
const PaymentValidationErrors& aErrors,
|
||
ErrorResult& aRv) {
|
||
if (mState == eInteractive) {
|
||
aRv.ThrowInvalidStateError(
|
||
"Call Retry() when the PaymentReqeust state is 'Interactive'");
|
||
return;
|
||
}
|
||
RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
|
||
MOZ_ASSERT(manager);
|
||
manager->RetryPayment(aCx, this, aErrors, aRv);
|
||
if (aRv.Failed()) {
|
||
return;
|
||
}
|
||
mState = eInteractive;
|
||
}
|
||
|
||
void PaymentRequest::GetId(nsAString& aRetVal) const { aRetVal = mId; }
|
||
|
||
void PaymentRequest::GetInternalId(nsAString& aRetVal) {
|
||
aRetVal = mInternalId;
|
||
}
|
||
|
||
void PaymentRequest::SetId(const nsAString& aId) { mId = aId; }
|
||
|
||
bool PaymentRequest::Equals(const nsAString& aInternalId) const {
|
||
return mInternalId.Equals(aInternalId);
|
||
}
|
||
|
||
bool PaymentRequest::ReadyForUpdate() {
|
||
return mState == eInteractive && !mUpdating;
|
||
}
|
||
|
||
void PaymentRequest::SetUpdating(bool aUpdating) { mUpdating = aUpdating; }
|
||
|
||
already_AddRefed<PaymentResponse> PaymentRequest::GetResponse() const {
|
||
RefPtr<PaymentResponse> response = mResponse;
|
||
return response.forget();
|
||
}
|
||
|
||
nsresult PaymentRequest::DispatchUpdateEvent(const nsAString& aType) {
|
||
MOZ_ASSERT(ReadyForUpdate());
|
||
|
||
PaymentRequestUpdateEventInit init;
|
||
init.mBubbles = false;
|
||
init.mCancelable = false;
|
||
|
||
RefPtr<PaymentRequestUpdateEvent> event =
|
||
PaymentRequestUpdateEvent::Constructor(this, aType, init);
|
||
event->SetTrusted(true);
|
||
event->SetRequest(this);
|
||
|
||
ErrorResult rv;
|
||
DispatchEvent(*event, rv);
|
||
return rv.StealNSResult();
|
||
}
|
||
|
||
nsresult PaymentRequest::DispatchMerchantValidationEvent(
|
||
const nsAString& aType) {
|
||
MOZ_ASSERT(ReadyForUpdate());
|
||
|
||
MerchantValidationEventInit init;
|
||
init.mBubbles = false;
|
||
init.mCancelable = false;
|
||
init.mValidationURL.Truncate();
|
||
|
||
ErrorResult rv;
|
||
RefPtr<MerchantValidationEvent> event =
|
||
MerchantValidationEvent::Constructor(this, aType, init, rv);
|
||
if (rv.Failed()) {
|
||
return rv.StealNSResult();
|
||
}
|
||
event->SetTrusted(true);
|
||
event->SetRequest(this);
|
||
|
||
DispatchEvent(*event, rv);
|
||
return rv.StealNSResult();
|
||
}
|
||
|
||
nsresult PaymentRequest::DispatchPaymentMethodChangeEvent(
|
||
const nsAString& aMethodName, const ChangeDetails& aMethodDetails) {
|
||
MOZ_ASSERT(ReadyForUpdate());
|
||
|
||
PaymentRequestUpdateEventInit init;
|
||
init.mBubbles = false;
|
||
init.mCancelable = false;
|
||
|
||
RefPtr<PaymentMethodChangeEvent> event =
|
||
PaymentMethodChangeEvent::Constructor(this, u"paymentmethodchange"_ns,
|
||
init, aMethodName, aMethodDetails);
|
||
event->SetTrusted(true);
|
||
event->SetRequest(this);
|
||
|
||
ErrorResult rv;
|
||
DispatchEvent(*event, rv);
|
||
return rv.StealNSResult();
|
||
}
|
||
|
||
already_AddRefed<PaymentAddress> PaymentRequest::GetShippingAddress() const {
|
||
RefPtr<PaymentAddress> address = mShippingAddress;
|
||
return address.forget();
|
||
}
|
||
|
||
nsresult PaymentRequest::UpdateShippingAddress(
|
||
const nsAString& aCountry, const nsTArray<nsString>& aAddressLine,
|
||
const nsAString& aRegion, const nsAString& aRegionCode,
|
||
const nsAString& aCity, const nsAString& aDependentLocality,
|
||
const nsAString& aPostalCode, const nsAString& aSortingCode,
|
||
const nsAString& aOrganization, const nsAString& aRecipient,
|
||
const nsAString& aPhone) {
|
||
nsTArray<nsString> emptyArray;
|
||
mShippingAddress = new PaymentAddress(
|
||
GetOwner(), aCountry, emptyArray, aRegion, aRegionCode, aCity,
|
||
aDependentLocality, aPostalCode, aSortingCode, u""_ns, u""_ns, u""_ns);
|
||
mFullShippingAddress =
|
||
new PaymentAddress(GetOwner(), aCountry, aAddressLine, aRegion,
|
||
aRegionCode, aCity, aDependentLocality, aPostalCode,
|
||
aSortingCode, aOrganization, aRecipient, aPhone);
|
||
// Fire shippingaddresschange event
|
||
return DispatchUpdateEvent(u"shippingaddresschange"_ns);
|
||
}
|
||
|
||
void PaymentRequest::SetShippingOption(const nsAString& aShippingOption) {
|
||
mShippingOption = aShippingOption;
|
||
}
|
||
|
||
void PaymentRequest::GetShippingOption(nsAString& aRetVal) const {
|
||
aRetVal = mShippingOption;
|
||
}
|
||
|
||
nsresult PaymentRequest::UpdateShippingOption(
|
||
const nsAString& aShippingOption) {
|
||
mShippingOption = aShippingOption;
|
||
|
||
// Fire shippingaddresschange event
|
||
return DispatchUpdateEvent(u"shippingoptionchange"_ns);
|
||
}
|
||
|
||
nsresult PaymentRequest::UpdatePaymentMethod(
|
||
const nsAString& aMethodName, const ChangeDetails& aMethodDetails) {
|
||
return DispatchPaymentMethodChangeEvent(aMethodName, aMethodDetails);
|
||
}
|
||
|
||
void PaymentRequest::SetShippingType(
|
||
const Nullable<PaymentShippingType>& aShippingType) {
|
||
mShippingType = aShippingType;
|
||
}
|
||
|
||
Nullable<PaymentShippingType> PaymentRequest::GetShippingType() const {
|
||
return mShippingType;
|
||
}
|
||
|
||
void PaymentRequest::GetOptions(PaymentOptions& aRetVal) const {
|
||
aRetVal = mOptions;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
ErrorResult result;
|
||
// Converting value to a PaymentDetailsUpdate dictionary
|
||
RootedDictionary<PaymentDetailsUpdate> details(aCx);
|
||
if (!details.Init(aCx, aValue)) {
|
||
result.StealExceptionFromJSContext(aCx);
|
||
AbortUpdate(result);
|
||
return;
|
||
}
|
||
|
||
IsValidDetailsUpdate(details, mRequestShipping, result);
|
||
if (result.Failed()) {
|
||
AbortUpdate(result);
|
||
return;
|
||
}
|
||
|
||
// Update the PaymentRequest with the new details
|
||
UpdatePayment(aCx, details, result);
|
||
if (result.Failed()) {
|
||
AbortUpdate(result);
|
||
return;
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::RejectedCallback(JSContext* aCx,
|
||
JS::Handle<JS::Value> aValue) {
|
||
if (!InFullyActiveDocument()) {
|
||
return;
|
||
}
|
||
|
||
mUpdating = false;
|
||
ErrorResult result;
|
||
result.ThrowAbortError(
|
||
"Details promise for PaymentRequest.show() is rejected by merchant");
|
||
AbortUpdate(result);
|
||
}
|
||
|
||
bool PaymentRequest::InFullyActiveDocument() {
|
||
nsIGlobalObject* global = GetOwnerGlobal();
|
||
if (!global) {
|
||
return false;
|
||
}
|
||
|
||
nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
|
||
Document* 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
|
||
Document* parentDoc = doc->GetInProcessParentDocument();
|
||
while (parentDoc) {
|
||
if (parentDoc && !parentDoc->IsCurrentActiveDocument()) {
|
||
return false;
|
||
}
|
||
parentDoc = parentDoc->GetInProcessParentDocument();
|
||
}
|
||
return true;
|
||
}
|
||
|
||
void PaymentRequest::RegisterActivityObserver() {
|
||
if (nsPIDOMWindowInner* window = GetOwner()) {
|
||
mDocument = window->GetExtantDoc();
|
||
if (mDocument) {
|
||
mDocument->RegisterActivityObserver(
|
||
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
|
||
}
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::UnregisterActivityObserver() {
|
||
if (mDocument) {
|
||
mDocument->UnregisterActivityObserver(
|
||
NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
|
||
}
|
||
}
|
||
|
||
void PaymentRequest::NotifyOwnerDocumentActivityChanged() {
|
||
nsPIDOMWindowInner* window = GetOwner();
|
||
NS_ENSURE_TRUE_VOID(window);
|
||
Document* doc = window->GetExtantDoc();
|
||
NS_ENSURE_TRUE_VOID(doc);
|
||
|
||
if (!InFullyActiveDocument()) {
|
||
if (mState == eInteractive) {
|
||
if (mAcceptPromise) {
|
||
mAcceptPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
|
||
mAcceptPromise = nullptr;
|
||
}
|
||
if (mResponse) {
|
||
ErrorResult rejectReason;
|
||
rejectReason.ThrowAbortError("The owner documnet is not fully active");
|
||
mResponse->RejectRetry(std::move(rejectReason));
|
||
}
|
||
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() {
|
||
// Suppress any pending unreported exception on mUpdateError. We don't use
|
||
// IgnoredErrorResult for mUpdateError because that doesn't play very nice
|
||
// with move assignment operators.
|
||
mUpdateError.SuppressException();
|
||
|
||
if (mIPC) {
|
||
// If we're being destroyed, the PaymentRequestManager isn't holding any
|
||
// references to us and we can't be waiting for any replies.
|
||
mIPC->MaybeDelete(false);
|
||
}
|
||
UnregisterActivityObserver();
|
||
}
|
||
|
||
JSObject* PaymentRequest::WrapObject(JSContext* aCx,
|
||
JS::Handle<JSObject*> aGivenProto) {
|
||
return PaymentRequest_Binding::Wrap(aCx, this, aGivenProto);
|
||
}
|
||
|
||
} // namespace mozilla::dom
|