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..35ce2b303028059456de98e440412bc68e06b122 100644 | 
| --- a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp | 
| +++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp | 
| @@ -6,13 +6,107 @@ | 
| #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/PaymentsValidators.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 { | 
| + | 
| +template <typename T> | 
| +bool areValidItems(HeapVector<T> items, ExceptionState& exceptionState) | 
| 
 
Marijn Kruisselbrink
2016/03/25 17:13:31
Maybe add a comment explaining why this is a templ
 
please use gerrit instead
2016/03/25 19:26:37
Done.
 
 | 
| +{ | 
| + String errorMessage; | 
| + for (const auto& item : items) { | 
| + if (!item.hasId()) { | 
| + exceptionState.throwTypeError("Item id required"); | 
| + return false; | 
| + } | 
| + | 
| + if (!item.hasLabel()) { | 
| + exceptionState.throwTypeError("Item label required"); | 
| + return false; | 
| + } | 
| + | 
| + if (!item.hasAmount()) { | 
| + exceptionState.throwTypeError("Currency amount required"); | 
| + return false; | 
| + } | 
| + | 
| + if (!item.amount().hasCurrencyCode()) { | 
| + exceptionState.throwTypeError("Currency code required"); | 
| + return false; | 
| + } | 
| + | 
| + if (!item.amount().hasValue()) { | 
| + exceptionState.throwTypeError("Currency value required"); | 
| + return false; | 
| + } | 
| + | 
| + if (!isValidCurrencyCodeFormat(item.amount().currencyCode(), &errorMessage)) { | 
| + exceptionState.throwTypeError(errorMessage); | 
| + return false; | 
| + } | 
| + | 
| + if (!isValidAmountFormat(item.amount().value(), &errorMessage)) { | 
| + exceptionState.throwTypeError(errorMessage); | 
| + return false; | 
| + } | 
| + } | 
| + | 
| + return true; | 
| +} | 
| + | 
| +template <typename Output, typename Input> | 
| 
 
Marijn Kruisselbrink
2016/03/25 17:13:30
Same here, without already knowing the rest of the
 
please use gerrit instead
2016/03/25 19:26:37
Done.
 
 | 
| +void toMojoItem(const Input& input, Output& output) | 
| +{ | 
| + output->id = input.id(); | 
| + output->label = input.label(); | 
| + output->amount = mojom::wtf::CurrencyAmount::New(); | 
| + output->amount->currency_code = input.amount().currencyCode(); | 
| + output->amount->value = input.amount().value(); | 
| +} | 
| + | 
| +mojom::wtf::PaymentDetailsPtr toMojoPaymentDetails(const PaymentDetails& input) | 
| 
 
Marijn Kruisselbrink
2016/03/25 17:13:30
All these "toMojo*" methods should probably be spe
 
please use gerrit instead
2016/03/25 19:26:37
Done.
 
 | 
| +{ | 
| + mojom::wtf::PaymentDetailsPtr output = mojom::wtf::PaymentDetails::New(); | 
| + output->items = mojo::WTFArray<mojom::wtf::PaymentItemPtr>::New(input.items().size()); | 
| + for (size_t i = 0; i < input.items().size(); ++i) { | 
| + output->items[i] = mojom::wtf::PaymentItem::New(); | 
| + toMojoItem(input.items()[i], output->items[i]); | 
| + } | 
| + | 
| + output->shipping_options = mojo::WTFArray<mojom::wtf::ShippingOptionPtr>::New(input.hasShippingOptions() ? input.shippingOptions().size() : 0); | 
| + if (input.hasShippingOptions()) { | 
| + for (size_t i = 0; i < input.shippingOptions().size(); ++i) { | 
| + output->shipping_options[i] = mojom::wtf::ShippingOption::New(); | 
| + toMojoItem(input.shippingOptions()[i], output->shipping_options[i]); | 
| + } | 
| + } | 
| + return output; | 
| +} | 
| + | 
| +mojom::wtf::PaymentOptionsPtr toMojoPaymentOptions(const PaymentOptions& input) | 
| +{ | 
| + mojom::wtf::PaymentOptionsPtr output = mojom::wtf::PaymentOptions::New(); | 
| + output->request_shipping = input.requestShipping(); | 
| + return output; | 
| +} | 
| + | 
| +} // namespace | 
| // static | 
| PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<String>& supportedMethods, const PaymentDetails& details, ExceptionState& exceptionState) | 
| @@ -38,11 +132,26 @@ PaymentRequest::~PaymentRequest() | 
| ScriptPromise PaymentRequest::show(ScriptState* scriptState) | 
| { | 
| - return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(NotSupportedError, "Not implemented.")); | 
| + if (m_showResolver) | 
| + return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called show() once")); | 
| + | 
| + ASSERT(!m_paymentProvider.is_bound()); | 
| 
 
Marijn Kruisselbrink
2016/03/25 17:13:31
DCHECK?
 
please use gerrit instead
2016/03/25 19:26:37
Done.
 
 | 
| + blink::Platform::current()->connectToRemoteService(mojo::GetProxy(&m_paymentProvider)); | 
| 
 
Marijn Kruisselbrink
2016/03/25 17:13:30
Maybe add a TODO to handle errors for the two mojo
 
please use gerrit instead
2016/03/25 19:26:37
Done.
 
 | 
| + m_paymentProvider->SetClient(m_clientBinding.CreateInterfacePtrAndBind()); | 
| + m_paymentProvider->Show(std::move(m_supportedMethods), toMojoPaymentDetails(m_details), toMojoPaymentOptions(m_options), m_stringifiedData.isNull() ? "" : m_stringifiedData); | 
| + | 
| + 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 +164,24 @@ ExecutionContext* PaymentRequest::getExecutionContext() const | 
| return m_scriptState->getExecutionContext(); | 
| } | 
| +ScriptPromise PaymentRequest::complete(ScriptState* scriptState, bool success) | 
| +{ | 
| + if (m_completeResolver) | 
| + return ScriptPromise::rejectWithDOMException(scriptState, DOMException::create(InvalidStateError, "Already called complete() once")); | 
| + | 
| + 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 +190,131 @@ 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("Must specify at least one payment methods identifiers"); | 
| 
 
Marijn Kruisselbrink
2016/03/25 17:13:30
"one payment methods identifiers" doesn't seem lik
 
please use gerrit instead
2016/03/25 19:26:37
Done.
 
 | 
| + return; | 
| + } | 
| + | 
| + if (!details.hasItems()) { | 
| + exceptionState.throwTypeError("Must specify items"); | 
| + return; | 
| + } | 
| + | 
| + if (details.items().isEmpty()) { | 
| + exceptionState.throwTypeError("Must specify at least one item"); | 
| + return; | 
| + } | 
| + | 
| + if (!areValidItems(details.items(), exceptionState)) | 
| + return; | 
| + | 
| + if (details.hasShippingOptions() && !areValidItems(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 + "' should 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) | 
| + m_shippingOption = details.shippingOptions()[0].id(); | 
| +} | 
| + | 
| +void PaymentRequest::OnShippingAddressChange(mojom::wtf::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? | 
| + String errorMessage; | 
| + if (!isValidRegionCodeFormat(address->region_code, &errorMessage)) { | 
| + m_showResolver->reject(DOMException::create(SyntaxError, errorMessage)); | 
| + return; | 
| + } | 
| + | 
| + if (!isValidLanguageCodeFormat(address->language_code, &errorMessage)) { | 
| + m_showResolver->reject(DOMException::create(SyntaxError, errorMessage)); | 
| + return; | 
| + } | 
| + | 
| + if (!isValidScriptCodeFormat(address->script_code, &errorMessage)) { | 
| + m_showResolver->reject(DOMException::create(SyntaxError, errorMessage)); | 
| + return; | 
| + } | 
| + | 
| + if (address->language_code.isEmpty() && !address->script_code.isEmpty()) { | 
| + m_showResolver->reject(DOMException::create(SyntaxError, "If language code is empty, then script code should also be empty")); | 
| + return; | 
| + } | 
| + | 
| + m_shippingAddress = new ShippingAddress(std::move(address)); | 
| + 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::wtf::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")); | 
| + m_paymentProvider.reset(); | 
| 
 
Marijn Kruisselbrink
2016/03/25 17:13:30
I wonder if wherever the connection to the payment
 
please use gerrit instead
2016/03/25 19:26:37
Done.
 
 | 
| +} | 
| + | 
| +void PaymentRequest::OnComplete() | 
| +{ | 
| + ASSERT(m_completeResolver); | 
| + m_completeResolver->resolve(); | 
| + m_paymentProvider.reset(); | 
| } | 
| } // namespace blink |