blob: 5ba89bcdda328b2d5e6171c1b385fdbc44f20b41 [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 "base/base64.h"
#include "base/bind_helpers.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_thread.h"
#include "content/public/browser/manifest_icon_downloader.h"
#include "content/public/common/console_message_level.h"
#include "third_party/blink/public/common/manifest/manifest_icon_selector.h"
#include "ui/gfx/image/image.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<std::pair<int, int>>> provider_hosts =
service_worker_context->GetProviderHostIds(context_url.GetOrigin());
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
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<std::pair<int, int>>>& 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<std::pair<int, int>>>& provider_hosts) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (provider_hosts->size() == 0U) {
RunCallbackAndDestroy();
return;
}
for (const auto& frame : *provider_hosts) {
// Find out the render frame host registering the payment app.
RenderFrameHostImpl* render_frame_host =
RenderFrameHostImpl::FromID(frame.first, frame.second);
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_web_content->IsHidden() ||
!url::IsSameOriginWith(context_url,
top_level_web_content->GetLastCommittedURL())) {
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;
}
RunCallbackAndDestroy();
}
void PaymentAppInfoFetcher::SelfDeleteFetcher::RunCallbackAndDestroy() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
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::Bind(&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;
}
gfx::Image decoded_image = gfx::Image::CreateFrom1xBitmap(icon);
scoped_refptr<base::RefCountedMemory> raw_data = decoded_image.As1xPNGBytes();
base::Base64Encode(
base::StringPiece(raw_data->front_as<char>(), raw_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(
CONSOLE_MESSAGE_LEVEL_WARNING, message);
} else {
LOG(WARNING) << message;
}
}
} // namespace content