// Copyright (c) 2012 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/bind.h" #include "base/bind_helpers.h" #include "base/file_util.h" #include "base/path_service.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/favicon/favicon_tab_helper.h" #include "chrome/browser/history/select_favicon_frames.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/web_applications/web_app.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/web_contents.h" #include "url/gurl.h" #if defined(OS_POSIX) && !defined(OS_MACOSX) #include "base/environment.h" #endif #if defined(OS_WIN) #include "base/win/shortcut.h" #include "base/win/windows_version.h" #include "chrome/browser/web_applications/web_app_win.h" #include "ui/gfx/icon_util.h" #endif using content::BrowserThread; using content::NavigationController; using content::WebContents; namespace { // TODO(jackhou): Move all win-specific code to web_app_win. #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 content::NotificationObserver { public: explicit UpdateShortcutWorker(WebContents* web_contents); void Run(); private: // Overridden from content::NotificationObserver: virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details); // Downloads icon via the FaviconTabHelper. void DownloadIcon(); // Favicon download callback. void DidDownloadFavicon( int requested_size, int id, int http_status_code, const GURL& image_url, const std::vector& bitmaps, const std::vector& original_bitmap_sizes); // 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(); content::NotificationRegistrar registrar_; // Underlying WebContents whose shortcuts will be updated. WebContents* web_contents_; // Icons info from web_contents_'s web app data. web_app::IconInfoList unprocessed_icons_; // Cached shortcut data from the web_contents_. ShellIntegration::ShortcutInfo shortcut_info_; // Our copy of profile path. base::FilePath profile_path_; // File name of shortcut/ico file based on app title. base::FilePath file_name_; // Existing shortcuts. std::vector shortcut_files_; DISALLOW_COPY_AND_ASSIGN(UpdateShortcutWorker); }; UpdateShortcutWorker::UpdateShortcutWorker(WebContents* web_contents) : web_contents_(web_contents), profile_path_(Profile::FromBrowserContext( web_contents->GetBrowserContext())->GetPath()) { extensions::TabHelper* extensions_tab_helper = extensions::TabHelper::FromWebContents(web_contents); web_app::GetShortcutInfoForTab(web_contents_, &shortcut_info_); web_app::GetIconsInfo(extensions_tab_helper->web_app_info(), &unprocessed_icons_); file_name_ = web_app::internals::GetSanitizedFileName(shortcut_info_.title); registrar_.Add( this, chrome::NOTIFICATION_TAB_CLOSING, content::Source(&web_contents->GetController())); } void UpdateShortcutWorker::Run() { // Starting by downloading app icon. DownloadIcon(); } void UpdateShortcutWorker::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { if (type == chrome::NOTIFICATION_TAB_CLOSING && content::Source(source).ptr() == &web_contents_->GetController()) { // Underlying tab is closing. web_contents_ = NULL; } } void UpdateShortcutWorker::DownloadIcon() { // FetchIcon must run on UI thread because it relies on WebContents // to download the icon. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (web_contents_ == NULL) { DeleteMe(); // We are done if underlying WebContents is gone. return; } if (unprocessed_icons_.empty()) { // No app icon. Just use the favicon from WebContents. UpdateShortcuts(); return; } int preferred_size = std::max(unprocessed_icons_.back().width, unprocessed_icons_.back().height); web_contents_->DownloadImage( unprocessed_icons_.back().url, true, // favicon 0, // no maximum size base::Bind(&UpdateShortcutWorker::DidDownloadFavicon, base::Unretained(this), preferred_size)); unprocessed_icons_.pop_back(); } void UpdateShortcutWorker::DidDownloadFavicon( int requested_size, int id, int http_status_code, const GURL& image_url, const std::vector& bitmaps, const std::vector& original_sizes) { std::vector scale_factors; scale_factors.push_back(ui::SCALE_FACTOR_100P); std::vector closest_indices; SelectFaviconFrameIndices(original_sizes, scale_factors, requested_size, &closest_indices, NULL); size_t closest_index = closest_indices[0]; if (!bitmaps.empty() && !bitmaps[closest_index].isNull()) { // Update icon with download image and update shortcut. shortcut_info_.favicon.Add( gfx::Image::CreateFrom1xBitmap(bitmaps[closest_index])); extensions::TabHelper* extensions_tab_helper = extensions::TabHelper::FromWebContents(web_contents_); extensions_tab_helper->SetAppIcon(bitmaps[closest_index]); UpdateShortcuts(); } else { // Try the next icon otherwise. DownloadIcon(); } } void UpdateShortcutWorker::CheckExistingShortcuts() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); // Locations to check to shortcut_paths. struct { int location_id; const wchar_t* sub_dir; } locations[] = { { base::DIR_USER_DESKTOP, NULL }, { base::DIR_START_MENU, NULL }, { // 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) { base::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); base::FilePath shortcut_file = path.Append(file_name_). ReplaceExtension(FILE_PATH_LITERAL(".lnk")); if (base::PathExists(shortcut_file)) { shortcut_files_.push_back(shortcut_file); } } } void UpdateShortcutWorker::UpdateShortcuts() { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(&UpdateShortcutWorker::UpdateShortcutsOnFileThread, base::Unretained(this))); } void UpdateShortcutWorker::UpdateShortcutsOnFileThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); base::FilePath web_app_path = web_app::GetWebAppDataDirectory( profile_path_, shortcut_info_.extension_id, shortcut_info_.url); // Ensure web_app_path exists. web_app_path could be missing for a legacy // shortcut created by Gears. if (!base::PathExists(web_app_path) && !base::CreateDirectory(web_app_path)) { NOTREACHED(); return; } base::FilePath icon_file = web_app::internals::GetIconFilePath(web_app_path, shortcut_info_.title); 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. base::string16 app_id = ShellIntegration::GetAppModelIdForProfile( base::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) { base::win::ShortcutProperties shortcut_properties; shortcut_properties.set_target(shortcut_files_[i]); shortcut_properties.set_description(shortcut_info_.description); shortcut_properties.set_icon(icon_file, 0); shortcut_properties.set_app_id(app_id); base::win::CreateOrUpdateShortcutLink( shortcut_files_[i], shortcut_properties, base::win::SHORTCUT_UPDATE_EXISTING); } } 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, base::Bind(&UpdateShortcutWorker::DeleteMeOnUIThread, base::Unretained(this))); } } void UpdateShortcutWorker::DeleteMeOnUIThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); delete this; } #endif // defined(OS_WIN) } // namespace namespace web_app { void GetShortcutInfoForTab(WebContents* web_contents, ShellIntegration::ShortcutInfo* info) { DCHECK(info); // Must provide a valid info. const FaviconTabHelper* favicon_tab_helper = FaviconTabHelper::FromWebContents(web_contents); const extensions::TabHelper* extensions_tab_helper = extensions::TabHelper::FromWebContents(web_contents); const WebApplicationInfo& app_info = extensions_tab_helper->web_app_info(); info->url = app_info.app_url.is_empty() ? web_contents->GetURL() : app_info.app_url; info->title = app_info.title.empty() ? (web_contents->GetTitle().empty() ? base::UTF8ToUTF16(info->url.spec()) : web_contents->GetTitle()) : app_info.title; info->description = app_info.description; info->favicon.Add(favicon_tab_helper->GetFavicon()); Profile* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); info->profile_path = profile->GetPath(); } void UpdateShortcutForTabContents(WebContents* web_contents) { #if defined(OS_WIN) // UpdateShortcutWorker will delete itself when it's done. UpdateShortcutWorker* worker = new UpdateShortcutWorker(web_contents); worker->Run(); #endif // defined(OS_WIN) } } // namespace web_app