| 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..1d112fe950ad899ccc88097e69635991d125f01b 100644 | 
| --- a/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp | 
| +++ b/third_party/WebKit/Source/modules/payments/PaymentRequest.cpp | 
| @@ -6,27 +6,146 @@ | 
|  | 
| #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 "mojo/public/cpp/bindings/wtf_array.h" | 
| +#include "platform/MojoHelper.h" | 
| +#include "public/platform/Platform.h" | 
| +#include <utility> | 
| + | 
| +namespace mojo { | 
| + | 
| +template <> | 
| +struct TypeConverter<mojom::wtf::CurrencyAmountPtr, blink::CurrencyAmount> { | 
| +    static mojom::wtf::CurrencyAmountPtr Convert(const blink::CurrencyAmount& input) | 
| +    { | 
| +        mojom::wtf::CurrencyAmountPtr output = mojom::wtf::CurrencyAmount::New(); | 
| +        output->currency_code = input.currencyCode(); | 
| +        output->value = input.value(); | 
| +        return output; | 
| +    } | 
| +}; | 
| + | 
| +template <> | 
| +struct TypeConverter<mojom::wtf::PaymentItemPtr, blink::PaymentItem> { | 
| +    static mojom::wtf::PaymentItemPtr Convert(const blink::PaymentItem& input) | 
| +    { | 
| +        mojom::wtf::PaymentItemPtr output = mojom::wtf::PaymentItem::New(); | 
| +        output->id = input.id(); | 
| +        output->label = input.label(); | 
| +        output->amount = mojom::wtf::CurrencyAmount::From(input.amount()); | 
| +        return output; | 
| +    } | 
| +}; | 
| + | 
| +template <> | 
| +struct TypeConverter<mojom::wtf::ShippingOptionPtr, blink::ShippingOption> { | 
| +    static mojom::wtf::ShippingOptionPtr Convert(const blink::ShippingOption& input) | 
| +    { | 
| +        mojom::wtf::ShippingOptionPtr output = mojom::wtf::ShippingOption::New(); | 
| +        output->id = input.id(); | 
| +        output->label = input.label(); | 
| +        output->amount = mojom::wtf::CurrencyAmount::From(input.amount()); | 
| +        return output; | 
| +    } | 
| +}; | 
| + | 
| +template <> | 
| +struct TypeConverter<mojom::wtf::PaymentDetailsPtr, blink::PaymentDetails> { | 
| +    static mojom::wtf::PaymentDetailsPtr Convert(const blink::PaymentDetails& input) | 
| +    { | 
| +        mojom::wtf::PaymentDetailsPtr output = mojom::wtf::PaymentDetails::New(); | 
| +        output->items = mojo::WTFArray<mojom::wtf::PaymentItemPtr>::From(input.items()); | 
| +        if (input.hasShippingOptions()) | 
| +            output->shipping_options = mojo::WTFArray<mojom::wtf::ShippingOptionPtr>::From(input.shippingOptions()); | 
| +        else | 
| +            output->shipping_options = mojo::WTFArray<mojom::wtf::ShippingOptionPtr>::New(0); | 
| +        return output; | 
| +    } | 
| +}; | 
| + | 
| +template <> | 
| +struct TypeConverter<mojom::wtf::PaymentOptionsPtr, blink::PaymentOptions> { | 
| +    static mojom::wtf::PaymentOptionsPtr Convert(const blink::PaymentOptions& input) | 
| +    { | 
| +        mojom::wtf::PaymentOptionsPtr output = mojom::wtf::PaymentOptions::New(); | 
| +        output->request_shipping = input.requestShipping(); | 
| +        return output; | 
| +    } | 
| +}; | 
| + | 
| +} // namespace mojo | 
|  | 
| namespace blink { | 
| +namespace { | 
| + | 
| +// Validates ShippingOption and PaymentItem dictionaries, which happen to have identical fields. | 
| +template <typename T> | 
| +void validateShippingOptionsOrPaymentItems(HeapVector<T> items, ExceptionState& exceptionState) | 
| +{ | 
| +    String errorMessage; | 
| +    for (const auto& item : items) { | 
| +        if (!item.hasId()) { | 
| +            exceptionState.throwTypeError("Item id required"); | 
| +            return; | 
| +        } | 
| + | 
| +        if (!item.hasLabel()) { | 
| +            exceptionState.throwTypeError("Item label required"); | 
| +            return; | 
| +        } | 
| + | 
| +        if (!item.hasAmount()) { | 
| +            exceptionState.throwTypeError("Currency amount required"); | 
| +            return; | 
| +        } | 
| + | 
| +        if (!item.amount().hasCurrencyCode()) { | 
| +            exceptionState.throwTypeError("Currency code required"); | 
| +            return; | 
| +        } | 
| + | 
| +        if (!item.amount().hasValue()) { | 
| +            exceptionState.throwTypeError("Currency value required"); | 
| +            return; | 
| +        } | 
| + | 
| +        if (!PaymentsValidators::isValidCurrencyCodeFormat(item.amount().currencyCode(), &errorMessage)) { | 
| +            exceptionState.throwTypeError(errorMessage); | 
| +            return; | 
| +        } | 
| + | 
| +        if (!PaymentsValidators::isValidAmountFormat(item.amount().value(), &errorMessage)) { | 
| +            exceptionState.throwTypeError(errorMessage); | 
| +            return; | 
| +        } | 
| +    } | 
| +} | 
| + | 
| +} // namespace | 
|  | 
| -// static | 
| PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<String>& supportedMethods, const PaymentDetails& details, ExceptionState& exceptionState) | 
| { | 
| return new PaymentRequest(scriptState, supportedMethods, details, PaymentOptions(), ScriptValue(), exceptionState); | 
| } | 
|  | 
| -// static | 
| PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<String>& supportedMethods, const PaymentDetails& details, const PaymentOptions& options, ExceptionState& exceptionState) | 
| { | 
| return new PaymentRequest(scriptState, supportedMethods, details, options, ScriptValue(), exceptionState); | 
| } | 
|  | 
| -// static | 
| PaymentRequest* PaymentRequest::create(ScriptState* scriptState, const Vector<String>& supportedMethods, const PaymentDetails& details, const PaymentOptions& options, const ScriptValue& data, ExceptionState& exceptionState) | 
| { | 
| return new PaymentRequest(scriptState, supportedMethods, details, options, data, exceptionState); | 
| @@ -38,11 +157,27 @@ 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")); | 
| + | 
| +    DCHECK(!m_paymentProvider.is_bound()); | 
| +    blink::Platform::current()->connectToRemoteService(mojo::GetProxy(&m_paymentProvider)); | 
| +    m_paymentProvider.set_connection_error_handler(sameThreadBindForMojo(&PaymentRequest::OnError, this)); | 
| +    m_paymentProvider->SetClient(m_clientBinding.CreateInterfacePtrAndBind()); | 
| +    m_paymentProvider->Show(std::move(m_supportedMethods), mojom::wtf::PaymentDetails::From(m_details), mojom::wtf::PaymentOptions::From(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 +190,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 +216,138 @@ 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 method identifier"); | 
| +        return; | 
| +    } | 
| + | 
| +    if (!details.hasItems()) { | 
| +        exceptionState.throwTypeError("Must specify items"); | 
| +        return; | 
| +    } | 
| + | 
| +    if (details.items().isEmpty()) { | 
| +        exceptionState.throwTypeError("Must specify at least one item"); | 
| +        return; | 
| +    } | 
| + | 
| +    validateShippingOptionsOrPaymentItems(details.items(), exceptionState); | 
| +    if (exceptionState.hadException()) | 
| +        return; | 
| + | 
| +    if (details.hasShippingOptions()) { | 
| +        validateShippingOptionsOrPaymentItems(details.shippingOptions(), exceptionState); | 
| +        if (exceptionState.hadException()) | 
| +            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) { | 
| +            exceptionState.throwTypeError("Unable to parse payment method specific data"); | 
| +            return; | 
| +        } | 
| +        if (!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->getType() != JSONValue::TypeObject) { | 
| +                    exceptionState.throwTypeError("Data for '" + paymentMethodSpecificKeyValue.key + "' should be a JSON-serializable object"); | 
| +                    return; | 
| +                } | 
| +            } | 
| + | 
| +            m_stringifiedData = jsonData->toJSONString(); | 
| +        } | 
| +    } | 
| + | 
| +    // Set the currently selected option if only one option was passed. | 
| +    if (details.hasShippingOptions() && details.shippingOptions().size() == 1) | 
| +        m_shippingOption = details.shippingOptions().begin()->id(); | 
| +} | 
| + | 
| +void PaymentRequest::OnShippingAddressChange(mojom::wtf::ShippingAddressPtr address) | 
| +{ | 
| +    DCHECK(m_showResolver); | 
| +    DCHECK(!m_completeResolver); | 
| + | 
| +    String errorMessage; | 
| +    if (!PaymentsValidators::isValidRegionCodeFormat(address->region_code, &errorMessage) | 
| +        || !PaymentsValidators::isValidLanguageCodeFormat(address->language_code, &errorMessage) | 
| +        || !PaymentsValidators::isValidScriptCodeFormat(address->script_code, &errorMessage)) { | 
| +        m_showResolver->reject(DOMException::create(SyntaxError, errorMessage)); | 
| +        cleanUp(); | 
| +        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")); | 
| +        cleanUp(); | 
| +        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) | 
| +{ | 
| +    DCHECK(m_showResolver); | 
| +    DCHECK(!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) | 
| +{ | 
| +    DCHECK(m_showResolver); | 
| +    DCHECK(!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")); | 
| +    if (m_showResolver) | 
| +        m_showResolver->reject(DOMException::create(SyntaxError, "Request cancelled")); | 
| +    cleanUp(); | 
| +} | 
| + | 
| +void PaymentRequest::OnComplete() | 
| +{ | 
| +    DCHECK(m_completeResolver); | 
| +    m_completeResolver->resolve(); | 
| +    cleanUp(); | 
| +} | 
| + | 
| +void PaymentRequest::cleanUp() | 
| +{ | 
| +    m_completeResolver.clear(); | 
| +    m_showResolver.clear(); | 
| +    m_clientBinding.Close(); | 
| +    m_paymentProvider.reset(); | 
| } | 
|  | 
| } // namespace blink | 
|  |