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/ui/web_applications/web_app_ui.h"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/path_service.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "chrome/browser/chrome_notification_types.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/history/select_favicon_frames.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/web_applications/web_app.h"
21 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
22 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
23 #include "chrome/common/pref_names.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/browser/notification_details.h"
26 #include "content/public/browser/notification_registrar.h"
27 #include "content/public/browser/notification_source.h"
28 #include "content/public/browser/web_contents.h"
29 #include "extensions/common/extension.h"
30 #include "grit/theme_resources.h"
31 #include "skia/ext/image_operations.h"
32 #include "third_party/skia/include/core/SkBitmap.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/gfx/image/image.h"
35 #include "ui/gfx/image/image_family.h"
36 #include "ui/gfx/image/image_skia.h"
39 #if defined(OS_POSIX) && !defined(OS_MACOSX)
40 #include "base/environment.h"
44 #include "base/win/shortcut.h"
45 #include "base/win/windows_version.h"
46 #include "chrome/browser/web_applications/web_app_win.h"
47 #include "ui/gfx/icon_util.h"
50 using content::BrowserThread;
51 using content::NavigationController;
52 using content::WebContents;
56 #if defined(OS_MACOSX)
57 const int kDesiredSizes[] = {16, 32, 128, 256, 512};
58 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
59 #elif defined(OS_LINUX)
60 // Linux supports icons of any size. FreeDesktop Icon Theme Specification states
61 // that "Minimally you should install a 48x48 icon in the hicolor theme."
62 const int kDesiredSizes[] = {16, 32, 48, 128, 256, 512};
63 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
65 const int* kDesiredSizes = IconUtil::kIconDimensions;
66 const size_t kNumDesiredSizes = IconUtil::kNumIconDimensions;
68 const int kDesiredSizes[] = {32};
69 const size_t kNumDesiredSizes = arraysize(kDesiredSizes);
73 // UpdateShortcutWorker holds all context data needed for update shortcut.
74 // It schedules a pre-update check to find all shortcuts that needs to be
75 // updated. If there are such shortcuts, it schedules icon download and
76 // update them when icons are downloaded. It observes TAB_CLOSING notification
77 // and cancels all the work when the underlying tab is closing.
78 class UpdateShortcutWorker : public content::NotificationObserver {
80 explicit UpdateShortcutWorker(WebContents* web_contents);
85 // Overridden from content::NotificationObserver:
86 virtual void Observe(int type,
87 const content::NotificationSource& source,
88 const content::NotificationDetails& details);
90 // Downloads icon via the FaviconTabHelper.
93 // Favicon download callback.
94 void DidDownloadFavicon(
98 const GURL& image_url,
99 const std::vector<SkBitmap>& bitmaps,
100 const std::vector<gfx::Size>& original_bitmap_sizes);
102 // Checks if shortcuts exists on desktop, start menu and quick launch.
103 void CheckExistingShortcuts();
105 // Update shortcut files and icons.
106 void UpdateShortcuts();
107 void UpdateShortcutsOnFileThread();
109 // Callback after shortcuts are updated.
110 void OnShortcutsUpdated(bool);
112 // Deletes the worker on UI thread where it gets created.
114 void DeleteMeOnUIThread();
116 content::NotificationRegistrar registrar_;
118 // Underlying WebContents whose shortcuts will be updated.
119 WebContents* web_contents_;
121 // Icons info from web_contents_'s web app data.
122 web_app::IconInfoList unprocessed_icons_;
124 // Cached shortcut data from the web_contents_.
125 ShellIntegration::ShortcutInfo shortcut_info_;
127 // Our copy of profile path.
128 base::FilePath profile_path_;
130 // File name of shortcut/ico file based on app title.
131 base::FilePath file_name_;
133 // Existing shortcuts.
134 std::vector<base::FilePath> shortcut_files_;
136 DISALLOW_COPY_AND_ASSIGN(UpdateShortcutWorker);
139 UpdateShortcutWorker::UpdateShortcutWorker(WebContents* web_contents)
140 : web_contents_(web_contents),
141 profile_path_(Profile::FromBrowserContext(
142 web_contents->GetBrowserContext())->GetPath()) {
143 extensions::TabHelper* extensions_tab_helper =
144 extensions::TabHelper::FromWebContents(web_contents);
145 web_app::GetShortcutInfoForTab(web_contents_, &shortcut_info_);
146 web_app::GetIconsInfo(extensions_tab_helper->web_app_info(),
147 &unprocessed_icons_);
148 file_name_ = web_app::internals::GetSanitizedFileName(shortcut_info_.title);
152 chrome::NOTIFICATION_TAB_CLOSING,
153 content::Source<NavigationController>(&web_contents->GetController()));
156 void UpdateShortcutWorker::Run() {
157 // Starting by downloading app icon.
161 void UpdateShortcutWorker::Observe(
163 const content::NotificationSource& source,
164 const content::NotificationDetails& details) {
165 if (type == chrome::NOTIFICATION_TAB_CLOSING &&
166 content::Source<NavigationController>(source).ptr() ==
167 &web_contents_->GetController()) {
168 // Underlying tab is closing.
169 web_contents_ = NULL;
173 void UpdateShortcutWorker::DownloadIcon() {
174 // FetchIcon must run on UI thread because it relies on WebContents
175 // to download the icon.
176 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
178 if (web_contents_ == NULL) {
179 DeleteMe(); // We are done if underlying WebContents is gone.
183 if (unprocessed_icons_.empty()) {
184 // No app icon. Just use the favicon from WebContents.
189 int preferred_size = std::max(unprocessed_icons_.back().width,
190 unprocessed_icons_.back().height);
191 web_contents_->DownloadImage(
192 unprocessed_icons_.back().url,
194 0, // no maximum size
195 base::Bind(&UpdateShortcutWorker::DidDownloadFavicon,
196 base::Unretained(this),
198 unprocessed_icons_.pop_back();
201 void UpdateShortcutWorker::DidDownloadFavicon(
204 int http_status_code,
205 const GURL& image_url,
206 const std::vector<SkBitmap>& bitmaps,
207 const std::vector<gfx::Size>& original_sizes) {
208 std::vector<ui::ScaleFactor> scale_factors;
209 scale_factors.push_back(ui::SCALE_FACTOR_100P);
211 std::vector<size_t> closest_indices;
212 SelectFaviconFrameIndices(original_sizes,
219 if (!bitmaps.empty()) {
220 size_t closest_index = closest_indices[0];
221 bitmap = bitmaps[closest_index];
224 if (!bitmap.isNull()) {
225 // Update icon with download image and update shortcut.
226 shortcut_info_.favicon.Add(gfx::Image::CreateFrom1xBitmap(bitmap));
227 extensions::TabHelper* extensions_tab_helper =
228 extensions::TabHelper::FromWebContents(web_contents_);
229 extensions_tab_helper->SetAppIcon(bitmap);
232 // Try the next icon otherwise.
237 void UpdateShortcutWorker::CheckExistingShortcuts() {
238 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
240 // Locations to check to shortcut_paths.
243 const wchar_t* sub_dir;
246 base::DIR_USER_DESKTOP,
249 base::DIR_START_MENU,
252 // For Win7, create_in_quick_launch_bar means pinning to taskbar.
254 (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
255 L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar" :
256 L"Microsoft\\Internet Explorer\\Quick Launch"
260 for (int i = 0; i < arraysize(locations); ++i) {
262 if (!PathService::Get(locations[i].location_id, &path)) {
267 if (locations[i].sub_dir != NULL)
268 path = path.Append(locations[i].sub_dir);
270 base::FilePath shortcut_file = path.Append(file_name_).
271 ReplaceExtension(FILE_PATH_LITERAL(".lnk"));
272 if (base::PathExists(shortcut_file)) {
273 shortcut_files_.push_back(shortcut_file);
278 void UpdateShortcutWorker::UpdateShortcuts() {
279 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
280 base::Bind(&UpdateShortcutWorker::UpdateShortcutsOnFileThread,
281 base::Unretained(this)));
284 void UpdateShortcutWorker::UpdateShortcutsOnFileThread() {
285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
287 base::FilePath web_app_path = web_app::GetWebAppDataDirectory(
288 profile_path_, shortcut_info_.extension_id, shortcut_info_.url);
290 // Ensure web_app_path exists. web_app_path could be missing for a legacy
291 // shortcut created by Gears.
292 if (!base::PathExists(web_app_path) &&
293 !base::CreateDirectory(web_app_path)) {
298 base::FilePath icon_file = web_app_path.Append(file_name_).ReplaceExtension(
299 FILE_PATH_LITERAL(".ico"));
300 web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info_.favicon);
302 // Update existing shortcuts' description, icon and app id.
303 CheckExistingShortcuts();
304 if (!shortcut_files_.empty()) {
305 // Generates app id from web app url and profile path.
306 base::string16 app_id = ShellIntegration::GetAppModelIdForProfile(
308 web_app::GenerateApplicationNameFromURL(shortcut_info_.url)),
311 // Sanitize description
312 if (shortcut_info_.description.length() >= MAX_PATH)
313 shortcut_info_.description.resize(MAX_PATH - 1);
315 for (size_t i = 0; i < shortcut_files_.size(); ++i) {
316 base::win::ShortcutProperties shortcut_properties;
317 shortcut_properties.set_target(shortcut_files_[i]);
318 shortcut_properties.set_description(shortcut_info_.description);
319 shortcut_properties.set_icon(icon_file, 0);
320 shortcut_properties.set_app_id(app_id);
321 base::win::CreateOrUpdateShortcutLink(
322 shortcut_files_[i], shortcut_properties,
323 base::win::SHORTCUT_UPDATE_EXISTING);
327 OnShortcutsUpdated(true);
330 void UpdateShortcutWorker::OnShortcutsUpdated(bool) {
331 DeleteMe(); // We are done.
334 void UpdateShortcutWorker::DeleteMe() {
335 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
336 DeleteMeOnUIThread();
338 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
339 base::Bind(&UpdateShortcutWorker::DeleteMeOnUIThread,
340 base::Unretained(this)));
344 void UpdateShortcutWorker::DeleteMeOnUIThread() {
345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
348 #endif // defined(OS_WIN)
350 void OnImageLoaded(ShellIntegration::ShortcutInfo shortcut_info,
351 web_app::ShortcutInfoCallback callback,
352 const gfx::ImageFamily& image_family) {
353 // If the image failed to load (e.g. if the resource being loaded was empty)
354 // use the standard application icon.
355 if (image_family.empty()) {
356 gfx::Image default_icon =
357 ResourceBundle::GetSharedInstance().GetImageNamed(IDR_APP_DEFAULT_ICON);
358 int size = kDesiredSizes[kNumDesiredSizes - 1];
359 SkBitmap bmp = skia::ImageOperations::Resize(
360 *default_icon.ToSkBitmap(), skia::ImageOperations::RESIZE_BEST,
362 gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bmp);
363 // We are on the UI thread, and this image is needed from the FILE thread,
364 // for creating shortcut icon files.
365 image_skia.MakeThreadSafe();
366 shortcut_info.favicon.Add(gfx::Image(image_skia));
368 shortcut_info.favicon = image_family;
371 callback.Run(shortcut_info);
378 ShellIntegration::ShortcutInfo ShortcutInfoForExtensionAndProfile(
379 const extensions::Extension* extension, Profile* profile) {
380 ShellIntegration::ShortcutInfo shortcut_info;
381 web_app::UpdateShortcutInfoForApp(*extension, profile, &shortcut_info);
382 return shortcut_info;
385 void GetShortcutInfoForTab(WebContents* web_contents,
386 ShellIntegration::ShortcutInfo* info) {
387 DCHECK(info); // Must provide a valid info.
389 const FaviconTabHelper* favicon_tab_helper =
390 FaviconTabHelper::FromWebContents(web_contents);
391 const extensions::TabHelper* extensions_tab_helper =
392 extensions::TabHelper::FromWebContents(web_contents);
393 const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info();
395 info->url = app_info.app_url.is_empty() ? web_contents->GetURL() :
397 info->title = app_info.title.empty() ?
398 (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) :
399 web_contents->GetTitle()) :
401 info->description = app_info.description;
402 info->favicon.Add(favicon_tab_helper->GetFavicon());
405 Profile::FromBrowserContext(web_contents->GetBrowserContext());
406 info->profile_path = profile->GetPath();
409 void UpdateShortcutForTabContents(WebContents* web_contents) {
411 // UpdateShortcutWorker will delete itself when it's done.
412 UpdateShortcutWorker* worker = new UpdateShortcutWorker(web_contents);
414 #endif // defined(OS_WIN)
417 void UpdateShortcutInfoForApp(const extensions::Extension& app,
419 ShellIntegration::ShortcutInfo* shortcut_info) {
420 shortcut_info->extension_id = app.id();
421 shortcut_info->is_platform_app = app.is_platform_app();
422 shortcut_info->url = extensions::AppLaunchInfo::GetLaunchWebURL(&app);
423 shortcut_info->title = base::UTF8ToUTF16(app.name());
424 shortcut_info->description = base::UTF8ToUTF16(app.description());
425 shortcut_info->extension_path = app.path();
426 shortcut_info->profile_path = profile->GetPath();
427 shortcut_info->profile_name =
428 profile->GetPrefs()->GetString(prefs::kProfileName);
431 void UpdateShortcutInfoAndIconForApp(
432 const extensions::Extension* extension,
434 const web_app::ShortcutInfoCallback& callback) {
435 ShellIntegration::ShortcutInfo shortcut_info =
436 ShortcutInfoForExtensionAndProfile(extension, profile);
438 std::vector<extensions::ImageLoader::ImageRepresentation> info_list;
439 for (size_t i = 0; i < kNumDesiredSizes; ++i) {
440 int size = kDesiredSizes[i];
441 extensions::ExtensionResource resource =
442 extensions::IconsInfo::GetIconResource(
443 extension, size, ExtensionIconSet::MATCH_EXACTLY);
444 if (!resource.empty()) {
445 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
447 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
448 gfx::Size(size, size),
449 ui::SCALE_FACTOR_100P));
453 if (info_list.empty()) {
454 size_t i = kNumDesiredSizes - 1;
455 int size = kDesiredSizes[i];
457 // If there is no icon at the desired sizes, we will resize what we can get.
458 // Making a large icon smaller is preferred to making a small icon larger,
459 // so look for a larger icon first:
460 extensions::ExtensionResource resource =
461 extensions::IconsInfo::GetIconResource(
462 extension, size, ExtensionIconSet::MATCH_BIGGER);
463 if (resource.empty()) {
464 resource = extensions::IconsInfo::GetIconResource(
465 extension, size, ExtensionIconSet::MATCH_SMALLER);
467 info_list.push_back(extensions::ImageLoader::ImageRepresentation(
469 extensions::ImageLoader::ImageRepresentation::ALWAYS_RESIZE,
470 gfx::Size(size, size),
471 ui::SCALE_FACTOR_100P));
474 // |info_list| may still be empty at this point, in which case
475 // LoadImageFamilyAsync will call the OnImageLoaded callback with an empty
476 // image and exit immediately.
477 extensions::ImageLoader::Get(profile)->LoadImageFamilyAsync(
480 base::Bind(&OnImageLoaded, shortcut_info, callback));
483 } // namespace web_app