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/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/image_loader.h"
16 #include "chrome/browser/extensions/tab_helper.h"
17 #include "chrome/browser/favicon/favicon_tab_helper.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/common/chrome_constants.h"
20 #include "chrome/common/chrome_version_info.h"
21 #include "chrome/common/extensions/api/file_handlers/file_handlers_parser.h"
22 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/common/url_constants.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "extensions/common/constants.h"
27 #include "extensions/common/extension.h"
28 #include "extensions/common/manifest_handlers/icons_handler.h"
29 #include "grit/theme_resources.h"
30 #include "skia/ext/image_operations.h"
31 #include "third_party/skia/include/core/SkBitmap.h"
32 #include "ui/base/resource/resource_bundle.h"
33 #include "ui/gfx/image/image.h"
34 #include "ui/gfx/image/image_family.h"
35 #include "ui/gfx/image/image_skia.h"
38 #include "ui/gfx/icon_util.h"
41 using content::BrowserThread
;
45 typedef base::Callback
<void(const web_app::ShortcutInfo
&,
46 const extensions::FileHandlersInfo
&)> InfoCallback
;
48 #if defined(OS_MACOSX)
49 const int kDesiredSizes
[] = {16, 32, 128, 256, 512};
50 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
51 #elif defined(OS_LINUX)
52 // Linux supports icons of any size. FreeDesktop Icon Theme Specification states
53 // that "Minimally you should install a 48x48 icon in the hicolor theme."
54 const int kDesiredSizes
[] = {16, 32, 48, 128, 256, 512};
55 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
57 const int* kDesiredSizes
= IconUtil::kIconDimensions
;
58 const size_t kNumDesiredSizes
= IconUtil::kNumIconDimensions
;
60 const int kDesiredSizes
[] = {32};
61 const size_t kNumDesiredSizes
= arraysize(kDesiredSizes
);
64 #if defined(TOOLKIT_VIEWS)
65 // Predicator for sorting images from largest to smallest.
66 bool IconPrecedes(const WebApplicationInfo::IconInfo
& left
,
67 const WebApplicationInfo::IconInfo
& right
) {
68 return left
.width
< right
.width
;
72 bool CreateShortcutsWithInfoOnFileThread(
73 web_app::ShortcutCreationReason reason
,
74 const web_app::ShortcutLocations
& locations
,
75 const web_app::ShortcutInfo
& shortcut_info
,
76 const extensions::FileHandlersInfo
& file_handlers_info
) {
77 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
79 base::FilePath shortcut_data_dir
=
80 web_app::GetWebAppDataDirectory(shortcut_info
.profile_path
,
81 shortcut_info
.extension_id
,
83 return web_app::internals::CreatePlatformShortcuts(
84 shortcut_data_dir
, shortcut_info
, file_handlers_info
, locations
, reason
);
87 void DeleteShortcutsOnFileThread(
88 const web_app::ShortcutInfo
& shortcut_info
) {
89 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
91 base::FilePath shortcut_data_dir
= web_app::GetWebAppDataDirectory(
92 shortcut_info
.profile_path
, shortcut_info
.extension_id
, GURL());
93 return web_app::internals::DeletePlatformShortcuts(
94 shortcut_data_dir
, shortcut_info
);
97 void UpdateShortcutsOnFileThread(
98 const base::string16
& old_app_title
,
99 const web_app::ShortcutInfo
& shortcut_info
,
100 const extensions::FileHandlersInfo
& file_handlers_info
) {
101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
103 base::FilePath shortcut_data_dir
= web_app::GetWebAppDataDirectory(
104 shortcut_info
.profile_path
, shortcut_info
.extension_id
, GURL());
105 return web_app::internals::UpdatePlatformShortcuts(
106 shortcut_data_dir
, old_app_title
, shortcut_info
, file_handlers_info
);
109 void CreateShortcutsWithInfo(
110 web_app::ShortcutCreationReason reason
,
111 const web_app::ShortcutLocations
& locations
,
112 const web_app::ShortcutInfo
& shortcut_info
,
113 const extensions::FileHandlersInfo
& file_handlers_info
) {
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
116 BrowserThread::PostTask(
120 base::IgnoreResult(&CreateShortcutsWithInfoOnFileThread
),
121 reason
, locations
, shortcut_info
, file_handlers_info
));
124 void UpdateAllShortcutsForShortcutInfo(
125 const base::string16
& old_app_title
,
126 const web_app::ShortcutInfo
& shortcut_info
,
127 const extensions::FileHandlersInfo
& file_handlers_info
) {
128 BrowserThread::PostTask(
131 base::Bind(&UpdateShortcutsOnFileThread
,
132 old_app_title
, shortcut_info
, file_handlers_info
));
135 void OnImageLoaded(web_app::ShortcutInfo shortcut_info
,
136 extensions::FileHandlersInfo file_handlers_info
,
137 InfoCallback callback
,
138 const gfx::ImageFamily
& image_family
) {
139 // If the image failed to load (e.g. if the resource being loaded was empty)
140 // use the standard application icon.
141 if (image_family
.empty()) {
142 gfx::Image default_icon
=
143 ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON
);
144 int size
= kDesiredSizes
[kNumDesiredSizes
- 1];
145 SkBitmap bmp
= skia::ImageOperations::Resize(
146 *default_icon
.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST
,
148 gfx::ImageSkia image_skia
= gfx::ImageSkia::CreateFrom1xBitmap(bmp
);
149 // We are on the UI thread, and this image is needed from the FILE thread,
150 // for creating shortcut icon files.
151 image_skia
.MakeThreadSafe();
152 shortcut_info
.favicon
.Add(gfx::Image(image_skia
));
154 shortcut_info
.favicon
= image_family
;
157 callback
.Run(shortcut_info
, file_handlers_info
);
160 void GetInfoForApp(const extensions::Extension
* extension
,
162 const InfoCallback
& callback
) {
163 web_app::ShortcutInfo shortcut_info
=
164 web_app::ShortcutInfoForExtensionAndProfile(extension
, profile
);
165 extensions::FileHandlersInfo file_handlers_info
;
166 const std::vector
<extensions::FileHandlerInfo
>* file_handlers
=
167 extensions::FileHandlers::GetFileHandlers(extension
);
169 file_handlers_info
.handlers
= *file_handlers
;
171 std::vector
<extensions::ImageLoader::ImageRepresentation
> info_list
;
172 for (size_t i
= 0; i
< kNumDesiredSizes
; ++i
) {
173 int size
= kDesiredSizes
[i
];
174 extensions::ExtensionResource resource
=
175 extensions::IconsInfo::GetIconResource(
176 extension
, size
, ExtensionIconSet::MATCH_EXACTLY
);
177 if (!resource
.empty()) {
178 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
180 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
181 gfx::Size(size
, size
),
182 ui::SCALE_FACTOR_100P
));
186 if (info_list
.empty()) {
187 size_t i
= kNumDesiredSizes
- 1;
188 int size
= kDesiredSizes
[i
];
190 // If there is no icon at the desired sizes, we will resize what we can get.
191 // Making a large icon smaller is preferred to making a small icon larger,
192 // so look for a larger icon first:
193 extensions::ExtensionResource resource
=
194 extensions::IconsInfo::GetIconResource(
195 extension
, size
, ExtensionIconSet::MATCH_BIGGER
);
196 if (resource
.empty()) {
197 resource
= extensions::IconsInfo::GetIconResource(
198 extension
, size
, ExtensionIconSet::MATCH_SMALLER
);
200 info_list
.push_back(extensions::ImageLoader::ImageRepresentation(
202 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE
,
203 gfx::Size(size
, size
),
204 ui::SCALE_FACTOR_100P
));
207 // |info_list| may still be empty at this point, in which case
208 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
209 // image and exit immediately.
210 extensions::ImageLoader::Get(profile
)->LoadImageFamilyAsync(
213 base::Bind(&OnImageLoaded
, shortcut_info
, file_handlers_info
, callback
));
216 void IgnoreFileHandlersInfo(
217 const web_app::ShortcutInfoCallback
& shortcut_info_callback
,
218 const web_app::ShortcutInfo
& shortcut_info
,
219 const extensions::FileHandlersInfo
& file_handlers_info
) {
220 shortcut_info_callback
.Run(shortcut_info
);
227 // The following string is used to build the directory name for
228 // shortcuts to chrome applications (the kind which are installed
229 // from a CRX). Application shortcuts to URLs use the {host}_{path}
230 // for the name of this directory. Hosts can't include an underscore.
231 // By starting this string with an underscore, we ensure that there
232 // are no naming conflicts.
233 static const char* kCrxAppPrefix
= "_crx_";
235 namespace internals
{
237 base::FilePath
GetSanitizedFileName(const base::string16
& name
) {
239 base::string16 file_name
= name
;
241 std::string file_name
= base::UTF16ToUTF8(name
);
243 file_util::ReplaceIllegalCharactersInPath(&file_name
, '_');
244 return base::FilePath(file_name
);
247 bool CreateShortcutsOnFileThread(
248 ShortcutCreationReason reason
,
249 const web_app::ShortcutLocations
& locations
,
250 const web_app::ShortcutInfo
& shortcut_info
) {
251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
253 return CreateShortcutsWithInfoOnFileThread(
254 reason
, locations
, shortcut_info
, extensions::FileHandlersInfo());
257 } // namespace internals
259 web_app::ShortcutInfo::ShortcutInfo()
260 : is_platform_app(false) {
263 web_app::ShortcutInfo::~ShortcutInfo() {}
265 web_app::ShortcutLocations::ShortcutLocations()
267 applications_menu_location(APP_MENU_LOCATION_NONE
),
268 in_quick_launch_bar(false)
269 #if defined(OS_POSIX)
275 void GetShortcutInfoForTab(content::WebContents
* web_contents
,
276 web_app::ShortcutInfo
* info
) {
277 DCHECK(info
); // Must provide a valid info.
279 const FaviconTabHelper
* favicon_tab_helper
=
280 FaviconTabHelper::FromWebContents(web_contents
);
281 const extensions::TabHelper
* extensions_tab_helper
=
282 extensions::TabHelper::FromWebContents(web_contents
);
283 const WebApplicationInfo
& app_info
= extensions_tab_helper
->web_app_info();
285 info
->url
= app_info
.app_url
.is_empty() ? web_contents
->GetURL() :
287 info
->title
= app_info
.title
.empty() ?
288 (web_contents
->GetTitle().empty() ? base::UTF8ToUTF16(info
->url
.spec()) :
289 web_contents
->GetTitle()) :
291 info
->description
= app_info
.description
;
292 info
->favicon
.Add(favicon_tab_helper
->GetFavicon());
295 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
296 info
->profile_path
= profile
->GetPath();
300 void UpdateShortcutForTabContents(content::WebContents
* web_contents
) {}
303 web_app::ShortcutInfo
ShortcutInfoForExtensionAndProfile(
304 const extensions::Extension
* app
, Profile
* profile
) {
305 web_app::ShortcutInfo shortcut_info
;
306 shortcut_info
.extension_id
= app
->id();
307 shortcut_info
.is_platform_app
= app
->is_platform_app();
308 shortcut_info
.url
= extensions::AppLaunchInfo::GetLaunchWebURL(app
);
309 shortcut_info
.title
= base::UTF8ToUTF16(app
->name());
310 shortcut_info
.description
= base::UTF8ToUTF16(app
->description());
311 shortcut_info
.extension_path
= app
->path();
312 shortcut_info
.profile_path
= profile
->GetPath();
313 shortcut_info
.profile_name
=
314 profile
->GetPrefs()->GetString(prefs::kProfileName
);
315 return shortcut_info
;
318 void UpdateShortcutInfoAndIconForApp(
319 const extensions::Extension
* extension
,
321 const web_app::ShortcutInfoCallback
& callback
) {
322 GetInfoForApp(extension
,
324 base::Bind(&IgnoreFileHandlersInfo
, callback
));
327 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
328 const std::string
& extension_id
,
330 DCHECK(!profile_path
.empty());
331 base::FilePath
app_data_dir(profile_path
.Append(chrome::kWebAppDirname
));
333 if (!extension_id
.empty()) {
334 return app_data_dir
.AppendASCII(
335 GenerateApplicationNameFromExtensionId(extension_id
));
338 std::string
host(url
.host());
339 std::string
scheme(url
.has_scheme() ? url
.scheme() : "http");
340 std::string
port(url
.has_port() ? url
.port() : "80");
341 std::string
scheme_port(scheme
+ "_" + port
);
344 base::FilePath::StringType
host_path(base::UTF8ToUTF16(host
));
345 base::FilePath::StringType
scheme_port_path(base::UTF8ToUTF16(scheme_port
));
346 #elif defined(OS_POSIX)
347 base::FilePath::StringType
host_path(host
);
348 base::FilePath::StringType
scheme_port_path(scheme_port
);
351 return app_data_dir
.Append(host_path
).Append(scheme_port_path
);
354 base::FilePath
GetWebAppDataDirectory(const base::FilePath
& profile_path
,
355 const extensions::Extension
& extension
) {
356 return GetWebAppDataDirectory(
359 GURL(extensions::AppLaunchInfo::GetLaunchWebURL(&extension
)));
362 std::string
GenerateApplicationNameFromInfo(
363 const web_app::ShortcutInfo
& shortcut_info
) {
364 if (!shortcut_info
.extension_id
.empty()) {
365 return web_app::GenerateApplicationNameFromExtensionId(
366 shortcut_info
.extension_id
);
368 return web_app::GenerateApplicationNameFromURL(
373 std::string
GenerateApplicationNameFromURL(const GURL
& url
) {
375 t
.append(url
.host());
377 t
.append(url
.path());
381 std::string
GenerateApplicationNameFromExtensionId(const std::string
& id
) {
382 std::string
t(web_app::kCrxAppPrefix
);
387 std::string
GetExtensionIdFromApplicationName(const std::string
& app_name
) {
388 std::string
prefix(kCrxAppPrefix
);
389 if (app_name
.substr(0, prefix
.length()) != prefix
)
390 return std::string();
391 return app_name
.substr(prefix
.length());
394 void CreateShortcutsForShortcutInfo(
395 web_app::ShortcutCreationReason reason
,
396 const web_app::ShortcutLocations
& locations
,
397 const web_app::ShortcutInfo
& shortcut_info
) {
398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
400 BrowserThread::PostTask(
404 base::IgnoreResult(&web_app::internals::CreateShortcutsOnFileThread
),
405 reason
, locations
, shortcut_info
));
408 void CreateShortcuts(
409 ShortcutCreationReason reason
,
410 const web_app::ShortcutLocations
& locations
,
412 const extensions::Extension
* app
) {
413 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
417 base::Bind(&CreateShortcutsWithInfo
, reason
, locations
));
420 void DeleteAllShortcuts(Profile
* profile
, const extensions::Extension
* app
) {
421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
423 BrowserThread::PostTask(
426 base::Bind(&DeleteShortcutsOnFileThread
,
427 web_app::ShortcutInfoForExtensionAndProfile(app
, profile
)));
430 void UpdateAllShortcuts(const base::string16
& old_app_title
,
432 const extensions::Extension
* app
) {
433 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
437 base::Bind(&UpdateAllShortcutsForShortcutInfo
, old_app_title
));
440 bool IsValidUrl(const GURL
& url
) {
441 static const char* const kValidUrlSchemes
[] = {
442 content::kFileScheme
,
443 content::kFileSystemScheme
,
445 content::kHttpScheme
,
446 content::kHttpsScheme
,
447 extensions::kExtensionScheme
,
450 for (size_t i
= 0; i
< arraysize(kValidUrlSchemes
); ++i
) {
451 if (url
.SchemeIs(kValidUrlSchemes
[i
]))
458 #if defined(TOOLKIT_VIEWS)
459 void GetIconsInfo(const WebApplicationInfo
& app_info
,
460 IconInfoList
* icons
) {
464 for (size_t i
= 0; i
< app_info
.icons
.size(); ++i
) {
465 // We only take square shaped icons (i.e. width == height).
466 if (app_info
.icons
[i
].width
== app_info
.icons
[i
].height
) {
467 icons
->push_back(app_info
.icons
[i
]);
471 std::sort(icons
->begin(), icons
->end(), &IconPrecedes
);
475 #if defined(OS_LINUX)
476 std::string
GetWMClassFromAppName(std::string app_name
) {
477 file_util::ReplaceIllegalCharactersInPath(&app_name
, '_');
478 base::TrimString(app_name
, "_", &app_name
);
483 } // namespace web_app