Source code

Revision control

Other Tools

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