| 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
 | 
| 
 |