// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/web_applications/web_app_ui.h" #include "base/file_util.h" #include "base/path_service.h" #include "base/task.h" #include "base/win/windows_version.h" #include "chrome/browser/extensions/extension_tab_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/web_applications/web_app.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/chrome_paths.h" #include "content/browser/browser_thread.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/common/notification_registrar.h" #if defined(OS_LINUX) #include "base/environment.h" #endif // defined(OS_LINUX) #if defined(OS_WIN) #include "content/common/notification_details.h" #include "content/common/notification_source.h" #endif // defined(OS_WIN) namespace { #if defined(OS_WIN) // UpdateShortcutWorker holds all context data needed for update shortcut. // It schedules a pre-update check to find all shortcuts that needs to be // updated. If there are such shortcuts, it schedules icon download and // update them when icons are downloaded. It observes TAB_CLOSING notification // and cancels all the work when the underlying tab is closing. class UpdateShortcutWorker : public NotificationObserver { public: explicit UpdateShortcutWorker(TabContentsWrapper* tab_contents); void Run(); private: // Overridden from NotificationObserver: virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details); // Downloads icon via TabContents. void DownloadIcon(); // Callback when icon downloaded. void OnIconDownloaded(int download_id, bool errored, const SkBitmap& image); // Checks if shortcuts exists on desktop, start menu and quick launch. void CheckExistingShortcuts(); // Update shortcut files and icons. void UpdateShortcuts(); void UpdateShortcutsOnFileThread(); // Callback after shortcuts are updated. void OnShortcutsUpdated(bool); // Deletes the worker on UI thread where it gets created. void DeleteMe(); void DeleteMeOnUIThread(); NotificationRegistrar registrar_; // Underlying TabContentsWrapper whose shortcuts will be updated. TabContentsWrapper* tab_contents_; // Icons info from tab_contents_'s web app data. web_app::IconInfoList unprocessed_icons_; // Cached shortcut data from the tab_contents_. ShellIntegration::ShortcutInfo shortcut_info_; // Our copy of profile path. FilePath profile_path_; // File name of shortcut/ico file based on app title. FilePath file_name_; // Existing shortcuts. std::vector<FilePath> shortcut_files_; DISALLOW_COPY_AND_ASSIGN(UpdateShortcutWorker); }; UpdateShortcutWorker::UpdateShortcutWorker(TabContentsWrapper* tab_contents) : tab_contents_(tab_contents), profile_path_(tab_contents->profile()->GetPath()) { web_app::GetShortcutInfoForTab(tab_contents_, &shortcut_info_); web_app::GetIconsInfo(tab_contents_->extension_tab_helper()->web_app_info(), &unprocessed_icons_); file_name_ = web_app::internals::GetSanitizedFileName(shortcut_info_.title); registrar_.Add(this, NotificationType::TAB_CLOSING, Source<NavigationController>(&tab_contents_->controller())); } void UpdateShortcutWorker::Run() { // Starting by downloading app icon. DownloadIcon(); } void UpdateShortcutWorker::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::TAB_CLOSING && Source<NavigationController>(source).ptr() == &tab_contents_->controller()) { // Underlying tab is closing. tab_contents_ = NULL; } } void UpdateShortcutWorker::DownloadIcon() { // FetchIcon must run on UI thread because it relies on TabContents // to download the icon. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (tab_contents_ == NULL) { DeleteMe(); // We are done if underlying TabContents is gone. return; } if (unprocessed_icons_.empty()) { // No app icon. Just use the favicon from TabContents. UpdateShortcuts(); return; } tab_contents_->tab_contents()->favicon_helper().DownloadImage( unprocessed_icons_.back().url, std::max(unprocessed_icons_.back().width, unprocessed_icons_.back().height), history::FAVICON, NewCallback(this, &UpdateShortcutWorker::OnIconDownloaded)); unprocessed_icons_.pop_back(); } void UpdateShortcutWorker::OnIconDownloaded(int download_id, bool errored, const SkBitmap& image) { if (tab_contents_ == NULL) { DeleteMe(); // We are done if underlying TabContents is gone. return; } if (!errored && !image.isNull()) { // Update icon with download image and update shortcut. shortcut_info_.favicon = image; tab_contents_->extension_tab_helper()->SetAppIcon(image); UpdateShortcuts(); } else { // Try the next icon otherwise. DownloadIcon(); } } void UpdateShortcutWorker::CheckExistingShortcuts() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Locations to check to shortcut_paths. struct { bool& use_this_location; int location_id; const wchar_t* sub_dir; } locations[] = { { shortcut_info_.create_on_desktop, chrome::DIR_USER_DESKTOP, NULL }, { shortcut_info_.create_in_applications_menu, base::DIR_START_MENU, NULL }, { shortcut_info_.create_in_quick_launch_bar, // For Win7, create_in_quick_launch_bar means pinning to taskbar. base::DIR_APP_DATA, (base::win::GetVersion() >= base::win::VERSION_WIN7) ? L"Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar" : L"Microsoft\\Internet Explorer\\Quick Launch" } }; for (int i = 0; i < arraysize(locations); ++i) { locations[i].use_this_location = false; FilePath path; if (!PathService::Get(locations[i].location_id, &path)) { NOTREACHED(); continue; } if (locations[i].sub_dir != NULL) path = path.Append(locations[i].sub_dir); FilePath shortcut_file = path.Append(file_name_). ReplaceExtension(FILE_PATH_LITERAL(".lnk")); if (file_util::PathExists(shortcut_file)) { locations[i].use_this_location = true; shortcut_files_.push_back(shortcut_file); } } } void UpdateShortcutWorker::UpdateShortcuts() { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, NewRunnableMethod(this, &UpdateShortcutWorker::UpdateShortcutsOnFileThread)); } void UpdateShortcutWorker::UpdateShortcutsOnFileThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); FilePath web_app_path = web_app::internals::GetWebAppDataDirectory( web_app::GetDataDir(profile_path_), shortcut_info_); // Ensure web_app_path exists. web_app_path could be missing for a legacy // shortcut created by Gears. if (!file_util::PathExists(web_app_path) && !file_util::CreateDirectory(web_app_path)) { NOTREACHED(); return; } FilePath icon_file = web_app_path.Append(file_name_).ReplaceExtension( FILE_PATH_LITERAL(".ico")); web_app::internals::CheckAndSaveIcon(icon_file, shortcut_info_.favicon); // Update existing shortcuts' description, icon and app id. CheckExistingShortcuts(); if (!shortcut_files_.empty()) { // Generates app id from web app url and profile path. std::wstring app_id = ShellIntegration::GetAppId( UTF8ToWide(web_app::GenerateApplicationNameFromURL(shortcut_info_.url)), profile_path_); // Sanitize description if (shortcut_info_.description.length() >= MAX_PATH) shortcut_info_.description.resize(MAX_PATH - 1); for (size_t i = 0; i < shortcut_files_.size(); ++i) { file_util::UpdateShortcutLink(NULL, shortcut_files_[i].value().c_str(), NULL, NULL, shortcut_info_.description.c_str(), icon_file.value().c_str(), 0, app_id.c_str()); } } OnShortcutsUpdated(true); } void UpdateShortcutWorker::OnShortcutsUpdated(bool) { DeleteMe(); // We are done. } void UpdateShortcutWorker::DeleteMe() { if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { DeleteMeOnUIThread(); } else { BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, NewRunnableMethod(this, &UpdateShortcutWorker::DeleteMeOnUIThread)); } } void UpdateShortcutWorker::DeleteMeOnUIThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); delete this; } #endif // defined(OS_WIN) } // namespace #if defined(OS_WIN) // Allows UpdateShortcutWorker without adding refcounting. UpdateShortcutWorker // manages its own life time and will delete itself when it's done. DISABLE_RUNNABLE_METHOD_REFCOUNT(UpdateShortcutWorker); #endif // defined(OS_WIN) namespace web_app { void GetShortcutInfoForTab(TabContentsWrapper* tab_contents_wrapper, ShellIntegration::ShortcutInfo* info) { DCHECK(info); // Must provide a valid info. const TabContents* tab_contents = tab_contents_wrapper->tab_contents(); const WebApplicationInfo& app_info = tab_contents_wrapper->extension_tab_helper()->web_app_info(); info->url = app_info.app_url.is_empty() ? tab_contents->GetURL() : app_info.app_url; info->title = app_info.title.empty() ? (tab_contents->GetTitle().empty() ? UTF8ToUTF16(info->url.spec()) : tab_contents->GetTitle()) : app_info.title; info->description = app_info.description; info->favicon = tab_contents->GetFavicon(); } void UpdateShortcutForTabContents(TabContentsWrapper* tab_contents) { #if defined(OS_WIN) // UpdateShortcutWorker will delete itself when it's done. UpdateShortcutWorker* worker = new UpdateShortcutWorker(tab_contents); worker->Run(); #endif // defined(OS_WIN) } } // namespace web_app