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/files/file_util.h"
10 #include "base/i18n/file_util_icu.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/threading/thread.h"
15 #include "chrome/browser/extensions/extension_ui_util.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/chrome_constants.h"
18 #include "chrome/common/chrome_version_info.h"
19 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
20 #include "chrome/common/pref_names.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "extensions/browser/extension_registry.h"
23 #include "extensions/browser/image_loader.h"
24 #include "extensions/common/constants.h"
25 #include "extensions/common/extension.h"
26 #include "extensions/common/extension_set.h"
27 #include "extensions/common/manifest_handlers/icons_handler.h"
28 #include "extensions/grit/extensions_browser_resources.h"
29 #include "skia/ext/image_operations.h"
30 #include "third_party/skia/include/core/SkBitmap.h"
31 #include "ui/base/resource/resource_bundle.h"
32 #include "ui/gfx/image/image.h"
33 #include "ui/gfx/image/image_family.h"
34 #include "ui/gfx/image/image_skia.h"
35 #include "url/url_constants.h"
38 #include "ui/gfx/icon_util.h"
41 #if defined(TOOLKIT_VIEWS)
42 #include "chrome/browser/extensions/tab_helper.h"
43 #include "chrome/browser/favicon/favicon_tab_helper.h"
46 using content::BrowserThread
;
50 #if defined(OS_MACOSX)
51 const int kDesiredSizes
[] = {16, 32, 128, 256, 512};
52 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
53 #elif defined(OS_LINUX)
54 // Linux supports icons of any size. FreeDesktop Icon Theme Specification states
55 // that "Minimally you should install a 48x48 icon in the hicolor theme."
56 const int kDesiredSizes
[] = {16, 32, 48, 128, 256, 512};
57 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
59 const int* kDesiredSizes
= IconUtil::kIconDimensions
;
60 const size_t kNumDesiredSizes
= IconUtil::kNumIconDimensions
;
62 const int kDesiredSizes
[] = {32};
63 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
66 #if defined(TOOLKIT_VIEWS)
67 // Predicator for sorting images from largest to smallest.
68 bool IconPrecedes(const WebApplicationInfo::IconInfo
& left
,
69 const WebApplicationInfo::IconInfo
& right
) {
70 return left
.width
< right
.width
;
74 base::FilePath
GetShortcutDataDir(const web_app::ShortcutInfo
& shortcut_info
) {
75 return web_app::GetWebAppDataDirectory(shortcut_info
.profile_path
,
76 shortcut_info
.extension_id
,
80 void UpdateAllShortcutsForShortcutInfo(
81 const base::string16
& old_app_title
,
82 const web_app::ShortcutInfo
& shortcut_info
,
83 const extensions::FileHandlersInfo
& file_handlers_info
) {
84 BrowserThread::PostTask(
87 base::Bind(&web_app::internals::UpdatePlatformShortcuts
,
88 GetShortcutDataDir(shortcut_info
),
89 old_app_title
, shortcut_info
, file_handlers_info
));
92 void OnImageLoaded(web_app::ShortcutInfo shortcut_info
,
93 extensions::FileHandlersInfo file_handlers_info
,
94 web_app::InfoCallback callback
,
95 const gfx::ImageFamily
& image_family
) {
96 // If the image failed to load (e.g. if the resource being loaded was empty)
97 // use the standard application icon.
98 if (image_family
.empty()) {
99 gfx::Image default_icon
=
100 ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON
);
101 int size
= kDesiredSizes
[kNumDesiredSizes
- 1];
102 SkBitmap bmp
= skia::ImageOperations::Resize(
103 *default_icon
.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST
,
105 gfx::ImageSkia image_skia
= gfx::ImageSkia::CreateFrom1xBitmap(bmp
);
106 // We are on the UI thread, and this image is needed from the FILE thread,
107 // for creating shortcut icon files.
108 image_skia
.MakeThreadSafe();
109 shortcut_info
.favicon
.Add(gfx::Image(image_skia
));
111 shortcut_info
.favicon
= image_family
;
114 callback
.Run(shortcut_info
, file_handlers_info
);
117 void IgnoreFileHandlersInfo(
118 const web_app::ShortcutInfoCallback
& shortcut_info_callback
,
119 const web_app::ShortcutInfo
& shortcut_info
,
120 const extensions::FileHandlersInfo
& file_handlers_info
) {
121 shortcut_info_callback
.Run(shortcut_info
);
128 // The following string is used to build the directory name for
129 // shortcuts to chrome applications (the kind which are installed
130 // from a CRX). Application shortcuts to URLs use the {host}_{path}
131 // for the name of this directory. Hosts can't include an underscore.
132 // By starting this string with an underscore, we ensure that there
133 // are no naming conflicts.
134 static const char kCrxAppPrefix
[] = "_crx_";
136 namespace internals
{
138 base::FilePath
GetSanitizedFileName(const base::string16
& name
) {
140 base::string16 file_name
= name
;
142 std::string file_name
= base::UTF16ToUTF8(name
);
144 base::i18n::ReplaceIllegalCharactersInPath(&file_name
, '_');
145 return base::FilePath(file_name
);
148 } // namespace internals
150 ShortcutInfo::ShortcutInfo()
151 : is_platform_app(false) {
154 ShortcutInfo::~ShortcutInfo() {}
156 ShortcutLocations::ShortcutLocations()
158 applications_menu_location(APP_MENU_LOCATION_NONE
),
159 in_quick_launch_bar(false) {
162 #if defined(TOOLKIT_VIEWS)
163 void GetShortcutInfoForTab(content::WebContents
* web_contents
,
164 ShortcutInfo
* info
) {
165 DCHECK(info
); // Must provide a valid info.
167 const FaviconTabHelper
* favicon_tab_helper
=
168 FaviconTabHelper::FromWebContents(web_contents
);
169 const extensions::TabHelper
* extensions_tab_helper
=
170 extensions::TabHelper::FromWebContents(web_contents
);
171 const WebApplicationInfo
& app_info
= extensions_tab_helper
->web_app_info();
173 info
->url
= app_info
.app_url
.is_empty() ? web_contents
->GetURL() :
175 info
->title
= app_info
.title
.empty() ?
176 (web_contents
->GetTitle().empty() ? base::UTF8ToUTF16(info
->url
.spec()) :
177 web_contents
->GetTitle()) :
179 info
->description
= app_info
.description
;
180 info
->favicon
.Add(favicon_tab_helper
->GetFavicon());
183 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
184 info
->profile_path
= profile
->GetPath();
189 void UpdateShortcutForTabContents(content::WebContents
* web_contents
) {}
192 ShortcutInfo
ShortcutInfoForExtensionAndProfile(
193 const extensions::Extension
* app
, Profile
* profile
) {
194 ShortcutInfo shortcut_info
;
195 shortcut_info
.extension_id
= app
->id();
196 shortcut_info
.is_platform_app
= app
->is_platform_app();
197 shortcut_info
.url
= extensions::AppLaunchInfo::GetLaunchWebURL(app
);
198 shortcut_info
.title
= base::UTF8ToUTF16(app
->name());
199 shortcut_info
.description
= base::UTF8ToUTF16(app
->description());
200 shortcut_info
.extension_path
= app
->path();
201 shortcut_info
.profile_path
= profile
->GetPath();
202 shortcut_info
.profile_name
=
203 profile
->GetPrefs()->GetString(prefs::kProfileName
);
204 return shortcut_info
;
207 void GetInfoForApp(const extensions::Extension
* extension
,
209 const InfoCallback
& callback
) {
210 web_app::ShortcutInfo shortcut_info
=
211 web_app::ShortcutInfoForExtensionAndProfile(extension
, profile
);
212 const std::vector
<extensions::FileHandlerInfo
>* file_handlers
=
213 extensions::FileHandlers::GetFileHandlers(extension
);
214 extensions::FileHandlersInfo file_handlers_info
=
215 file_handlers
? *file_handlers
: extensions::FileHandlersInfo();
217 std::vector
<extensions::ImageLoader::ImageRepresentation
> info_list
;
218 for (size_t i
= 0; i
< kNumDesiredSizes
; ++i
) {
219 int size
= kDesiredSizes
[i
];
220 extensions::ExtensionResource resource
=
221 extensions::IconsInfo::GetIconResource(
222 extension
, size
, ExtensionIconSet::MATCH_EXACTLY
);
223 if (!resource
.empty()) {
224 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
226 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
227 gfx::Size(size
, size
),
228 ui::SCALE_FACTOR_100P
));
232 if (info_list
.empty()) {
233 size_t i
= kNumDesiredSizes
- 1;
234 int size
= kDesiredSizes
[i
];
236 // If there is no icon at the desired sizes, we will resize what we can get.
237 // Making a large icon smaller is preferred to making a small icon larger,
238 // so look for a larger icon first:
239 extensions::ExtensionResource resource
=
240 extensions::IconsInfo::GetIconResource(
241 extension
, size
, ExtensionIconSet::MATCH_BIGGER
);
242 if (resource
.empty()) {
243 resource
= extensions::IconsInfo::GetIconResource(
244 extension
, size
, ExtensionIconSet::MATCH_SMALLER
);
246 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
248 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
249 gfx::Size(size
, size
),
250 ui::SCALE_FACTOR_100P
));
253 // |info_list| may still be empty at this point, in which case
254 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
255 // image and exit immediately.
256 extensions::ImageLoader::Get(profile
)->LoadImageFamilyAsync(
259 base::Bind(&OnImageLoaded
, shortcut_info
, file_handlers_info
, callback
));
262 void GetShortcutInfoForApp(const extensions::Extension
* extension
,
264 const ShortcutInfoCallback
& callback
) {
266 extension
, profile
, base::Bind(&IgnoreFileHandlersInfo
, callback
));
269 bool ShouldCreateShortcutFor(Profile
* profile
,
270 const extensions::Extension
* extension
) {
271 return extension
->is_platform_app() &&
272 extension
->location() != extensions::Manifest::COMPONENT
&&
273 extensions::ui_util::CanDisplayInAppLauncher(extension
, profile
);
276 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
277 const std::string
& extension_id
,
279 DCHECK(!profile_path
.empty());
280 base::FilePath
app_data_dir(profile_path
.Append(chrome::kWebAppDirname
));
282 if (!extension_id
.empty()) {
283 return app_data_dir
.AppendASCII(
284 GenerateApplicationNameFromExtensionId(extension_id
));
287 std::string
host(url
.host());
288 std::string
scheme(url
.has_scheme() ? url
.scheme() : "http");
289 std::string
port(url
.has_port() ? url
.port() : "80");
290 std::string
scheme_port(scheme
+ "_" + port
);
293 base::FilePath::StringType
host_path(base::UTF8ToUTF16(host
));
294 base::FilePath::StringType
scheme_port_path(base::UTF8ToUTF16(scheme_port
));
295 #elif defined(OS_POSIX)
296 base::FilePath::StringType
host_path(host
);
297 base::FilePath::StringType
scheme_port_path(scheme_port
);
300 return app_data_dir
.Append(host_path
).Append(scheme_port_path
);
303 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
304 const extensions::Extension
& extension
) {
305 return GetWebAppDataDirectory(
308 GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension
)));
311 std::string
GenerateApplicationNameFromInfo(const ShortcutInfo
& shortcut_info
) {
312 if (!shortcut_info
.extension_id
.empty())
313 return GenerateApplicationNameFromExtensionId(shortcut_info
.extension_id
);
315 return GenerateApplicationNameFromURL(shortcut_info
.url
);
318 std::string
GenerateApplicationNameFromURL(const GURL
& url
) {
320 t
.append(url
.host());
322 t
.append(url
.path());
326 std::string
GenerateApplicationNameFromExtensionId(const std::string
& id
) {
327 std::string
t(kCrxAppPrefix
);
332 std::string
GetExtensionIdFromApplicationName(const std::string
& app_name
) {
333 std::string
prefix(kCrxAppPrefix
);
334 if (app_name
.substr(0, prefix
.length()) != prefix
)
335 return std::string();
336 return app_name
.substr(prefix
.length());
339 void CreateShortcutsWithInfo(
340 ShortcutCreationReason reason
,
341 const ShortcutLocations
& locations
,
342 const ShortcutInfo
& shortcut_info
,
343 const extensions::FileHandlersInfo
& file_handlers_info
) {
344 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
346 BrowserThread::PostTask(
349 base::Bind(base::IgnoreResult(&internals::CreatePlatformShortcuts
),
350 GetShortcutDataDir(shortcut_info
),
357 void CreateShortcuts(ShortcutCreationReason reason
,
358 const ShortcutLocations
& locations
,
360 const extensions::Extension
* app
) {
361 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
363 if (!ShouldCreateShortcutFor(profile
, app
))
367 app
, profile
, base::Bind(&CreateShortcutsWithInfo
, reason
, locations
));
370 void DeleteAllShortcuts(Profile
* profile
, const extensions::Extension
* app
) {
371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
373 ShortcutInfo shortcut_info
=
374 ShortcutInfoForExtensionAndProfile(app
, profile
);
375 BrowserThread::PostTask(
378 base::Bind(&web_app::internals::DeletePlatformShortcuts
,
379 GetShortcutDataDir(shortcut_info
), shortcut_info
));
382 void UpdateAllShortcuts(const base::string16
& old_app_title
,
384 const extensions::Extension
* app
) {
385 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
389 base::Bind(&UpdateAllShortcutsForShortcutInfo
, old_app_title
));
392 bool IsValidUrl(const GURL
& url
) {
393 static const char* const kValidUrlSchemes
[] = {
395 url::kFileSystemScheme
,
399 extensions::kExtensionScheme
,
402 for (size_t i
= 0; i
< arraysize(kValidUrlSchemes
); ++i
) {
403 if (url
.SchemeIs(kValidUrlSchemes
[i
]))
410 #if defined(TOOLKIT_VIEWS)
411 void GetIconsInfo(const WebApplicationInfo
& app_info
,
412 IconInfoList
* icons
) {
416 for (size_t i
= 0; i
< app_info
.icons
.size(); ++i
) {
417 // We only take square shaped icons (i.e. width == height).
418 if (app_info
.icons
[i
].width
== app_info
.icons
[i
].height
) {
419 icons
->push_back(app_info
.icons
[i
]);
423 std::sort(icons
->begin(), icons
->end(), &IconPrecedes
);
427 #if defined(OS_LINUX)
428 std::string
GetWMClassFromAppName(std::string app_name
) {
429 base::i18n::ReplaceIllegalCharactersInPath(&app_name
, '_');
430 base::TrimString(app_name
, "_", &app_name
);
435 } // namespace web_app