1 // Copyright 2014 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. 4 5 #include "chrome/browser/web_applications/update_shortcut_worker_win.h" 6 7 #include <algorithm> 8 9 #include "base/bind.h" 10 #include "base/bind_helpers.h" 11 #include "base/files/file_util.h" 12 #include "base/path_service.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "base/win/shortcut.h" 15 #include "base/win/windows_version.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/extensions/tab_helper.h" 18 #include "chrome/browser/favicon/favicon_tab_helper.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/browser/web_applications/web_app.h" 21 #include "chrome/browser/web_applications/web_app_win.h" 22 #include "components/favicon_base/select_favicon_frames.h" 23 #include "content/public/browser/browser_thread.h" 24 #include "content/public/browser/notification_details.h" 25 #include "content/public/browser/notification_source.h" 26 #include "content/public/browser/web_contents.h" 27 #include "ui/gfx/icon_util.h" 28 #include "url/gurl.h" 29 30 using content::BrowserThread; 31 using content::NavigationController; 32 using content::WebContents; 33 34 namespace web_app { 35 36 UpdateShortcutWorker::UpdateShortcutWorker(WebContents* web_contents) 37 : web_contents_(web_contents), 38 profile_path_(Profile::FromBrowserContext( 39 web_contents->GetBrowserContext())->GetPath()) { 40 extensions::TabHelper* extensions_tab_helper = 41 extensions::TabHelper::FromWebContents(web_contents); 42 web_app::GetShortcutInfoForTab(web_contents_, &shortcut_info_); 43 web_app::GetIconsInfo(extensions_tab_helper->web_app_info(), 44 &unprocessed_icons_); 45 file_name_ = web_app::internals::GetSanitizedFileName(shortcut_info_.title); 46 47 registrar_.Add( 48 this, 49 chrome::NOTIFICATION_TAB_CLOSING, 50 content::Source<NavigationController>(&web_contents->GetController())); 51 } 52 53 void UpdateShortcutWorker::Run() { 54 // Starting by downloading app icon. 55 DownloadIcon(); 56 } 57 58 void UpdateShortcutWorker::Observe( 59 int type, 60 const content::NotificationSource& source, 61 const content::NotificationDetails& details) { 62 if (type == chrome::NOTIFICATION_TAB_CLOSING && 63 content::Source<NavigationController>(source).ptr() == 64 &web_contents_->GetController()) { 65 // Underlying tab is closing. 66 web_contents_ = NULL; 67 } 68 } 69 70 void UpdateShortcutWorker::DownloadIcon() { 71 // FetchIcon must run on UI thread because it relies on WebContents 72 // to download the icon. 73 DCHECK_CURRENTLY_ON(BrowserThread::UI); 74 75 if (web_contents_ == NULL) { 76 DeleteMe(); // We are done if underlying WebContents is gone. 77 return; 78 } 79 80 if (unprocessed_icons_.empty()) { 81 // No app icon. Just use the favicon from WebContents. 82 UpdateShortcuts(); 83 return; 84 } 85 86 int preferred_size = std::max(unprocessed_icons_.back().width, 87 unprocessed_icons_.back().height); 88 web_contents_->DownloadImage( 89 unprocessed_icons_.back().url, 90 true, // favicon 91 0, // no maximum size 92 base::Bind(&UpdateShortcutWorker::DidDownloadFavicon, 93 base::Unretained(this), 94 preferred_size)); 95 unprocessed_icons_.pop_back(); 96 } 97 98 void UpdateShortcutWorker::DidDownloadFavicon( 99 int requested_size, 100 int id, 101 int http_status_code, 102 const GURL& image_url, 103 const std::vector<SkBitmap>& bitmaps, 104 const std::vector<gfx::Size>& original_sizes) { 105 std::vector<int> requested_sizes_in_pixel; 106 requested_sizes_in_pixel.push_back(requested_size); 107 108 std::vector<size_t> closest_indices; 109 SelectFaviconFrameIndices( 110 original_sizes, requested_sizes_in_pixel, &closest_indices, NULL); 111 112 SkBitmap bitmap; 113 if (!bitmaps.empty()) { 114 size_t closest_index = closest_indices[0]; 115 bitmap = bitmaps[closest_index]; 116 } 117 118 if (!bitmap.isNull()) { 119 // Update icon with download image and update shortcut. 120 shortcut_info_.favicon.Add(gfx::Image::CreateFrom1xBitmap(bitmap)); 121 extensions::TabHelper* extensions_tab_helper = 122 extensions::TabHelper::FromWebContents(web_contents_); 123 extensions_tab_helper->SetAppIcon(bitmap); 124 UpdateShortcuts(); 125 } else { 126 // Try the next icon otherwise. 127 DownloadIcon(); 128 } 129 } 130 131 void UpdateShortcutWorker::CheckExistingShortcuts() { 132 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 133 134 // Locations to check to shortcut_paths. 135 struct { 136 int location_id; 137 const wchar_t* sub_dir; 138 } locations[] = { 139 { 140 base::DIR_USER_DESKTOP, 141 NULL 142 }, { 143 base::DIR_START_MENU, 144 NULL 145 }, { 146 // For Win7, create_in_quick_launch_bar means pinning to taskbar. 147 base::DIR_APP_DATA, 148 (base::win::GetVersion() >= base::win::VERSION_WIN7) ? 149 L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar" : 150 L"Microsoft\\Internet Explorer\\Quick Launch" 151 } 152 }; 153 154 for (int i = 0; i < arraysize(locations); ++i) { 155 base::FilePath path; 156 if (!PathService::Get(locations[i].location_id, &path)) { 157 NOTREACHED(); 158 continue; 159 } 160 161 if (locations[i].sub_dir != NULL) 162 path = path.Append(locations[i].sub_dir); 163 164 base::FilePath shortcut_file = path.Append(file_name_). 165 ReplaceExtension(FILE_PATH_LITERAL(".lnk")); 166 if (base::PathExists(shortcut_file)) { 167 shortcut_files_.push_back(shortcut_file); 168 } 169 } 170 } 171 172 void UpdateShortcutWorker::UpdateShortcuts() { 173 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 174 base::Bind(&UpdateShortcutWorker::UpdateShortcutsOnFileThread, 175 base::Unretained(this))); 176 } 177 178 void UpdateShortcutWorker::UpdateShortcutsOnFileThread() { 179 DCHECK_CURRENTLY_ON(BrowserThread::FILE); 180 181 base::FilePath web_app_path = web_app::GetWebAppDataDirectory( 182 profile_path_, shortcut_info_.extension_id, shortcut_info_.url); 183 184 // Ensure web_app_path exists. web_app_path could be missing for a legacy 185 // shortcut created by Gears. 186 if (!base::PathExists(web_app_path) && 187 !base::CreateDirectory(web_app_path)) { 188 NOTREACHED(); 189 return; 190 } 191 192 base::FilePath icon_file = 193 web_app::internals::GetIconFilePath(web_app_path, shortcut_info_.title); 194 web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info_.favicon); 195 196 // Update existing shortcuts' description, icon and app id. 197 CheckExistingShortcuts(); 198 if (!shortcut_files_.empty()) { 199 // Generates app id from web app url and profile path. 200 base::string16 app_id = ShellIntegration::GetAppModelIdForProfile( 201 base::UTF8ToWide( 202 web_app::GenerateApplicationNameFromURL(shortcut_info_.url)), 203 profile_path_); 204 205 // Sanitize description 206 if (shortcut_info_.description.length() >= MAX_PATH) 207 shortcut_info_.description.resize(MAX_PATH - 1); 208 209 for (size_t i = 0; i < shortcut_files_.size(); ++i) { 210 base::win::ShortcutProperties shortcut_properties; 211 shortcut_properties.set_target(shortcut_files_[i]); 212 shortcut_properties.set_description(shortcut_info_.description); 213 shortcut_properties.set_icon(icon_file, 0); 214 shortcut_properties.set_app_id(app_id); 215 base::win::CreateOrUpdateShortcutLink( 216 shortcut_files_[i], shortcut_properties, 217 base::win::SHORTCUT_UPDATE_EXISTING); 218 } 219 } 220 221 OnShortcutsUpdated(true); 222 } 223 224 void UpdateShortcutWorker::OnShortcutsUpdated(bool) { 225 DeleteMe(); // We are done. 226 } 227 228 void UpdateShortcutWorker::DeleteMe() { 229 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { 230 DeleteMeOnUIThread(); 231 } else { 232 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 233 base::Bind(&UpdateShortcutWorker::DeleteMeOnUIThread, 234 base::Unretained(this))); 235 } 236 } 237 238 void UpdateShortcutWorker::DeleteMeOnUIThread() { 239 DCHECK_CURRENTLY_ON(BrowserThread::UI); 240 delete this; 241 } 242 243 } // namespace web_app 244