Chromium Code Reviews| Index: third_party/WebKit/Source/modules/payments/PaymentRequest.cpp |
| diff --git a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp |
| index fe3ab4642012884480a3a1fe8edd66b0e6bdd892..95ca13d98f83fe2a9b9533a854ce5d4402c333bb 100644 |
| --- a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp |
| +++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp |
| @@ -6,13 +6,113 @@ |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/JSONValuesForV8.h" |
| +#include "bindings/core/v8/ScriptPromiseResolver.h" |
| #include "bindings/core/v8/ScriptState.h" |
| +#include "core/EventTypeNames.h" |
| #include "core/dom/DOMException.h" |
| #include "core/dom/ExceptionCode.h" |
| +#include "core/events/Event.h" |
| +#include "core/events/EventQueue.h" |
| #include "modules/EventTargetModulesNames.h" |
| +#include "modules/payments/PaymentItem.h" |
| +#include "modules/payments/PaymentResponse.h" |
| #include "modules/payments/ShippingAddress.h" |
| +#include "modules/payments/ShippingOption.h" |
| +#include "mojo/public/cpp/bindings/interface_request.h" |
| +#include "public/platform/Platform.h" |
| +#include <utility> |
| namespace blink { |
| +namespace { |
| + |
| +bool isValidCurrencyCode(const String& code, ExceptionState& exceptionState) |
|
esprehn
2016/03/18 00:20:50
Is this all covered by tests? Seems like we should
please use gerrit instead
2016/03/23 00:14:51
Covered in PaymentRequestDetailsTest.
|
| +{ |
| + if (code.length() != 3) { |
| + exceptionState.throwTypeError("'" + code + "' is not a valid ISO 4217 currency code, should be 3 letters"); |
|
haraken
2016/03/18 09:19:44
Just to confirm: Maybe should this be a DOMExcepti
please use gerrit instead
2016/03/23 00:14:51
Spec does not touch on this yet. It's using a lot
|
| + return false; |
| + } |
| + |
| + for (size_t i = 0; i < code.length(); ++i) { |
| + if (code[i] < 'A' || code[i] > 'Z') { |
| + exceptionState.throwTypeError("'" + code + "' is not a valid ISO 4217 currency code, should be upper case letters [A-Z]"); |
| + return false; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool isValidAmount(const String& amount, ExceptionState& exceptionState) |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
This doesn't seem to exactly match the (informativ
groby-ooo-7-16
2016/03/21 19:36:26
Hm. If we have a regexp, why not use ScriptRegex a
please use gerrit instead
2016/03/23 00:14:51
Using ScriptRegexp with the exact regular expressi
|
| +{ |
| + if (amount.isEmpty()) { |
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should be nonempty"); |
|
haraken
2016/03/18 09:19:44
Ditto.
please use gerrit instead
2016/03/23 00:14:51
Acknowledged.
|
| + return false; |
| + } |
| + |
| + if (amount.length() > 32) { |
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should be at most 32 characters"); |
|
haraken
2016/03/18 09:19:44
Ditto.
please use gerrit instead
2016/03/23 00:14:51
Acknowledged.
|
| + return false; |
| + } |
| + |
| + int numberOfPeriods = 0; |
| + int numberOfDigits = 0; |
| + int numberOfFractionDigits = 0; |
| + for (size_t i = 0; i < amount.length(); ++i) { |
| + if (i == 0 && amount[i] == '-') |
| + continue; |
| + |
| + bool isPeriod = amount[i] == '.'; |
| + bool isDigit = amount[i] >= '0' && amount[i] <= '9'; |
|
groby-ooo-7-16
2016/03/18 00:47:25
isdigit? (Well, OK, isASCIIDigit - I forgot we're
please use gerrit instead
2016/03/23 00:14:51
Using the regex from the spec instead.
|
| + if (!isPeriod && !isDigit) { |
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain only '-', '.', and [0-9]"); |
|
groby-ooo-7-16
2016/03/18 00:47:25
CurrencyAnd30Amount? (Here and elsewhere)
please use gerrit instead
2016/03/23 00:14:51
That's a newer replacement for ISO 4217 that our W
|
| + return false; |
| + } |
| + |
| + if (isPeriod) |
| + ++numberOfPeriods; |
| + |
| + if (isDigit) { |
| + ++numberOfDigits; |
| + if (numberOfPeriods > 0) |
| + ++numberOfFractionDigits; |
| + } |
| + } |
| + |
| + if (numberOfPeriods > 1) { |
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain only one '.'"); |
| + return false; |
| + } |
| + |
| + if (numberOfDigits > 30) { |
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain at most 30 digits"); |
| + return false; |
| + } |
| + |
| + if (numberOfFractionDigits > 10) { |
| + exceptionState.throwTypeError("'" + amount + "' is not a valid ISO 200222 CurrencyAnd30Amount currency amount, should contain at most 10 fraction digits"); |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +template <typename T> |
| +bool areValidCurrencyAndAmounts(HeapVector<T> items, ExceptionState& exceptionState) |
| +{ |
| + for (const auto& item : items) { |
| + if (item.hasAmount()) { |
| + if (item.amount().hasCurrencyCode() && !isValidCurrencyCode(item.amount().currencyCode(), exceptionState)) |
| + return false; |
| + |
| + if (item.amount().hasValue() && isValidAmount(item.amount().value(), exceptionState)) |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
Should this be !isValidAmount?
And some kind of te
please use gerrit instead
2016/03/23 00:14:51
Good catch. Tested and fixed.
|
| + return false; |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +} // namespace |
| // static |
| PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<String>& supportedMethods, const PaymentDetails& details, ExceptionState& exceptionState) |
| @@ -38,11 +138,29 @@ PaymentRequest::~PaymentRequest() |
| ScriptPromise PaymentRequest::show(ScriptState* scriptState) |
| { |
| - return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError, "Not implemented.")); |
| + if (m_showResolver) |
| + return m_showResolver->promise().rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called show() once")); |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
This seems weird. rejectWithDOMException is a stat
please use gerrit instead
2016/03/23 00:14:51
Did not realize it's static. Calling it properly n
|
| + |
| + ASSERT(!m_paymentProvider.is_bound()); |
| + blink::Platform::current()->connectToRemoteService(mojo::GetProxy(&m_paymentProvider)); |
| + if (!m_paymentProvider) |
| + return m_showResolver->promise().rejectWithDOMException(scriptState, DOMException::create(SyntaxError, "Not implemented on this platform")); |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
Same here, should just be ScriptPromise::rejectWit
please use gerrit instead
2016/03/23 00:14:51
Removed the check from this patch. Not writing con
|
| + |
| + m_paymentProvider->SetClient(m_clientBinding.CreateInterfacePtrAndBind()); |
| + // TODO(rouslan): Call show() after WTF-Mojo serialization works. |
| + |
| + m_showResolver = ScriptPromiseResolver::create(scriptState); |
| + return m_showResolver->promise(); |
| } |
| -void PaymentRequest::abort() |
| +void PaymentRequest::abort(ExceptionState& exceptionState) |
| { |
| + if (!m_showResolver) { |
| + exceptionState.throwDOMException(InvalidStateError, "Never called show(), so nothing to abort"); |
| + return; |
| + } |
| + |
| + m_paymentProvider->Abort(); |
| } |
| const AtomicString& PaymentRequest::interfaceName() const |
| @@ -55,11 +173,24 @@ ExecutionContext* PaymentRequest::getExecutionContext() const |
| return m_scriptState->getExecutionContext(); |
| } |
| +ScriptPromise PaymentRequest::complete(ScriptState* scriptState, bool success) |
| +{ |
| + if (m_completeResolver) |
| + return m_completeResolver->promise().rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called complete() once")); |
|
Marijn Kruisselbrink
2016/03/18 01:39:48
Same static method call thing.
please use gerrit instead
2016/03/23 00:14:51
Done.
|
| + |
| + m_completeResolver = ScriptPromiseResolver::create(scriptState); |
| + m_paymentProvider->Complete(success); |
| + |
| + return m_completeResolver->promise(); |
| +} |
| + |
| DEFINE_TRACE(PaymentRequest) |
| { |
| visitor->trace(m_details); |
| visitor->trace(m_options); |
| visitor->trace(m_shippingAddress); |
| + visitor->trace(m_showResolver); |
| + visitor->trace(m_completeResolver); |
| RefCountedGarbageCollectedEventTargetWithInlineData<PaymentRequest>::trace(visitor); |
| } |
| @@ -68,12 +199,109 @@ PaymentRequest::PaymentRequest(ScriptState* scriptState, const Vector<String>& s |
| , m_supportedMethods(supportedMethods) |
| , m_details(details) |
| , m_options(options) |
| + , m_clientBinding(this) |
| { |
| + // TODO(rouslan): Also check for a top-level browsing context. |
| + // https://github.com/w3c/browser-payment-api/issues/2 |
| + if (!scriptState->getExecutionContext()->isSecureContext()) { |
| + exceptionState.throwSecurityError("Must be in a secure context"); |
| + return; |
| + } |
| + |
| + if (supportedMethods.isEmpty()) { |
| + exceptionState.throwTypeError("No payment methods identifiers provided"); |
| + return; |
| + } |
| + |
| + if (details.hasItems() && !areValidCurrencyAndAmounts(details.items(), exceptionState)) |
| + return; |
| + |
| + if (details.hasShippingOptions() && !areValidCurrencyAndAmounts(details.shippingOptions(), exceptionState)) |
| + return; |
| + |
| if (!data.isEmpty()) { |
| RefPtr<JSONValue> value = toJSONValue(data.context(), data.v8Value()); |
| - if (value && value->getType() == JSONValue::TypeObject) |
| - m_stringifiedData = JSONObject::cast(value)->toJSONString(); |
| + if (value && !value->isNull()) { |
| + if (value->getType() != JSONValue::TypeObject) { |
| + exceptionState.throwTypeError("Payment method specific data should be a JSON-serializable object"); |
| + return; |
| + } |
| + |
| + RefPtr<JSONObject> jsonData = JSONObject::cast(value); |
| + for (const auto& paymentMethodSpecificKeyValue : *jsonData) { |
| + if (!supportedMethods.contains(paymentMethodSpecificKeyValue.key)) { |
| + exceptionState.throwTypeError("'" + paymentMethodSpecificKeyValue.key + "' does not match one of the payment method identifiers"); |
| + return; |
| + } |
| + if (!paymentMethodSpecificKeyValue.value || paymentMethodSpecificKeyValue.value->isNull() || paymentMethodSpecificKeyValue.value->getType() != JSONValue::TypeObject) { |
| + exceptionState.throwTypeError("Data for '" + paymentMethodSpecificKeyValue.key + "' should be a JSON-serializable object"); |
| + return; |
| + } |
| + } |
| + |
| + m_stringifiedData = jsonData->toJSONString(); |
| + } |
| } |
| + |
| + if (details.hasShippingOptions() && details.shippingOptions().size() == 1 && details.shippingOptions()[0].hasId()) |
| + m_shippingOption = details.shippingOptions()[0].id(); |
| +} |
| + |
| +void PaymentRequest::OnShippingAddressChange(mojom::blink::ShippingAddressPtr address) |
| +{ |
| + ASSERT(m_showResolver); |
| + ASSERT(!m_completeResolver); |
| + // TODO(rouslan): Should the merchant website be notified of invalid shipping address |
| + // from the browser or the payment app? |
| + m_shippingAddress = new ShippingAddress(std::move(address)); |
| + if (!m_shippingAddress->isValidRegionCode()) { |
| + m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 3166 country code")); |
| + return; |
| + } |
| + if (!m_shippingAddress->isValidLanguageCode()) { |
| + m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 639 language code")); |
| + return; |
| + } |
| + if (!m_shippingAddress->isValidScriptCode()) { |
| + m_showResolver->reject(DOMException::create(SyntaxError, "Invalid ISO 1524 script code")); |
| + return; |
| + } |
| + RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingaddresschange); |
| + event->setTarget(this); |
| + getExecutionContext()->getEventQueue()->enqueueEvent(event); |
| +} |
| + |
| +void PaymentRequest::OnShippingOptionChange(const String& shippingOptionId) |
| +{ |
| + ASSERT(m_showResolver); |
| + ASSERT(!m_completeResolver); |
| + m_shippingOption = shippingOptionId; |
| + RefPtrWillBeRawPtr<Event> event = Event::create(EventTypeNames::shippingoptionchange); |
| + event->setTarget(this); |
| + getExecutionContext()->getEventQueue()->enqueueEvent(event); |
| +} |
| + |
| +void PaymentRequest::OnPaymentResponse(mojom::blink::PaymentResponsePtr response) |
| +{ |
| + ASSERT(m_showResolver); |
| + ASSERT(!m_completeResolver); |
| + m_showResolver->resolve(new PaymentResponse(std::move(response), this)); |
| +} |
| + |
| +void PaymentRequest::OnError() |
| +{ |
| + if (m_completeResolver) { |
| + m_completeResolver->reject(DOMException::create(SyntaxError, "Request cancelled")); |
| + return; |
| + } |
| + ASSERT(m_showResolver); |
| + m_showResolver->reject(DOMException::create(SyntaxError, "Request cancelled")); |
| +} |
| + |
| +void PaymentRequest::OnComplete() |
| +{ |
| + ASSERT(m_completeResolver); |
| + m_completeResolver->resolve(); |
| } |
| } // namespace blink |