| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "chrome/browser/ui/web_applications/web_app_ui.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/bind_helpers.h" | |
| 9 #include "base/file_util.h" | |
| 10 #include "base/path_service.h" | |
| 11 #include "base/strings/utf_string_conversions.h" | |
| 12 #include "chrome/browser/chrome_notification_types.h" | |
| 13 #include "chrome/browser/extensions/tab_helper.h" | |
| 14 #include "chrome/browser/favicon/favicon_tab_helper.h" | |
| 15 #include "chrome/browser/history/select_favicon_frames.h" | |
| 16 #include "chrome/browser/profiles/profile.h" | |
| 17 #include "chrome/browser/web_applications/web_app.h" | |
| 18 #include "content/public/browser/browser_thread.h" | |
| 19 #include "content/public/browser/notification_details.h" | |
| 20 #include "content/public/browser/notification_registrar.h" | |
| 21 #include "content/public/browser/notification_source.h" | |
| 22 #include "content/public/browser/web_contents.h" | |
| 23 #include "url/gurl.h" | |
| 24 | |
| 25 #if defined(OS_POSIX) && !defined(OS_MACOSX) | |
| 26 #include "base/environment.h" | |
| 27 #endif | |
| 28 | |
| 29 #if defined(OS_WIN) | |
| 30 #include "base/win/shortcut.h" | |
| 31 #include "base/win/windows_version.h" | |
| 32 #include "chrome/browser/web_applications/web_app_win.h" | |
| 33 #include "ui/gfx/icon_util.h" | |
| 34 #endif | |
| 35 | |
| 36 using content::BrowserThread; | |
| 37 using content::NavigationController; | |
| 38 using content::WebContents; | |
| 39 | |
| 40 namespace { | |
| 41 | |
| 42 // TODO(jackhou): Move all win-specific code to web_app_win. | |
| 43 #if defined(OS_WIN) | |
| 44 // UpdateShortcutWorker holds all context data needed for update shortcut. | |
| 45 // It schedules a pre-update check to find all shortcuts that needs to be | |
| 46 // updated. If there are such shortcuts, it schedules icon download and | |
| 47 // update them when icons are downloaded. It observes TAB_CLOSING notification | |
| 48 // and cancels all the work when the underlying tab is closing. | |
| 49 class UpdateShortcutWorker : public content::NotificationObserver { | |
| 50 public: | |
| 51 explicit UpdateShortcutWorker(WebContents* web_contents); | |
| 52 | |
| 53 void Run(); | |
| 54 | |
| 55 private: | |
| 56 // Overridden from content::NotificationObserver: | |
| 57 virtual void Observe(int type, | |
| 58 const content::NotificationSource& source, | |
| 59 const content::NotificationDetails& details); | |
| 60 | |
| 61 // Downloads icon via the FaviconTabHelper. | |
| 62 void DownloadIcon(); | |
| 63 | |
| 64 // Favicon download callback. | |
| 65 void DidDownloadFavicon( | |
| 66 int requested_size, | |
| 67 int id, | |
| 68 int http_status_code, | |
| 69 const GURL& image_url, | |
| 70 const std::vector<SkBitmap>& bitmaps, | |
| 71 const std::vector<gfx::Size>& original_bitmap_sizes); | |
| 72 | |
| 73 // Checks if shortcuts exists on desktop, start menu and quick launch. | |
| 74 void CheckExistingShortcuts(); | |
| 75 | |
| 76 // Update shortcut files and icons. | |
| 77 void UpdateShortcuts(); | |
| 78 void UpdateShortcutsOnFileThread(); | |
| 79 | |
| 80 // Callback after shortcuts are updated. | |
| 81 void OnShortcutsUpdated(bool); | |
| 82 | |
| 83 // Deletes the worker on UI thread where it gets created. | |
| 84 void DeleteMe(); | |
| 85 void DeleteMeOnUIThread(); | |
| 86 | |
| 87 content::NotificationRegistrar registrar_; | |
| 88 | |
| 89 // Underlying WebContents whose shortcuts will be updated. | |
| 90 WebContents* web_contents_; | |
| 91 | |
| 92 // Icons info from web_contents_'s web app data. | |
| 93 web_app::IconInfoList unprocessed_icons_; | |
| 94 | |
| 95 // Cached shortcut data from the web_contents_. | |
| 96 ShellIntegration::ShortcutInfo shortcut_info_; | |
| 97 | |
| 98 // Our copy of profile path. | |
| 99 base::FilePath profile_path_; | |
| 100 | |
| 101 // File name of shortcut/ico file based on app title. | |
| 102 base::FilePath file_name_; | |
| 103 | |
| 104 // Existing shortcuts. | |
| 105 std::vector<base::FilePath> shortcut_files_; | |
| 106 | |
| 107 DISALLOW_COPY_AND_ASSIGN(UpdateShortcutWorker); | |
| 108 }; | |
| 109 | |
| 110 UpdateShortcutWorker::UpdateShortcutWorker(WebContents* web_contents) | |
| 111 : web_contents_(web_contents), | |
| 112 profile_path_(Profile::FromBrowserContext( | |
| 113 web_contents->GetBrowserContext())->GetPath()) { | |
| 114 extensions::TabHelper* extensions_tab_helper = | |
| 115 extensions::TabHelper::FromWebContents(web_contents); | |
| 116 web_app::GetShortcutInfoForTab(web_contents_, &shortcut_info_); | |
| 117 web_app::GetIconsInfo(extensions_tab_helper->web_app_info(), | |
| 118 &unprocessed_icons_); | |
| 119 file_name_ = web_app::internals::GetSanitizedFileName(shortcut_info_.title); | |
| 120 | |
| 121 registrar_.Add( | |
| 122 this, | |
| 123 chrome::NOTIFICATION_TAB_CLOSING, | |
| 124 content::Source<NavigationController>(&web_contents->GetController())); | |
| 125 } | |
| 126 | |
| 127 void UpdateShortcutWorker::Run() { | |
| 128 // Starting by downloading app icon. | |
| 129 DownloadIcon(); | |
| 130 } | |
| 131 | |
| 132 void UpdateShortcutWorker::Observe( | |
| 133 int type, | |
| 134 const content::NotificationSource& source, | |
| 135 const content::NotificationDetails& details) { | |
| 136 if (type == chrome::NOTIFICATION_TAB_CLOSING && | |
| 137 content::Source<NavigationController>(source).ptr() == | |
| 138 &web_contents_->GetController()) { | |
| 139 // Underlying tab is closing. | |
| 140 web_contents_ = NULL; | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 void UpdateShortcutWorker::DownloadIcon() { | |
| 145 // FetchIcon must run on UI thread because it relies on WebContents | |
| 146 // to download the icon. | |
| 147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 148 | |
| 149 if (web_contents_ == NULL) { | |
| 150 DeleteMe(); // We are done if underlying WebContents is gone. | |
| 151 return; | |
| 152 } | |
| 153 | |
| 154 if (unprocessed_icons_.empty()) { | |
| 155 // No app icon. Just use the favicon from WebContents. | |
| 156 UpdateShortcuts(); | |
| 157 return; | |
| 158 } | |
| 159 | |
| 160 int preferred_size = std::max(unprocessed_icons_.back().width, | |
| 161 unprocessed_icons_.back().height); | |
| 162 web_contents_->DownloadImage( | |
| 163 unprocessed_icons_.back().url, | |
| 164 true, // favicon | |
| 165 0, // no maximum size | |
| 166 base::Bind(&UpdateShortcutWorker::DidDownloadFavicon, | |
| 167 base::Unretained(this), | |
| 168 preferred_size)); | |
| 169 unprocessed_icons_.pop_back(); | |
| 170 } | |
| 171 | |
| 172 void UpdateShortcutWorker::DidDownloadFavicon( | |
| 173 int requested_size, | |
| 174 int id, | |
| 175 int http_status_code, | |
| 176 const GURL& image_url, | |
| 177 const std::vector<SkBitmap>& bitmaps, | |
| 178 const std::vector<gfx::Size>& original_sizes) { | |
| 179 std::vector<ui::ScaleFactor> scale_factors; | |
| 180 scale_factors.push_back(ui::SCALE_FACTOR_100P); | |
| 181 | |
| 182 std::vector<size_t> closest_indices; | |
| 183 SelectFaviconFrameIndices(original_sizes, | |
| 184 scale_factors, | |
| 185 requested_size, | |
| 186 &closest_indices, | |
| 187 NULL); | |
| 188 size_t closest_index = closest_indices[0]; | |
| 189 | |
| 190 if (!bitmaps.empty() && !bitmaps[closest_index].isNull()) { | |
| 191 // Update icon with download image and update shortcut. | |
| 192 shortcut_info_.favicon.Add( | |
| 193 gfx::Image::CreateFrom1xBitmap(bitmaps[closest_index])); | |
| 194 extensions::TabHelper* extensions_tab_helper = | |
| 195 extensions::TabHelper::FromWebContents(web_contents_); | |
| 196 extensions_tab_helper->SetAppIcon(bitmaps[closest_index]); | |
| 197 UpdateShortcuts(); | |
| 198 } else { | |
| 199 // Try the next icon otherwise. | |
| 200 DownloadIcon(); | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 void UpdateShortcutWorker::CheckExistingShortcuts() { | |
| 205 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 206 | |
| 207 // Locations to check to shortcut_paths. | |
| 208 struct { | |
| 209 int location_id; | |
| 210 const wchar_t* sub_dir; | |
| 211 } locations[] = { | |
| 212 { | |
| 213 base::DIR_USER_DESKTOP, | |
| 214 NULL | |
| 215 }, { | |
| 216 base::DIR_START_MENU, | |
| 217 NULL | |
| 218 }, { | |
| 219 // For Win7, create_in_quick_launch_bar means pinning to taskbar. | |
| 220 base::DIR_APP_DATA, | |
| 221 (base::win::GetVersion() >= base::win::VERSION_WIN7) ? | |
| 222 L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar" : | |
| 223 L"Microsoft\\Internet Explorer\\Quick Launch" | |
| 224 } | |
| 225 }; | |
| 226 | |
| 227 for (int i = 0; i < arraysize(locations); ++i) { | |
| 228 base::FilePath path; | |
| 229 if (!PathService::Get(locations[i].location_id, &path)) { | |
| 230 NOTREACHED(); | |
| 231 continue; | |
| 232 } | |
| 233 | |
| 234 if (locations[i].sub_dir != NULL) | |
| 235 path = path.Append(locations[i].sub_dir); | |
| 236 | |
| 237 base::FilePath shortcut_file = path.Append(file_name_). | |
| 238 ReplaceExtension(FILE_PATH_LITERAL(".lnk")); | |
| 239 if (base::PathExists(shortcut_file)) { | |
| 240 shortcut_files_.push_back(shortcut_file); | |
| 241 } | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 void UpdateShortcutWorker::UpdateShortcuts() { | |
| 246 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, | |
| 247 base::Bind(&UpdateShortcutWorker::UpdateShortcutsOnFileThread, | |
| 248 base::Unretained(this))); | |
| 249 } | |
| 250 | |
| 251 void UpdateShortcutWorker::UpdateShortcutsOnFileThread() { | |
| 252 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
| 253 | |
| 254 base::FilePath web_app_path = web_app::GetWebAppDataDirectory( | |
| 255 profile_path_, shortcut_info_.extension_id, shortcut_info_.url); | |
| 256 | |
| 257 // Ensure web_app_path exists. web_app_path could be missing for a legacy | |
| 258 // shortcut created by Gears. | |
| 259 if (!base::PathExists(web_app_path) && | |
| 260 !base::CreateDirectory(web_app_path)) { | |
| 261 NOTREACHED(); | |
| 262 return; | |
| 263 } | |
| 264 | |
| 265 base::FilePath icon_file = web_app_path.Append(file_name_).ReplaceExtension( | |
| 266 FILE_PATH_LITERAL(".ico")); | |
| 267 web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info_.favicon); | |
| 268 | |
| 269 // Update existing shortcuts' description, icon and app id. | |
| 270 CheckExistingShortcuts(); | |
| 271 if (!shortcut_files_.empty()) { | |
| 272 // Generates app id from web app url and profile path. | |
| 273 base::string16 app_id = ShellIntegration::GetAppModelIdForProfile( | |
| 274 base::UTF8ToWide( | |
| 275 web_app::GenerateApplicationNameFromURL(shortcut_info_.url)), | |
| 276 profile_path_); | |
| 277 | |
| 278 // Sanitize description | |
| 279 if (shortcut_info_.description.length() >= MAX_PATH) | |
| 280 shortcut_info_.description.resize(MAX_PATH - 1); | |
| 281 | |
| 282 for (size_t i = 0; i < shortcut_files_.size(); ++i) { | |
| 283 base::win::ShortcutProperties shortcut_properties; | |
| 284 shortcut_properties.set_target(shortcut_files_[i]); | |
| 285 shortcut_properties.set_description(shortcut_info_.description); | |
| 286 shortcut_properties.set_icon(icon_file, 0); | |
| 287 shortcut_properties.set_app_id(app_id); | |
| 288 base::win::CreateOrUpdateShortcutLink( | |
| 289 shortcut_files_[i], shortcut_properties, | |
| 290 base::win::SHORTCUT_UPDATE_EXISTING); | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 OnShortcutsUpdated(true); | |
| 295 } | |
| 296 | |
| 297 void UpdateShortcutWorker::OnShortcutsUpdated(bool) { | |
| 298 DeleteMe(); // We are done. | |
| 299 } | |
| 300 | |
| 301 void UpdateShortcutWorker::DeleteMe() { | |
| 302 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
| 303 DeleteMeOnUIThread(); | |
| 304 } else { | |
| 305 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, | |
| 306 base::Bind(&UpdateShortcutWorker::DeleteMeOnUIThread, | |
| 307 base::Unretained(this))); | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 void UpdateShortcutWorker::DeleteMeOnUIThread() { | |
| 312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 313 delete this; | |
| 314 } | |
| 315 #endif // defined(OS_WIN) | |
| 316 | |
| 317 } // namespace | |
| 318 | |
| 319 namespace web_app { | |
| 320 | |
| 321 void GetShortcutInfoForTab(WebContents* web_contents, | |
| 322 ShellIntegration::ShortcutInfo* info) { | |
| 323 DCHECK(info); // Must provide a valid info. | |
| 324 | |
| 325 const FaviconTabHelper* favicon_tab_helper = | |
| 326 FaviconTabHelper::FromWebContents(web_contents); | |
| 327 const extensions::TabHelper* extensions_tab_helper = | |
| 328 extensions::TabHelper::FromWebContents(web_contents); | |
| 329 const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info(); | |
| 330 | |
| 331 info->url = app_info.app_url.is_empty() ? web_contents->GetURL() : | |
| 332 app_info.app_url; | |
| 333 info->title = app_info.title.empty() ? | |
| 334 (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) : | |
| 335 web_contents->GetTitle()) : | |
| 336 app_info.title; | |
| 337 info->description = app_info.description; | |
| 338 info->favicon.Add(favicon_tab_helper->GetFavicon()); | |
| 339 | |
| 340 Profile* profile = | |
| 341 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
| 342 info->profile_path = profile->GetPath(); | |
| 343 } | |
| 344 | |
| 345 void UpdateShortcutForTabContents(WebContents* web_contents) { | |
| 346 #if defined(OS_WIN) | |
| 347 // UpdateShortcutWorker will delete itself when it's done. | |
| 348 UpdateShortcutWorker* worker = new UpdateShortcutWorker(web_contents); | |
| 349 worker->Run(); | |
| 350 #endif // defined(OS_WIN) | |
| 351 } | |
| 352 | |
| 353 } // namespace web_app | |
| OLD | NEW |