Bug 1854367 - Mark storage-access-permission.sub.https.window.html subtest as intermi...
[gecko.git] / dom / payments / PaymentRequest.cpp
blob00f6345f0b371443257c1b48656ec19130e12e59
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "BasicCardPayment.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/dom/Element.h"
10 #include "mozilla/dom/FeaturePolicyUtils.h"
11 #include "mozilla/dom/PaymentMethodChangeEvent.h"
12 #include "mozilla/dom/PaymentRequest.h"
13 #include "mozilla/dom/PaymentRequestChild.h"
14 #include "mozilla/dom/PaymentRequestManager.h"
15 #include "mozilla/dom/RootedDictionary.h"
16 #include "mozilla/dom/UserActivation.h"
17 #include "mozilla/dom/WindowContext.h"
18 #include "mozilla/intl/Locale.h"
19 #include "mozilla/intl/LocaleService.h"
20 #include "mozilla/StaticPrefs_dom.h"
21 #include "nsContentUtils.h"
22 #include "nsIDUtils.h"
23 #include "nsImportModule.h"
24 #include "nsIRegion.h"
25 #include "nsIScriptError.h"
26 #include "nsIURLParser.h"
27 #include "nsNetCID.h"
28 #include "nsServiceManagerUtils.h"
29 #include "mozilla/dom/MerchantValidationEvent.h"
30 #include "PaymentResponse.h"
32 using mozilla::intl::LocaleService;
34 namespace mozilla::dom {
36 NS_IMPL_CYCLE_COLLECTION_CLASS(PaymentRequest)
38 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PaymentRequest,
39 DOMEventTargetHelper)
40 // Don't need NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER because
41 // DOMEventTargetHelper does it for us.
42 NS_IMPL_CYCLE_COLLECTION_TRACE_END
44 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PaymentRequest,
45 DOMEventTargetHelper)
46 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultPromise)
47 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAcceptPromise)
48 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbortPromise)
49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResponse)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mShippingAddress)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFullShippingAddress)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
55 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PaymentRequest,
56 DOMEventTargetHelper)
57 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResultPromise)
58 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAcceptPromise)
59 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAbortPromise)
60 NS_IMPL_CYCLE_COLLECTION_UNLINK(mResponse)
61 NS_IMPL_CYCLE_COLLECTION_UNLINK(mShippingAddress)
62 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFullShippingAddress)
63 tmp->UnregisterActivityObserver();
64 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
65 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
67 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PaymentRequest)
68 NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
69 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
71 NS_IMPL_ADDREF_INHERITED(PaymentRequest, DOMEventTargetHelper)
72 NS_IMPL_RELEASE_INHERITED(PaymentRequest, DOMEventTargetHelper)
74 bool PaymentRequest::PrefEnabled(JSContext* aCx, JSObject* aObj) {
75 #if defined(NIGHTLY_BUILD)
76 if (!XRE_IsContentProcess()) {
77 return false;
79 if (!StaticPrefs::dom_payments_request_enabled()) {
80 return false;
82 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
83 MOZ_ASSERT(manager);
85 nsCOMPtr<nsIRegion> regionJsm =
86 do_ImportESModule("resource://gre/modules/Region.sys.mjs", "Region");
87 nsAutoString region;
88 nsresult rv = regionJsm->GetHome(region);
89 if (NS_FAILED(rv)) {
90 return false;
93 if (!manager->IsRegionSupported(region)) {
94 return false;
96 nsAutoCString locale;
97 LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
98 mozilla::intl::Locale loc;
99 auto result = mozilla::intl::LocaleParser::TryParse(locale, loc);
100 if (!(result.isOk() && loc.Canonicalize().isOk() &&
101 loc.Language().EqualTo("en") && loc.Region().EqualTo("US"))) {
102 return false;
105 return true;
106 #else
107 return false;
108 #endif
111 void PaymentRequest::IsValidStandardizedPMI(const nsAString& aIdentifier,
112 ErrorResult& aRv) {
114 * The syntax of a standardized payment method identifier is given by the
115 * following [ABNF]:
117 * stdpmi = part *( "-" part )
118 * part = 1loweralpha *( DIGIT / loweralpha )
119 * loweralpha = %x61-7A
121 const char16_t* start = aIdentifier.BeginReading();
122 const char16_t* end = aIdentifier.EndReading();
123 while (start != end) {
124 // the first char must be in the range %x61-7A
125 if ((*start < 'a' || *start > 'z')) {
126 nsAutoCString error;
127 error.AssignLiteral("'");
128 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
129 error.AppendLiteral("' is not valid. The character '");
130 error.Append(NS_ConvertUTF16toUTF8(start, 1));
131 error.AppendLiteral(
132 "' at the beginning or after the '-' must be in the range [a-z].");
133 aRv.ThrowRangeError(error);
134 return;
136 ++start;
137 // the rest can be in the range %x61-7A + DIGITs
138 while (start != end && *start != '-' &&
139 ((*start >= 'a' && *start <= 'z') ||
140 (*start >= '0' && *start <= '9'))) {
141 ++start;
143 // if the char is not in the range %x61-7A + DIGITs, it must be '-'
144 if (start != end && *start != '-') {
145 nsAutoCString error;
146 error.AssignLiteral("'");
147 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
148 error.AppendLiteral("' is not valid. The character '");
149 error.Append(NS_ConvertUTF16toUTF8(start, 1));
150 error.AppendLiteral("' must be in the range [a-zA-z0-9-].");
151 aRv.ThrowRangeError(error);
152 return;
154 if (*start == '-') {
155 ++start;
156 // the last char can not be '-'
157 if (start == end) {
158 nsAutoCString error;
159 error.AssignLiteral("'");
160 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
161 error.AppendLiteral("' is not valid. The last character '");
162 error.Append(NS_ConvertUTF16toUTF8(start, 1));
163 error.AppendLiteral("' must be in the range [a-z0-9].");
164 aRv.ThrowRangeError(error);
165 return;
171 void PaymentRequest::IsValidPaymentMethodIdentifier(
172 const nsAString& aIdentifier, ErrorResult& aRv) {
173 if (aIdentifier.IsEmpty()) {
174 aRv.ThrowTypeError("Payment method identifier is required.");
175 return;
178 * URL-based payment method identifier
180 * 1. If url's scheme is not "https", return false.
181 * 2. If url's username or password is not the empty string, return false.
182 * 3. Otherwise, return true.
184 nsCOMPtr<nsIURLParser> urlParser = do_GetService(NS_STDURLPARSER_CONTRACTID);
185 MOZ_ASSERT(urlParser);
186 uint32_t schemePos = 0;
187 int32_t schemeLen = 0;
188 uint32_t authorityPos = 0;
189 int32_t authorityLen = 0;
190 NS_ConvertUTF16toUTF8 url(aIdentifier);
191 nsresult rv =
192 urlParser->ParseURL(url.get(), url.Length(), &schemePos, &schemeLen,
193 &authorityPos, &authorityLen, nullptr, nullptr);
194 if (NS_FAILED(rv)) {
195 nsAutoCString error;
196 error.AppendLiteral("Error parsing payment method identifier '");
197 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
198 error.AppendLiteral("'as a URL.");
199 aRv.ThrowRangeError(error);
200 return;
203 if (schemeLen == -1) {
204 // The PMI is not a URL-based PMI, check if it is a standardized PMI
205 IsValidStandardizedPMI(aIdentifier, aRv);
206 return;
208 if (!Substring(aIdentifier, schemePos, schemeLen).EqualsASCII("https")) {
209 nsAutoCString error;
210 error.AssignLiteral("'");
211 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
212 error.AppendLiteral("' is not valid. The scheme must be 'https'.");
213 aRv.ThrowRangeError(error);
214 return;
216 if (Substring(aIdentifier, authorityPos, authorityLen).IsEmpty()) {
217 nsAutoCString error;
218 error.AssignLiteral("'");
219 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
220 error.AppendLiteral("' is not valid. hostname can not be empty.");
221 aRv.ThrowRangeError(error);
222 return;
225 uint32_t usernamePos = 0;
226 int32_t usernameLen = 0;
227 uint32_t passwordPos = 0;
228 int32_t passwordLen = 0;
229 uint32_t hostnamePos = 0;
230 int32_t hostnameLen = 0;
231 int32_t port = 0;
233 NS_ConvertUTF16toUTF8 authority(
234 Substring(aIdentifier, authorityPos, authorityLen));
235 rv = urlParser->ParseAuthority(
236 authority.get(), authority.Length(), &usernamePos, &usernameLen,
237 &passwordPos, &passwordLen, &hostnamePos, &hostnameLen, &port);
238 if (NS_FAILED(rv)) {
239 // Handle the special cases that URLParser treats it as an invalid URL, but
240 // are used in web-platform-test
241 // For exmaple:
242 // https://:@example.com // should be considered as valid
243 // https://:password@example.com. // should be considered as invalid
244 int32_t atPos = authority.FindChar('@');
245 if (atPos >= 0) {
246 // only accept the case https://:@xxx
247 if (atPos == 1 && authority.CharAt(0) == ':') {
248 usernamePos = 0;
249 usernameLen = 0;
250 passwordPos = 0;
251 passwordLen = 0;
252 } else {
253 // for the fail cases, don't care about what the actual length is.
254 usernamePos = 0;
255 usernameLen = INT32_MAX;
256 passwordPos = 0;
257 passwordLen = INT32_MAX;
259 } else {
260 usernamePos = 0;
261 usernameLen = -1;
262 passwordPos = 0;
263 passwordLen = -1;
265 // Parse server information when both username and password are empty or do
266 // not exist.
267 if ((usernameLen <= 0) && (passwordLen <= 0)) {
268 if (authority.Length() - atPos - 1 == 0) {
269 nsAutoCString error;
270 error.AssignLiteral("'");
271 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
272 error.AppendLiteral("' is not valid. hostname can not be empty.");
273 aRv.ThrowRangeError(error);
274 return;
276 // Re-using nsIURLParser::ParseServerInfo to extract the hostname and port
277 // information. This can help us to handle complicated IPv6 cases.
278 nsAutoCString serverInfo(
279 Substring(authority, atPos + 1, authority.Length() - atPos - 1));
280 rv = urlParser->ParseServerInfo(serverInfo.get(), serverInfo.Length(),
281 &hostnamePos, &hostnameLen, &port);
282 if (NS_FAILED(rv)) {
283 // ParseServerInfo returns NS_ERROR_MALFORMED_URI in all fail cases, we
284 // probably need a followup bug to figure out the fail reason.
285 nsAutoCString error;
286 error.AssignLiteral("Error extracting hostname from '");
287 error.Append(serverInfo);
288 error.AppendLiteral("'.");
289 aRv.ThrowRangeError(error);
290 return;
294 // PMI is valid when usernameLen/passwordLen equals to -1 or 0.
295 if (usernameLen > 0 || passwordLen > 0) {
296 nsAutoCString error;
297 error.AssignLiteral("'");
298 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
299 error.AssignLiteral("' is not valid. Username and password must be empty.");
300 aRv.ThrowRangeError(error);
301 return;
304 // PMI is valid when hostnameLen is larger than 0
305 if (hostnameLen <= 0) {
306 nsAutoCString error;
307 error.AssignLiteral("'");
308 error.Append(NS_ConvertUTF16toUTF8(aIdentifier));
309 error.AppendLiteral("' is not valid. hostname can not be empty.");
310 aRv.ThrowRangeError(error);
311 return;
315 void PaymentRequest::IsValidMethodData(
316 JSContext* aCx, const Sequence<PaymentMethodData>& aMethodData,
317 ErrorResult& aRv) {
318 if (!aMethodData.Length()) {
319 aRv.ThrowTypeError("At least one payment method is required.");
320 return;
323 nsTArray<nsString> methods;
324 for (const PaymentMethodData& methodData : aMethodData) {
325 IsValidPaymentMethodIdentifier(methodData.mSupportedMethods, aRv);
326 if (aRv.Failed()) {
327 return;
330 RefPtr<BasicCardService> service = BasicCardService::GetService();
331 MOZ_ASSERT(service);
332 if (service->IsBasicCardPayment(methodData.mSupportedMethods)) {
333 if (!methodData.mData.WasPassed()) {
334 continue;
336 MOZ_ASSERT(aCx);
337 nsAutoString error;
338 if (!service->IsValidBasicCardRequest(aCx, methodData.mData.Value(),
339 error)) {
340 aRv.ThrowTypeError(NS_ConvertUTF16toUTF8(error));
341 return;
344 if (!methods.Contains(methodData.mSupportedMethods)) {
345 methods.AppendElement(methodData.mSupportedMethods);
346 } else {
347 aRv.ThrowRangeError(nsPrintfCString(
348 "Duplicate payment method '%s'",
349 NS_ConvertUTF16toUTF8(methodData.mSupportedMethods).get()));
350 return;
355 void PaymentRequest::IsValidNumber(const nsAString& aItem,
356 const nsAString& aStr, ErrorResult& aRv) {
357 nsresult error = NS_ERROR_FAILURE;
359 if (!aStr.IsEmpty()) {
360 nsAutoString aValue(aStr);
362 // If the beginning character is '-', we will check the second one.
363 int beginningIndex = (aValue.First() == '-') ? 1 : 0;
365 // Ensure
366 // - the beginning character is a digit in [0-9], and
367 // - the last character is not '.'
368 // to follow spec:
369 // https://w3c.github.io/browser-payment-api/#dfn-valid-decimal-monetary-value
371 // For example, ".1" is not valid for '.' is not in [0-9],
372 // and " 0.1" either for beginning with ' '
373 if (aValue.Last() != '.' && aValue.CharAt(beginningIndex) >= '0' &&
374 aValue.CharAt(beginningIndex) <= '9') {
375 aValue.ToFloat(&error);
379 if (NS_FAILED(error)) {
380 nsAutoCString errorMsg;
381 errorMsg.AssignLiteral("The amount.value of \"");
382 errorMsg.Append(NS_ConvertUTF16toUTF8(aItem));
383 errorMsg.AppendLiteral("\"(");
384 errorMsg.Append(NS_ConvertUTF16toUTF8(aStr));
385 errorMsg.AppendLiteral(") must be a valid decimal monetary value.");
386 aRv.ThrowTypeError(errorMsg);
387 return;
391 void PaymentRequest::IsNonNegativeNumber(const nsAString& aItem,
392 const nsAString& aStr,
393 ErrorResult& aRv) {
394 nsresult error = NS_ERROR_FAILURE;
396 if (!aStr.IsEmpty()) {
397 nsAutoString aValue(aStr);
398 // Ensure
399 // - the beginning character is a digit in [0-9], and
400 // - the last character is not '.'
401 if (aValue.Last() != '.' && aValue.First() >= '0' &&
402 aValue.First() <= '9') {
403 aValue.ToFloat(&error);
407 if (NS_FAILED(error)) {
408 nsAutoCString errorMsg;
409 errorMsg.AssignLiteral("The amount.value of \"");
410 errorMsg.Append(NS_ConvertUTF16toUTF8(aItem));
411 errorMsg.AppendLiteral("\"(");
412 errorMsg.Append(NS_ConvertUTF16toUTF8(aStr));
413 errorMsg.AppendLiteral(
414 ") must be a valid and non-negative decimal monetary value.");
415 aRv.ThrowTypeError(errorMsg);
416 return;
420 void PaymentRequest::IsValidCurrency(const nsAString& aItem,
421 const nsAString& aCurrency,
422 ErrorResult& aRv) {
424 * According to spec in
425 * https://w3c.github.io/payment-request/#validity-checkers, perform currency
426 * validation with following criteria
427 * 1. The currency length must be 3.
428 * 2. The currency contains any character that must be in the range "A" to
429 * "Z" (U+0041 to U+005A) or the range "a" to "z" (U+0061 to U+007A)
431 if (aCurrency.Length() != 3) {
432 nsAutoCString error;
433 error.AssignLiteral("The length amount.currency of \"");
434 error.Append(NS_ConvertUTF16toUTF8(aItem));
435 error.AppendLiteral("\"(");
436 error.Append(NS_ConvertUTF16toUTF8(aCurrency));
437 error.AppendLiteral(") must be 3.");
438 aRv.ThrowRangeError(error);
439 return;
441 // Don't use nsUnicharUtils::ToUpperCase, it converts the invalid "ınr" PMI to
442 // to the valid one "INR".
443 for (uint32_t idx = 0; idx < aCurrency.Length(); ++idx) {
444 if ((aCurrency.CharAt(idx) >= 'A' && aCurrency.CharAt(idx) <= 'Z') ||
445 (aCurrency.CharAt(idx) >= 'a' && aCurrency.CharAt(idx) <= 'z')) {
446 continue;
448 nsAutoCString error;
449 error.AssignLiteral("The character amount.currency of \"");
450 error.Append(NS_ConvertUTF16toUTF8(aItem));
451 error.AppendLiteral("\"(");
452 error.Append(NS_ConvertUTF16toUTF8(aCurrency));
453 error.AppendLiteral(
454 ") must be in the range 'A' to 'Z'(U+0041 to U+005A) or 'a' to "
455 "'z'(U+0061 to U+007A).");
456 aRv.ThrowRangeError(error);
457 return;
461 void PaymentRequest::IsValidCurrencyAmount(const nsAString& aItem,
462 const PaymentCurrencyAmount& aAmount,
463 const bool aIsTotalItem,
464 ErrorResult& aRv) {
465 IsValidCurrency(aItem, aAmount.mCurrency, aRv);
466 if (aRv.Failed()) {
467 return;
469 if (aIsTotalItem) {
470 IsNonNegativeNumber(aItem, aAmount.mValue, aRv);
471 if (aRv.Failed()) {
472 return;
474 } else {
475 IsValidNumber(aItem, aAmount.mValue, aRv);
476 if (aRv.Failed()) {
477 return;
482 void PaymentRequest::IsValidDetailsInit(const PaymentDetailsInit& aDetails,
483 const bool aRequestShipping,
484 ErrorResult& aRv) {
485 // Check the amount.value and amount.currency of detail.total
486 IsValidCurrencyAmount(u"details.total"_ns, aDetails.mTotal.mAmount,
487 true, // isTotalItem
488 aRv);
489 if (aRv.Failed()) {
490 return;
492 return IsValidDetailsBase(aDetails, aRequestShipping, aRv);
495 void PaymentRequest::IsValidDetailsUpdate(const PaymentDetailsUpdate& aDetails,
496 const bool aRequestShipping,
497 ErrorResult& aRv) {
498 // Check the amount.value and amount.currency of detail.total
499 if (aDetails.mTotal.WasPassed()) {
500 IsValidCurrencyAmount(u"details.total"_ns, aDetails.mTotal.Value().mAmount,
501 true, // isTotalItem
502 aRv);
503 if (aRv.Failed()) {
504 return;
507 IsValidDetailsBase(aDetails, aRequestShipping, aRv);
510 void PaymentRequest::IsValidDetailsBase(const PaymentDetailsBase& aDetails,
511 const bool aRequestShipping,
512 ErrorResult& aRv) {
513 // Check the amount.value of each item in the display items
514 if (aDetails.mDisplayItems.WasPassed()) {
515 const Sequence<PaymentItem>& displayItems = aDetails.mDisplayItems.Value();
516 for (const PaymentItem& displayItem : displayItems) {
517 IsValidCurrencyAmount(displayItem.mLabel, displayItem.mAmount,
518 false, // isTotalItem
519 aRv);
520 if (aRv.Failed()) {
521 return;
526 // Check the shipping option
527 if (aDetails.mShippingOptions.WasPassed() && aRequestShipping) {
528 const Sequence<PaymentShippingOption>& shippingOptions =
529 aDetails.mShippingOptions.Value();
530 nsTArray<nsString> seenIDs;
531 for (const PaymentShippingOption& shippingOption : shippingOptions) {
532 IsValidCurrencyAmount(u"details.shippingOptions"_ns,
533 shippingOption.mAmount,
534 false, // isTotalItem
535 aRv);
536 if (aRv.Failed()) {
537 return;
539 if (seenIDs.Contains(shippingOption.mId)) {
540 nsAutoCString error;
541 error.AssignLiteral("Duplicate shippingOption id '");
542 error.Append(NS_ConvertUTF16toUTF8(shippingOption.mId));
543 error.AppendLiteral("'");
544 aRv.ThrowTypeError(error);
545 return;
547 seenIDs.AppendElement(shippingOption.mId);
551 // Check payment details modifiers
552 if (aDetails.mModifiers.WasPassed()) {
553 const Sequence<PaymentDetailsModifier>& modifiers =
554 aDetails.mModifiers.Value();
555 for (const PaymentDetailsModifier& modifier : modifiers) {
556 IsValidPaymentMethodIdentifier(modifier.mSupportedMethods, aRv);
557 if (aRv.Failed()) {
558 return;
560 if (modifier.mTotal.WasPassed()) {
561 IsValidCurrencyAmount(u"details.modifiers.total"_ns,
562 modifier.mTotal.Value().mAmount,
563 true, // isTotalItem
564 aRv);
565 if (aRv.Failed()) {
566 return;
569 if (modifier.mAdditionalDisplayItems.WasPassed()) {
570 const Sequence<PaymentItem>& displayItems =
571 modifier.mAdditionalDisplayItems.Value();
572 for (const PaymentItem& displayItem : displayItems) {
573 IsValidCurrencyAmount(displayItem.mLabel, displayItem.mAmount,
574 false, // isTotalItem
575 aRv);
576 if (aRv.Failed()) {
577 return;
585 already_AddRefed<PaymentRequest> PaymentRequest::Constructor(
586 const GlobalObject& aGlobal, const Sequence<PaymentMethodData>& aMethodData,
587 const PaymentDetailsInit& aDetails, const PaymentOptions& aOptions,
588 ErrorResult& aRv) {
589 nsCOMPtr<nsPIDOMWindowInner> window =
590 do_QueryInterface(aGlobal.GetAsSupports());
591 if (!window) {
592 aRv.ThrowAbortError("No global object for creating PaymentRequest");
593 return nullptr;
596 nsCOMPtr<Document> doc = window->GetExtantDoc();
597 if (!doc) {
598 aRv.ThrowAbortError("No document for creating PaymentRequest");
599 return nullptr;
602 // the feature can only be used in an active document
603 if (!doc->IsCurrentActiveDocument()) {
604 aRv.ThrowSecurityError(
605 "Can't create a PaymentRequest for an inactive document");
606 return nullptr;
609 if (!FeaturePolicyUtils::IsFeatureAllowed(doc, u"payment"_ns)) {
610 aRv.ThrowSecurityError(
611 "Document's Feature Policy does not allow to create a PaymentRequest");
612 return nullptr;
615 // Get the top same process document
616 nsCOMPtr<Document> topSameProcessDoc = doc;
617 topSameProcessDoc = doc;
618 while (topSameProcessDoc) {
619 nsCOMPtr<Document> parent = topSameProcessDoc->GetInProcessParentDocument();
620 if (!parent || !parent->IsContentDocument()) {
621 break;
623 topSameProcessDoc = parent;
625 nsCOMPtr<nsIPrincipal> topLevelPrincipal = topSameProcessDoc->NodePrincipal();
627 // Check payment methods and details
628 IsValidMethodData(aGlobal.Context(), aMethodData, aRv);
629 if (aRv.Failed()) {
630 return nullptr;
632 IsValidDetailsInit(aDetails, aOptions.mRequestShipping, aRv);
633 if (aRv.Failed()) {
634 return nullptr;
637 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
638 if (NS_WARN_IF(!manager)) {
639 return nullptr;
642 // Create PaymentRequest and set its |mId|
643 RefPtr<PaymentRequest> request;
644 manager->CreatePayment(aGlobal.Context(), window, topLevelPrincipal,
645 aMethodData, aDetails, aOptions,
646 getter_AddRefs(request), aRv);
647 if (aRv.Failed()) {
648 return nullptr;
650 return request.forget();
653 already_AddRefed<PaymentRequest> PaymentRequest::CreatePaymentRequest(
654 nsPIDOMWindowInner* aWindow, ErrorResult& aRv) {
655 // Generate a unique id for identification
656 nsID uuid;
657 if (NS_WARN_IF(NS_FAILED(nsID::GenerateUUIDInPlace(uuid)))) {
658 aRv.ThrowAbortError(
659 "Failed to create an internal UUID for the PaymentRequest");
660 return nullptr;
663 NSID_TrimBracketsUTF16 id(uuid);
665 // Create payment request with generated id
666 RefPtr<PaymentRequest> request = new PaymentRequest(aWindow, id);
667 return request.forget();
670 PaymentRequest::PaymentRequest(nsPIDOMWindowInner* aWindow,
671 const nsAString& aInternalId)
672 : DOMEventTargetHelper(aWindow),
673 mInternalId(aInternalId),
674 mShippingAddress(nullptr),
675 mUpdating(false),
676 mRequestShipping(false),
677 mState(eCreated),
678 mIPC(nullptr) {
679 MOZ_ASSERT(aWindow);
680 RegisterActivityObserver();
683 already_AddRefed<Promise> PaymentRequest::CanMakePayment(ErrorResult& aRv) {
684 if (!InFullyActiveDocument()) {
685 aRv.ThrowAbortError("The owner document is not fully active");
686 return nullptr;
689 if (mState != eCreated) {
690 aRv.ThrowInvalidStateError(
691 "The PaymentRequest's state should be 'Created'");
692 return nullptr;
695 if (mResultPromise) {
696 // XXX This doesn't match the spec but does match Chromium.
697 aRv.ThrowNotAllowedError(
698 "PaymentRequest.CanMakePayment() has already been called");
699 return nullptr;
702 nsIGlobalObject* global = GetOwnerGlobal();
703 RefPtr<Promise> promise = Promise::Create(global, aRv);
704 if (aRv.Failed()) {
705 return nullptr;
708 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
709 MOZ_ASSERT(manager);
710 manager->CanMakePayment(this, aRv);
711 if (aRv.Failed()) {
712 return nullptr;
714 mResultPromise = promise;
715 return promise.forget();
718 void PaymentRequest::RespondCanMakePayment(bool aResult) {
719 MOZ_ASSERT(mResultPromise);
720 mResultPromise->MaybeResolve(aResult);
721 mResultPromise = nullptr;
724 already_AddRefed<Promise> PaymentRequest::Show(
725 const Optional<OwningNonNull<Promise>>& aDetailsPromise, ErrorResult& aRv) {
726 if (!InFullyActiveDocument()) {
727 aRv.ThrowAbortError("The owner document is not fully active");
728 return nullptr;
731 nsIGlobalObject* global = GetOwnerGlobal();
732 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
733 Document* doc = win->GetExtantDoc();
735 if (!UserActivation::IsHandlingUserInput()) {
736 nsString msg = nsLiteralString(
737 u"User activation is now required to call PaymentRequest.show()");
738 nsContentUtils::ReportToConsoleNonLocalized(
739 msg, nsIScriptError::warningFlag, "Security"_ns, doc);
740 if (StaticPrefs::dom_payments_request_user_interaction_required()) {
741 aRv.ThrowSecurityError(NS_ConvertUTF16toUTF8(msg));
742 return nullptr;
746 if (mState != eCreated) {
747 aRv.ThrowInvalidStateError(
748 "The PaymentRequest's state should be 'Created'");
749 return nullptr;
752 RefPtr<Promise> promise = Promise::Create(global, aRv);
753 if (aRv.Failed()) {
754 mState = eClosed;
755 return nullptr;
758 if (aDetailsPromise.WasPassed()) {
759 aDetailsPromise.Value().AppendNativeHandler(this);
760 mUpdating = true;
763 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
764 MOZ_ASSERT(manager);
765 manager->ShowPayment(this, aRv);
766 if (aRv.Failed()) {
767 mState = eClosed;
768 return nullptr;
771 mAcceptPromise = promise;
772 mState = eInteractive;
773 return promise.forget();
776 void PaymentRequest::RejectShowPayment(ErrorResult&& aRejectReason) {
777 MOZ_ASSERT(mAcceptPromise || mResponse);
778 MOZ_ASSERT(mState == eInteractive);
780 if (mResponse) {
781 mResponse->RejectRetry(std::move(aRejectReason));
782 } else {
783 mAcceptPromise->MaybeReject(std::move(aRejectReason));
785 mState = eClosed;
786 mAcceptPromise = nullptr;
789 void PaymentRequest::RespondShowPayment(const nsAString& aMethodName,
790 const ResponseData& aDetails,
791 const nsAString& aPayerName,
792 const nsAString& aPayerEmail,
793 const nsAString& aPayerPhone,
794 ErrorResult&& aResult) {
795 MOZ_ASSERT(mState == eInteractive);
797 if (aResult.Failed()) {
798 RejectShowPayment(std::move(aResult));
799 return;
802 // https://github.com/w3c/payment-request/issues/692
803 mShippingAddress.swap(mFullShippingAddress);
804 mFullShippingAddress = nullptr;
806 if (mResponse) {
807 mResponse->RespondRetry(aMethodName, mShippingOption, mShippingAddress,
808 aDetails, aPayerName, aPayerEmail, aPayerPhone);
809 } else if (mAcceptPromise) {
810 RefPtr<PaymentResponse> paymentResponse = new PaymentResponse(
811 GetOwner(), this, mId, aMethodName, mShippingOption, mShippingAddress,
812 aDetails, aPayerName, aPayerEmail, aPayerPhone);
813 mResponse = paymentResponse;
814 mAcceptPromise->MaybeResolve(paymentResponse);
815 } else {
816 // mAccpetPromise could be nulled through document activity changed. And
817 // there is nothing to do here.
818 mState = eClosed;
819 return;
822 mState = eClosed;
823 mAcceptPromise = nullptr;
826 void PaymentRequest::RespondComplete() {
827 MOZ_ASSERT(mResponse);
828 mResponse->RespondComplete();
831 already_AddRefed<Promise> PaymentRequest::Abort(ErrorResult& aRv) {
832 if (!InFullyActiveDocument()) {
833 aRv.ThrowAbortError("The owner document is not fully active");
834 return nullptr;
837 if (mState != eInteractive) {
838 aRv.ThrowSecurityError(
839 "The PaymentRequest's state should be 'Interactive'");
840 return nullptr;
843 if (mAbortPromise) {
844 aRv.ThrowInvalidStateError(
845 "PaymentRequest.abort() has already been called");
846 return nullptr;
849 nsIGlobalObject* global = GetOwnerGlobal();
850 RefPtr<Promise> promise = Promise::Create(global, aRv);
851 if (aRv.Failed()) {
852 return nullptr;
855 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
856 MOZ_ASSERT(manager);
857 manager->AbortPayment(this, aRv);
858 if (aRv.Failed()) {
859 return nullptr;
862 mAbortPromise = promise;
863 return promise.forget();
866 void PaymentRequest::RespondAbortPayment(bool aSuccess) {
867 // Check whether we are aborting the update:
869 // - If |mUpdateError| is failed, we are aborting the update as
870 // |mUpdateError| was set in method |AbortUpdate|.
871 // => Reject |mAcceptPromise| and reset |mUpdateError| to complete
872 // the action, regardless of |aSuccess|.
874 // - Otherwise, we are handling |Abort| method call from merchant.
875 // => Resolve/Reject |mAbortPromise| based on |aSuccess|.
876 if (mUpdateError.Failed()) {
877 // Respond show with mUpdateError, set mUpdating to false.
878 mUpdating = false;
879 RespondShowPayment(u""_ns, ResponseData(), u""_ns, u""_ns, u""_ns,
880 std::move(mUpdateError));
881 return;
884 if (mState != eInteractive) {
885 return;
888 if (mAbortPromise) {
889 if (aSuccess) {
890 mAbortPromise->MaybeResolve(JS::UndefinedHandleValue);
891 mAbortPromise = nullptr;
892 ErrorResult abortResult;
893 abortResult.ThrowAbortError("The PaymentRequest is aborted");
894 RejectShowPayment(std::move(abortResult));
895 } else {
896 mAbortPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
897 mAbortPromise = nullptr;
902 void PaymentRequest::UpdatePayment(JSContext* aCx,
903 const PaymentDetailsUpdate& aDetails,
904 ErrorResult& aRv) {
905 MOZ_ASSERT(aCx);
906 if (mState != eInteractive) {
907 aRv.ThrowInvalidStateError(
908 "The PaymentRequest state should be 'Interactive'");
909 return;
911 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
912 MOZ_ASSERT(manager);
913 manager->UpdatePayment(aCx, this, aDetails, mRequestShipping, aRv);
916 void PaymentRequest::AbortUpdate(ErrorResult& aReason) {
917 // AbortUpdate has the responsiblity to call aReason.SuppressException() when
918 // fail to update.
920 MOZ_ASSERT(aReason.Failed());
922 // Completely ignoring the call when the owner document is not fully active.
923 if (!InFullyActiveDocument()) {
924 aReason.SuppressException();
925 return;
928 // Completely ignoring the call when the PaymentRequest state is not
929 // eInteractive.
930 if (mState != eInteractive) {
931 aReason.SuppressException();
932 return;
934 // Try to close down any remaining user interface. Should recevie
935 // RespondAbortPayment from chrome process.
936 // Completely ignoring the call when failed to send action to chrome process.
937 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
938 MOZ_ASSERT(manager);
939 IgnoredErrorResult result;
940 manager->AbortPayment(this, result);
941 if (result.Failed()) {
942 aReason.SuppressException();
943 return;
946 // Remember update error |aReason| and do the following steps in
947 // RespondShowPayment.
948 // 1. Set target.state to closed
949 // 2. Reject the promise target.acceptPromise with exception "aRv"
950 // 3. Abort the algorithm with update error
951 mUpdateError = std::move(aReason);
954 void PaymentRequest::RetryPayment(JSContext* aCx,
955 const PaymentValidationErrors& aErrors,
956 ErrorResult& aRv) {
957 if (mState == eInteractive) {
958 aRv.ThrowInvalidStateError(
959 "Call Retry() when the PaymentReqeust state is 'Interactive'");
960 return;
962 RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
963 MOZ_ASSERT(manager);
964 manager->RetryPayment(aCx, this, aErrors, aRv);
965 if (aRv.Failed()) {
966 return;
968 mState = eInteractive;
971 void PaymentRequest::GetId(nsAString& aRetVal) const { aRetVal = mId; }
973 void PaymentRequest::GetInternalId(nsAString& aRetVal) {
974 aRetVal = mInternalId;
977 void PaymentRequest::SetId(const nsAString& aId) { mId = aId; }
979 bool PaymentRequest::Equals(const nsAString& aInternalId) const {
980 return mInternalId.Equals(aInternalId);
983 bool PaymentRequest::ReadyForUpdate() {
984 return mState == eInteractive && !mUpdating;
987 void PaymentRequest::SetUpdating(bool aUpdating) { mUpdating = aUpdating; }
989 already_AddRefed<PaymentResponse> PaymentRequest::GetResponse() const {
990 RefPtr<PaymentResponse> response = mResponse;
991 return response.forget();
994 nsresult PaymentRequest::DispatchUpdateEvent(const nsAString& aType) {
995 MOZ_ASSERT(ReadyForUpdate());
997 PaymentRequestUpdateEventInit init;
998 init.mBubbles = false;
999 init.mCancelable = false;
1001 RefPtr<PaymentRequestUpdateEvent> event =
1002 PaymentRequestUpdateEvent::Constructor(this, aType, init);
1003 event->SetTrusted(true);
1004 event->SetRequest(this);
1006 ErrorResult rv;
1007 DispatchEvent(*event, rv);
1008 return rv.StealNSResult();
1011 nsresult PaymentRequest::DispatchMerchantValidationEvent(
1012 const nsAString& aType) {
1013 MOZ_ASSERT(ReadyForUpdate());
1015 MerchantValidationEventInit init;
1016 init.mBubbles = false;
1017 init.mCancelable = false;
1018 init.mValidationURL.Truncate();
1020 ErrorResult rv;
1021 RefPtr<MerchantValidationEvent> event =
1022 MerchantValidationEvent::Constructor(this, aType, init, rv);
1023 if (rv.Failed()) {
1024 return rv.StealNSResult();
1026 event->SetTrusted(true);
1027 event->SetRequest(this);
1029 DispatchEvent(*event, rv);
1030 return rv.StealNSResult();
1033 nsresult PaymentRequest::DispatchPaymentMethodChangeEvent(
1034 const nsAString& aMethodName, const ChangeDetails& aMethodDetails) {
1035 MOZ_ASSERT(ReadyForUpdate());
1037 PaymentRequestUpdateEventInit init;
1038 init.mBubbles = false;
1039 init.mCancelable = false;
1041 RefPtr<PaymentMethodChangeEvent> event =
1042 PaymentMethodChangeEvent::Constructor(this, u"paymentmethodchange"_ns,
1043 init, aMethodName, aMethodDetails);
1044 event->SetTrusted(true);
1045 event->SetRequest(this);
1047 ErrorResult rv;
1048 DispatchEvent(*event, rv);
1049 return rv.StealNSResult();
1052 already_AddRefed<PaymentAddress> PaymentRequest::GetShippingAddress() const {
1053 RefPtr<PaymentAddress> address = mShippingAddress;
1054 return address.forget();
1057 nsresult PaymentRequest::UpdateShippingAddress(
1058 const nsAString& aCountry, const nsTArray<nsString>& aAddressLine,
1059 const nsAString& aRegion, const nsAString& aRegionCode,
1060 const nsAString& aCity, const nsAString& aDependentLocality,
1061 const nsAString& aPostalCode, const nsAString& aSortingCode,
1062 const nsAString& aOrganization, const nsAString& aRecipient,
1063 const nsAString& aPhone) {
1064 nsTArray<nsString> emptyArray;
1065 mShippingAddress = new PaymentAddress(
1066 GetOwner(), aCountry, emptyArray, aRegion, aRegionCode, aCity,
1067 aDependentLocality, aPostalCode, aSortingCode, u""_ns, u""_ns, u""_ns);
1068 mFullShippingAddress =
1069 new PaymentAddress(GetOwner(), aCountry, aAddressLine, aRegion,
1070 aRegionCode, aCity, aDependentLocality, aPostalCode,
1071 aSortingCode, aOrganization, aRecipient, aPhone);
1072 // Fire shippingaddresschange event
1073 return DispatchUpdateEvent(u"shippingaddresschange"_ns);
1076 void PaymentRequest::SetShippingOption(const nsAString& aShippingOption) {
1077 mShippingOption = aShippingOption;
1080 void PaymentRequest::GetShippingOption(nsAString& aRetVal) const {
1081 aRetVal = mShippingOption;
1084 nsresult PaymentRequest::UpdateShippingOption(
1085 const nsAString& aShippingOption) {
1086 mShippingOption = aShippingOption;
1088 // Fire shippingaddresschange event
1089 return DispatchUpdateEvent(u"shippingoptionchange"_ns);
1092 nsresult PaymentRequest::UpdatePaymentMethod(
1093 const nsAString& aMethodName, const ChangeDetails& aMethodDetails) {
1094 return DispatchPaymentMethodChangeEvent(aMethodName, aMethodDetails);
1097 void PaymentRequest::SetShippingType(
1098 const Nullable<PaymentShippingType>& aShippingType) {
1099 mShippingType = aShippingType;
1102 Nullable<PaymentShippingType> PaymentRequest::GetShippingType() const {
1103 return mShippingType;
1106 void PaymentRequest::GetOptions(PaymentOptions& aRetVal) const {
1107 aRetVal = mOptions;
1110 void PaymentRequest::SetOptions(const PaymentOptions& aOptions) {
1111 mOptions = aOptions;
1114 void PaymentRequest::ResolvedCallback(JSContext* aCx,
1115 JS::Handle<JS::Value> aValue,
1116 ErrorResult& aRv) {
1117 if (!InFullyActiveDocument()) {
1118 return;
1121 MOZ_ASSERT(aCx);
1122 mUpdating = false;
1123 if (NS_WARN_IF(!aValue.isObject())) {
1124 return;
1127 ErrorResult result;
1128 // Converting value to a PaymentDetailsUpdate dictionary
1129 RootedDictionary<PaymentDetailsUpdate> details(aCx);
1130 if (!details.Init(aCx, aValue)) {
1131 result.StealExceptionFromJSContext(aCx);
1132 AbortUpdate(result);
1133 return;
1136 IsValidDetailsUpdate(details, mRequestShipping, result);
1137 if (result.Failed()) {
1138 AbortUpdate(result);
1139 return;
1142 // Update the PaymentRequest with the new details
1143 UpdatePayment(aCx, details, result);
1144 if (result.Failed()) {
1145 AbortUpdate(result);
1146 return;
1150 void PaymentRequest::RejectedCallback(JSContext* aCx,
1151 JS::Handle<JS::Value> aValue,
1152 ErrorResult& aRv) {
1153 if (!InFullyActiveDocument()) {
1154 return;
1157 mUpdating = false;
1158 ErrorResult result;
1159 result.ThrowAbortError(
1160 "Details promise for PaymentRequest.show() is rejected by merchant");
1161 AbortUpdate(result);
1164 bool PaymentRequest::InFullyActiveDocument() {
1165 nsIGlobalObject* global = GetOwnerGlobal();
1166 if (!global) {
1167 return false;
1170 nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global);
1172 Document* doc = win->GetExtantDoc();
1173 if (!doc || !doc->IsCurrentActiveDocument()) {
1174 return false;
1177 WindowContext* winContext = win->GetWindowContext();
1178 if (!winContext) {
1179 return false;
1182 while (winContext) {
1183 if (!winContext->IsCurrent()) {
1184 return false;
1186 winContext = winContext->GetParentWindowContext();
1189 return true;
1192 void PaymentRequest::RegisterActivityObserver() {
1193 if (nsPIDOMWindowInner* window = GetOwner()) {
1194 mDocument = window->GetExtantDoc();
1195 if (mDocument) {
1196 mDocument->RegisterActivityObserver(
1197 NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1202 void PaymentRequest::UnregisterActivityObserver() {
1203 if (mDocument) {
1204 mDocument->UnregisterActivityObserver(
1205 NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
1209 void PaymentRequest::NotifyOwnerDocumentActivityChanged() {
1210 nsPIDOMWindowInner* window = GetOwner();
1211 NS_ENSURE_TRUE_VOID(window);
1212 Document* doc = window->GetExtantDoc();
1213 NS_ENSURE_TRUE_VOID(doc);
1215 if (!InFullyActiveDocument()) {
1216 if (mState == eInteractive) {
1217 if (mAcceptPromise) {
1218 mAcceptPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
1219 mAcceptPromise = nullptr;
1221 if (mResponse) {
1222 ErrorResult rejectReason;
1223 rejectReason.ThrowAbortError("The owner documnet is not fully active");
1224 mResponse->RejectRetry(std::move(rejectReason));
1226 if (mAbortPromise) {
1227 mAbortPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
1228 mAbortPromise = nullptr;
1231 if (mState == eCreated) {
1232 if (mResultPromise) {
1233 mResultPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
1234 mResultPromise = nullptr;
1237 RefPtr<PaymentRequestManager> mgr = PaymentRequestManager::GetSingleton();
1238 mgr->ClosePayment(this);
1242 PaymentRequest::~PaymentRequest() {
1243 // Suppress any pending unreported exception on mUpdateError. We don't use
1244 // IgnoredErrorResult for mUpdateError because that doesn't play very nice
1245 // with move assignment operators.
1246 mUpdateError.SuppressException();
1248 if (mIPC) {
1249 // If we're being destroyed, the PaymentRequestManager isn't holding any
1250 // references to us and we can't be waiting for any replies.
1251 mIPC->MaybeDelete(false);
1253 UnregisterActivityObserver();
1256 JSObject* PaymentRequest::WrapObject(JSContext* aCx,
1257 JS::Handle<JSObject*> aGivenProto) {
1258 return PaymentRequest_Binding::Wrap(aCx, this, aGivenProto);
1261 } // namespace mozilla::dom