diff options
author | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-11 06:13:51 +0000 |
---|---|---|
committer | xiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-12-11 06:13:51 +0000 |
commit | eabfdae9101aa33a212fdaf240d8e0af70874e16 (patch) | |
tree | 4b27ea5ae52ce96ccfaab87960d067fc1b58968c /chrome | |
parent | ffb01d3e998a32156790af7c1367d733bb9e75dd (diff) | |
download | chromium_src-eabfdae9101aa33a212fdaf240d8e0af70874e16.zip chromium_src-eabfdae9101aa33a212fdaf240d8e0af70874e16.tar.gz chromium_src-eabfdae9101aa33a212fdaf240d8e0af70874e16.tar.bz2 |
Get web app icon via FavIconHelper and auto repair/update
- Expose a DownloadImage method from FavIconHelper to download/decode
image for an image url;
- Expose FavIconHelper from TabContents;
- Update CreateApplicationShortcutView to use the exposed DownloadImage
method to get web app icon instead of do it via URLFetcher/PNGCodec;
- Check and update web app icon and shortcuts when chrome is lauched as
app for OS_WIN;
- Code cleanup:
- Move a bunch of FavIconHelper methods that are not used externally
to private;
- Remove an unused cancelable_consumer_ from TabContents;
BUG=8539
TEST=Verify issue 8539 is fixed. And create a web page with a non PNG shortcut icon and verify it shows up in create application shortcut dialog.
Review URL: http://codereview.chromium.org/482003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@34332 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/browser.cc | 42 | ||||
-rw-r--r-- | chrome/browser/browser.h | 12 | ||||
-rw-r--r-- | chrome/browser/fav_icon_helper.cc | 85 | ||||
-rw-r--r-- | chrome/browser/fav_icon_helper.h | 49 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents.cc | 1 | ||||
-rw-r--r-- | chrome/browser/tab_contents/tab_contents.h | 10 | ||||
-rw-r--r-- | chrome/browser/views/create_application_shortcut_view.cc | 131 | ||||
-rw-r--r-- | chrome/browser/views/create_application_shortcut_view.h | 41 | ||||
-rw-r--r-- | chrome/browser/web_applications/web_app.cc | 379 | ||||
-rw-r--r-- | chrome/browser/web_applications/web_app.h | 24 |
10 files changed, 599 insertions, 175 deletions
diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc index a52e157..40690ba 100644 --- a/chrome/browser/browser.cc +++ b/chrome/browser/browser.cc @@ -143,7 +143,8 @@ Browser::Browser(Type type, Profile* profile) method_factory_(this), block_command_execution_(false), last_blocked_command_id_(-1), - last_blocked_command_disposition_(CURRENT_TAB) { + last_blocked_command_disposition_(CURRENT_TAB), + pending_web_app_action_(NONE) { tabstrip_model_.AddObserver(this); registrar_.Add(this, NotificationType::SSL_VISIBLE_STATE_CHANGED, @@ -348,6 +349,13 @@ void Browser::OpenApplicationWindow(Profile* profile, const GURL& url) { // TODO(jcampan): http://crbug.com/8123 we should not need to set the initial // focus explicitly. tab_contents->view()->SetInitialFocus(); + + // Set UPDATE_SHORTCUT as the pending web app action. This action is picked + // up in LoadingStateChanged to schedule a GetApplicationInfo. And when + // the web app info is available, TabContents notifies Browser via + // OnDidGetApplicationInfo, which calls web_app::UpdateShortcutForTabContents + // when it sees UPDATE_SHORTCUT as pending web app action. + browser->pending_web_app_action_ = UPDATE_SHORTCUT; } #if defined(OS_MACOSX) @@ -1177,6 +1185,10 @@ void Browser::OpenCreateShortcutsDialog() { if (!entry) return; + // RVH's GetApplicationInfo should not be called before it returns. + DCHECK(pending_web_app_action_ == NONE); + pending_web_app_action_ = CREATE_SHORTCUT; + // Start fetching web app info for CreateApplicatoinShortcut dialog and // show the dialog when the data is available in OnDidGetApplicationInfo. current_tab->render_view_host()->GetApplicationInfo(entry->page_id()); @@ -2030,6 +2042,18 @@ void Browser::LoadingStateChanged(TabContents* source) { UpdateStopGoState(source->is_loading(), false); if (GetStatusBubble()) GetStatusBubble()->SetStatus(GetSelectedTabContents()->GetStatusText()); + + if (!source->is_loading() && + pending_web_app_action_ == UPDATE_SHORTCUT) { + // Schedule a shortcut update when web application info is available. + NavigationEntry* entry = source->controller().GetLastCommittedEntry(); + if (entry) { + source->render_view_host()->GetApplicationInfo(entry->page_id()); + } else { + pending_web_app_action_ = NONE; + NOTREACHED(); + } + } } } @@ -2265,7 +2289,21 @@ void Browser::OnDidGetApplicationInfo(TabContents* tab_contents, if (!entry || (entry->page_id() != page_id)) return; - window()->ShowCreateShortcutsDialog(current_tab); + switch (pending_web_app_action_) { + case CREATE_SHORTCUT: { + window()->ShowCreateShortcutsDialog(current_tab); + break; + } + case UPDATE_SHORTCUT: { + web_app::UpdateShortcutForTabContents(current_tab); + break; + } + default: + NOTREACHED(); + break; + } + + pending_web_app_action_ = NONE; } /////////////////////////////////////////////////////////////////////////////// diff --git a/chrome/browser/browser.h b/chrome/browser/browser.h index 05fa0d8..7f5f85c 100644 --- a/chrome/browser/browser.h +++ b/chrome/browser/browser.h @@ -851,6 +851,18 @@ class Browser : public TabStripModelDelegate, // Stores the disposition type of the last blocked command. WindowOpenDisposition last_blocked_command_disposition_; + // Different types of action when web app info is available. + // OnDidGetApplicationInfo uses this to dispatch calls. + enum WebAppAction { + NONE, // No action at all. + CREATE_SHORTCUT, // Bring up create application shortcut dialog. + UPDATE_SHORTCUT // Update icon for app shortcut. + }; + + // Which deferred action to perform when OnDidGetApplicationInfo is notified + // from a TabContents. Currently, only one pending action is allowed. + WebAppAction pending_web_app_action_; + DISALLOW_COPY_AND_ASSIGN(Browser); }; diff --git a/chrome/browser/fav_icon_helper.cc b/chrome/browser/fav_icon_helper.cc index 3563406..e2eaa77 100644 --- a/chrome/browser/fav_icon_helper.cc +++ b/chrome/browser/fav_icon_helper.cc @@ -23,6 +23,18 @@ FavIconHelper::FavIconHelper(TabContents* tab_contents) fav_icon_expired_(false) { } +FavIconHelper::~FavIconHelper() { + SkBitmap empty_image; + + // Call pending download callbacks with error to allow caller to clean up. + for (DownloadRequests::iterator i = download_requests_.begin(); + i != download_requests_.end(); ++i) { + if (i->second.callback) { + i->second.callback->Run(i->first, true, empty_image); + } + } +} + void FavIconHelper::FetchFavIcon(const GURL& url) { cancelable_consumer_.CancelAllRequests(); @@ -39,6 +51,13 @@ void FavIconHelper::FetchFavIcon(const GURL& url) { } } +int FavIconHelper::DownloadImage(const GURL& image_url, + int image_size, + ImageDownloadCallback* callback) { + DCHECK(callback); // Must provide a callback. + return ScheduleDownload(GURL(), image_url, image_size, callback); +} + Profile* FavIconHelper::profile() { return tab_contents_->profile(); } @@ -48,16 +67,9 @@ FaviconService* FavIconHelper::GetFaviconService() { } void FavIconHelper::SetFavIcon( - int download_id, + const GURL& url, const GURL& image_url, const SkBitmap& image) { - DownloadRequests::iterator i = download_requests_.find(download_id); - if (i == download_requests_.end()) { - // Currently TabContents notifies us of ANY downloads so that it is - // possible to get here. - return; - } - const SkBitmap& sized_image = (image.width() == kFavIconSize && image.height() == kFavIconSize) ? image : ConvertToFavIconSize(image); @@ -65,23 +77,14 @@ void FavIconHelper::SetFavIcon( if (GetFaviconService() && !profile()->IsOffTheRecord()) { std::vector<unsigned char> image_data; gfx::PNGCodec::EncodeBGRASkBitmap(sized_image, false, &image_data); - GetFaviconService()->SetFavicon(i->second.url, i->second.fav_icon_url, - image_data); + GetFaviconService()->SetFavicon(url, image_url, image_data); } - if (i->second.url == url_) { + if (url == url_) { NavigationEntry* entry = GetEntry(); if (entry) UpdateFavIcon(entry, sized_image); } - - download_requests_.erase(i); -} - -void FavIconHelper::FavIconDownloadFailed(int download_id) { - DownloadRequests::iterator i = download_requests_.find(download_id); - if (i != download_requests_.end()) - download_requests_.erase(i); } void FavIconHelper::UpdateFavIcon(NavigationEntry* entry, @@ -132,10 +135,20 @@ void FavIconHelper::DidDownloadFavIcon(RenderViewHost* render_view_host, const GURL& image_url, bool errored, const SkBitmap& image) { - if (errored) - FavIconDownloadFailed(id); - else - SetFavIcon(id, image_url, image); + DownloadRequests::iterator i = download_requests_.find(id); + if (i == download_requests_.end()) { + // Currently TabContents notifies us of ANY downloads so that it is + // possible to get here. + return; + } + + if (i->second.callback) { + i->second.callback->Run(id, errored, image); + } else if (!errored) { + SetFavIcon(i->second.url, image_url, image); + } + + download_requests_.erase(i); } NavigationEntry* FavIconHelper::GetEntry() { @@ -196,7 +209,7 @@ void FavIconHelper::DownloadFavIconOrAskHistory(NavigationEntry* entry) { DCHECK(entry); // We should only get here if entry is valid. if (fav_icon_expired_) { // We have the mapping, but the favicon is out of date. Download it now. - ScheduleDownload(entry); + ScheduleDownload(entry->url(), entry->favicon().url(), kFavIconSize, NULL); } else if (GetFaviconService()) { // We don't know the favicon, but we may have previously downloaded the // favicon for another page that shares the same favicon. Ask for the @@ -243,20 +256,24 @@ void FavIconHelper::OnFavIconData( if (!know_favicon || expired) { // We don't know the favicon, or it is out of date. Request the current one. - ScheduleDownload(entry); + ScheduleDownload(entry->url(), entry->favicon().url(), kFavIconSize, NULL); } } -void FavIconHelper::ScheduleDownload(NavigationEntry* entry) { +int FavIconHelper::ScheduleDownload(const GURL& url, + const GURL& image_url, + int image_size, + ImageDownloadCallback* callback) { const int download_id = tab_contents_->render_view_host()->DownloadFavIcon( - entry->favicon().url(), kFavIconSize); - if (!download_id) - return; // Download request failed. - - // Download ids should be unique. - DCHECK(download_requests_.find(download_id) == download_requests_.end()); - download_requests_[download_id] = - DownloadRequest(entry->url(), entry->favicon().url()); + image_url, image_size); + + if (download_id) { + // Download ids should be unique. + DCHECK(download_requests_.find(download_id) == download_requests_.end()); + download_requests_[download_id] = DownloadRequest(url, image_url, callback); + } + + return download_id; } SkBitmap FavIconHelper::ConvertToFavIconSize(const SkBitmap& image) { diff --git a/chrome/browser/fav_icon_helper.h b/chrome/browser/fav_icon_helper.h index d6d1867..39c3991 100644 --- a/chrome/browser/fav_icon_helper.h +++ b/chrome/browser/fav_icon_helper.h @@ -67,36 +67,35 @@ class TabContents; class FavIconHelper : public RenderViewHostDelegate::FavIcon { public: explicit FavIconHelper(TabContents* tab_contents); + virtual ~FavIconHelper(); // Initiates loading the favicon for the specified url. void FetchFavIcon(const GURL& url); - // Sets the image data for the favicon. This is invoked asynchronously after - // we request the TabContents to download the favicon. - void SetFavIcon(int download_id, - const GURL& icon_url, - const SkBitmap& image); - - // Invoked when a request to download the favicon failed. - void FavIconDownloadFailed(int download_id); - - // Converts the image data to an SkBitmap and sets it on the NavigationEntry. - // If the TabContents has a delegate, it is notified of the new favicon - // (INVALIDATE_FAVICON). - void UpdateFavIcon(NavigationEntry* entry, - const std::vector<unsigned char>& data); - void UpdateFavIcon(NavigationEntry* entry, const SkBitmap& image); + // Initiates loading an image from given |image_url|. Returns a download id + // for caller to track the request. When download completes, |callback| is + // called with the three params: the download_id, a boolean flag to indicate + // whether the download succeeds and a SkBitmap as the downloaded image. + // Note that |image_size| is a hint for images with multiple sizes. The + // downloaded image is not resized to the given image_size. If 0 is passed, + // the first frame of the image is returned. + typedef Callback3<int, bool, const SkBitmap&>::Type ImageDownloadCallback; + int DownloadImage(const GURL& image_url, int image_size, + ImageDownloadCallback* callback); private: struct DownloadRequest { DownloadRequest() {} DownloadRequest(const GURL& url, - const GURL& fav_icon_url) + const GURL& image_url, + ImageDownloadCallback* callback) : url(url), - fav_icon_url(fav_icon_url) { } + image_url(image_url), + callback(callback) { } GURL url; - GURL fav_icon_url; + GURL image_url; + ImageDownloadCallback* callback; }; // RenderViewHostDelegate::Favicon implementation. @@ -138,7 +137,19 @@ class FavIconHelper : public RenderViewHostDelegate::FavIcon { // Schedules a download for the specified entry. This adds the request to // download_requests_. - void ScheduleDownload(NavigationEntry* entry); + int ScheduleDownload(const GURL& url, const GURL& image_url, int image_size, + ImageDownloadCallback* callback); + + // Sets the image data for the favicon. This is invoked asynchronously after + // we request the TabContents to download the favicon. + void SetFavIcon(const GURL& url, const GURL& icon_url, const SkBitmap& image); + + // Converts the image data to an SkBitmap and sets it on the NavigationEntry. + // If the TabContents has a delegate, it is notified of the new favicon + // (INVALIDATE_FAVICON). + void UpdateFavIcon(NavigationEntry* entry, + const std::vector<unsigned char>& data); + void UpdateFavIcon(NavigationEntry* entry, const SkBitmap& image); // Scales the image such that either the width and/or height is 16 pixels // wide. Does nothing if the image is empty. diff --git a/chrome/browser/tab_contents/tab_contents.cc b/chrome/browser/tab_contents/tab_contents.cc index 2d62549..fd97827 100644 --- a/chrome/browser/tab_contents/tab_contents.cc +++ b/chrome/browser/tab_contents/tab_contents.cc @@ -221,7 +221,6 @@ TabContents::TabContents(Profile* profile, registrar_(), ALLOW_THIS_IN_INITIALIZER_LIST(printing_(*this)), save_package_(), - cancelable_consumer_(), form_field_history_manager_(), autofill_manager_(), password_manager_(), diff --git a/chrome/browser/tab_contents/tab_contents.h b/chrome/browser/tab_contents/tab_contents.h index 18f0d44..2a84b8c 100644 --- a/chrome/browser/tab_contents/tab_contents.h +++ b/chrome/browser/tab_contents/tab_contents.h @@ -175,6 +175,11 @@ class TabContents : public PageNavigator, return view_.get(); } + // Returns the FavIconHelper of this TabContents. + FavIconHelper& fav_icon_helper() { + return fav_icon_helper_; + } + #ifdef UNIT_TEST // Expose the render manager for testing. RenderViewHostManager* render_manager() { return &render_manager_; } @@ -994,11 +999,6 @@ class TabContents : public PageNavigator, // SavePackage, lazily created. scoped_refptr<SavePackage> save_package_; - // Tracks our pending CancelableRequests. This maps pending requests to - // page IDs so that we know whether a given callback still applies. The - // page ID -1 means no page ID was set. - CancelableRequestConsumerT<int32, -1> cancelable_consumer_; - // FormFieldHistoryManager, lazily created. scoped_ptr<FormFieldHistoryManager> form_field_history_manager_; diff --git a/chrome/browser/views/create_application_shortcut_view.cc b/chrome/browser/views/create_application_shortcut_view.cc index 0736d55..6551d15 100644 --- a/chrome/browser/views/create_application_shortcut_view.cc +++ b/chrome/browser/views/create_application_shortcut_view.cc @@ -35,12 +35,6 @@ namespace { const int kAppIconSize = 32; -bool IconPrecedes( - const webkit_glue::WebApplicationInfo::IconInfo& left, - const webkit_glue::WebApplicationInfo::IconInfo& right) { - return left.width < right.width; -} - // AppInfoView shows the application icon and title. class AppInfoView : public views::View { public: @@ -203,37 +197,52 @@ void ShowCreateShortcutsDialog(gfx::NativeWindow parent_window, }; // namespace browser +class CreateApplicationShortcutView::IconDownloadCallbackFunctor { + public: + explicit IconDownloadCallbackFunctor(CreateApplicationShortcutView* owner) + : owner_(owner) { + } + + void Run(int download_id, bool errored, const SkBitmap& image) { + if (owner_) + owner_->OnIconDownloaded(errored, image); + delete this; + } + + void Cancel() { + owner_ = NULL; + } + + private: + CreateApplicationShortcutView* owner_; +}; + CreateApplicationShortcutView::CreateApplicationShortcutView( TabContents* tab_contents) - : tab_contents_(tab_contents) { + : tab_contents_(tab_contents), + pending_download_(NULL) { Init(); } CreateApplicationShortcutView::~CreateApplicationShortcutView() { + if (pending_download_) + pending_download_->Cancel(); } void CreateApplicationShortcutView::Init() { // Prepare data + web_app::GetShortcutInfoForTab(tab_contents_, &shortcut_info_); + const webkit_glue::WebApplicationInfo& app_info = tab_contents_->web_app_info(); - - url_ = app_info.app_url.is_empty() ? tab_contents_->GetURL() : - app_info.app_url; - title_ = app_info.title.empty() ? tab_contents_->GetTitle() : - app_info.title; - description_ = app_info.description; - - icon_ = tab_contents_->GetFavIcon(); if (!app_info.icons.empty()) { - SetIconsInfo(app_info.icons); + web_app::GetIconsInfo(app_info, &unprocessed_icons_); FetchIcon(); } - if (title_.empty()) - title_ = UTF8ToUTF16(url_.spec()); - // Create controls - app_info_ = new AppInfoView(title_, description_, icon_); + app_info_ = new AppInfoView(shortcut_info_.title, shortcut_info_.description, + shortcut_info_.favicon); create_shortcuts_label_ = new views::Label( l10n_util::GetString(IDS_CREATE_SHORTCUTS_LABEL)); create_shortcuts_label_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); @@ -359,27 +368,22 @@ bool CreateApplicationShortcutView::Accept() { return false; } - ShellIntegration::ShortcutInfo shortcut_info; - shortcut_info.url = url_; - shortcut_info.title = title_; - shortcut_info.description = description_; - shortcut_info.favicon = icon_; - shortcut_info.create_on_desktop = desktop_check_box_->checked(); - shortcut_info.create_in_applications_menu = menu_check_box_ == NULL ? false : + shortcut_info_.create_on_desktop = desktop_check_box_->checked(); + shortcut_info_.create_in_applications_menu = menu_check_box_ == NULL ? false : menu_check_box_->checked(); #if defined(OS_WIN) - shortcut_info.create_in_quick_launch_bar = quick_launch_check_box_ == NULL ? + shortcut_info_.create_in_quick_launch_bar = quick_launch_check_box_ == NULL ? NULL : quick_launch_check_box_->checked(); #elif defined(OS_POSIX) // Create shortcut in Mac dock or as Linux (gnome/kde) application launcher // are not implemented yet. - shortcut_info.create_in_quick_launch_bar = false; + shortcut_info_.create_in_quick_launch_bar = false; #endif - web_app::CreateShortcut( - tab_contents_->profile()->GetPath().Append(chrome::kWebAppDirname), - shortcut_info, NULL); + web_app::CreateShortcut(web_app::GetDataDir(tab_contents_->profile()), + shortcut_info_, + NULL); if (tab_contents_->delegate()) tab_contents_->delegate()->ConvertContentsToApplication(tab_contents_); @@ -398,39 +402,25 @@ views::Checkbox* CreateApplicationShortcutView::AddCheckbox( return checkbox; } -void CreateApplicationShortcutView::SetIconsInfo(const IconInfoList& icons) { - unprocessed_icons_.clear(); - for (size_t i = 0; i < icons.size(); ++i) { - // We only take square shaped icons (i.e. width == height). - if (icons[i].width == icons[i].height) { - unprocessed_icons_.push_back(icons[i]); - } - } - - std::sort(unprocessed_icons_.begin(), unprocessed_icons_.end(), - &IconPrecedes); -} - void CreateApplicationShortcutView::FetchIcon() { // There should only be fetch job at a time. - DCHECK(icon_fetcher_ == NULL); + DCHECK(pending_download_ == NULL); if (unprocessed_icons_.empty()) { // No icons to fetch. return; } - icon_fetcher_.reset(new URLFetcher(unprocessed_icons_.back().url, - URLFetcher::GET, - this)); - DCHECK(icon_fetcher_.get() != NULL); - unprocessed_icons_.pop_back(); + pending_download_ = new IconDownloadCallbackFunctor(this); + DCHECK(pending_download_); + + tab_contents_->fav_icon_helper().DownloadImage( + unprocessed_icons_.back().url, + std::max(unprocessed_icons_.back().width, + unprocessed_icons_.back().height), + NewCallback(pending_download_, &IconDownloadCallbackFunctor::Run)); - icon_fetcher_->set_load_flags(icon_fetcher_->load_flags() | - net::LOAD_IS_DOWNLOAD); - icon_fetcher_->set_request_context( - tab_contents_->profile()->GetRequestContext()); - icon_fetcher_->Start(); + unprocessed_icons_.pop_back(); } void CreateApplicationShortcutView::ButtonPressed(views::Button* sender, @@ -450,27 +440,14 @@ void CreateApplicationShortcutView::ButtonPressed(views::Button* sender, GetDialogClientView()->UpdateDialogButtons(); } -void CreateApplicationShortcutView::OnURLFetchComplete(const URLFetcher* source, - const GURL& url, - const URLRequestStatus& status, - int response_code, - const ResponseCookies& cookies, - const std::string& data) { - // Delete the fetcher on this function's exit. - scoped_ptr<URLFetcher> clean_up_fetcher(icon_fetcher_.release()); - - bool success = status.is_success() && (response_code == 200) && !data.empty(); - - if (success) { - success = gfx::PNGCodec::Decode( - reinterpret_cast<const unsigned char*>(data.c_str()), - data.size(), - &icon_); - - if (success) - static_cast<AppInfoView*>(app_info_)->UpdateIcon(icon_); - } +void CreateApplicationShortcutView::OnIconDownloaded(bool errored, + const SkBitmap& image) { + pending_download_ = NULL; - if (!success) + if (!errored && !image.isNull()) { + shortcut_info_.favicon = image; + static_cast<AppInfoView*>(app_info_)->UpdateIcon(shortcut_info_.favicon); + } else { FetchIcon(); + } } diff --git a/chrome/browser/views/create_application_shortcut_view.h b/chrome/browser/views/create_application_shortcut_view.h index 28972ca..8daefd1 100644 --- a/chrome/browser/views/create_application_shortcut_view.h +++ b/chrome/browser/views/create_application_shortcut_view.h @@ -8,13 +8,11 @@ #include <string> #include <vector> -#include "chrome/browser/net/url_fetcher.h" #include "chrome/browser/web_applications/web_app.h" #include "views/controls/label.h" #include "views/view.h" #include "views/window/dialog_delegate.h" #include "third_party/skia/include/core/SkBitmap.h" -#include "webkit/glue/dom_operations.h" namespace views { class Checkbox; @@ -30,8 +28,7 @@ class TabContents; // the shortcut for given web app. class CreateApplicationShortcutView : public views::View, public views::DialogDelegate, - public views::ButtonListener, - public URLFetcher::Delegate { + public views::ButtonListener { public: explicit CreateApplicationShortcutView(TabContents* tab_contents); virtual ~CreateApplicationShortcutView(); @@ -59,27 +56,17 @@ class CreateApplicationShortcutView : public views::View, // Overridden from views::ButtonListener: virtual void ButtonPressed(views::Button* sender, const views::Event& event); - // URLFetcher::Delegate - virtual void OnURLFetchComplete(const URLFetcher* source, - const GURL& url, - const URLRequestStatus& status, - int response_code, - const ResponseCookies& cookies, - const std::string& data); - private: - typedef std::vector<webkit_glue::WebApplicationInfo::IconInfo> IconInfoList; - // Adds a new check-box as a child to the view. views::Checkbox* AddCheckbox(const std::wstring& text, bool checked); - // Set icons info from passed-in WebApplicationInfo. - void SetIconsInfo(const IconInfoList& icons); - // Fetch the largest unprocessed icon. // The first largest icon downloaded and decoded successfully will be used. void FetchIcon(); + // Callback of icon download. + void OnIconDownloaded(bool errored, const SkBitmap& image); + // TabContents of the page that we want to create shortcut. TabContents* tab_contents_; @@ -90,23 +77,15 @@ class CreateApplicationShortcutView : public views::View, views::Checkbox* menu_check_box_; views::Checkbox* quick_launch_check_box_; - // Target url of the app shortcut. - GURL url_; - - // Title of the app shortcut. - string16 title_; - - // Description of the app shortcut. - string16 description_; - - // Icon of the app shortcut. - SkBitmap icon_; + // Target shortcut info. + ShellIntegration::ShortcutInfo shortcut_info_; // Unprocessed icons from the WebApplicationInfo passed in. - IconInfoList unprocessed_icons_; + web_app::IconInfoList unprocessed_icons_; - // Icon fetcher. - scoped_ptr<URLFetcher> icon_fetcher_; + // Pending app icon download tracked by us. + class IconDownloadCallbackFunctor; + IconDownloadCallbackFunctor* pending_download_; DISALLOW_COPY_AND_ASSIGN(CreateApplicationShortcutView); }; diff --git a/chrome/browser/web_applications/web_app.cc b/chrome/browser/web_applications/web_app.cc index f15f95d..4184501 100644 --- a/chrome/browser/web_applications/web_app.cc +++ b/chrome/browser/web_applications/web_app.cc @@ -4,15 +4,27 @@ #include "chrome/browser/web_applications/web_app.h" +#if defined(OS_WIN) +#include <ShellAPI.h> +#endif // defined(OS_WIN) + #include "base/file_util.h" +#include "base/md5.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/thread.h" +#include "base/scoped_ptr.h" #include "base/string_util.h" -#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_plugin_util.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" #include "chrome/common/url_constants.h" +#include "webkit/glue/dom_operations.h" #if defined(OS_WIN) #include "app/gfx/icon_util.h" @@ -21,6 +33,8 @@ namespace { +const FilePath::CharType kIconChecksumFileExt[] = FILE_PATH_LITERAL(".ico.md5"); + // Returns true if |ch| is in visible ASCII range and not one of // "/ \ : * ? " < > | ; ,". bool IsValidFilePathChar(char16 c) { @@ -88,6 +102,80 @@ FilePath GetWebAppDataDirectory(const FilePath& root_dir, return root_dir.Append(GetWebAppDir(url)); } +// Predicator for sorting images from largest to smallest. +bool IconPrecedes( + const webkit_glue::WebApplicationInfo::IconInfo& left, + const webkit_glue::WebApplicationInfo::IconInfo& right) { + return left.width < right.width; +} + +// Calculates image checksum using MD5. +void GetImageCheckSum(const SkBitmap& image, MD5Digest* digest) { + DCHECK(digest); + + SkAutoLockPixels image_lock(image); + MD5Sum(image.getPixels(), image.getSize(), digest); +} + +#if defined(OS_WIN) +// Saves |image| as an |icon_file| with the checksum. +bool SaveIconWithCheckSum(const FilePath& icon_file, const SkBitmap& image) { + if (!IconUtil::CreateIconFileFromSkBitmap(image, icon_file.value())) + return false; + + MD5Digest digest; + GetImageCheckSum(image, &digest); + + FilePath cheksum_file(icon_file.ReplaceExtension(kIconChecksumFileExt)); + return file_util::WriteFile(cheksum_file, + reinterpret_cast<const char*>(&digest), + sizeof(digest)) == sizeof(digest); +} + +// Returns true if |icon_file| is missing or different from |image|. +bool ShouldUpdateIcon(const FilePath& icon_file, const SkBitmap& image) { + FilePath checksum_file(icon_file.ReplaceExtension(kIconChecksumFileExt)); + + // Returns true if icon_file or checksum file is missing. + if (!file_util::PathExists(icon_file) || + !file_util::PathExists(checksum_file)) + return true; + + MD5Digest persisted_image_checksum; + if (sizeof(persisted_image_checksum) != file_util::ReadFile(checksum_file, + reinterpret_cast<char*>(&persisted_image_checksum), + sizeof(persisted_image_checksum))) + return true; + + MD5Digest downloaded_image_checksum; + GetImageCheckSum(image, &downloaded_image_checksum); + + // Update icon if checksums are not equal. + return memcmp(&persisted_image_checksum, &downloaded_image_checksum, + sizeof(MD5Digest)) != 0; +} + +// Saves |image| to |icon_file| if the file is outdated and refresh shell's +// icon cache to ensure correct icon is displayed. Returns true if icon_file +// is up to date or successfully updated. +bool CheckAndSaveIcon(const FilePath& icon_file, const SkBitmap& image) { + if (ShouldUpdateIcon(icon_file, image)) { + if (SaveIconWithCheckSum(icon_file, image)) { + // Refresh shell's icon cache. This call is quite disruptive as user would + // see explorer rebuilding the icon cache. It would be great that we find + // a better way to achieve this. + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT, + NULL, NULL); + } else { + return false; + } + } + + return true; +} + +#endif // defined(OS_WIN) + // Represents a task that creates web application shortcut. This runs on // file thread and schedules the callback (if any) on the calling thread // when finished (either success or failure). @@ -122,7 +210,7 @@ class CreateShortcutTask : public Task { // Returns true if shortcut is created successfully. bool CreateShortcut(); - // Path to store persisted data for web_app. + // Path to store persisted data for web app. FilePath web_app_path_; // Our copy of short cut data. @@ -131,6 +219,8 @@ class CreateShortcutTask : public Task { // Callback when task is finished. web_app::CreateShortcutCallback* callback_; MessageLoop* message_loop_; + + DISALLOW_COPY_AND_ASSIGN(CreateShortcutTask); }; CreateShortcutTask::CreateShortcutTask( @@ -176,7 +266,7 @@ bool CreateShortcutTask::CreateShortcut() { NULL }, { shortcut_info_.create_in_quick_launch_bar, - // For Win7, create_in_quick_launch_bar means pining to taskbar. Use + // For Win7, create_in_quick_launch_bar means pinning to taskbar. Use // base::PATH_START as a flag for this case. (win_util::GetWinVersion() >= win_util::WINVERSION_WIN7) ? base::PATH_START : base::DIR_APP_DATA, @@ -236,8 +326,7 @@ bool CreateShortcutTask::CreateShortcut() { // Creates an ico file to use with shortcut. FilePath icon_file = web_app_path_.Append(file_name).ReplaceExtension( FILE_PATH_LITERAL(".ico")); - if (!IconUtil::CreateIconFileFromSkBitmap(shortcut_info_.favicon, - icon_file.value())) { + if (!CheckAndSaveIcon(icon_file, shortcut_info_.favicon)) { NOTREACHED(); return false; } @@ -288,8 +377,241 @@ bool CreateShortcutTask::CreateShortcut() { #endif } +#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(TabContents* 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 TabContents whose shortcuts will be updated. + TabContents* 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_; + + // Root dir of web app data. + FilePath root_dir_; + + // 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(TabContents* tab_contents) + : tab_contents_(tab_contents), + root_dir_(web_app::GetDataDir(tab_contents->profile())) { + web_app::GetShortcutInfoForTab(tab_contents_, &shortcut_info_); + web_app::GetIconsInfo(tab_contents_->web_app_info(), &unprocessed_icons_); + file_name_ = 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(ChromeThread::CurrentlyOn(ChromeThread::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_->fav_icon_helper().DownloadImage( + unprocessed_icons_.back().url, + std::max(unprocessed_icons_.back().width, + unprocessed_icons_.back().height), + 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; + UpdateShortcuts(); + } else { + // Try the next icon otherwise. + DownloadIcon(); + } +} + +void UpdateShortcutWorker::CheckExistingShortcuts() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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, + (win_util::GetWinVersion() >= win_util::WINVERSION_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() { + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod(this, + &UpdateShortcutWorker::UpdateShortcutsOnFileThread)); +} + +void UpdateShortcutWorker::UpdateShortcutsOnFileThread() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + + FilePath web_app_path = GetWebAppDataDirectory(root_dir_, shortcut_info_.url); + FilePath icon_file = web_app_path.Append(file_name_).ReplaceExtension( + FILE_PATH_LITERAL(".ico")); + CheckAndSaveIcon(icon_file, shortcut_info_.favicon); + + CheckExistingShortcuts(); + if (shortcut_files_.empty()) { + // No shortcuts to update. + OnShortcutsUpdated(true); + } else { + // Re-create shortcuts to make sure application url, name and description + // are up to date + web_app::CreateShortcut(root_dir_, shortcut_info_, + NewCallback(this, &UpdateShortcutWorker::OnShortcutsUpdated)); + } +} + +void UpdateShortcutWorker::OnShortcutsUpdated(bool) { + DeleteMe(); // We are done. +} + +void UpdateShortcutWorker::DeleteMe() { + if (ChromeThread::CurrentlyOn(ChromeThread::UI)) { + DeleteMeOnUIThread(); + } else { + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &UpdateShortcutWorker::DeleteMeOnUIThread)); + } +} + +void UpdateShortcutWorker::DeleteMeOnUIThread() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::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. +template <> +struct RunnableMethodTraits<UpdateShortcutWorker> { + void RetainCallee(UpdateShortcutWorker* worker) {} + void ReleaseCallee(UpdateShortcutWorker* worker) {} +}; +#endif // defined(OS_WIN) + namespace web_app { std::wstring GenerateApplicationNameFromURL(const GURL& url) { @@ -304,7 +626,7 @@ void CreateShortcut( const FilePath& data_dir, const ShellIntegration::ShortcutInfo& shortcut_info, CreateShortcutCallback* callback) { - g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, new CreateShortcutTask(data_dir, shortcut_info, callback)); } @@ -324,4 +646,49 @@ bool IsValidUrl(const GURL& url) { return false; } +FilePath GetDataDir(Profile* profile) { + DCHECK(profile); + return profile->GetPath().Append(chrome::kWebAppDirname); +} + +void GetIconsInfo(const webkit_glue::WebApplicationInfo& app_info, + IconInfoList* icons) { + DCHECK(icons); + + icons->clear(); + for (size_t i = 0; i < app_info.icons.size(); ++i) { + // We only take square shaped icons (i.e. width == height). + if (app_info.icons[i].width == app_info.icons[i].height) { + icons->push_back(app_info.icons[i]); + } + } + + std::sort(icons->begin(), icons->end(), &IconPrecedes); +} + +void GetShortcutInfoForTab(TabContents* tab_contents, + ShellIntegration::ShortcutInfo* info) { + DCHECK(info); // Must provide a valid info. + + const webkit_glue::WebApplicationInfo& app_info = + tab_contents->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(TabContents* 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 diff --git a/chrome/browser/web_applications/web_app.h b/chrome/browser/web_applications/web_app.h index 9bcfe0f..28b1e9c 100644 --- a/chrome/browser/web_applications/web_app.h +++ b/chrome/browser/web_applications/web_app.h @@ -8,6 +8,10 @@ #include "base/file_path.h" #include "base/task.h" #include "chrome/browser/shell_integration.h" +#include "webkit/glue/dom_operations.h" + +class Profile; +class TabContents; namespace web_app { @@ -33,6 +37,26 @@ void CreateShortcut( // Returns true if given url is a valid web app url. bool IsValidUrl(const GURL& url); +// Returns data dir for web apps for given profile. +FilePath GetDataDir(Profile* profile); + +// Extracts icons info from web app data. Take only square shaped icons and +// sort them from smallest to largest. +typedef std::vector<webkit_glue::WebApplicationInfo::IconInfo> IconInfoList; +void GetIconsInfo(const webkit_glue::WebApplicationInfo& app_info, + IconInfoList* icons); + +// Extracts shortcut info of given TabContents. +void GetShortcutInfoForTab(TabContents* tab_contents, + ShellIntegration::ShortcutInfo* info); + +// Updates web app shortcut of the TabContents. This function checks and +// updates web app icon and shortcuts if needed. For icon, the check is based +// on MD5 hash of icon image. For shortcuts, it checks the desktop, start menu +// and quick launch (as well as pinned shortcut) for shortcut and only +// updates (recreates) them if they exits. +void UpdateShortcutForTabContents(TabContents* tab_contents); + }; // namespace web_app #endif // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_H_ |