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/extensions/manifest_handlers/app_launch_info.h"
23 #include "chrome/common/pref_names.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "extensions/browser/extension_registry.h"
26 #include "extensions/browser/image_loader.h"
27 #include "extensions/common/constants.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_set.h"
30 #include "extensions/common/manifest_handlers/icons_handler.h"
31 #include "extensions/grit/extensions_browser_resources.h"
32 #include "skia/ext/image_operations.h"
33 #include "third_party/skia/include/core/SkBitmap.h"
34 #include "ui/base/resource/resource_bundle.h"
35 #include "ui/gfx/image/image.h"
36 #include "ui/gfx/image/image_family.h"
37 #include "ui/gfx/image/image_skia.h"
38 #include "url/url_constants.h"
41 #include "ui/gfx/icon_util.h"
44 #if defined(TOOLKIT_VIEWS)
45 #include "chrome/browser/extensions/tab_helper.h"
46 #include "components/favicon/content/content_favicon_driver.h"
49 using content::BrowserThread
;
53 #if defined(OS_MACOSX)
54 const int kDesiredSizes
[] = {16, 32, 128, 256, 512};
55 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
56 #elif defined(OS_LINUX)
57 // Linux supports icons of any size. FreeDesktop Icon Theme Specification states
58 // that "Minimally you should install a 48x48 icon in the hicolor theme."
59 const int kDesiredSizes
[] = {16, 32, 48, 128, 256, 512};
60 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
62 const int* kDesiredSizes
= IconUtil::kIconDimensions
;
63 const size_t kNumDesiredSizes
= IconUtil::kNumIconDimensions
;
65 const int kDesiredSizes
[] = {32};
66 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
69 #if defined(TOOLKIT_VIEWS)
70 // Predicator for sorting images from largest to smallest.
71 bool IconPrecedes(const WebApplicationInfo::IconInfo
& left
,
72 const WebApplicationInfo::IconInfo
& right
) {
73 return left
.width
< right
.width
;
77 base::FilePath
GetShortcutDataDir(const web_app::ShortcutInfo
& shortcut_info
) {
78 return web_app::GetWebAppDataDirectory(shortcut_info
.profile_path
,
79 shortcut_info
.extension_id
,
83 void UpdateAllShortcutsForShortcutInfo(
84 const base::string16
& old_app_title
,
85 scoped_ptr
<web_app::ShortcutInfo
> shortcut_info
,
86 const extensions::FileHandlersInfo
& file_handlers_info
) {
87 base::FilePath shortcut_data_dir
= GetShortcutDataDir(*shortcut_info
);
88 BrowserThread::PostTask(
89 BrowserThread::FILE, FROM_HERE
,
90 base::Bind(&web_app::internals::UpdatePlatformShortcuts
,
91 shortcut_data_dir
, old_app_title
, base::Passed(&shortcut_info
),
95 void OnImageLoaded(scoped_ptr
<web_app::ShortcutInfo
> shortcut_info
,
96 extensions::FileHandlersInfo file_handlers_info
,
97 web_app::InfoCallback callback
,
98 const gfx::ImageFamily
& image_family
) {
99 // If the image failed to load (e.g. if the resource being loaded was empty)
100 // use the standard application icon.
101 if (image_family
.empty()) {
102 gfx::Image default_icon
=
103 ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON
);
104 int size
= kDesiredSizes
[kNumDesiredSizes
- 1];
105 SkBitmap bmp
= skia::ImageOperations::Resize(
106 *default_icon
.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST
,
108 gfx::ImageSkia image_skia
= gfx::ImageSkia::CreateFrom1xBitmap(bmp
);
109 // We are on the UI thread, and this image is needed from the FILE thread,
110 // for creating shortcut icon files.
111 image_skia
.MakeThreadSafe();
112 shortcut_info
->favicon
.Add(gfx::Image(image_skia
));
114 shortcut_info
->favicon
= image_family
;
117 callback
.Run(shortcut_info
.Pass(), file_handlers_info
);
120 void IgnoreFileHandlersInfo(
121 const web_app::ShortcutInfoCallback
& shortcut_info_callback
,
122 scoped_ptr
<web_app::ShortcutInfo
> shortcut_info
,
123 const extensions::FileHandlersInfo
& file_handlers_info
) {
124 shortcut_info_callback
.Run(shortcut_info
.Pass());
127 void ScheduleCreatePlatformShortcut(
128 web_app::ShortcutCreationReason reason
,
129 const web_app::ShortcutLocations
& locations
,
130 scoped_ptr
<web_app::ShortcutInfo
> shortcut_info
,
131 const extensions::FileHandlersInfo
& file_handlers_info
) {
132 base::FilePath shortcut_data_dir
= GetShortcutDataDir(*shortcut_info
);
133 BrowserThread::PostTask(
134 BrowserThread::FILE, FROM_HERE
,
136 base::IgnoreResult(&web_app::internals::CreatePlatformShortcuts
),
137 shortcut_data_dir
, base::Passed(&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 scoped_ptr
<ShortcutInfo
> GetShortcutInfoForTab(
181 content::WebContents
* web_contents
) {
182 const favicon::FaviconDriver
* favicon_driver
=
183 favicon::ContentFaviconDriver::FromWebContents(web_contents
);
184 const extensions::TabHelper
* extensions_tab_helper
=
185 extensions::TabHelper::FromWebContents(web_contents
);
186 const WebApplicationInfo
& app_info
= extensions_tab_helper
->web_app_info();
188 scoped_ptr
<ShortcutInfo
> info(new ShortcutInfo
);
189 info
->url
= app_info
.app_url
.is_empty() ? web_contents
->GetURL() :
191 info
->title
= app_info
.title
.empty() ?
192 (web_contents
->GetTitle().empty() ? base::UTF8ToUTF16(info
->url
.spec()) :
193 web_contents
->GetTitle()) :
195 info
->description
= app_info
.description
;
196 info
->favicon
.Add(favicon_driver
->GetFavicon());
199 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
200 info
->profile_path
= profile
->GetPath();
207 void UpdateShortcutForTabContents(content::WebContents
* web_contents
) {}
210 scoped_ptr
<ShortcutInfo
> ShortcutInfoForExtensionAndProfile(
211 const extensions::Extension
* app
,
213 scoped_ptr
<ShortcutInfo
> shortcut_info(new ShortcutInfo
);
214 shortcut_info
->extension_id
= app
->id();
215 shortcut_info
->is_platform_app
= app
->is_platform_app();
216 shortcut_info
->from_bookmark
= app
->from_bookmark();
217 shortcut_info
->url
= extensions::AppLaunchInfo::GetLaunchWebURL(app
);
218 shortcut_info
->title
= base::UTF8ToUTF16(app
->name());
219 shortcut_info
->description
= base::UTF8ToUTF16(app
->description());
220 shortcut_info
->extension_path
= app
->path();
221 shortcut_info
->profile_path
= profile
->GetPath();
222 shortcut_info
->profile_name
=
223 profile
->GetPrefs()->GetString(prefs::kProfileName
);
224 shortcut_info
->version_for_display
= app
->GetVersionForDisplay();
225 return shortcut_info
;
228 void GetInfoForApp(const extensions::Extension
* extension
,
230 const InfoCallback
& callback
) {
231 scoped_ptr
<web_app::ShortcutInfo
> shortcut_info(
232 web_app::ShortcutInfoForExtensionAndProfile(extension
, profile
));
233 const std::vector
<extensions::FileHandlerInfo
>* file_handlers
=
234 extensions::FileHandlers::GetFileHandlers(extension
);
235 extensions::FileHandlersInfo file_handlers_info
=
236 file_handlers
? *file_handlers
: extensions::FileHandlersInfo();
238 std::vector
<extensions::ImageLoader::ImageRepresentation
> info_list
;
239 for (size_t i
= 0; i
< kNumDesiredSizes
; ++i
) {
240 int size
= kDesiredSizes
[i
];
241 extensions::ExtensionResource resource
=
242 extensions::IconsInfo::GetIconResource(
243 extension
, size
, ExtensionIconSet::MATCH_EXACTLY
);
244 if (!resource
.empty()) {
245 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
247 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
248 gfx::Size(size
, size
),
249 ui::SCALE_FACTOR_100P
));
253 if (info_list
.empty()) {
254 size_t i
= kNumDesiredSizes
- 1;
255 int size
= kDesiredSizes
[i
];
257 // If there is no icon at the desired sizes, we will resize what we can get.
258 // Making a large icon smaller is preferred to making a small icon larger,
259 // so look for a larger icon first:
260 extensions::ExtensionResource resource
=
261 extensions::IconsInfo::GetIconResource(
262 extension
, size
, ExtensionIconSet::MATCH_BIGGER
);
263 if (resource
.empty()) {
264 resource
= extensions::IconsInfo::GetIconResource(
265 extension
, size
, ExtensionIconSet::MATCH_SMALLER
);
267 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
269 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
270 gfx::Size(size
, size
),
271 ui::SCALE_FACTOR_100P
));
274 // |info_list| may still be empty at this point, in which case
275 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
276 // image and exit immediately.
277 extensions::ImageLoader::Get(profile
)->LoadImageFamilyAsync(
278 extension
, info_list
,
279 base::Bind(&OnImageLoaded
, base::Passed(&shortcut_info
),
280 file_handlers_info
, callback
));
283 void GetShortcutInfoForApp(const extensions::Extension
* extension
,
285 const ShortcutInfoCallback
& callback
) {
287 extension
, profile
, base::Bind(&IgnoreFileHandlersInfo
, callback
));
290 bool ShouldCreateShortcutFor(web_app::ShortcutCreationReason reason
,
292 const extensions::Extension
* extension
) {
293 // Shortcuts should never be created for component apps, or for apps that
294 // cannot be shown in the launcher.
295 if (extension
->location() == extensions::Manifest::COMPONENT
||
296 !extensions::ui_util::CanDisplayInAppLauncher(extension
, profile
)) {
300 // Otherwise, always create shortcuts for v2 packaged apps.
301 if (extension
->is_platform_app())
304 #if defined(OS_MACOSX)
305 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
306 switches::kDisableHostedAppShimCreation
)) {
307 return extension
->is_hosted_app();
312 // For other platforms, allow shortcut creation if it was explicitly
313 // requested by the user (i.e. is not automatic).
314 return reason
== SHORTCUT_CREATION_BY_USER
;
318 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
319 const std::string
& extension_id
,
321 DCHECK(!profile_path
.empty());
322 base::FilePath
app_data_dir(profile_path
.Append(chrome::kWebAppDirname
));
324 if (!extension_id
.empty()) {
325 return app_data_dir
.AppendASCII(
326 GenerateApplicationNameFromExtensionId(extension_id
));
329 std::string
host(url
.host());
330 std::string
scheme(url
.has_scheme() ? url
.scheme() : "http");
331 std::string
port(url
.has_port() ? url
.port() : "80");
332 std::string
scheme_port(scheme
+ "_" + port
);
335 base::FilePath::StringType
host_path(base::UTF8ToUTF16(host
));
336 base::FilePath::StringType
scheme_port_path(base::UTF8ToUTF16(scheme_port
));
337 #elif defined(OS_POSIX)
338 base::FilePath::StringType
host_path(host
);
339 base::FilePath::StringType
scheme_port_path(scheme_port
);
342 return app_data_dir
.Append(host_path
).Append(scheme_port_path
);
345 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
346 const extensions::Extension
& extension
) {
347 return GetWebAppDataDirectory(
350 GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension
)));
353 std::string
GenerateApplicationNameFromInfo(const ShortcutInfo
& shortcut_info
) {
354 if (!shortcut_info
.extension_id
.empty())
355 return GenerateApplicationNameFromExtensionId(shortcut_info
.extension_id
);
357 return GenerateApplicationNameFromURL(shortcut_info
.url
);
360 std::string
GenerateApplicationNameFromURL(const GURL
& url
) {
362 t
.append(url
.host());
364 t
.append(url
.path());
368 std::string
GenerateApplicationNameFromExtensionId(const std::string
& id
) {
369 std::string
t(kCrxAppPrefix
);
374 std::string
GetExtensionIdFromApplicationName(const std::string
& app_name
) {
375 std::string
prefix(kCrxAppPrefix
);
376 if (app_name
.substr(0, prefix
.length()) != prefix
)
377 return std::string();
378 return app_name
.substr(prefix
.length());
381 void CreateShortcutsWithInfo(
382 ShortcutCreationReason reason
,
383 const ShortcutLocations
& locations
,
384 scoped_ptr
<ShortcutInfo
> shortcut_info
,
385 const extensions::FileHandlersInfo
& file_handlers_info
) {
386 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
388 // If the shortcut is for an application shortcut with the new bookmark app
389 // flow disabled, there will be no corresponding extension.
390 if (!shortcut_info
->extension_id
.empty()) {
391 // It's possible for the extension to be deleted before we get here.
392 // For example, creating a hosted app from a website. Double check that
394 Profile
* profile
= g_browser_process
->profile_manager()->GetProfileByPath(
395 shortcut_info
->profile_path
);
399 extensions::ExtensionRegistry
* registry
=
400 extensions::ExtensionRegistry::Get(profile
);
401 const extensions::Extension
* extension
= registry
->GetExtensionById(
402 shortcut_info
->extension_id
, extensions::ExtensionRegistry::EVERYTHING
);
407 ScheduleCreatePlatformShortcut(reason
, locations
, shortcut_info
.Pass(),
411 void CreateNonAppShortcut(const ShortcutLocations
& locations
,
412 scoped_ptr
<ShortcutInfo
> shortcut_info
) {
413 ScheduleCreatePlatformShortcut(SHORTCUT_CREATION_AUTOMATED
, locations
,
414 shortcut_info
.Pass(),
415 extensions::FileHandlersInfo());
418 void CreateShortcuts(ShortcutCreationReason reason
,
419 const ShortcutLocations
& locations
,
421 const extensions::Extension
* app
) {
422 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
424 if (!ShouldCreateShortcutFor(reason
, profile
, app
))
428 app
, profile
, base::Bind(&CreateShortcutsWithInfo
, reason
, locations
));
431 void DeleteAllShortcuts(Profile
* profile
, const extensions::Extension
* app
) {
432 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
434 scoped_ptr
<ShortcutInfo
> shortcut_info(
435 ShortcutInfoForExtensionAndProfile(app
, profile
));
436 base::FilePath shortcut_data_dir
= GetShortcutDataDir(*shortcut_info
);
437 BrowserThread::PostTask(
438 BrowserThread::FILE, FROM_HERE
,
439 base::Bind(&web_app::internals::DeletePlatformShortcuts
,
440 shortcut_data_dir
, base::Passed(&shortcut_info
)));
443 void UpdateAllShortcuts(const base::string16
& old_app_title
,
445 const extensions::Extension
* app
) {
446 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
450 base::Bind(&UpdateAllShortcutsForShortcutInfo
, old_app_title
));
453 bool IsValidUrl(const GURL
& url
) {
454 static const char* const kValidUrlSchemes
[] = {
456 url::kFileSystemScheme
,
460 extensions::kExtensionScheme
,
463 for (size_t i
= 0; i
< arraysize(kValidUrlSchemes
); ++i
) {
464 if (url
.SchemeIs(kValidUrlSchemes
[i
]))
471 #if defined(TOOLKIT_VIEWS)
472 void GetIconsInfo(const WebApplicationInfo
& app_info
,
473 IconInfoList
* icons
) {
477 for (size_t i
= 0; i
< app_info
.icons
.size(); ++i
) {
478 // We only take square shaped icons (i.e. width == height).
479 if (app_info
.icons
[i
].width
== app_info
.icons
[i
].height
) {
480 icons
->push_back(app_info
.icons
[i
]);
484 std::sort(icons
->begin(), icons
->end(), &IconPrecedes
);
488 #if defined(OS_LINUX)
489 std::string
GetWMClassFromAppName(std::string app_name
) {
490 base::i18n::ReplaceIllegalCharactersInPath(&app_name
, '_');
491 base::TrimString(app_name
, "_", &app_name
);
496 } // namespace web_app