summaryrefslogtreecommitdiffstats
path: root/chrome/browser/web_applications
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/browser/web_applications
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/browser/web_applications')
-rw-r--r--chrome/browser/web_applications/web_app.cc379
-rw-r--r--chrome/browser/web_applications/web_app.h24
2 files changed, 397 insertions, 6 deletions
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_