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"
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"
42 #include "ui/gfx/icon_util.h"
45 #if defined(TOOLKIT_VIEWS)
46 #include "chrome/browser/extensions/tab_helper.h"
47 #include "chrome/browser/favicon/favicon_tab_helper.h"
50 using content::BrowserThread
;
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
);
63 const int* kDesiredSizes
= IconUtil::kIconDimensions
;
64 const size_t kNumDesiredSizes
= IconUtil::kNumIconDimensions
;
66 const int kDesiredSizes
[] = {32};
67 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
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
;
78 base::FilePath
GetShortcutDataDir(const web_app::ShortcutInfo
& shortcut_info
) {
79 return web_app::GetWebAppDataDirectory(shortcut_info
.profile_path
,
80 shortcut_info
.extension_id
,
84 void UpdateAllShortcutsForShortcutInfo(
85 const base::string16
& old_app_title
,
86 const web_app::ShortcutInfo
& shortcut_info
,
87 const extensions::FileHandlersInfo
& file_handlers_info
) {
88 BrowserThread::PostTask(
91 base::Bind(&web_app::internals::UpdatePlatformShortcuts
,
92 GetShortcutDataDir(shortcut_info
),
93 old_app_title
, shortcut_info
, file_handlers_info
));
96 void OnImageLoaded(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
,
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
));
115 shortcut_info
.favicon
= image_family
;
118 callback
.Run(shortcut_info
, file_handlers_info
);
121 void IgnoreFileHandlersInfo(
122 const web_app::ShortcutInfoCallback
& shortcut_info_callback
,
123 const web_app::ShortcutInfo
& shortcut_info
,
124 const extensions::FileHandlersInfo
& file_handlers_info
) {
125 shortcut_info_callback
.Run(shortcut_info
);
128 void ScheduleCreatePlatformShortcut(
129 web_app::ShortcutCreationReason reason
,
130 const web_app::ShortcutLocations
& locations
,
131 const web_app::ShortcutInfo
& shortcut_info
,
132 const extensions::FileHandlersInfo
& file_handlers_info
) {
133 BrowserThread::PostTask(
134 BrowserThread::FILE, FROM_HERE
,
136 base::IgnoreResult(&web_app::internals::CreatePlatformShortcuts
),
137 GetShortcutDataDir(shortcut_info
), shortcut_info
, file_handlers_info
,
145 // The following string is used to build the directory name for
146 // shortcuts to chrome applications (the kind which are installed
147 // from a CRX). Application shortcuts to URLs use the {host}_{path}
148 // for the name of this directory. Hosts can't include an underscore.
149 // By starting this string with an underscore, we ensure that there
150 // are no naming conflicts.
151 static const char kCrxAppPrefix
[] = "_crx_";
153 namespace internals
{
155 base::FilePath
GetSanitizedFileName(const base::string16
& name
) {
157 base::string16 file_name
= name
;
159 std::string file_name
= base::UTF16ToUTF8(name
);
161 base::i18n::ReplaceIllegalCharactersInPath(&file_name
, '_');
162 return base::FilePath(file_name
);
165 } // namespace internals
167 ShortcutInfo::ShortcutInfo()
168 : is_platform_app(false) {
171 ShortcutInfo::~ShortcutInfo() {}
173 ShortcutLocations::ShortcutLocations()
175 applications_menu_location(APP_MENU_LOCATION_NONE
),
176 in_quick_launch_bar(false) {
179 #if defined(TOOLKIT_VIEWS)
180 void GetShortcutInfoForTab(content::WebContents
* web_contents
,
181 ShortcutInfo
* info
) {
182 DCHECK(info
); // Must provide a valid info.
184 const FaviconTabHelper
* favicon_tab_helper
=
185 FaviconTabHelper::FromWebContents(web_contents
);
186 const extensions::TabHelper
* extensions_tab_helper
=
187 extensions::TabHelper::FromWebContents(web_contents
);
188 const WebApplicationInfo
& app_info
= extensions_tab_helper
->web_app_info();
190 info
->url
= app_info
.app_url
.is_empty() ? web_contents
->GetURL() :
192 info
->title
= app_info
.title
.empty() ?
193 (web_contents
->GetTitle().empty() ? base::UTF8ToUTF16(info
->url
.spec()) :
194 web_contents
->GetTitle()) :
196 info
->description
= app_info
.description
;
197 info
->favicon
.Add(favicon_tab_helper
->GetFavicon());
200 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
201 info
->profile_path
= profile
->GetPath();
206 void UpdateShortcutForTabContents(content::WebContents
* web_contents
) {}
209 ShortcutInfo
ShortcutInfoForExtensionAndProfile(
210 const extensions::Extension
* app
, Profile
* profile
) {
211 ShortcutInfo shortcut_info
;
212 shortcut_info
.extension_id
= app
->id();
213 shortcut_info
.is_platform_app
= app
->is_platform_app();
214 shortcut_info
.url
= extensions::AppLaunchInfo::GetLaunchWebURL(app
);
215 shortcut_info
.title
= base::UTF8ToUTF16(app
->name());
216 shortcut_info
.description
= base::UTF8ToUTF16(app
->description());
217 shortcut_info
.extension_path
= app
->path();
218 shortcut_info
.profile_path
= profile
->GetPath();
219 shortcut_info
.profile_name
=
220 profile
->GetPrefs()->GetString(prefs::kProfileName
);
221 shortcut_info
.version_for_display
= app
->GetVersionForDisplay();
222 return shortcut_info
;
225 void GetInfoForApp(const extensions::Extension
* extension
,
227 const InfoCallback
& callback
) {
228 web_app::ShortcutInfo shortcut_info
=
229 web_app::ShortcutInfoForExtensionAndProfile(extension
, profile
);
230 const std::vector
<extensions::FileHandlerInfo
>* file_handlers
=
231 extensions::FileHandlers::GetFileHandlers(extension
);
232 extensions::FileHandlersInfo file_handlers_info
=
233 file_handlers
? *file_handlers
: extensions::FileHandlersInfo();
235 std::vector
<extensions::ImageLoader::ImageRepresentation
> info_list
;
236 for (size_t i
= 0; i
< kNumDesiredSizes
; ++i
) {
237 int size
= kDesiredSizes
[i
];
238 extensions::ExtensionResource resource
=
239 extensions::IconsInfo::GetIconResource(
240 extension
, size
, ExtensionIconSet::MATCH_EXACTLY
);
241 if (!resource
.empty()) {
242 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
244 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
245 gfx::Size(size
, size
),
246 ui::SCALE_FACTOR_100P
));
250 if (info_list
.empty()) {
251 size_t i
= kNumDesiredSizes
- 1;
252 int size
= kDesiredSizes
[i
];
254 // If there is no icon at the desired sizes, we will resize what we can get.
255 // Making a large icon smaller is preferred to making a small icon larger,
256 // so look for a larger icon first:
257 extensions::ExtensionResource resource
=
258 extensions::IconsInfo::GetIconResource(
259 extension
, size
, ExtensionIconSet::MATCH_BIGGER
);
260 if (resource
.empty()) {
261 resource
= extensions::IconsInfo::GetIconResource(
262 extension
, size
, ExtensionIconSet::MATCH_SMALLER
);
264 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
266 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
267 gfx::Size(size
, size
),
268 ui::SCALE_FACTOR_100P
));
271 // |info_list| may still be empty at this point, in which case
272 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
273 // image and exit immediately.
274 extensions::ImageLoader::Get(profile
)->LoadImageFamilyAsync(
277 base::Bind(&OnImageLoaded
, shortcut_info
, file_handlers_info
, callback
));
280 void GetShortcutInfoForApp(const extensions::Extension
* extension
,
282 const ShortcutInfoCallback
& callback
) {
284 extension
, profile
, base::Bind(&IgnoreFileHandlersInfo
, callback
));
287 bool ShouldCreateShortcutFor(web_app::ShortcutCreationReason reason
,
289 const extensions::Extension
* extension
) {
290 // Shortcuts should never be created for component apps, or for apps that
291 // cannot be shown in the launcher.
292 if (extension
->location() == extensions::Manifest::COMPONENT
||
293 !extensions::ui_util::CanDisplayInAppLauncher(extension
, profile
)) {
297 // Otherwise, always create shortcuts for v2 packaged apps.
298 if (extension
->is_platform_app())
301 #if defined(OS_MACOSX)
302 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
303 switches::kDisableHostedAppShimCreation
)) {
304 return extension
->is_hosted_app();
309 // For other platforms, allow shortcut creation if it was explicitly
310 // requested by the user (i.e. is not automatic).
311 return reason
== SHORTCUT_CREATION_BY_USER
;
315 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
316 const std::string
& extension_id
,
318 DCHECK(!profile_path
.empty());
319 base::FilePath
app_data_dir(profile_path
.Append(chrome::kWebAppDirname
));
321 if (!extension_id
.empty()) {
322 return app_data_dir
.AppendASCII(
323 GenerateApplicationNameFromExtensionId(extension_id
));
326 std::string
host(url
.host());
327 std::string
scheme(url
.has_scheme() ? url
.scheme() : "http");
328 std::string
port(url
.has_port() ? url
.port() : "80");
329 std::string
scheme_port(scheme
+ "_" + port
);
332 base::FilePath::StringType
host_path(base::UTF8ToUTF16(host
));
333 base::FilePath::StringType
scheme_port_path(base::UTF8ToUTF16(scheme_port
));
334 #elif defined(OS_POSIX)
335 base::FilePath::StringType
host_path(host
);
336 base::FilePath::StringType
scheme_port_path(scheme_port
);
339 return app_data_dir
.Append(host_path
).Append(scheme_port_path
);
342 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
343 const extensions::Extension
& extension
) {
344 return GetWebAppDataDirectory(
347 GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension
)));
350 std::string
GenerateApplicationNameFromInfo(const ShortcutInfo
& shortcut_info
) {
351 if (!shortcut_info
.extension_id
.empty())
352 return GenerateApplicationNameFromExtensionId(shortcut_info
.extension_id
);
354 return GenerateApplicationNameFromURL(shortcut_info
.url
);
357 std::string
GenerateApplicationNameFromURL(const GURL
& url
) {
359 t
.append(url
.host());
361 t
.append(url
.path());
365 std::string
GenerateApplicationNameFromExtensionId(const std::string
& id
) {
366 std::string
t(kCrxAppPrefix
);
371 std::string
GetExtensionIdFromApplicationName(const std::string
& app_name
) {
372 std::string
prefix(kCrxAppPrefix
);
373 if (app_name
.substr(0, prefix
.length()) != prefix
)
374 return std::string();
375 return app_name
.substr(prefix
.length());
378 void CreateShortcutsWithInfo(
379 ShortcutCreationReason reason
,
380 const ShortcutLocations
& locations
,
381 const ShortcutInfo
& shortcut_info
,
382 const extensions::FileHandlersInfo
& file_handlers_info
) {
383 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
385 // If the shortcut is for an application shortcut with the new bookmark app
386 // flow disabled, there will be no corresponding extension.
387 if (!shortcut_info
.extension_id
.empty()) {
388 // It's possible for the extension to be deleted before we get here.
389 // For example, creating a hosted app from a website. Double check that
391 Profile
* profile
= g_browser_process
->profile_manager()->GetProfileByPath(
392 shortcut_info
.profile_path
);
396 extensions::ExtensionRegistry
* registry
=
397 extensions::ExtensionRegistry::Get(profile
);
398 const extensions::Extension
* extension
= registry
->GetExtensionById(
399 shortcut_info
.extension_id
, extensions::ExtensionRegistry::EVERYTHING
);
404 ScheduleCreatePlatformShortcut(reason
, locations
, shortcut_info
,
408 void CreateNonAppShortcut(const ShortcutLocations
& locations
,
409 const ShortcutInfo
& shortcut_info
) {
410 ScheduleCreatePlatformShortcut(SHORTCUT_CREATION_AUTOMATED
, locations
,
411 shortcut_info
, extensions::FileHandlersInfo());
414 void CreateShortcuts(ShortcutCreationReason reason
,
415 const ShortcutLocations
& locations
,
417 const extensions::Extension
* app
) {
418 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
420 if (!ShouldCreateShortcutFor(reason
, profile
, app
))
424 app
, profile
, base::Bind(&CreateShortcutsWithInfo
, reason
, locations
));
427 void DeleteAllShortcuts(Profile
* profile
, const extensions::Extension
* app
) {
428 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
430 ShortcutInfo shortcut_info
=
431 ShortcutInfoForExtensionAndProfile(app
, profile
);
432 BrowserThread::PostTask(
435 base::Bind(&web_app::internals::DeletePlatformShortcuts
,
436 GetShortcutDataDir(shortcut_info
), shortcut_info
));
439 void UpdateAllShortcuts(const base::string16
& old_app_title
,
441 const extensions::Extension
* app
) {
442 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
446 base::Bind(&UpdateAllShortcutsForShortcutInfo
, old_app_title
));
449 bool IsValidUrl(const GURL
& url
) {
450 static const char* const kValidUrlSchemes
[] = {
452 url::kFileSystemScheme
,
456 extensions::kExtensionScheme
,
459 for (size_t i
= 0; i
< arraysize(kValidUrlSchemes
); ++i
) {
460 if (url
.SchemeIs(kValidUrlSchemes
[i
]))
467 #if defined(TOOLKIT_VIEWS)
468 void GetIconsInfo(const WebApplicationInfo
& app_info
,
469 IconInfoList
* icons
) {
473 for (size_t i
= 0; i
< app_info
.icons
.size(); ++i
) {
474 // We only take square shaped icons (i.e. width == height).
475 if (app_info
.icons
[i
].width
== app_info
.icons
[i
].height
) {
476 icons
->push_back(app_info
.icons
[i
]);
480 std::sort(icons
->begin(), icons
->end(), &IconPrecedes
);
484 #if defined(OS_LINUX)
485 std::string
GetWMClassFromAppName(std::string app_name
) {
486 base::i18n::ReplaceIllegalCharactersInPath(&app_name
, '_');
487 base::TrimString(app_name
, "_", &app_name
);
492 } // namespace web_app