blob: e90433054d7cfde737988aada41fa2b71464a875 [file] [log] [blame]
// Copyright (c) 2012 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/web_applications/web_app.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/i18n/file_util_icu.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread.h"
#include "chrome/browser/extensions/extension_ui_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "grit/theme_resources.h"
#include "skia/ext/image_operations.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_family.h"
#include "ui/gfx/image/image_skia.h"
#include "url/url_constants.h"
#if defined(OS_WIN)
#include "ui/gfx/icon_util.h"
#endif
#if defined(TOOLKIT_VIEWS)
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/favicon/favicon_tab_helper.h"
#endif
using content::BrowserThread;
namespace {
#if defined(OS_MACOSX)
const int kDesiredSizes[] = {16, 32, 128, 256, 512};
const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
#elif defined(OS_LINUX)
// Linux supports icons of any size. FreeDesktop Icon Theme Specification states
// that "Minimally you should install a 48x48 icon in the hicolor theme."
const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
#elif defined(OS_WIN)
const int* kDesiredSizes = IconUtil::kIconDimensions;
const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
#else
const int kDesiredSizes[] = {32};
const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
#endif
#if defined(TOOLKIT_VIEWS)
// Predicator for sorting images from largest to smallest.
bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
const WebApplicationInfo::IconInfo& right) {
return left.width < right.width;
}
#endif
base::FilePath GetShortcutDataDir(const web_app::ShortcutInfo& shortcut_info) {
return web_app::GetWebAppDataDirectory(shortcut_info.profile_path,
shortcut_info.extension_id,
shortcut_info.url);
}
void UpdateAllShortcutsForShortcutInfo(
const base::string16& old_app_title,
const web_app::ShortcutInfo& shortcut_info,
const extensions::FileHandlersInfo& file_handlers_info) {
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&web_app::internals::UpdatePlatformShortcuts,
GetShortcutDataDir(shortcut_info),
old_app_title, shortcut_info, file_handlers_info));
}
void OnImageLoaded(web_app::ShortcutInfo shortcut_info,
extensions::FileHandlersInfo file_handlers_info,
web_app::InfoCallback callback,
const gfx::ImageFamily& image_family) {
// If the image failed to load (e.g. if the resource being loaded was empty)
// use the standard application icon.
if (image_family.empty()) {
gfx::Image default_icon =
ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
int size = kDesiredSizes[kNumDesiredSizes - 1];
SkBitmap bmp = skia::ImageOperations::Resize(
*default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
size, size);
gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bmp);
// We are on the UI thread, and this image is needed from the FILE thread,
// for creating shortcut icon files.
image_skia.MakeThreadSafe();
shortcut_info.favicon.Add(gfx::Image(image_skia));
} else {
shortcut_info.favicon = image_family;
}
callback.Run(shortcut_info, file_handlers_info);
}
void IgnoreFileHandlersInfo(
const web_app::ShortcutInfoCallback& shortcut_info_callback,
const web_app::ShortcutInfo& shortcut_info,
const extensions::FileHandlersInfo& file_handlers_info) {
shortcut_info_callback.Run(shortcut_info);
}
} // namespace
namespace web_app {
// The following string is used to build the directory name for
// shortcuts to chrome applications (the kind which are installed
// from a CRX). Application shortcuts to URLs use the {host}_{path}
// for the name of this directory. Hosts can't include an underscore.
// By starting this string with an underscore, we ensure that there
// are no naming conflicts.
static const char kCrxAppPrefix[] = "_crx_";
namespace internals {
base::FilePath GetSanitizedFileName(const base::string16& name) {
#if defined(OS_WIN)
base::string16 file_name = name;
#else
std::string file_name = base::UTF16ToUTF8(name);
#endif
base::i18n::ReplaceIllegalCharactersInPath(&file_name, '_');
return base::FilePath(file_name);
}
} // namespace internals
ShortcutInfo::ShortcutInfo()
: is_platform_app(false) {
}
ShortcutInfo::~ShortcutInfo() {}
ShortcutLocations::ShortcutLocations()
: on_desktop(false),
applications_menu_location(APP_MENU_LOCATION_NONE),
in_quick_launch_bar(false) {
}
#if defined(TOOLKIT_VIEWS)
void GetShortcutInfoForTab(content::WebContents* web_contents,
ShortcutInfo* info) {
DCHECK(info); // Must provide a valid info.
const FaviconTabHelper* favicon_tab_helper =
FaviconTabHelper::FromWebContents(web_contents);
const extensions::TabHelper* extensions_tab_helper =
extensions::TabHelper::FromWebContents(web_contents);
const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info();
info->url = app_info.app_url.is_empty() ? web_contents->GetURL() :
app_info.app_url;
info->title = app_info.title.empty() ?
(web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) :
web_contents->GetTitle()) :
app_info.title;
info->description = app_info.description;
info->favicon.Add(favicon_tab_helper->GetFavicon());
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
info->profile_path = profile->GetPath();
}
#endif
#if !defined(OS_WIN)
void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
#endif
ShortcutInfo ShortcutInfoForExtensionAndProfile(
const extensions::Extension* app, Profile* profile) {
ShortcutInfo shortcut_info;
shortcut_info.extension_id = app->id();
shortcut_info.is_platform_app = app->is_platform_app();
shortcut_info.url = extensions::AppLaunchInfo::GetLaunchWebURL(app);
shortcut_info.title = base::UTF8ToUTF16(app->name());
shortcut_info.description = base::UTF8ToUTF16(app->description());
shortcut_info.extension_path = app->path();
shortcut_info.profile_path = profile->GetPath();
shortcut_info.profile_name =
profile->GetPrefs()->GetString(prefs::kProfileName);
return shortcut_info;
}
void GetInfoForApp(const extensions::Extension* extension,
Profile* profile,
const InfoCallback& callback) {
web_app::ShortcutInfo shortcut_info =
web_app::ShortcutInfoForExtensionAndProfile(extension, profile);
const std::vector<extensions::FileHandlerInfo>* file_handlers =
extensions::FileHandlers::GetFileHandlers(extension);
extensions::FileHandlersInfo file_handlers_info =
file_handlers ? *file_handlers : extensions::FileHandlersInfo();
std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
for (size_t i = 0; i < kNumDesiredSizes; ++i) {
int size = kDesiredSizes[i];
extensions::ExtensionResource resource =
extensions::IconsInfo::GetIconResource(
extension, size, ExtensionIconSet::MATCH_EXACTLY);
if (!resource.empty()) {
info_list.push_back(extensions::ImageLoader::ImageRepresentation(
resource,
extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
gfx::Size(size, size),
ui::SCALE_FACTOR_100P));
}
}
if (info_list.empty()) {
size_t i = kNumDesiredSizes - 1;
int size = kDesiredSizes[i];
// If there is no icon at the desired sizes, we will resize what we can get.
// Making a large icon smaller is preferred to making a small icon larger,
// so look for a larger icon first:
extensions::ExtensionResource resource =
extensions::IconsInfo::GetIconResource(
extension, size, ExtensionIconSet::MATCH_BIGGER);
if (resource.empty()) {
resource = extensions::IconsInfo::GetIconResource(
extension, size, ExtensionIconSet::MATCH_SMALLER);
}
info_list.push_back(extensions::ImageLoader::ImageRepresentation(
resource,
extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
gfx::Size(size, size),
ui::SCALE_FACTOR_100P));
}
// |info_list| may still be empty at this point, in which case
// LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
// image and exit immediately.
extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
extension,
info_list,
base::Bind(&OnImageLoaded, shortcut_info, file_handlers_info, callback));
}
void GetShortcutInfoForApp(const extensions::Extension* extension,
Profile* profile,
const ShortcutInfoCallback& callback) {
GetInfoForApp(
extension, profile, base::Bind(&IgnoreFileHandlersInfo, callback));
}
bool ShouldCreateShortcutFor(Profile* profile,
const extensions::Extension* extension) {
return extension->is_platform_app() &&
extension->location() != extensions::Manifest::COMPONENT &&
extensions::ui_util::CanDisplayInAppLauncher(extension, profile);
}
base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
const std::string& extension_id,
const GURL& url) {
DCHECK(!profile_path.empty());
base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
if (!extension_id.empty()) {
return app_data_dir.AppendASCII(
GenerateApplicationNameFromExtensionId(extension_id));
}
std::string host(url.host());
std::string scheme(url.has_scheme() ? url.scheme() : "http");
std::string port(url.has_port() ? url.port() : "80");
std::string scheme_port(scheme + "_" + port);
#if defined(OS_WIN)
base::FilePath::StringType host_path(base::UTF8ToUTF16(host));
base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port));
#elif defined(OS_POSIX)
base::FilePath::StringType host_path(host);
base::FilePath::StringType scheme_port_path(scheme_port);
#endif
return app_data_dir.Append(host_path).Append(scheme_port_path);
}
base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
const extensions::Extension& extension) {
return GetWebAppDataDirectory(
profile_path,
extension.id(),
GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension)));
}
std::string GenerateApplicationNameFromInfo(const ShortcutInfo& shortcut_info) {
if (!shortcut_info.extension_id.empty())
return GenerateApplicationNameFromExtensionId(shortcut_info.extension_id);
else
return GenerateApplicationNameFromURL(shortcut_info.url);
}
std::string GenerateApplicationNameFromURL(const GURL& url) {
std::string t;
t.append(url.host());
t.append("_");
t.append(url.path());
return t;
}
std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
std::string t(kCrxAppPrefix);
t.append(id);
return t;
}
std::string GetExtensionIdFromApplicationName(const std::string& app_name) {
std::string prefix(kCrxAppPrefix);
if (app_name.substr(0, prefix.length()) != prefix)
return std::string();
return app_name.substr(prefix.length());
}
void CreateShortcutsWithInfo(
ShortcutCreationReason reason,
const ShortcutLocations& locations,
const ShortcutInfo& shortcut_info,
const extensions::FileHandlersInfo& file_handlers_info) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(base::IgnoreResult(&internals::CreatePlatformShortcuts),
GetShortcutDataDir(shortcut_info),
shortcut_info,
file_handlers_info,
locations,
reason));
}
void CreateShortcuts(ShortcutCreationReason reason,
const ShortcutLocations& locations,
Profile* profile,
const extensions::Extension* app) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!ShouldCreateShortcutFor(profile, app))
return;
GetInfoForApp(
app, profile, base::Bind(&CreateShortcutsWithInfo, reason, locations));
}
void DeleteAllShortcuts(Profile* profile, const extensions::Extension* app) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
ShortcutInfo shortcut_info =
ShortcutInfoForExtensionAndProfile(app, profile);
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&web_app::internals::DeletePlatformShortcuts,
GetShortcutDataDir(shortcut_info), shortcut_info));
}
void UpdateAllShortcuts(const base::string16& old_app_title,
Profile* profile,
const extensions::Extension* app) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
GetInfoForApp(app,
profile,
base::Bind(&UpdateAllShortcutsForShortcutInfo, old_app_title));
}
bool IsValidUrl(const GURL& url) {
static const char* const kValidUrlSchemes[] = {
url::kFileScheme,
url::kFileSystemScheme,
url::kFtpScheme,
url::kHttpScheme,
url::kHttpsScheme,
extensions::kExtensionScheme,
};
for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
if (url.SchemeIs(kValidUrlSchemes[i]))
return true;
}
return false;
}
#if defined(TOOLKIT_VIEWS)
void GetIconsInfo(const WebApplicationInfo& app_info,
IconInfoList* icons) {
DCHECK(icons);
icons->clear();
for (size_t i = 0; i < app_info.icons.size(); ++i) {
// We only take square shaped icons (i.e. width == height).
if (app_info.icons[i].width == app_info.icons[i].height) {
icons->push_back(app_info.icons[i]);
}
}
std::sort(icons->begin(), icons->end(), &IconPrecedes);
}
#endif
#if defined(OS_LINUX)
std::string GetWMClassFromAppName(std::string app_name) {
base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
base::TrimString(app_name, "_", &app_name);
return app_name;
}
#endif
} // namespace web_app