blob: d784df929b5d32d73bc83d629f76011966c1d7c3 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// 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_info_fetcher.h"
#include <limits>
#include <string_view>
#include <utility>
#include "base/base64.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/task/sequenced_task_runner.h"
#include "components/payments/content/icon/icon_size.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/manifest_icon_downloader.h"
#include "content/public/browser/page.h"
#include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "ui/gfx/codec/png_codec.h"
#include "url/origin.h"
namespace content {
PaymentAppInfoFetcher::PaymentAppInfo::PaymentAppInfo() {}
PaymentAppInfoFetcher::PaymentAppInfo::~PaymentAppInfo() {}
void PaymentAppInfoFetcher::Start(
const GURL& context_url,
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
PaymentAppInfoFetchCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::unique_ptr<std::vector<GlobalRenderFrameHostId>> frame_routing_ids =
service_worker_context->GetWindowClientFrameRoutingIds(
blink::StorageKey::CreateFirstParty(
url::Origin::Create(context_url)));
SelfDeleteFetcher* fetcher = new SelfDeleteFetcher(std::move(callback));
fetcher->Start(context_url, std::move(frame_routing_ids));
}
PaymentAppInfoFetcher::SelfDeleteFetcher::SelfDeleteFetcher(
PaymentAppInfoFetchCallback callback)
: fetched_payment_app_info_(std::make_unique<PaymentAppInfo>()),
callback_(std::move(callback)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
PaymentAppInfoFetcher::SelfDeleteFetcher::~SelfDeleteFetcher() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::Start(
const GURL& context_url,
const std::unique_ptr<std::vector<GlobalRenderFrameHostId>>&
frame_routing_ids) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (frame_routing_ids->empty()) {
// Cannot print this error to the developer console, because the appropriate
// developer console has not been found.
LOG(ERROR)
<< "Unable to find the top level web content for retrieving the web "
"app manifest of a payment handler for \""
<< context_url << "\".";
RunCallbackAndDestroy();
return;
}
for (const auto& frame : *frame_routing_ids) {
// Find out the RenderFrameHost registering the payment app. Although a
// service worker can manage instruments, the first instrument must be set
// on a page that has a link to a web app manifest, so it can be fetched
// here.
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(frame.child_id, frame.frame_routing_id);
if (!render_frame_host ||
context_url.spec().compare(
render_frame_host->GetLastCommittedURL().spec()) != 0) {
continue;
}
// Get the WebContents associated with the frame and print console messages
// to the main frame of the WebContents since web app manifest is only
// available in the main frame's document by definition. The main frame's
// document must come from the same origin.
WebContentsImpl* web_content = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(render_frame_host));
if (!web_content) {
render_frame_host->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Unable to find the web page for \"" + context_url.spec() +
"\" to fetch payment handler manifest (for name and icon).");
continue;
}
if (web_content->IsHidden()) {
web_content->GetPrimaryMainFrame()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Unable to fetch payment handler manifest (for name and icon) for "
"\"" +
context_url.spec() + "\" from a hidden top level web page \"" +
web_content->GetLastCommittedURL().spec() + "\".");
continue;
}
if (!url::IsSameOriginWith(context_url,
web_content->GetLastCommittedURL())) {
web_content->GetPrimaryMainFrame()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
"Unable to fetch payment handler manifest (for name and icon) for "
"\"" +
context_url.spec() +
"\" from a cross-origin top level web page \"" +
web_content->GetLastCommittedURL().spec() + "\".");
continue;
}
web_contents_ = web_content->GetWeakPtr();
web_contents_->GetPrimaryPage().GetManifest(
base::BindOnce(&PaymentAppInfoFetcher::SelfDeleteFetcher::
FetchPaymentAppManifestCallback,
weak_ptr_factory_.GetWeakPtr()));
return;
}
// Cannot print this error to the developer console, because the appropriate
// developer console has not been found.
LOG(ERROR)
<< "Unable to find the top level web content for retrieving the web "
"app manifest of a payment handler for \""
<< context_url << "\".";
RunCallbackAndDestroy();
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::RunCallbackAndDestroy() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_),
std::move(fetched_payment_app_info_)));
delete this;
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::FetchPaymentAppManifestCallback(
blink::mojom::ManifestRequestResult result,
const GURL& url,
blink::mojom::ManifestPtr manifest) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
manifest_url_ = url;
if (manifest_url_.is_empty()) {
WarnIfPossible(
"The page that installed the payment handler does not contain a web "
"app manifest link: <link rel=\"manifest\" "
"href=\"some-file-name-here\">. This manifest defines the payment "
"handler's name and icon. User may not recognize this payment handler "
"in UI, because it will be labeled only by its origin.");
RunCallbackAndDestroy();
return;
}
if (blink::IsEmptyManifest(manifest) ||
result != blink::mojom::ManifestRequestResult::kSuccess) {
WarnIfPossible(
"Unable to download a valid payment handler web app manifest from \"" +
manifest_url_.spec() +
"\". This manifest cannot be empty and must in JSON format. The "
"manifest defines the payment handler's name and icon. User may not "
"recognize this payment handler in UI, because it will be labeled only "
"by its origin.");
RunCallbackAndDestroy();
return;
}
fetched_payment_app_info_->prefer_related_applications =
manifest->prefer_related_applications;
for (const auto& related_application : manifest->related_applications) {
fetched_payment_app_info_->related_applications.emplace_back(
StoredRelatedApplication());
if (related_application.platform) {
base::UTF16ToUTF8(
related_application.platform->c_str(),
related_application.platform->length(),
&(fetched_payment_app_info_->related_applications.back().platform));
}
if (related_application.id) {
base::UTF16ToUTF8(
related_application.id->c_str(), related_application.id->length(),
&(fetched_payment_app_info_->related_applications.back().id));
}
}
if (!manifest->name) {
WarnIfPossible("The payment handler's web app manifest \"" +
manifest_url_.spec() +
"\" does not contain a \"name\" field. User may not "
"recognize this payment handler in UI, because it will be "
"labeled only by its origin.");
} else if (manifest->name->empty()) {
WarnIfPossible(
"The \"name\" field in the payment handler's web app manifest \"" +
manifest_url_.spec() +
"\" is empty. User may not recognize this payment handler in UI, "
"because it will be labeled only by its origin.");
} else {
base::UTF16ToUTF8(manifest->name->c_str(), manifest->name->length(),
&(fetched_payment_app_info_->name));
}
if (manifest->icons.empty()) {
WarnIfPossible(
"Unable to download the payment handler's icon, because the web app "
"manifest \"" +
manifest_url_.spec() +
"\" does not contain an \"icons\" field with a valid URL in \"src\" "
"sub-field. User may not recognize this payment handler in UI.");
RunCallbackAndDestroy();
return;
}
if (!web_contents_) {
LOG(WARNING) << "Unable to download the payment handler's icon because no "
"renderer was found, possibly because the page was closed "
"or navigated away during installation. User may not "
"recognize this payment handler in UI, because it will be "
"labeled only by its name and origin.";
RunCallbackAndDestroy();
return;
}
gfx::NativeView native_view = web_contents_->GetNativeView();
icon_url_ = blink::ManifestIconSelector::FindBestMatchingIcon(
manifest->icons,
payments::IconSizeCalculator::IdealIconHeight(native_view),
payments::IconSizeCalculator::MinimumIconHeight(),
ManifestIconDownloader::kMaxWidthToHeightRatio,
blink::mojom::ManifestImageResource_Purpose::ANY);
if (!icon_url_.is_valid()) {
WarnIfPossible(
"No suitable payment handler icon found in the \"icons\" field defined "
"in the web app manifest \"" +
manifest_url_.spec() +
"\". This is most likely due to unsupported MIME types in the "
"\"icons\" field. User may not recognize this payment handler in UI.");
RunCallbackAndDestroy();
return;
}
bool can_download = ManifestIconDownloader::Download(
web_contents_.get(), icon_url_,
payments::IconSizeCalculator::IdealIconHeight(native_view),
payments::IconSizeCalculator::MinimumIconHeight(),
/* maximum_icon_size_in_px= */ std::numeric_limits<int>::max(),
base::BindOnce(&PaymentAppInfoFetcher::SelfDeleteFetcher::OnIconFetched,
weak_ptr_factory_.GetWeakPtr()),
false /* square_only */);
// |can_download| is false only if web contents are null or the icon URL is
// not valid. Both of these conditions are manually checked above, so
// |can_download| should never be false. The manual checks above are necessary
// to provide more detailed error messages.
DCHECK(can_download);
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::OnIconFetched(
const SkBitmap& icon) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (icon.drawsNothing()) {
WarnIfPossible("Unable to download a valid payment handler icon from \"" +
icon_url_.spec() +
"\", which is defined in the web app manifest \"" +
manifest_url_.spec() +
"\". User may not recognize this payment handler in UI.");
RunCallbackAndDestroy();
return;
}
std::optional<std::vector<uint8_t>> bitmap_data =
gfx::PNGCodec::EncodeBGRASkBitmap(icon, /*discard_transparency=*/false);
fetched_payment_app_info_->icon = base::Base64Encode(bitmap_data.value());
RunCallbackAndDestroy();
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::WarnIfPossible(
const std::string& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (web_contents_) {
web_contents_->GetPrimaryMainFrame()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning, message);
} else {
LOG(WARNING) << message;
}
}
} // namespace content