blob: 30b5798ad46b0c8460b661416d6cb27822e140bf [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_info_fetcher.h"
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/task/post_task.h"
#include "content/browser/frame_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 "third_party/blink/public/common/manifest/manifest_icon_selector.h"
#include "third_party/blink/public/mojom/devtools/console_message.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::IO);
std::unique_ptr<std::vector<GlobalFrameRoutingId>> provider_hosts =
service_worker_context->GetProviderHostIds(context_url.GetOrigin());
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&PaymentAppInfoFetcher::StartOnUI, context_url,
std::move(provider_hosts), std::move(callback)));
}
void PaymentAppInfoFetcher::StartOnUI(
const GURL& context_url,
const std::unique_ptr<std::vector<GlobalFrameRoutingId>>& provider_hosts,
PaymentAppInfoFetchCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
SelfDeleteFetcher* fetcher = new SelfDeleteFetcher(std::move(callback));
fetcher->Start(context_url, std::move(provider_hosts));
}
PaymentAppInfoFetcher::WebContentsHelper::WebContentsHelper(
WebContents* web_contents)
: WebContentsObserver(web_contents) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
PaymentAppInfoFetcher::WebContentsHelper::~WebContentsHelper() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
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<GlobalFrameRoutingId>>& provider_hosts) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (provider_hosts->size() == 0U) {
// 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 : *provider_hosts) {
// Find out the render frame host 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 main frame 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.
RenderFrameHostImpl* top_level_render_frame_host = render_frame_host;
while (top_level_render_frame_host->GetParent() != nullptr) {
top_level_render_frame_host = top_level_render_frame_host->GetParent();
}
WebContentsImpl* top_level_web_content = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(top_level_render_frame_host));
if (!top_level_web_content) {
top_level_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 (top_level_web_content->IsHidden()) {
top_level_render_frame_host->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 \"" +
top_level_web_content->GetLastCommittedURL().spec() + "\".");
continue;
}
if (!url::IsSameOriginWith(context_url,
top_level_web_content->GetLastCommittedURL())) {
top_level_render_frame_host->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 \"" +
top_level_web_content->GetLastCommittedURL().spec() + "\".");
continue;
}
web_contents_helper_ =
std::make_unique<WebContentsHelper>(top_level_web_content);
top_level_web_content->GetManifest(
base::BindOnce(&PaymentAppInfoFetcher::SelfDeleteFetcher::
FetchPaymentAppManifestCallback,
base::Unretained(this)));
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::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(std::move(callback_),
std::move(fetched_payment_app_info_)));
delete this;
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::FetchPaymentAppManifestCallback(
const GURL& url,
const blink::Manifest& 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 (manifest.IsEmpty()) {
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.is_null()) {
base::UTF16ToUTF8(
related_application.platform.string().c_str(),
related_application.platform.string().length(),
&(fetched_payment_app_info_->related_applications.back().platform));
}
if (!related_application.id.is_null()) {
base::UTF16ToUTF8(
related_application.id.string().c_str(),
related_application.id.string().length(),
&(fetched_payment_app_info_->related_applications.back().id));
}
}
if (manifest.name.is_null()) {
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.string().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.string().c_str(),
manifest.name.string().length(),
&(fetched_payment_app_info_->name));
}
// TODO(gogerald): Choose appropriate icon size dynamically on different
// platforms.
// Here we choose a large ideal icon size to be big enough for all platforms.
// Note that we only scale down for this icon size but not scale up.
const int kPaymentAppIdealIconSize = 0xFFFF;
const int kPaymentAppMinimumIconSize = 0;
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;
}
icon_url_ = blink::ManifestIconSelector::FindBestMatchingIcon(
manifest.icons, kPaymentAppIdealIconSize, kPaymentAppMinimumIconSize,
blink::Manifest::ImageResource::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;
}
if (!web_contents_helper_->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;
}
bool can_download = ManifestIconDownloader::Download(
web_contents_helper_->web_contents(), icon_url_, kPaymentAppIdealIconSize,
kPaymentAppMinimumIconSize,
base::BindOnce(&PaymentAppInfoFetcher::SelfDeleteFetcher::OnIconFetched,
base::Unretained(this)));
// |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::vector<unsigned char> bitmap_data;
bool success = gfx::PNGCodec::EncodeBGRASkBitmap(icon, false, &bitmap_data);
DCHECK(success);
base::Base64Encode(
base::StringPiece(reinterpret_cast<const char*>(&bitmap_data[0]),
bitmap_data.size()),
&(fetched_payment_app_info_->icon));
RunCallbackAndDestroy();
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::WarnIfPossible(
const std::string& message) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(web_contents_helper_);
if (web_contents_helper_->web_contents()) {
web_contents_helper_->web_contents()->GetMainFrame()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning, message);
} else {
LOG(WARNING) << message;
}
}
} // namespace content