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 "components/favicon/content/content_favicon_driver.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 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
),
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
,
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
.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
,
137 base::IgnoreResult(&web_app::internals::CreatePlatformShortcuts
),
138 shortcut_data_dir
, base::Passed(&shortcut_info
), file_handlers_info
,
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
) {
158 base::string16 file_name
= name
;
160 std::string file_name
= base::UTF16ToUTF8(name
);
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()
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() :
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_driver
->GetFavicon());
200 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
201 info
->profile_path
= profile
->GetPath();
208 void UpdateShortcutForTabContents(content::WebContents
* web_contents
) {}
211 scoped_ptr
<ShortcutInfo
> ShortcutInfoForExtensionAndProfile(
212 const extensions::Extension
* app
,
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
,
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(
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(
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
,
286 const ShortcutInfoCallback
& callback
) {
288 extension
, profile
, base::Bind(&IgnoreFileHandlersInfo
, callback
));
291 bool ShouldCreateShortcutFor(web_app::ShortcutCreationReason reason
,
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
)) {
301 // Otherwise, always create shortcuts for v2 packaged apps.
302 if (extension
->is_platform_app())
305 #if defined(OS_MACOSX)
306 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
307 switches::kDisableHostedAppShimCreation
)) {
308 return extension
->is_hosted_app();
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
;
319 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
320 const std::string
& extension_id
,
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
);
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
);
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(
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
);
358 return GenerateApplicationNameFromURL(shortcut_info
.url
);
361 std::string
GenerateApplicationNameFromURL(const GURL
& url
) {
363 t
.append(url
.host());
365 t
.append(url
.path());
369 std::string
GenerateApplicationNameFromExtensionId(const std::string
& id
) {
370 std::string
t(kCrxAppPrefix
);
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
395 Profile
* profile
= g_browser_process
->profile_manager()->GetProfileByPath(
396 shortcut_info
->profile_path
);
400 extensions::ExtensionRegistry
* registry
=
401 extensions::ExtensionRegistry::Get(profile
);
402 const extensions::Extension
* extension
= registry
->GetExtensionById(
403 shortcut_info
->extension_id
, extensions::ExtensionRegistry::EVERYTHING
);
408 ScheduleCreatePlatformShortcut(reason
, locations
, shortcut_info
.Pass(),
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
,
422 const extensions::Extension
* app
) {
423 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
425 if (!ShouldCreateShortcutFor(reason
, profile
, app
))
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
,
446 const extensions::Extension
* app
) {
447 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
451 base::Bind(&UpdateAllShortcutsForShortcutInfo
, old_app_title
));
454 bool IsValidUrl(const GURL
& url
) {
455 static const char* const kValidUrlSchemes
[] = {
457 url::kFileSystemScheme
,
461 extensions::kExtensionScheme
,
464 for (size_t i
= 0; i
< arraysize(kValidUrlSchemes
); ++i
) {
465 if (url
.SchemeIs(kValidUrlSchemes
[i
]))
472 #if defined(TOOLKIT_VIEWS)
473 void GetIconsInfo(const WebApplicationInfo
& app_info
,
474 IconInfoList
* icons
) {
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
);
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
);
497 } // namespace web_app