blob: f8c7ae0c7ad353008e1ef601e0a899e45d400e3b [file] [log] [blame]
// Copyright 2015 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 "chrome/browser/android/webapps/add_to_homescreen_data_fetcher.h"
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/task_scheduler/post_task.h"
#include "chrome/browser/android/shortcut_helper.h"
#include "chrome/browser/android/webapk/chrome_webapk_host.h"
#include "chrome/browser/android/webapk/webapk_web_manifest_checker.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/installable/installable_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/web_application_info.h"
#include "components/dom_distiller/core/url_utils.h"
#include "components/favicon/core/favicon_service.h"
#include "components/favicon_base/favicon_types.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/manifest_icon_selector.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/manifest.h"
#include "third_party/WebKit/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/WebKit/public/platform/modules/screen_orientation/WebScreenOrientationLockType.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/favicon_size.h"
#include "url/gurl.h"
namespace {
// Looks up the original, online, visible URL of |web_contents|. The current
// visible URL may be a distilled article which is not appropriate for a home
// screen shortcut.
GURL GetShortcutUrl(const content::WebContents* web_contents) {
return dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(
web_contents->GetVisibleURL());
}
InstallableParams ParamsToPerformManifestAndIconFetch() {
InstallableParams params;
params.valid_primary_icon = true;
params.valid_badge_icon = true;
params.wait_for_worker = true;
return params;
}
InstallableParams ParamsToPerformInstallableCheck() {
InstallableParams params;
params.check_eligibility = true;
params.valid_manifest = true;
params.has_worker = true;
params.valid_primary_icon = true;
params.wait_for_worker = true;
return params;
}
// Creates a launcher icon from |icon|. |start_url| is used to generate the icon
// if |icon| is empty or is not large enough. Returns a std::pair with:
// - the generated icon
// - whether |icon| was used in generating the launcher icon
std::pair<SkBitmap, bool> CreateLauncherIconInBackground(const GURL& start_url,
const SkBitmap& icon) {
base::AssertBlockingAllowed();
bool is_generated = false;
SkBitmap primary_icon = ShortcutHelper::FinalizeLauncherIconInBackground(
icon, start_url, &is_generated);
return std::make_pair(primary_icon, is_generated);
}
// Creates a launcher icon from |bitmap_result|. |start_url| is used to
// generate the icon if there is no bitmap in |bitmap_result| or the bitmap is
// not large enough. Returns a std::pair with:
// - the generated icon
// - whether the bitmap in |bitmap_result| was used in generating the launcher
// icon
std::pair<SkBitmap, bool> CreateLauncherIconFromFaviconInBackground(
const GURL& start_url,
const favicon_base::FaviconRawBitmapResult& bitmap_result) {
base::AssertBlockingAllowed();
SkBitmap decoded;
if (bitmap_result.is_valid()) {
gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
bitmap_result.bitmap_data->size(), &decoded);
}
return CreateLauncherIconInBackground(start_url, decoded);
}
void RecordAddToHomescreenDialogDuration(base::TimeDelta duration) {
UMA_HISTOGRAM_TIMES("Webapp.AddToHomescreenDialog.Timeout", duration);
}
} // namespace
AddToHomescreenDataFetcher::AddToHomescreenDataFetcher(
content::WebContents* web_contents,
int data_timeout_ms,
Observer* observer)
: content::WebContentsObserver(web_contents),
installable_manager_(InstallableManager::FromWebContents(web_contents)),
observer_(observer),
shortcut_info_(GetShortcutUrl(web_contents)),
data_timeout_ms_(base::TimeDelta::FromMilliseconds(data_timeout_ms)),
is_waiting_for_manifest_(true),
weak_ptr_factory_(this) {
DCHECK(shortcut_info_.url.is_valid());
// Send a message to the renderer to retrieve information about the page.
chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame;
web_contents->GetMainFrame()->GetRemoteAssociatedInterfaces()->GetInterface(
&chrome_render_frame);
// Bind the InterfacePtr into the callback so that it's kept alive
// until there's either a connection error or a response.
auto* web_app_info_proxy = chrome_render_frame.get();
web_app_info_proxy->GetWebApplicationInfo(base::Bind(
&AddToHomescreenDataFetcher::OnDidGetWebApplicationInfo,
weak_ptr_factory_.GetWeakPtr(), base::Passed(&chrome_render_frame)));
}
AddToHomescreenDataFetcher::~AddToHomescreenDataFetcher() {}
void AddToHomescreenDataFetcher::OnDidGetWebApplicationInfo(
chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame,
const WebApplicationInfo& received_web_app_info) {
if (!web_contents())
return;
// Sanitize received_web_app_info.
WebApplicationInfo web_app_info = received_web_app_info;
web_app_info.title =
web_app_info.title.substr(0, chrome::kMaxMetaTagAttributeLength);
// Simply set the user-editable title to be the page's title
shortcut_info_.user_title = web_app_info.title.empty()
? web_contents()->GetTitle()
: web_app_info.title;
shortcut_info_.short_name = shortcut_info_.user_title;
shortcut_info_.name = shortcut_info_.user_title;
if (web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE ||
web_app_info.mobile_capable == WebApplicationInfo::MOBILE_CAPABLE_APPLE) {
shortcut_info_.display = blink::kWebDisplayModeStandalone;
shortcut_info_.UpdateSource(
ShortcutInfo::SOURCE_ADD_TO_HOMESCREEN_STANDALONE);
}
// Record what type of shortcut was added by the user.
switch (web_app_info.mobile_capable) {
case WebApplicationInfo::MOBILE_CAPABLE:
base::RecordAction(
base::UserMetricsAction("webapps.AddShortcut.AppShortcut"));
break;
case WebApplicationInfo::MOBILE_CAPABLE_APPLE:
base::RecordAction(
base::UserMetricsAction("webapps.AddShortcut.AppShortcutApple"));
break;
case WebApplicationInfo::MOBILE_CAPABLE_UNSPECIFIED:
base::RecordAction(
base::UserMetricsAction("webapps.AddShortcut.Bookmark"));
break;
}
// Kick off a timeout for downloading data. If we haven't finished within the
// timeout, fall back to using a dynamically-generated launcher icon.
data_timeout_timer_.Start(
FROM_HERE, data_timeout_ms_,
base::Bind(&AddToHomescreenDataFetcher::OnDataTimedout,
weak_ptr_factory_.GetWeakPtr()));
start_time_ = base::TimeTicks::Now();
installable_manager_->GetData(
ParamsToPerformManifestAndIconFetch(),
base::Bind(&AddToHomescreenDataFetcher::OnDidGetManifestAndIcons,
weak_ptr_factory_.GetWeakPtr()));
}
void AddToHomescreenDataFetcher::StopTimer() {
data_timeout_timer_.Stop();
RecordAddToHomescreenDialogDuration(base::TimeTicks::Now() - start_time_);
}
void AddToHomescreenDataFetcher::OnDataTimedout() {
RecordAddToHomescreenDialogDuration(data_timeout_ms_);
weak_ptr_factory_.InvalidateWeakPtrs();
if (!web_contents())
return;
if (is_waiting_for_manifest_)
installable_manager_->RecordAddToHomescreenManifestAndIconTimeout();
else
installable_manager_->RecordAddToHomescreenInstallabilityTimeout();
observer_->OnUserTitleAvailable(shortcut_info_.user_title, shortcut_info_.url,
false);
CreateLauncherIcon(raw_primary_icon_);
}
void AddToHomescreenDataFetcher::OnDidGetManifestAndIcons(
const InstallableData& data) {
if (!web_contents())
return;
is_waiting_for_manifest_ = false;
if (!data.manifest->IsEmpty()) {
base::RecordAction(base::UserMetricsAction("webapps.AddShortcut.Manifest"));
shortcut_info_.UpdateFromManifest(*data.manifest);
shortcut_info_.manifest_url = data.manifest_url;
}
// Do this after updating from the manifest for the case where a site has
// a manifest with name and standalone specified, but no icons.
if (data.manifest->IsEmpty() || !data.primary_icon) {
observer_->OnUserTitleAvailable(shortcut_info_.user_title,
shortcut_info_.url, false);
StopTimer();
installable_manager_->RecordAddToHomescreenNoTimeout();
FetchFavicon();
return;
}
raw_primary_icon_ = *data.primary_icon;
shortcut_info_.best_primary_icon_url = data.primary_icon_url;
// Save the splash screen URL for the later download.
shortcut_info_.ideal_splash_image_size_in_px =
ShortcutHelper::GetIdealSplashImageSizeInPx();
shortcut_info_.minimum_splash_image_size_in_px =
ShortcutHelper::GetMinimumSplashImageSizeInPx();
shortcut_info_.splash_image_url =
content::ManifestIconSelector::FindBestMatchingIcon(
data.manifest->icons, shortcut_info_.ideal_splash_image_size_in_px,
shortcut_info_.minimum_splash_image_size_in_px,
content::Manifest::Icon::IconPurpose::ANY);
if (data.badge_icon) {
shortcut_info_.best_badge_icon_url = data.badge_icon_url;
badge_icon_ = *data.badge_icon;
}
installable_manager_->GetData(
ParamsToPerformInstallableCheck(),
base::Bind(&AddToHomescreenDataFetcher::OnDidPerformInstallableCheck,
weak_ptr_factory_.GetWeakPtr()));
}
void AddToHomescreenDataFetcher::OnDidPerformInstallableCheck(
const InstallableData& data) {
StopTimer();
if (!web_contents())
return;
installable_manager_->RecordAddToHomescreenNoTimeout();
bool webapk_compatible =
(data.error_code == NO_ERROR_DETECTED && data.valid_manifest &&
data.has_worker && AreWebManifestUrlsWebApkCompatible(*data.manifest) &&
ChromeWebApkHost::CanInstallWebApk());
observer_->OnUserTitleAvailable(
webapk_compatible ? shortcut_info_.name : shortcut_info_.user_title,
shortcut_info_.url, webapk_compatible);
if (webapk_compatible) {
shortcut_info_.UpdateSource(ShortcutInfo::SOURCE_ADD_TO_HOMESCREEN_PWA);
NotifyObserver(std::make_pair(raw_primary_icon_, false /* is_generated */));
} else {
CreateLauncherIcon(raw_primary_icon_);
}
}
void AddToHomescreenDataFetcher::FetchFavicon() {
if (!web_contents())
return;
// Grab the best, largest icon we can find to represent this bookmark.
// TODO(dfalcantara): Try combining with the new BookmarksHandler once its
// rewrite is further along.
std::vector<favicon_base::IconTypeSet> icon_types = {
{favicon_base::IconType::kWebManifestIcon},
{favicon_base::IconType::kFavicon},
{favicon_base::IconType::kTouchPrecomposedIcon,
favicon_base::IconType::kTouchIcon}};
favicon::FaviconService* favicon_service =
FaviconServiceFactory::GetForProfile(
Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
ServiceAccessType::EXPLICIT_ACCESS);
// Using favicon if its size is not smaller than platform required size,
// otherwise using the largest icon among all available icons.
int threshold_to_get_any_largest_icon =
ShortcutHelper::GetIdealHomescreenIconSizeInPx() - 1;
favicon_service->GetLargestRawFaviconForPageURL(
shortcut_info_.url, icon_types, threshold_to_get_any_largest_icon,
base::Bind(&AddToHomescreenDataFetcher::OnFaviconFetched,
weak_ptr_factory_.GetWeakPtr()),
&favicon_task_tracker_);
}
void AddToHomescreenDataFetcher::OnFaviconFetched(
const favicon_base::FaviconRawBitmapResult& bitmap_result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!web_contents())
return;
shortcut_info_.best_primary_icon_url = bitmap_result.icon_url;
// The user is waiting for the icon to be processed before they can proceed
// with add to homescreen. But if we shut down, there's no point starting the
// image processing. Use USER_VISIBLE with MayBlock and SKIP_ON_SHUTDOWN.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&CreateLauncherIconFromFaviconInBackground,
shortcut_info_.url, bitmap_result),
base::BindOnce(&AddToHomescreenDataFetcher::NotifyObserver,
weak_ptr_factory_.GetWeakPtr()));
}
void AddToHomescreenDataFetcher::CreateLauncherIcon(const SkBitmap& icon) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// The user is waiting for the icon to be processed before they can proceed
// with add to homescreen. But if we shut down, there's no point starting the
// image processing. Use USER_VISIBLE with MayBlock and SKIP_ON_SHUTDOWN.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&CreateLauncherIconInBackground, shortcut_info_.url, icon),
base::BindOnce(&AddToHomescreenDataFetcher::NotifyObserver,
weak_ptr_factory_.GetWeakPtr()));
}
void AddToHomescreenDataFetcher::NotifyObserver(
const std::pair<SkBitmap, bool /*is_generated*/>& primary_icon) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!web_contents())
return;
primary_icon_ = primary_icon.first;
if (primary_icon.second)
shortcut_info_.best_primary_icon_url = GURL();
observer_->OnDataAvailable(shortcut_info_, primary_icon_, badge_icon_);
}