summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorxiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-11 06:13:51 +0000
committerxiyuan@chromium.org <xiyuan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-12-11 06:13:51 +0000
commiteabfdae9101aa33a212fdaf240d8e0af70874e16 (patch)
tree4b27ea5ae52ce96ccfaab87960d067fc1b58968c /chrome
parentffb01d3e998a32156790af7c1367d733bb9e75dd (diff)
downloadchromium_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.cc42
-rw-r--r--chrome/browser/browser.h12
-rw-r--r--chrome/browser/fav_icon_helper.cc85
-rw-r--r--chrome/browser/fav_icon_helper.h49
-rw-r--r--chrome/browser/tab_contents/tab_contents.cc1
-rw-r--r--chrome/browser/tab_contents/tab_contents.h10
-rw-r--r--chrome/browser/views/create_application_shortcut_view.cc131
-rw-r--r--chrome/browser/views/create_application_shortcut_view.h41
-rw-r--r--chrome/browser/web_applications/web_app.cc379
-rw-r--r--chrome/browser/web_applications/web_app.h24
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_