No dual_mode on Win10+ shortcuts.
[chromium-blink-merge.git] / chrome / browser / web_applications / web_app.cc
blob6ed801ff9a0c0eca06a5756e70c39d4a477acc2c
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/web_applications/web_app.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/command_line.h"
10 #include "base/files/file_util.h"
11 #include "base/i18n/file_util_icu.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/thread.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/extensions/extension_ui_util.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/profiles/profile_manager.h"
20 #include "chrome/common/chrome_constants.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/common/chrome_version_info.h"
23 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
24 #include "chrome/common/pref_names.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "extensions/browser/extension_registry.h"
27 #include "extensions/browser/image_loader.h"
28 #include "extensions/common/constants.h"
29 #include "extensions/common/extension.h"
30 #include "extensions/common/extension_set.h"
31 #include "extensions/common/manifest_handlers/icons_handler.h"
32 #include "extensions/grit/extensions_browser_resources.h"
33 #include "skia/ext/image_operations.h"
34 #include "third_party/skia/include/core/SkBitmap.h"
35 #include "ui/base/resource/resource_bundle.h"
36 #include "ui/gfx/image/image.h"
37 #include "ui/gfx/image/image_family.h"
38 #include "ui/gfx/image/image_skia.h"
39 #include "url/url_constants.h"
41 #if defined(OS_WIN)
42 #include "ui/gfx/icon_util.h"
43 #endif
45 #if defined(TOOLKIT_VIEWS)
46 #include "chrome/browser/extensions/tab_helper.h"
47 #include "components/favicon/content/content_favicon_driver.h"
48 #endif
50 using content::BrowserThread;
52 namespace {
54 #if defined(OS_MACOSX)
55 const int kDesiredSizes[] = {16, 32, 128, 256, 512};
56 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
57 #elif defined(OS_LINUX)
58 // Linux supports icons of any size. FreeDesktop Icon Theme Specification states
59 // that "Minimally you should install a 48x48 icon in the hicolor theme."
60 const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
61 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
62 #elif defined(OS_WIN)
63 const int* kDesiredSizes = IconUtil::kIconDimensions;
64 const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
65 #else
66 const int kDesiredSizes[] = {32};
67 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
68 #endif
70 #if defined(TOOLKIT_VIEWS)
71 // Predicator for sorting images from largest to smallest.
72 bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
73 const WebApplicationInfo::IconInfo& right) {
74 return left.width < right.width;
76 #endif
78 base::FilePath GetShortcutDataDir(const web_app::ShortcutInfo& shortcut_info) {
79 return web_app::GetWebAppDataDirectory(shortcut_info.profile_path,
80 shortcut_info.extension_id,
81 shortcut_info.url);
84 void UpdateAllShortcutsForShortcutInfo(
85 const base::string16& old_app_title,
86 scoped_ptr<web_app::ShortcutInfo> shortcut_info,
87 const extensions::FileHandlersInfo& file_handlers_info) {
88 base::FilePath shortcut_data_dir = GetShortcutDataDir(*shortcut_info);
89 BrowserThread::PostTask(
90 BrowserThread::FILE, FROM_HERE,
91 base::Bind(&web_app::internals::UpdatePlatformShortcuts,
92 shortcut_data_dir, old_app_title, base::Passed(&shortcut_info),
93 file_handlers_info));
96 void OnImageLoaded(scoped_ptr<web_app::ShortcutInfo> shortcut_info,
97 extensions::FileHandlersInfo file_handlers_info,
98 web_app::InfoCallback callback,
99 const gfx::ImageFamily& image_family) {
100 // If the image failed to load (e.g. if the resource being loaded was empty)
101 // use the standard application icon.
102 if (image_family.empty()) {
103 gfx::Image default_icon =
104 ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
105 int size = kDesiredSizes[kNumDesiredSizes - 1];
106 SkBitmap bmp = skia::ImageOperations::Resize(
107 *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
108 size, size);
109 gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bmp);
110 // We are on the UI thread, and this image is needed from the FILE thread,
111 // for creating shortcut icon files.
112 image_skia.MakeThreadSafe();
113 shortcut_info->favicon.Add(gfx::Image(image_skia));
114 } else {
115 shortcut_info->favicon = image_family;
118 callback.Run(shortcut_info.Pass(), file_handlers_info);
121 void IgnoreFileHandlersInfo(
122 const web_app::ShortcutInfoCallback& shortcut_info_callback,
123 scoped_ptr<web_app::ShortcutInfo> shortcut_info,
124 const extensions::FileHandlersInfo& file_handlers_info) {
125 shortcut_info_callback.Run(shortcut_info.Pass());
128 void ScheduleCreatePlatformShortcut(
129 web_app::ShortcutCreationReason reason,
130 const web_app::ShortcutLocations& locations,
131 scoped_ptr<web_app::ShortcutInfo> shortcut_info,
132 const extensions::FileHandlersInfo& file_handlers_info) {
133 base::FilePath shortcut_data_dir = GetShortcutDataDir(*shortcut_info);
134 BrowserThread::PostTask(
135 BrowserThread::FILE, FROM_HERE,
136 base::Bind(
137 base::IgnoreResult(&web_app::internals::CreatePlatformShortcuts),
138 shortcut_data_dir, base::Passed(&shortcut_info), file_handlers_info,
139 locations, reason));
142 } // namespace
144 namespace web_app {
146 // The following string is used to build the directory name for
147 // shortcuts to chrome applications (the kind which are installed
148 // from a CRX). Application shortcuts to URLs use the {host}_{path}
149 // for the name of this directory. Hosts can't include an underscore.
150 // By starting this string with an underscore, we ensure that there
151 // are no naming conflicts.
152 static const char kCrxAppPrefix[] = "_crx_";
154 namespace internals {
156 base::FilePath GetSanitizedFileName(const base::string16& name) {
157 #if defined(OS_WIN)
158 base::string16 file_name = name;
159 #else
160 std::string file_name = base::UTF16ToUTF8(name);
161 #endif
162 base::i18n::ReplaceIllegalCharactersInPath(&file_name, '_');
163 return base::FilePath(file_name);
166 } // namespace internals
168 ShortcutInfo::ShortcutInfo()
169 : is_platform_app(false) {
172 ShortcutInfo::~ShortcutInfo() {}
174 ShortcutLocations::ShortcutLocations()
175 : on_desktop(false),
176 applications_menu_location(APP_MENU_LOCATION_NONE),
177 in_quick_launch_bar(false) {
180 #if defined(TOOLKIT_VIEWS)
181 scoped_ptr<ShortcutInfo> GetShortcutInfoForTab(
182 content::WebContents* web_contents) {
183 const favicon::FaviconDriver* favicon_driver =
184 favicon::ContentFaviconDriver::FromWebContents(web_contents);
185 const extensions::TabHelper* extensions_tab_helper =
186 extensions::TabHelper::FromWebContents(web_contents);
187 const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info();
189 scoped_ptr<ShortcutInfo> info(new ShortcutInfo);
190 info->url = app_info.app_url.is_empty() ? web_contents->GetURL() :
191 app_info.app_url;
192 info->title = app_info.title.empty() ?
193 (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) :
194 web_contents->GetTitle()) :
195 app_info.title;
196 info->description = app_info.description;
197 info->favicon.Add(favicon_driver->GetFavicon());
199 Profile* profile =
200 Profile::FromBrowserContext(web_contents->GetBrowserContext());
201 info->profile_path = profile->GetPath();
203 return info;
205 #endif
207 #if !defined(OS_WIN)
208 void UpdateShortcutForTabContents(content::WebContents* web_contents) {}
209 #endif
211 scoped_ptr<ShortcutInfo> ShortcutInfoForExtensionAndProfile(
212 const extensions::Extension* app,
213 Profile* profile) {
214 scoped_ptr<ShortcutInfo> shortcut_info(new ShortcutInfo);
215 shortcut_info->extension_id = app->id();
216 shortcut_info->is_platform_app = app->is_platform_app();
217 shortcut_info->from_bookmark = app->from_bookmark();
218 shortcut_info->url = extensions::AppLaunchInfo::GetLaunchWebURL(app);
219 shortcut_info->title = base::UTF8ToUTF16(app->name());
220 shortcut_info->description = base::UTF8ToUTF16(app->description());
221 shortcut_info->extension_path = app->path();
222 shortcut_info->profile_path = profile->GetPath();
223 shortcut_info->profile_name =
224 profile->GetPrefs()->GetString(prefs::kProfileName);
225 shortcut_info->version_for_display = app->GetVersionForDisplay();
226 return shortcut_info;
229 void GetInfoForApp(const extensions::Extension* extension,
230 Profile* profile,
231 const InfoCallback& callback) {
232 scoped_ptr<web_app::ShortcutInfo> shortcut_info(
233 web_app::ShortcutInfoForExtensionAndProfile(extension, profile));
234 const std::vector<extensions::FileHandlerInfo>* file_handlers =
235 extensions::FileHandlers::GetFileHandlers(extension);
236 extensions::FileHandlersInfo file_handlers_info =
237 file_handlers ? *file_handlers : extensions::FileHandlersInfo();
239 std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
240 for (size_t i = 0; i < kNumDesiredSizes; ++i) {
241 int size = kDesiredSizes[i];
242 extensions::ExtensionResource resource =
243 extensions::IconsInfo::GetIconResource(
244 extension, size, ExtensionIconSet::MATCH_EXACTLY);
245 if (!resource.empty()) {
246 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
247 resource,
248 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
249 gfx::Size(size, size),
250 ui::SCALE_FACTOR_100P));
254 if (info_list.empty()) {
255 size_t i = kNumDesiredSizes - 1;
256 int size = kDesiredSizes[i];
258 // If there is no icon at the desired sizes, we will resize what we can get.
259 // Making a large icon smaller is preferred to making a small icon larger,
260 // so look for a larger icon first:
261 extensions::ExtensionResource resource =
262 extensions::IconsInfo::GetIconResource(
263 extension, size, ExtensionIconSet::MATCH_BIGGER);
264 if (resource.empty()) {
265 resource = extensions::IconsInfo::GetIconResource(
266 extension, size, ExtensionIconSet::MATCH_SMALLER);
268 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
269 resource,
270 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
271 gfx::Size(size, size),
272 ui::SCALE_FACTOR_100P));
275 // |info_list| may still be empty at this point, in which case
276 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
277 // image and exit immediately.
278 extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
279 extension, info_list,
280 base::Bind(&OnImageLoaded, base::Passed(&shortcut_info),
281 file_handlers_info, callback));
284 void GetShortcutInfoForApp(const extensions::Extension* extension,
285 Profile* profile,
286 const ShortcutInfoCallback& callback) {
287 GetInfoForApp(
288 extension, profile, base::Bind(&IgnoreFileHandlersInfo, callback));
291 bool ShouldCreateShortcutFor(web_app::ShortcutCreationReason reason,
292 Profile* profile,
293 const extensions::Extension* extension) {
294 // Shortcuts should never be created for component apps, or for apps that
295 // cannot be shown in the launcher.
296 if (extension->location() == extensions::Manifest::COMPONENT ||
297 !extensions::ui_util::CanDisplayInAppLauncher(extension, profile)) {
298 return false;
301 // Otherwise, always create shortcuts for v2 packaged apps.
302 if (extension->is_platform_app())
303 return true;
305 #if defined(OS_MACOSX)
306 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
307 switches::kDisableHostedAppShimCreation)) {
308 return extension->is_hosted_app();
311 return false;
312 #else
313 // For other platforms, allow shortcut creation if it was explicitly
314 // requested by the user (i.e. is not automatic).
315 return reason == SHORTCUT_CREATION_BY_USER;
316 #endif
319 base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
320 const std::string& extension_id,
321 const GURL& url) {
322 DCHECK(!profile_path.empty());
323 base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
325 if (!extension_id.empty()) {
326 return app_data_dir.AppendASCII(
327 GenerateApplicationNameFromExtensionId(extension_id));
330 std::string host(url.host());
331 std::string scheme(url.has_scheme() ? url.scheme() : "http");
332 std::string port(url.has_port() ? url.port() : "80");
333 std::string scheme_port(scheme + "_" + port);
335 #if defined(OS_WIN)
336 base::FilePath::StringType host_path(base::UTF8ToUTF16(host));
337 base::FilePath::StringType scheme_port_path(base::UTF8ToUTF16(scheme_port));
338 #elif defined(OS_POSIX)
339 base::FilePath::StringType host_path(host);
340 base::FilePath::StringType scheme_port_path(scheme_port);
341 #endif
343 return app_data_dir.Append(host_path).Append(scheme_port_path);
346 base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
347 const extensions::Extension& extension) {
348 return GetWebAppDataDirectory(
349 profile_path,
350 extension.id(),
351 GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension)));
354 std::string GenerateApplicationNameFromInfo(const ShortcutInfo& shortcut_info) {
355 if (!shortcut_info.extension_id.empty())
356 return GenerateApplicationNameFromExtensionId(shortcut_info.extension_id);
357 else
358 return GenerateApplicationNameFromURL(shortcut_info.url);
361 std::string GenerateApplicationNameFromURL(const GURL& url) {
362 std::string t;
363 t.append(url.host());
364 t.append("_");
365 t.append(url.path());
366 return t;
369 std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
370 std::string t(kCrxAppPrefix);
371 t.append(id);
372 return t;
375 std::string GetExtensionIdFromApplicationName(const std::string& app_name) {
376 std::string prefix(kCrxAppPrefix);
377 if (app_name.substr(0, prefix.length()) != prefix)
378 return std::string();
379 return app_name.substr(prefix.length());
382 void CreateShortcutsWithInfo(
383 ShortcutCreationReason reason,
384 const ShortcutLocations& locations,
385 scoped_ptr<ShortcutInfo> shortcut_info,
386 const extensions::FileHandlersInfo& file_handlers_info) {
387 DCHECK_CURRENTLY_ON(BrowserThread::UI);
389 // If the shortcut is for an application shortcut with the new bookmark app
390 // flow disabled, there will be no corresponding extension.
391 if (!shortcut_info->extension_id.empty()) {
392 // It's possible for the extension to be deleted before we get here.
393 // For example, creating a hosted app from a website. Double check that
394 // it still exists.
395 Profile* profile = g_browser_process->profile_manager()->GetProfileByPath(
396 shortcut_info->profile_path);
397 if (!profile)
398 return;
400 extensions::ExtensionRegistry* registry =
401 extensions::ExtensionRegistry::Get(profile);
402 const extensions::Extension* extension = registry->GetExtensionById(
403 shortcut_info->extension_id, extensions::ExtensionRegistry::EVERYTHING);
404 if (!extension)
405 return;
408 ScheduleCreatePlatformShortcut(reason, locations, shortcut_info.Pass(),
409 file_handlers_info);
412 void CreateNonAppShortcut(const ShortcutLocations& locations,
413 scoped_ptr<ShortcutInfo> shortcut_info) {
414 ScheduleCreatePlatformShortcut(SHORTCUT_CREATION_AUTOMATED, locations,
415 shortcut_info.Pass(),
416 extensions::FileHandlersInfo());
419 void CreateShortcuts(ShortcutCreationReason reason,
420 const ShortcutLocations& locations,
421 Profile* profile,
422 const extensions::Extension* app) {
423 DCHECK_CURRENTLY_ON(BrowserThread::UI);
425 if (!ShouldCreateShortcutFor(reason, profile, app))
426 return;
428 GetInfoForApp(
429 app, profile, base::Bind(&CreateShortcutsWithInfo, reason, locations));
432 void DeleteAllShortcuts(Profile* profile, const extensions::Extension* app) {
433 DCHECK_CURRENTLY_ON(BrowserThread::UI);
435 scoped_ptr<ShortcutInfo> shortcut_info(
436 ShortcutInfoForExtensionAndProfile(app, profile));
437 base::FilePath shortcut_data_dir = GetShortcutDataDir(*shortcut_info);
438 BrowserThread::PostTask(
439 BrowserThread::FILE, FROM_HERE,
440 base::Bind(&web_app::internals::DeletePlatformShortcuts,
441 shortcut_data_dir, base::Passed(&shortcut_info)));
444 void UpdateAllShortcuts(const base::string16& old_app_title,
445 Profile* profile,
446 const extensions::Extension* app) {
447 DCHECK_CURRENTLY_ON(BrowserThread::UI);
449 GetInfoForApp(app,
450 profile,
451 base::Bind(&UpdateAllShortcutsForShortcutInfo, old_app_title));
454 bool IsValidUrl(const GURL& url) {
455 static const char* const kValidUrlSchemes[] = {
456 url::kFileScheme,
457 url::kFileSystemScheme,
458 url::kFtpScheme,
459 url::kHttpScheme,
460 url::kHttpsScheme,
461 extensions::kExtensionScheme,
464 for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
465 if (url.SchemeIs(kValidUrlSchemes[i]))
466 return true;
469 return false;
472 #if defined(TOOLKIT_VIEWS)
473 void GetIconsInfo(const WebApplicationInfo& app_info,
474 IconInfoList* icons) {
475 DCHECK(icons);
477 icons->clear();
478 for (size_t i = 0; i < app_info.icons.size(); ++i) {
479 // We only take square shaped icons (i.e. width == height).
480 if (app_info.icons[i].width == app_info.icons[i].height) {
481 icons->push_back(app_info.icons[i]);
485 std::sort(icons->begin(), icons->end(), &IconPrecedes);
487 #endif
489 #if defined(OS_LINUX)
490 std::string GetWMClassFromAppName(std::string app_name) {
491 base::i18n::ReplaceIllegalCharactersInPath(&app_name, '_');
492 base::TrimString(app_name, "_", &app_name);
493 return app_name;
495 #endif
497 } // namespace web_app