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