blob: ec0d8555267d2f73ad61fc71b518b2fd665cc944 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/payments/payment_app_provider_impl.h"
#include <map>
#include <sstream>
#include <string>
#include <vector>
#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/token.h"
#include "components/payments/core/native_error_strings.h"
#include "components/payments/core/payments_validators.h"
#include "content/browser/devtools/devtools_background_services_context_impl.h"
#include "content/browser/payments/payment_app_context_impl.h"
#include "content/browser/payments/payment_app_installer.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_metrics.h"
#include "content/browser/service_worker/service_worker_version.h"
#include "content/browser/storage_partition_impl.h"
#include "content/common/service_worker/service_worker_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_background_services_context.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/mojom/base/time.mojom.h"
#include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_container_type.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image.h"
namespace content {
namespace {
using payments::mojom::CanMakePaymentEventDataPtr;
using payments::mojom::CanMakePaymentEventResponseType;
using payments::mojom::CanMakePaymentResponse;
using payments::mojom::CanMakePaymentResponsePtr;
using payments::mojom::PaymentDetailsModifierPtr;
using payments::mojom::PaymentEventResponseType;
using payments::mojom::PaymentHandlerResponse;
using payments::mojom::PaymentHandlerResponseCallback;
using payments::mojom::PaymentHandlerResponsePtr;
using payments::mojom::PaymentMethodDataPtr;
using payments::mojom::PaymentRequestEventDataPtr;
using ServiceWorkerStartCallback =
base::OnceCallback<void(scoped_refptr<ServiceWorkerVersion>,
blink::ServiceWorkerStatusCode)>;
CanMakePaymentResponsePtr CreateBlankCanMakePaymentResponse(
CanMakePaymentEventResponseType response_type) {
return CanMakePaymentResponse::New(response_type, /*can_make_payment=*/false,
/*ready_for_minimal_ui=*/false,
/*account_balance=*/base::nullopt);
}
PaymentHandlerResponsePtr CreateBlankPaymentHandlerResponse(
PaymentEventResponseType response_type) {
return PaymentHandlerResponse::New(
"" /*=method_name*/, "" /*=stringified_details*/, response_type,
base::nullopt /*=payer_name*/, base::nullopt /*=payer_email*/,
base::nullopt /*=payer_phone*/, nullptr /*=shipping_address*/,
base::nullopt /*=shipping_option*/);
}
class InvokeRespondWithCallback;
// A repository to store invoking payment app callback. It is used to abort
// payment when the opened payment handler window is closed before payment
// response is received or timeout.
// Note that there is only one opened payment handler window per browser
// context.
class InvokePaymentAppCallbackRepository {
public:
static InvokePaymentAppCallbackRepository* GetInstance() {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
return base::Singleton<InvokePaymentAppCallbackRepository>::get();
}
// Disallow copy and assign.
InvokePaymentAppCallbackRepository(
const InvokePaymentAppCallbackRepository& other) = delete;
InvokePaymentAppCallbackRepository& operator=(
const InvokePaymentAppCallbackRepository& other) = delete;
InvokeRespondWithCallback* GetCallback(BrowserContext* browser_context) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
auto it = invoke_callbacks_.find(browser_context);
if (it != invoke_callbacks_.end()) {
return it->second;
}
return nullptr;
}
void SetCallback(BrowserContext* browser_context,
InvokeRespondWithCallback* callback) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
invoke_callbacks_[browser_context] = callback;
}
void RemoveCallback(BrowserContext* browser_context) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
invoke_callbacks_.erase(browser_context);
}
private:
InvokePaymentAppCallbackRepository() = default;
~InvokePaymentAppCallbackRepository() = default;
friend struct base::DefaultSingletonTraits<
InvokePaymentAppCallbackRepository>;
std::map<BrowserContext*, InvokeRespondWithCallback*> invoke_callbacks_;
};
// Abstract base class for event callbacks that are invoked when the payment
// handler resolves the promise passed in to TheEvent.respondWith() method.
class RespondWithCallback : public PaymentHandlerResponseCallback {
public:
// Disallow copy and assign.
RespondWithCallback(const RespondWithCallback& other) = delete;
RespondWithCallback& operator=(const RespondWithCallback& other) = delete;
mojo::PendingRemote<PaymentHandlerResponseCallback>
BindNewPipeAndPassRemote() {
return receiver_.BindNewPipeAndPassRemote();
}
protected:
RespondWithCallback(
BrowserContext* browser_context,
ServiceWorkerMetrics::EventType event_type,
scoped_refptr<ServiceWorkerVersion> service_worker_version)
: browser_context_(browser_context),
service_worker_version_(service_worker_version) {
request_id_ = service_worker_version->StartRequest(
event_type, base::BindOnce(&RespondWithCallback::OnServiceWorkerError,
weak_ptr_factory_.GetWeakPtr()));
}
~RespondWithCallback() override = default;
// PaymentHandlerResponseCallback implementation.
void OnResponseForCanMakePayment(
CanMakePaymentResponsePtr response) override {}
// PaymentHandlerResponseCallback implementation.
void OnResponseForPaymentRequest(
PaymentHandlerResponsePtr response) override {}
// PaymentHandlerResponseCallback implementation.
void OnResponseForAbortPayment(bool payment_aborted) override {}
virtual void OnServiceWorkerError(
blink::ServiceWorkerStatusCode service_worker_status) = 0;
void FinishServiceWorkerRequest() {
service_worker_version_->FinishRequest(request_id_, false);
}
void MaybeRecordTimeoutMetric(blink::ServiceWorkerStatusCode status) {
if (status == blink::ServiceWorkerStatusCode::kErrorTimeout) {
UMA_HISTOGRAM_BOOLEAN("PaymentRequest.ServiceWorkerStatusCodeTimeout",
true);
}
}
void ClearCallbackRepositoryAndCloseWindow() {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
InvokePaymentAppCallbackRepository::GetInstance()->RemoveCallback(
browser_context_);
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CloseClientWindowOnUIThread));
}
private:
static void CloseClientWindowOnUIThread() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PaymentAppProvider::GetInstance()->CloseOpenedWindow();
}
int request_id_;
BrowserContext* browser_context_;
scoped_refptr<ServiceWorkerVersion> service_worker_version_;
mojo::Receiver<PaymentHandlerResponseCallback> receiver_{this};
base::WeakPtrFactory<RespondWithCallback> weak_ptr_factory_{this};
};
// Self-deleting callback for "canmakepayment" event. Invoked when the payment
// handler resolves the promise passed into CanMakePaymentEvent.respondWith()
// method.
class CanMakePaymentRespondWithCallback : public RespondWithCallback {
public:
CanMakePaymentRespondWithCallback(
BrowserContext* browser_context,
scoped_refptr<ServiceWorkerVersion> service_worker_version,
PaymentAppProvider::CanMakePaymentCallback callback)
: RespondWithCallback(browser_context,
ServiceWorkerMetrics::EventType::CAN_MAKE_PAYMENT,
service_worker_version),
callback_(std::move(callback)) {}
// Disallow copy and assign.
CanMakePaymentRespondWithCallback(
const CanMakePaymentRespondWithCallback& other) = delete;
CanMakePaymentRespondWithCallback& operator=(
const CanMakePaymentRespondWithCallback& other) = delete;
private:
~CanMakePaymentRespondWithCallback() override = default;
// PaymentHandlerResponseCallback implementation.
void OnResponseForCanMakePayment(
CanMakePaymentResponsePtr response) override {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
FinishServiceWorkerRequest();
RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI,
base::BindOnce(std::move(callback_), std::move(response)));
delete this;
}
// RespondWithCallback implementation.
void OnServiceWorkerError(
blink::ServiceWorkerStatusCode service_worker_status) override {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
DCHECK_NE(service_worker_status, blink::ServiceWorkerStatusCode::kOk);
MaybeRecordTimeoutMetric(service_worker_status);
CanMakePaymentEventResponseType response_type =
CanMakePaymentEventResponseType::BROWSER_ERROR;
if (service_worker_status ==
blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected) {
response_type = CanMakePaymentEventResponseType::REJECT;
} else if (service_worker_status ==
blink::ServiceWorkerStatusCode::kErrorTimeout) {
response_type = CanMakePaymentEventResponseType::TIMEOUT;
}
RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI,
base::BindOnce(std::move(callback_),
CreateBlankCanMakePaymentResponse(response_type)));
delete this;
}
PaymentAppProvider::CanMakePaymentCallback callback_;
};
// Self-deleting callback for "paymentrequest" event. Invoked when the payment
// handler resolves the promise passed into PaymentRequestEvent.respondWith()
// method.
class InvokeRespondWithCallback : public RespondWithCallback {
public:
InvokeRespondWithCallback(
BrowserContext* browser_context,
scoped_refptr<ServiceWorkerVersion> service_worker_version,
PaymentAppProvider::InvokePaymentAppCallback callback)
: RespondWithCallback(browser_context,
ServiceWorkerMetrics::EventType::PAYMENT_REQUEST,
service_worker_version),
callback_(std::move(callback)) {
InvokePaymentAppCallbackRepository::GetInstance()->SetCallback(
browser_context, this);
}
// Disallow copy and assign.
InvokeRespondWithCallback(const InvokeRespondWithCallback& other) = delete;
InvokeRespondWithCallback& operator=(const InvokeRespondWithCallback& other) =
delete;
// Called only for "paymentrequest" event.
void AbortPaymentSinceOpennedWindowClosing(PaymentEventResponseType reason) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
FinishServiceWorkerRequest();
RespondToPaymentRequestWithErrorAndDeleteSelf(reason);
}
private:
~InvokeRespondWithCallback() override = default;
// PaymentHandlerResponseCallback implementation.
void OnResponseForPaymentRequest(
PaymentHandlerResponsePtr response) override {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
FinishServiceWorkerRequest();
RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI,
base::BindOnce(std::move(callback_), std::move(response)));
ClearCallbackRepositoryAndCloseWindow();
delete this;
}
// RespondWithCallback implementation.
void OnServiceWorkerError(
blink::ServiceWorkerStatusCode service_worker_status) override {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
DCHECK_NE(service_worker_status, blink::ServiceWorkerStatusCode::kOk);
MaybeRecordTimeoutMetric(service_worker_status);
PaymentEventResponseType response_type =
PaymentEventResponseType::PAYMENT_EVENT_BROWSER_ERROR;
if (service_worker_status ==
blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected) {
response_type = PaymentEventResponseType::PAYMENT_EVENT_REJECT;
} else if (service_worker_status ==
blink::ServiceWorkerStatusCode::kErrorTimeout) {
response_type = PaymentEventResponseType::PAYMENT_EVENT_TIMEOUT;
}
RespondToPaymentRequestWithErrorAndDeleteSelf(response_type);
}
void RespondToPaymentRequestWithErrorAndDeleteSelf(
PaymentEventResponseType response_type) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI,
base::BindOnce(std::move(callback_),
CreateBlankPaymentHandlerResponse(response_type)));
ClearCallbackRepositoryAndCloseWindow();
delete this;
}
PaymentAppProvider::InvokePaymentAppCallback callback_;
};
// Self-deleting callback for "abortpayment" event. Invoked when the payment
// handler resolves the promise passed into AbortPayment.respondWith() method.
class AbortRespondWithCallback : public RespondWithCallback {
public:
AbortRespondWithCallback(
BrowserContext* browser_context,
scoped_refptr<ServiceWorkerVersion> service_worker_version,
PaymentAppProvider::AbortCallback callback)
: RespondWithCallback(browser_context,
ServiceWorkerMetrics::EventType::ABORT_PAYMENT,
service_worker_version),
callback_(std::move(callback)) {}
// Disallow copy and assign.
AbortRespondWithCallback(const AbortRespondWithCallback& other) = delete;
AbortRespondWithCallback& operator=(const AbortRespondWithCallback& other) =
delete;
private:
~AbortRespondWithCallback() override = default;
// PaymentHandlerResponseCallback implementation.
void OnResponseForAbortPayment(bool payment_aborted) override {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
FinishServiceWorkerRequest();
RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI,
base::BindOnce(std::move(callback_), payment_aborted));
if (payment_aborted)
ClearCallbackRepositoryAndCloseWindow();
delete this;
}
// RespondWithCallback implementation.
void OnServiceWorkerError(
blink::ServiceWorkerStatusCode service_worker_status) override {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
DCHECK_NE(service_worker_status, blink::ServiceWorkerStatusCode::kOk);
MaybeRecordTimeoutMetric(service_worker_status);
RunOrPostTaskOnThread(
FROM_HERE, BrowserThread::UI,
base::BindOnce(std::move(callback_), /*payment_aborted=*/false));
// Do not call ClearCallbackRepositoryAndCloseWindow() here, because payment
// has not been aborted. The service worker either rejected, timed out, or
// threw a JavaScript exception in the "abortpayment" event, but that does
// not affect the ongoing "paymentrequest" event.
delete this;
}
PaymentAppProvider::AbortCallback callback_;
};
void DidUpdatePaymentAppIconOnCoreThread(
PaymentAppProvider::UpdatePaymentAppIconCallback callback,
payments::mojom::PaymentHandlerStatus status) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), status));
}
void UpdatePaymentAppIconOnCoreThread(
scoped_refptr<PaymentAppContextImpl> payment_app_context,
int64_t registration_id,
const std::string& instrument_key,
const std::string& name,
const std::string& string_encoded_icon,
const std::string& method_name,
const SupportedDelegations& supported_delegations,
PaymentAppProvider::UpdatePaymentAppIconCallback callback) {
DCHECK_CURRENTLY_ON(content::ServiceWorkerContext::GetCoreThreadId());
payment_app_context->payment_app_database()
->SetPaymentAppInfoForRegisteredServiceWorker(
registration_id, instrument_key, name, string_encoded_icon,
method_name, supported_delegations,
base::BindOnce(&DidUpdatePaymentAppIconOnCoreThread,
std::move(callback)));
}
void DispatchAbortPaymentEvent(
BrowserContext* browser_context,
PaymentAppProvider::AbortCallback callback,
scoped_refptr<ServiceWorkerVersion> active_version,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
DCHECK(active_version);
int event_finish_id = active_version->StartRequest(
ServiceWorkerMetrics::EventType::CAN_MAKE_PAYMENT, base::DoNothing());
// This object self-deletes after either success or error callback is
// invoked.
RespondWithCallback* respond_with_callback = new AbortRespondWithCallback(
browser_context, active_version, std::move(callback));
active_version->endpoint()->DispatchAbortPaymentEvent(
respond_with_callback->BindNewPipeAndPassRemote(),
active_version->CreateSimpleEventCallback(event_finish_id));
}
void DispatchCanMakePaymentEvent(
BrowserContext* browser_context,
CanMakePaymentEventDataPtr event_data,
PaymentAppProvider::CanMakePaymentCallback callback,
scoped_refptr<ServiceWorkerVersion> active_version,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
CreateBlankCanMakePaymentResponse(
CanMakePaymentEventResponseType::BROWSER_ERROR)));
return;
}
DCHECK(active_version);
int event_finish_id = active_version->StartRequest(
ServiceWorkerMetrics::EventType::CAN_MAKE_PAYMENT, base::DoNothing());
// This object self-deletes after either success or error callback is
// invoked.
RespondWithCallback* respond_with_callback =
new CanMakePaymentRespondWithCallback(browser_context, active_version,
std::move(callback));
active_version->endpoint()->DispatchCanMakePaymentEvent(
std::move(event_data), respond_with_callback->BindNewPipeAndPassRemote(),
active_version->CreateSimpleEventCallback(event_finish_id));
}
void DispatchPaymentRequestEvent(
BrowserContext* browser_context,
PaymentRequestEventDataPtr event_data,
PaymentAppProvider::InvokePaymentAppCallback callback,
scoped_refptr<ServiceWorkerVersion> active_version,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
CreateBlankPaymentHandlerResponse(
PaymentEventResponseType::PAYMENT_EVENT_BROWSER_ERROR)));
return;
}
DCHECK(active_version);
int event_finish_id = active_version->StartRequest(
ServiceWorkerMetrics::EventType::PAYMENT_REQUEST, base::DoNothing());
// This object self-deletes after either success or error callback is
// invoked.
RespondWithCallback* respond_with_callback = new InvokeRespondWithCallback(
browser_context, active_version, std::move(callback));
active_version->endpoint()->DispatchPaymentRequestEvent(
std::move(event_data), respond_with_callback->BindNewPipeAndPassRemote(),
active_version->CreateSimpleEventCallback(event_finish_id));
}
void DidFindRegistrationOnCoreThread(
ServiceWorkerStartCallback callback,
blink::ServiceWorkerStatusCode service_worker_status,
scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
std::move(callback).Run(nullptr, service_worker_status);
return;
}
ServiceWorkerVersion* active_version =
service_worker_registration->active_version();
DCHECK(active_version);
active_version->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::PAYMENT_REQUEST,
base::BindOnce(std::move(callback),
base::WrapRefCounted(active_version)));
}
void FindRegistrationOnCoreThread(
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
int64_t registration_id,
ServiceWorkerStartCallback callback) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
service_worker_context->FindReadyRegistrationForIdOnly(
registration_id,
base::BindOnce(&DidFindRegistrationOnCoreThread, std::move(callback)));
}
void StartServiceWorkerForDispatch(BrowserContext* browser_context,
int64_t registration_id,
ServiceWorkerStartCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context));
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context =
partition->GetServiceWorkerContext();
RunOrPostTaskOnThread(FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
base::BindOnce(&FindRegistrationOnCoreThread,
std::move(service_worker_context),
registration_id, std::move(callback)));
}
void OnInstallPaymentApp(
const url::Origin& sw_origin,
PaymentRequestEventDataPtr event_data,
PaymentAppProvider::RegistrationIdCallback registration_id_callback,
PaymentAppProvider::InvokePaymentAppCallback callback,
WebContents* web_contents,
int64_t registration_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (registration_id >= 0 && web_contents != nullptr) {
std::move(registration_id_callback).Run(registration_id);
PaymentAppProvider::GetInstance()->InvokePaymentApp(
web_contents, registration_id, sw_origin, std::move(event_data),
std::move(callback));
} else {
std::move(callback).Run(CreateBlankPaymentHandlerResponse(
PaymentEventResponseType::PAYMENT_EVENT_BROWSER_ERROR));
}
}
void AbortInvokePaymentApp(WebContents* web_contents,
PaymentEventResponseType reason) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
if (!web_contents)
return;
InvokeRespondWithCallback* callback =
InvokePaymentAppCallbackRepository::GetInstance()->GetCallback(
web_contents->GetBrowserContext());
if (callback)
callback->AbortPaymentSinceOpennedWindowClosing(reason);
}
void AddMethodDataToMap(const std::vector<PaymentMethodDataPtr>& method_data,
std::map<std::string, std::string>* out) {
for (size_t i = 0; i < method_data.size(); ++i) {
std::string counter =
method_data.size() == 1 ? "" : " #" + base::NumberToString(i);
out->emplace("Method Name" + counter, method_data[i]->supported_method);
out->emplace("Method Data" + counter, method_data[i]->stringified_data);
}
}
void AddModifiersToMap(const std::vector<PaymentDetailsModifierPtr>& modifiers,
std::map<std::string, std::string>* out) {
for (size_t i = 0; i < modifiers.size(); ++i) {
std::string prefix =
"Modifier" +
(modifiers.size() == 1 ? "" : " #" + base::NumberToString(i));
out->emplace(prefix + " Method Name",
modifiers[i]->method_data->supported_method);
out->emplace(prefix + " Method Data",
modifiers[i]->method_data->stringified_data);
if (!modifiers[i]->total)
continue;
out->emplace(prefix + " Total Currency",
modifiers[i]->total->amount->currency);
out->emplace(prefix + " Total Value", modifiers[i]->total->amount->value);
}
}
scoped_refptr<DevToolsBackgroundServicesContextImpl> GetDevTools(
BrowserContext* browser_context,
const url::Origin& sw_origin) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(browser_context);
auto* storage_partition = BrowserContext::GetStoragePartitionForSite(
browser_context, sw_origin.GetURL(), /*can_create=*/true);
if (!storage_partition)
return nullptr;
scoped_refptr<DevToolsBackgroundServicesContextImpl> dev_tools =
static_cast<DevToolsBackgroundServicesContextImpl*>(
storage_partition->GetDevToolsBackgroundServicesContext());
return dev_tools && dev_tools->IsRecording(
DevToolsBackgroundService::kPaymentHandler)
? dev_tools
: nullptr;
}
void OnResponseForCanMakePaymentOnUiThread(
scoped_refptr<DevToolsBackgroundServicesContextImpl> dev_tools,
int64_t registration_id,
const url::Origin& sw_origin,
const std::string& payment_request_id,
PaymentAppProvider::CanMakePaymentCallback callback,
CanMakePaymentResponsePtr response) {
std::string error_message;
if (response->account_balance && !response->account_balance->empty() &&
!payments::PaymentsValidators::IsValidAmountFormat(
*response->account_balance, &error_message)) {
mojo::ReportBadMessage(
payments::errors::kCanMakePaymentEventInvalidAccountBalanceValue);
response = CreateBlankCanMakePaymentResponse(
CanMakePaymentEventResponseType::INVALID_ACCOUNT_BALANCE_VALUE);
}
if (dev_tools) {
std::stringstream response_type;
response_type << response->response_type;
std::map<std::string, std::string> data = {
{"Type", response_type.str()},
{"Can Make Payment", response->can_make_payment ? "true" : "false"}};
if (base::FeatureList::IsEnabled(features::kWebPaymentsMinimalUI)) {
data["Ready for Minimal UI"] =
response->ready_for_minimal_ui ? "true" : "false";
data["Account Balance"] =
response->account_balance ? *response->account_balance : "";
}
dev_tools->LogBackgroundServiceEvent(
registration_id, sw_origin, DevToolsBackgroundService::kPaymentHandler,
"Can make payment response",
/*instance_id=*/payment_request_id, data);
}
std::move(callback).Run(std::move(response));
}
void OnResponseForAbortPaymentOnUiThread(
scoped_refptr<DevToolsBackgroundServicesContextImpl> dev_tools,
int64_t registration_id,
const url::Origin& sw_origin,
const std::string& payment_request_id,
PaymentAppProvider::AbortCallback callback,
bool payment_aborted) {
if (dev_tools) {
dev_tools->LogBackgroundServiceEvent(
registration_id, sw_origin, DevToolsBackgroundService::kPaymentHandler,
"Abort payment response",
/*instance_id=*/payment_request_id,
{{"Payment Aborted", payment_aborted ? "true" : "false"}});
}
std::move(callback).Run(payment_aborted);
}
void OnResponseForPaymentRequestOnUiThread(
scoped_refptr<DevToolsBackgroundServicesContextImpl> dev_tools,
int64_t registration_id,
const url::Origin& sw_origin,
const std::string& payment_request_id,
PaymentAppProvider::InvokePaymentAppCallback callback,
PaymentHandlerResponsePtr response) {
if (dev_tools) {
std::stringstream response_type;
response_type << response->response_type;
dev_tools->LogBackgroundServiceEvent(
registration_id, sw_origin, DevToolsBackgroundService::kPaymentHandler,
"Payment response",
/*instance_id=*/payment_request_id,
{{"Method Name", response->method_name},
{"Details", response->stringified_details},
{"Type", response_type.str()}});
}
std::move(callback).Run(std::move(response));
}
} // namespace
// static
PaymentAppProvider* PaymentAppProvider::GetInstance() {
return PaymentAppProviderImpl::GetInstance();
}
// static
PaymentAppProviderImpl* PaymentAppProviderImpl::GetInstance() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return base::Singleton<PaymentAppProviderImpl>::get();
}
void PaymentAppProviderImpl::InvokePaymentApp(
WebContents* web_contents,
int64_t registration_id,
const url::Origin& sw_origin,
PaymentRequestEventDataPtr event_data,
InvokePaymentAppCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents)
return;
scoped_refptr<DevToolsBackgroundServicesContextImpl> dev_tools =
GetDevTools(web_contents->GetBrowserContext(), sw_origin);
if (dev_tools) {
std::map<std::string, std::string> data = {
{"Merchant Top Origin", event_data->top_origin.spec()},
{"Merchant Payment Request Origin",
event_data->payment_request_origin.spec()},
{"Total Currency", event_data->total->currency},
{"Total Value", event_data->total->value},
{"Instrument Key", event_data->instrument_key},
};
AddMethodDataToMap(event_data->method_data, &data);
AddModifiersToMap(event_data->modifiers, &data);
dev_tools->LogBackgroundServiceEvent(
registration_id, sw_origin, DevToolsBackgroundService::kPaymentHandler,
"Payment request",
/*instance_id=*/event_data->payment_request_id, data);
}
StartServiceWorkerForDispatch(
web_contents->GetBrowserContext(), registration_id,
base::BindOnce(
&DispatchPaymentRequestEvent, web_contents->GetBrowserContext(),
std::move(event_data),
base::BindOnce(&OnResponseForPaymentRequestOnUiThread, dev_tools,
registration_id, sw_origin,
event_data->payment_request_id, std::move(callback))));
}
void PaymentAppProviderImpl::InstallAndInvokePaymentApp(
WebContents* web_contents,
PaymentRequestEventDataPtr event_data,
const std::string& app_name,
const SkBitmap& app_icon,
const GURL& sw_js_url,
const GURL& sw_scope,
bool sw_use_cache,
const std::string& method,
const SupportedDelegations& supported_delegations,
RegistrationIdCallback registration_id_callback,
InvokePaymentAppCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents)
return;
if (!sw_js_url.is_valid() || !sw_scope.is_valid() || method.empty()) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
CreateBlankPaymentHandlerResponse(
PaymentEventResponseType::PAYMENT_EVENT_BROWSER_ERROR)));
return;
}
std::string string_encoded_icon;
if (!app_icon.empty()) {
gfx::Image decoded_image = gfx::Image::CreateFrom1xBitmap(app_icon);
scoped_refptr<base::RefCountedMemory> raw_data =
decoded_image.As1xPNGBytes();
base::Base64Encode(
base::StringPiece(raw_data->front_as<char>(), raw_data->size()),
&string_encoded_icon);
}
PaymentAppInstaller::Install(
web_contents, app_name, string_encoded_icon, sw_js_url, sw_scope,
sw_use_cache, method, supported_delegations,
base::BindOnce(&OnInstallPaymentApp, url::Origin::Create(sw_scope),
std::move(event_data), std::move(registration_id_callback),
std::move(callback)));
}
void PaymentAppProviderImpl::UpdatePaymentAppIcon(
BrowserContext* browser_context,
int64_t registration_id,
const std::string& instrument_key,
const std::string& name,
const std::string& string_encoded_icon,
const std::string& method_name,
const SupportedDelegations& supported_delegations,
PaymentAppProvider::UpdatePaymentAppIconCallback callback) {
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context));
scoped_refptr<PaymentAppContextImpl> payment_app_context =
partition->GetPaymentAppContext();
RunOrPostTaskOnThread(
FROM_HERE, content::ServiceWorkerContext::GetCoreThreadId(),
base::BindOnce(&UpdatePaymentAppIconOnCoreThread, payment_app_context,
registration_id, instrument_key, name, string_encoded_icon,
method_name, supported_delegations, std::move(callback)));
}
void PaymentAppProviderImpl::CanMakePayment(
WebContents* web_contents,
int64_t registration_id,
const url::Origin& sw_origin,
const std::string& payment_request_id,
CanMakePaymentEventDataPtr event_data,
CanMakePaymentCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents)
return;
scoped_refptr<DevToolsBackgroundServicesContextImpl> dev_tools =
GetDevTools(web_contents->GetBrowserContext(), sw_origin);
if (dev_tools) {
std::map<std::string, std::string> data = {
{"Merchant Top Origin", event_data->top_origin.spec()},
{"Merchant Payment Request Origin",
event_data->payment_request_origin.spec()}};
if (event_data->currency &&
base::FeatureList::IsEnabled(features::kWebPaymentsMinimalUI)) {
data["Currency"] = *event_data->currency;
}
AddMethodDataToMap(event_data->method_data, &data);
AddModifiersToMap(event_data->modifiers, &data);
dev_tools->LogBackgroundServiceEvent(
registration_id, sw_origin, DevToolsBackgroundService::kPaymentHandler,
"Can make payment",
/*instance_id=*/payment_request_id, data);
}
StartServiceWorkerForDispatch(
web_contents->GetBrowserContext(), registration_id,
base::BindOnce(&DispatchCanMakePaymentEvent,
web_contents->GetBrowserContext(), std::move(event_data),
base::BindOnce(&OnResponseForCanMakePaymentOnUiThread,
dev_tools, registration_id, sw_origin,
payment_request_id, std::move(callback))));
}
void PaymentAppProviderImpl::AbortPayment(WebContents* web_contents,
int64_t registration_id,
const url::Origin& sw_origin,
const std::string& payment_request_id,
AbortCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents)
return;
scoped_refptr<DevToolsBackgroundServicesContextImpl> dev_tools =
GetDevTools(web_contents->GetBrowserContext(), sw_origin);
if (dev_tools) {
dev_tools->LogBackgroundServiceEvent(
registration_id, sw_origin, DevToolsBackgroundService::kPaymentHandler,
"Abort payment",
/*instance_id=*/payment_request_id, {});
}
StartServiceWorkerForDispatch(
web_contents->GetBrowserContext(), registration_id,
base::BindOnce(&DispatchAbortPaymentEvent,
web_contents->GetBrowserContext(),
base::BindOnce(&OnResponseForAbortPaymentOnUiThread,
dev_tools, registration_id, sw_origin,
payment_request_id, std::move(callback))));
}
void PaymentAppProviderImpl::SetOpenedWindow(WebContents* web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents)
return;
CloseOpenedWindow();
DCHECK(!payment_handler_window_);
payment_handler_window_ =
std::make_unique<PaymentHandlerWindowObserver>(web_contents);
}
void PaymentAppProviderImpl::CloseOpenedWindow() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(crbug.com/1099270): Fix cases where the web contents has already been
// destroyed without calling this function, e.g. when the bottom sheet UI is
// closed.
if (payment_handler_window_ && payment_handler_window_->web_contents()) {
payment_handler_window_->web_contents()->Close();
}
payment_handler_window_.reset();
}
void PaymentAppProviderImpl::OnClosingOpenedWindow(
WebContents* web_contents,
PaymentEventResponseType reason) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!web_contents)
return;
RunOrPostTaskOnThread(
FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
base::BindOnce(&AbortInvokePaymentApp, web_contents, reason));
}
bool PaymentAppProviderImpl::IsValidInstallablePaymentApp(
const GURL& manifest_url,
const GURL& sw_js_url,
const GURL& sw_scope,
std::string* error_message) {
DCHECK(manifest_url.is_valid() && sw_js_url.is_valid() &&
sw_scope.is_valid());
// Scope will be checked against service worker js url when registering, but
// we check it here earlier to avoid presenting unusable payment handlers.
if (!ServiceWorkerUtils::IsPathRestrictionSatisfiedWithoutHeader(
sw_scope, sw_js_url, error_message)) {
return false;
}
// TODO(crbug.com/855312): Unify duplicated code between here and
// ServiceWorkerProviderHost::IsValidRegisterMessage.
std::vector<GURL> urls = {manifest_url, sw_js_url, sw_scope};
if (!ServiceWorkerUtils::AllOriginsMatchAndCanAccessServiceWorkers(urls)) {
*error_message =
"Origins are not matching, or some origins cannot access service "
"worker "
"(manifest:" +
manifest_url.spec() + " scope:" + sw_scope.spec() +
" sw:" + sw_js_url.spec() + ")";
return false;
}
return true;
}
ukm::SourceId PaymentAppProviderImpl::GetSourceIdForPaymentAppFromScope(
const GURL& sw_scope) {
return ukm::UkmRecorder::GetSourceIdForPaymentAppFromScope(
sw_scope.GetOrigin());
}
PaymentAppProviderImpl::PaymentAppProviderImpl() = default;
PaymentAppProviderImpl::~PaymentAppProviderImpl() = default;
PaymentAppProviderImpl::PaymentHandlerWindowObserver::
PaymentHandlerWindowObserver(WebContents* web_contents)
: WebContentsObserver(web_contents) {}
PaymentAppProviderImpl::PaymentHandlerWindowObserver::
~PaymentHandlerWindowObserver() = default;
} // namespace content