blob: e5f3bc8762e6a643138a6cdda0056b39e99880f3 [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/user_metrics.h"
#include "base/strings/string16.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/android/offline_pages/offline_page_utils.h"
#include "chrome/browser/android/shortcut_helper.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/manifest/manifest_icon_selector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/render_messages.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/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/manifest.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 {
// The default number of milliseconds to wait for the data download to complete.
const int kDataTimeoutInMilliseconds = 4000;
// Looks up the original, online URL of the site requested. The URL from the
// WebContents may be a distilled article which is not appropriate for a home
// screen shortcut.
GURL GetShortcutUrl(content::BrowserContext* browser_context,
const GURL& actual_url) {
return dom_distiller::url_utils::GetOriginalUrlFromDistillerUrl(actual_url);
}
InstallableParams ParamsToPerformInstallableCheck(
int ideal_icon_size_in_px,
int minimum_icon_size_in_px,
int badge_size_in_px,
bool check_webapk_compatibility) {
InstallableParams params;
params.ideal_primary_icon_size_in_px = ideal_icon_size_in_px;
params.minimum_primary_icon_size_in_px = minimum_icon_size_in_px;
params.check_installable = check_webapk_compatibility;
params.fetch_valid_primary_icon = true;
if (check_webapk_compatibility) {
params.ideal_badge_icon_size_in_px = badge_size_in_px;
params.minimum_badge_icon_size_in_px = badge_size_in_px;
params.fetch_valid_badge_icon = true;
}
return params;
}
} // namespace
AddToHomescreenDataFetcher::AddToHomescreenDataFetcher(
content::WebContents* web_contents,
int ideal_icon_size_in_px,
int minimum_icon_size_in_px,
int ideal_splash_image_size_in_px,
int minimum_splash_image_size_in_px,
int badge_size_in_px,
bool check_webapk_compatibility,
Observer* observer)
: WebContentsObserver(web_contents),
background_task_runner_(
content::BrowserThread::GetBlockingPool()
->GetTaskRunnerWithShutdownBehavior(
base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
weak_observer_(observer),
shortcut_info_(GetShortcutUrl(web_contents->GetBrowserContext(),
web_contents->GetLastCommittedURL())),
ideal_icon_size_in_px_(ideal_icon_size_in_px),
minimum_icon_size_in_px_(minimum_icon_size_in_px),
ideal_splash_image_size_in_px_(ideal_splash_image_size_in_px),
minimum_splash_image_size_in_px_(minimum_splash_image_size_in_px),
badge_size_in_px_(badge_size_in_px),
check_webapk_compatibility_(check_webapk_compatibility),
is_waiting_for_web_application_info_(true),
is_installable_check_complete_(false),
is_icon_saved_(false),
is_ready_(false) {
DCHECK(minimum_icon_size_in_px <= ideal_icon_size_in_px);
DCHECK(minimum_splash_image_size_in_px <= ideal_splash_image_size_in_px);
// Send a message to the renderer to retrieve information about the page.
Send(new ChromeViewMsg_GetWebApplicationInfo(routing_id()));
}
void AddToHomescreenDataFetcher::OnDidGetWebApplicationInfo(
const WebApplicationInfo& received_web_app_info) {
is_waiting_for_web_application_info_ = false;
if (!web_contents() || !weak_observer_)
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;
}
InstallableManager::CreateForWebContents(web_contents());
InstallableManager* manager =
InstallableManager::FromWebContents(web_contents());
DCHECK(manager);
// 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, base::TimeDelta::FromMilliseconds(kDataTimeoutInMilliseconds),
base::Bind(&AddToHomescreenDataFetcher::OnDataTimedout, this));
manager->GetData(
ParamsToPerformInstallableCheck(ideal_icon_size_in_px_,
minimum_icon_size_in_px_,
badge_size_in_px_,
check_webapk_compatibility_),
base::Bind(&AddToHomescreenDataFetcher::OnDidPerformInstallableCheck,
this));
}
AddToHomescreenDataFetcher::~AddToHomescreenDataFetcher() {
DCHECK(!weak_observer_);
}
bool AddToHomescreenDataFetcher::OnMessageReceived(
const IPC::Message& message) {
if (!is_waiting_for_web_application_info_)
return false;
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(AddToHomescreenDataFetcher, message)
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_DidGetWebApplicationInfo,
OnDidGetWebApplicationInfo)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void AddToHomescreenDataFetcher::OnDataTimedout() {
if (!web_contents() || !weak_observer_)
return;
if (!is_installable_check_complete_) {
is_installable_check_complete_ = true;
if (check_webapk_compatibility_)
weak_observer_->OnDidDetermineWebApkCompatibility(false);
weak_observer_->OnUserTitleAvailable(base::string16());
}
badge_icon_.reset();
CreateLauncherIcon(SkBitmap());
}
void AddToHomescreenDataFetcher::OnDidPerformInstallableCheck(
const InstallableData& data) {
data_timeout_timer_.Stop();
badge_icon_.reset();
if (!web_contents() || !weak_observer_ || is_installable_check_complete_)
return;
is_installable_check_complete_ = true;
bool webapk_compatible = false;
if (check_webapk_compatibility_) {
webapk_compatible = (data.error_code == NO_ERROR_DETECTED &&
AreWebManifestUrlsWebApkCompatible(data.manifest));
weak_observer_->OnDidDetermineWebApkCompatibility(webapk_compatible);
if (webapk_compatible) {
// WebAPKs are wholly defined by the Web Manifest. Ignore the <meta> tag
// data received in OnDidGetWebApplicationInfo().
shortcut_info_ = ShortcutInfo(GURL());
}
}
if (!data.manifest.IsEmpty()) {
base::RecordAction(base::UserMetricsAction("webapps.AddShortcut.Manifest"));
shortcut_info_.UpdateFromManifest(data.manifest);
shortcut_info_.manifest_url = data.manifest_url;
if (webapk_compatible) {
shortcut_info_.UpdateSource(ShortcutInfo::SOURCE_ADD_TO_HOMESCREEN_PWA);
if (data.badge_icon && !data.badge_icon->drawsNothing()) {
shortcut_info_.best_badge_icon_url = data.badge_icon_url;
badge_icon_ = *data.badge_icon;
}
}
}
// Save the splash screen URL for the later download.
shortcut_info_.splash_image_url = ManifestIconSelector::FindBestMatchingIcon(
data.manifest.icons, ideal_splash_image_size_in_px_,
minimum_splash_image_size_in_px_,
content::Manifest::Icon::IconPurpose::ANY);
shortcut_info_.ideal_splash_image_size_in_px = ideal_splash_image_size_in_px_;
shortcut_info_.minimum_splash_image_size_in_px =
minimum_splash_image_size_in_px_;
weak_observer_->OnUserTitleAvailable(shortcut_info_.user_title);
if (data.primary_icon) {
shortcut_info_.best_primary_icon_url = data.primary_icon_url;
CreateLauncherIcon(*(data.primary_icon));
return;
}
FetchFavicon();
}
void AddToHomescreenDataFetcher::FetchFavicon() {
if (!web_contents() || !weak_observer_)
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<int> icon_types{
favicon_base::WEB_MANIFEST_ICON, favicon_base::FAVICON,
favicon_base::TOUCH_PRECOMPOSED_ICON | favicon_base::TOUCH_ICON};
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 avaliable icons.
int threshold_to_get_any_largest_icon = ideal_icon_size_in_px_ - 1;
favicon_service->GetLargestRawFaviconForPageURL(
shortcut_info_.url, icon_types, threshold_to_get_any_largest_icon,
base::Bind(&AddToHomescreenDataFetcher::OnFaviconFetched, this),
&favicon_task_tracker_);
}
void AddToHomescreenDataFetcher::OnFaviconFetched(
const favicon_base::FaviconRawBitmapResult& bitmap_result) {
if (!web_contents() || !weak_observer_ || is_icon_saved_)
return;
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::Bind(&AddToHomescreenDataFetcher::
CreateLauncherIconFromFaviconInBackground,
base::Unretained(this), bitmap_result),
base::Bind(&AddToHomescreenDataFetcher::NotifyObserver,
base::RetainedRef(this)));
}
SkBitmap AddToHomescreenDataFetcher::CreateLauncherIconFromFaviconInBackground(
const favicon_base::FaviconRawBitmapResult& bitmap_result) {
base::ThreadRestrictions::AssertIOAllowed();
SkBitmap raw_icon;
if (bitmap_result.is_valid()) {
gfx::PNGCodec::Decode(bitmap_result.bitmap_data->front(),
bitmap_result.bitmap_data->size(), &raw_icon);
}
shortcut_info_.best_primary_icon_url = bitmap_result.icon_url;
return CreateLauncherIconInBackground(raw_icon);
}
void AddToHomescreenDataFetcher::CreateLauncherIcon(const SkBitmap& raw_icon) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::Bind(&AddToHomescreenDataFetcher::CreateLauncherIconInBackground,
base::Unretained(this), raw_icon),
base::Bind(&AddToHomescreenDataFetcher::NotifyObserver,
base::RetainedRef(this)));
}
SkBitmap AddToHomescreenDataFetcher::CreateLauncherIconInBackground(
const SkBitmap& raw_icon) {
base::ThreadRestrictions::AssertIOAllowed();
SkBitmap primary_icon;
bool is_generated = false;
if (weak_observer_) {
primary_icon = weak_observer_->FinalizeLauncherIconInBackground(
raw_icon, shortcut_info_.url, &is_generated);
}
if (is_generated)
shortcut_info_.best_primary_icon_url = GURL();
return primary_icon;
}
void AddToHomescreenDataFetcher::NotifyObserver(const SkBitmap& primary_icon) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!web_contents() || !weak_observer_ || is_icon_saved_)
return;
is_icon_saved_ = true;
primary_icon_ = primary_icon;
is_ready_ = true;
weak_observer_->OnDataAvailable(shortcut_info_, primary_icon_, badge_icon_);
}