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