Upstream oodles of Chrome for Android code into Chromium.
[chromium-blink-merge.git] / chrome / browser / jumplist_win.cc
blobf1e8a879b115547292fa55884733de91cb83c669
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/jumplist_win.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/command_line.h"
10 #include "base/files/file_util.h"
11 #include "base/path_service.h"
12 #include "base/prefs/pref_change_registrar.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/thread.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/favicon/favicon_service_factory.h"
18 #include "chrome/browser/history/top_sites_factory.h"
19 #include "chrome/browser/metrics/jumplist_metrics_win.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/sessions/tab_restore_service.h"
22 #include "chrome/browser/sessions/tab_restore_service_factory.h"
23 #include "chrome/browser/shell_integration.h"
24 #include "chrome/common/chrome_constants.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/common/pref_names.h"
27 #include "chrome/common/url_constants.h"
28 #include "chrome/grit/generated_resources.h"
29 #include "components/favicon/core/favicon_service.h"
30 #include "components/favicon_base/favicon_types.h"
31 #include "components/history/core/browser/history_service.h"
32 #include "components/history/core/browser/page_usage_data.h"
33 #include "components/history/core/browser/top_sites.h"
34 #include "components/sessions/session_types.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/notification_registrar.h"
37 #include "content/public/browser/notification_source.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/gfx/codec/png_codec.h"
40 #include "ui/gfx/favicon_size.h"
41 #include "ui/gfx/icon_util.h"
42 #include "ui/gfx/image/image_family.h"
43 #include "url/gurl.h"
45 using content::BrowserThread;
47 namespace {
49 // Append the common switches to each shell link.
50 void AppendCommonSwitches(ShellLinkItem* shell_link) {
51 const char* kSwitchNames[] = { switches::kUserDataDir };
52 const base::CommandLine& command_line =
53 *base::CommandLine::ForCurrentProcess();
54 shell_link->GetCommandLine()->CopySwitchesFrom(command_line,
55 kSwitchNames,
56 arraysize(kSwitchNames));
59 // Create a ShellLinkItem preloaded with common switches.
60 scoped_refptr<ShellLinkItem> CreateShellLink() {
61 scoped_refptr<ShellLinkItem> link(new ShellLinkItem);
62 AppendCommonSwitches(link.get());
63 return link;
66 // Creates a temporary icon file to be shown in JumpList.
67 bool CreateIconFile(const SkBitmap& bitmap,
68 const base::FilePath& icon_dir,
69 base::FilePath* icon_path) {
70 // Retrieve the path to a temporary file.
71 // We don't have to care about the extension of this temporary file because
72 // JumpList does not care about it.
73 base::FilePath path;
74 if (!base::CreateTemporaryFileInDir(icon_dir, &path))
75 return false;
77 // Create an icon file from the favicon attached to the given |page|, and
78 // save it as the temporary file.
79 gfx::ImageFamily image_family;
80 image_family.Add(gfx::Image::CreateFrom1xBitmap(bitmap));
81 if (!IconUtil::CreateIconFileFromImageFamily(image_family, path))
82 return false;
84 // Add this icon file to the list and return its absolute path.
85 // The IShellLink::SetIcon() function needs the absolute path to an icon.
86 *icon_path = path;
87 return true;
90 // Updates the "Tasks" category of the JumpList.
91 bool UpdateTaskCategory(
92 JumpListUpdater* jumplist_updater,
93 IncognitoModePrefs::Availability incognito_availability) {
94 base::FilePath chrome_path;
95 if (!PathService::Get(base::FILE_EXE, &chrome_path))
96 return false;
98 ShellLinkItemList items;
100 // Create an IShellLink object which launches Chrome, and add it to the
101 // collection. We use our application icon as the icon for this item.
102 // We remove '&' characters from this string so we can share it with our
103 // system menu.
104 if (incognito_availability != IncognitoModePrefs::FORCED) {
105 scoped_refptr<ShellLinkItem> chrome = CreateShellLink();
106 base::string16 chrome_title = l10n_util::GetStringUTF16(IDS_NEW_WINDOW);
107 ReplaceSubstringsAfterOffset(&chrome_title, 0, L"&", L"");
108 chrome->set_title(chrome_title);
109 chrome->set_icon(chrome_path.value(), 0);
110 items.push_back(chrome);
113 // Create an IShellLink object which launches Chrome in incognito mode, and
114 // add it to the collection. We use our application icon as the icon for
115 // this item.
116 if (incognito_availability != IncognitoModePrefs::DISABLED) {
117 scoped_refptr<ShellLinkItem> incognito = CreateShellLink();
118 incognito->GetCommandLine()->AppendSwitch(switches::kIncognito);
119 base::string16 incognito_title =
120 l10n_util::GetStringUTF16(IDS_NEW_INCOGNITO_WINDOW);
121 ReplaceSubstringsAfterOffset(&incognito_title, 0, L"&", L"");
122 incognito->set_title(incognito_title);
123 incognito->set_icon(chrome_path.value(), 0);
124 items.push_back(incognito);
127 return jumplist_updater->AddTasks(items);
130 // Updates the application JumpList.
131 bool UpdateJumpList(const wchar_t* app_id,
132 const ShellLinkItemList& most_visited_pages,
133 const ShellLinkItemList& recently_closed_pages,
134 IncognitoModePrefs::Availability incognito_availability) {
135 // JumpList is implemented only on Windows 7 or later.
136 // So, we should return now when this function is called on earlier versions
137 // of Windows.
138 if (!JumpListUpdater::IsEnabled())
139 return true;
141 JumpListUpdater jumplist_updater(app_id);
142 if (!jumplist_updater.BeginUpdate())
143 return false;
145 // We allocate 60% of the given JumpList slots to "most-visited" items
146 // and 40% to "recently-closed" items, respectively.
147 // Nevertheless, if there are not so many items in |recently_closed_pages|,
148 // we give the remaining slots to "most-visited" items.
149 const int kMostVisited = 60;
150 const int kRecentlyClosed = 40;
151 const int kTotal = kMostVisited + kRecentlyClosed;
152 size_t most_visited_items =
153 MulDiv(jumplist_updater.user_max_items(), kMostVisited, kTotal);
154 size_t recently_closed_items =
155 jumplist_updater.user_max_items() - most_visited_items;
156 if (recently_closed_pages.size() < recently_closed_items) {
157 most_visited_items += recently_closed_items - recently_closed_pages.size();
158 recently_closed_items = recently_closed_pages.size();
161 // Update the "Most Visited" category of the JumpList if it exists.
162 // This update request is applied into the JumpList when we commit this
163 // transaction.
164 if (!jumplist_updater.AddCustomCategory(
165 l10n_util::GetStringUTF16(IDS_NEW_TAB_MOST_VISITED),
166 most_visited_pages, most_visited_items)) {
167 return false;
170 // Update the "Recently Closed" category of the JumpList.
171 if (!jumplist_updater.AddCustomCategory(
172 l10n_util::GetStringUTF16(IDS_RECENTLY_CLOSED),
173 recently_closed_pages, recently_closed_items)) {
174 return false;
177 // Update the "Tasks" category of the JumpList.
178 if (!UpdateTaskCategory(&jumplist_updater, incognito_availability))
179 return false;
181 // Commit this transaction and send the updated JumpList to Windows.
182 if (!jumplist_updater.CommitUpdate())
183 return false;
185 return true;
188 } // namespace
190 JumpList::JumpList(Profile* profile)
191 : profile_(profile),
192 task_id_(base::CancelableTaskTracker::kBadTaskId),
193 weak_ptr_factory_(this) {
194 DCHECK(Enabled());
195 // To update JumpList when a tab is added or removed, we add this object to
196 // the observer list of the TabRestoreService class.
197 // When we add this object to the observer list, we save the pointer to this
198 // TabRestoreService object. This pointer is used when we remove this object
199 // from the observer list.
200 TabRestoreService* tab_restore_service =
201 TabRestoreServiceFactory::GetForProfile(profile_);
202 if (!tab_restore_service)
203 return;
205 app_id_ = ShellIntegration::GetChromiumModelIdForProfile(profile_->GetPath());
206 icon_dir_ = profile_->GetPath().Append(chrome::kJumpListIconDirname);
208 scoped_refptr<history::TopSites> top_sites =
209 TopSitesFactory::GetForProfile(profile_);
210 if (top_sites) {
211 // TopSites updates itself after a delay. This is especially noticable when
212 // your profile is empty. Ask TopSites to update itself when jumplist is
213 // initialized.
214 top_sites->SyncWithHistory();
215 registrar_.reset(new content::NotificationRegistrar);
216 // Register as TopSitesObserver so that we can update ourselves when the
217 // TopSites changes.
218 top_sites->AddObserver(this);
219 // Register for notification when profile is destroyed to ensure that all
220 // observers are detatched at that time.
221 registrar_->Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
222 content::Source<Profile>(profile_));
224 tab_restore_service->AddObserver(this);
225 pref_change_registrar_.reset(new PrefChangeRegistrar);
226 pref_change_registrar_->Init(profile_->GetPrefs());
227 pref_change_registrar_->Add(
228 prefs::kIncognitoModeAvailability,
229 base::Bind(&JumpList::OnIncognitoAvailabilityChanged, this));
232 JumpList::~JumpList() {
233 Terminate();
236 // static
237 bool JumpList::Enabled() {
238 return JumpListUpdater::IsEnabled();
241 void JumpList::Observe(int type,
242 const content::NotificationSource& source,
243 const content::NotificationDetails& details) {
244 DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
245 // Profile was destroyed, do clean-up.
246 Terminate();
249 void JumpList::CancelPendingUpdate() {
250 if (task_id_ != base::CancelableTaskTracker::kBadTaskId) {
251 cancelable_task_tracker_.TryCancel(task_id_);
252 task_id_ = base::CancelableTaskTracker::kBadTaskId;
256 void JumpList::Terminate() {
257 CancelPendingUpdate();
258 if (profile_) {
259 TabRestoreService* tab_restore_service =
260 TabRestoreServiceFactory::GetForProfile(profile_);
261 if (tab_restore_service)
262 tab_restore_service->RemoveObserver(this);
263 scoped_refptr<history::TopSites> top_sites =
264 TopSitesFactory::GetForProfile(profile_);
265 if (top_sites)
266 top_sites->RemoveObserver(this);
267 registrar_.reset();
268 pref_change_registrar_.reset();
270 profile_ = NULL;
273 void JumpList::OnMostVisitedURLsAvailable(
274 const history::MostVisitedURLList& data) {
275 // If we have a pending favicon request, cancel it here (it is out of date).
276 CancelPendingUpdate();
279 base::AutoLock auto_lock(list_lock_);
280 most_visited_pages_.clear();
281 for (size_t i = 0; i < data.size(); i++) {
282 const history::MostVisitedURL& url = data[i];
283 scoped_refptr<ShellLinkItem> link = CreateShellLink();
284 std::string url_string = url.url.spec();
285 std::wstring url_string_wide = base::UTF8ToWide(url_string);
286 link->GetCommandLine()->AppendArgNative(url_string_wide);
287 link->GetCommandLine()->AppendSwitchASCII(
288 switches::kWinJumplistAction, jumplist::kMostVisitedCategory);
289 link->set_title(!url.title.empty()? url.title : url_string_wide);
290 most_visited_pages_.push_back(link);
291 icon_urls_.push_back(make_pair(url_string, link));
295 // Send a query that retrieves the first favicon.
296 StartLoadingFavicon();
299 void JumpList::TabRestoreServiceChanged(TabRestoreService* service) {
300 // if we have a pending handle request, cancel it here (it is out of date).
301 CancelPendingUpdate();
303 // local list to pass to methods
304 ShellLinkItemList temp_list;
306 // Create a list of ShellLinkItems from the "Recently Closed" pages.
307 // As noted above, we create a ShellLinkItem objects with the following
308 // parameters.
309 // * arguments
310 // The last URL of the tab object.
311 // * title
312 // The title of the last URL.
313 // * icon
314 // An empty string. This value is to be updated in OnFaviconDataAvailable().
315 // This code is copied from
316 // RecentlyClosedTabsHandler::TabRestoreServiceChanged() to emulate it.
317 const int kRecentlyClosedCount = 4;
318 TabRestoreService* tab_restore_service =
319 TabRestoreServiceFactory::GetForProfile(profile_);
320 const TabRestoreService::Entries& entries = tab_restore_service->entries();
321 for (TabRestoreService::Entries::const_iterator it = entries.begin();
322 it != entries.end(); ++it) {
323 const TabRestoreService::Entry* entry = *it;
324 if (entry->type == TabRestoreService::TAB) {
325 AddTab(static_cast<const TabRestoreService::Tab*>(entry),
326 &temp_list, kRecentlyClosedCount);
327 } else if (entry->type == TabRestoreService::WINDOW) {
328 AddWindow(static_cast<const TabRestoreService::Window*>(entry),
329 &temp_list, kRecentlyClosedCount);
332 // Lock recently_closed_pages and copy temp_list into it.
334 base::AutoLock auto_lock(list_lock_);
335 recently_closed_pages_ = temp_list;
338 // Send a query that retrieves the first favicon.
339 StartLoadingFavicon();
342 void JumpList::TabRestoreServiceDestroyed(TabRestoreService* service) {
345 bool JumpList::AddTab(const TabRestoreService::Tab* tab,
346 ShellLinkItemList* list,
347 size_t max_items) {
348 // This code adds the URL and the title strings of the given tab to the
349 // specified list.
350 if (list->size() >= max_items)
351 return false;
353 scoped_refptr<ShellLinkItem> link = CreateShellLink();
354 const sessions::SerializedNavigationEntry& current_navigation =
355 tab->navigations.at(tab->current_navigation_index);
356 std::string url = current_navigation.virtual_url().spec();
357 link->GetCommandLine()->AppendArgNative(base::UTF8ToWide(url));
358 link->GetCommandLine()->AppendSwitchASCII(
359 switches::kWinJumplistAction, jumplist::kRecentlyClosedCategory);
360 link->set_title(current_navigation.title());
361 list->push_back(link);
362 icon_urls_.push_back(make_pair(url, link));
363 return true;
366 void JumpList::AddWindow(const TabRestoreService::Window* window,
367 ShellLinkItemList* list,
368 size_t max_items) {
369 // This code enumerates al the tabs in the given window object and add their
370 // URLs and titles to the list.
371 DCHECK(!window->tabs.empty());
373 for (size_t i = 0; i < window->tabs.size(); ++i) {
374 if (!AddTab(&window->tabs[i], list, max_items))
375 return;
379 void JumpList::StartLoadingFavicon() {
380 GURL url;
381 bool waiting_for_icons = true;
383 base::AutoLock auto_lock(list_lock_);
384 waiting_for_icons = !icon_urls_.empty();
385 if (waiting_for_icons) {
386 // Ask FaviconService if it has a favicon of a URL.
387 // When FaviconService has one, it will call OnFaviconDataAvailable().
388 url = GURL(icon_urls_.front().first);
392 if (!waiting_for_icons) {
393 // No more favicons are needed by the application JumpList. Schedule a
394 // RunUpdateOnFileThread call.
395 PostRunUpdate();
396 return;
399 favicon::FaviconService* favicon_service =
400 FaviconServiceFactory::GetForProfile(profile_,
401 ServiceAccessType::EXPLICIT_ACCESS);
402 task_id_ = favicon_service->GetFaviconImageForPageURL(
403 url,
404 base::Bind(&JumpList::OnFaviconDataAvailable, base::Unretained(this)),
405 &cancelable_task_tracker_);
408 void JumpList::OnFaviconDataAvailable(
409 const favicon_base::FaviconImageResult& image_result) {
410 // If there is currently a favicon request in progress, it is now outdated,
411 // as we have received another, so nullify the handle from the old request.
412 task_id_ = base::CancelableTaskTracker::kBadTaskId;
413 // Lock the list to set icon data and pop the url.
415 base::AutoLock auto_lock(list_lock_);
416 // Attach the received data to the ShellLinkItem object.
417 // This data will be decoded by the RunUpdateOnFileThread method.
418 if (!image_result.image.IsEmpty()) {
419 if (!icon_urls_.empty() && icon_urls_.front().second.get())
420 icon_urls_.front().second->set_icon_data(image_result.image.AsBitmap());
423 if (!icon_urls_.empty())
424 icon_urls_.pop_front();
426 // Check whether we need to load more favicons.
427 StartLoadingFavicon();
430 void JumpList::OnIncognitoAvailabilityChanged() {
431 bool waiting_for_icons = true;
433 base::AutoLock auto_lock(list_lock_);
434 waiting_for_icons = !icon_urls_.empty();
436 if (!waiting_for_icons)
437 PostRunUpdate();
438 // If |icon_urls_| isn't empty then OnFaviconDataAvailable will eventually
439 // call PostRunUpdate().
442 void JumpList::PostRunUpdate() {
443 // Check if incognito windows (or normal windows) are disabled by policy.
444 IncognitoModePrefs::Availability incognito_availability =
445 profile_ ? IncognitoModePrefs::GetAvailability(profile_->GetPrefs())
446 : IncognitoModePrefs::ENABLED;
448 BrowserThread::PostTask(
449 BrowserThread::FILE, FROM_HERE,
450 base::Bind(&JumpList::RunUpdateOnFileThread,
451 this,
452 incognito_availability));
455 void JumpList::RunUpdateOnFileThread(
456 IncognitoModePrefs::Availability incognito_availability) {
457 ShellLinkItemList local_most_visited_pages;
458 ShellLinkItemList local_recently_closed_pages;
461 base::AutoLock auto_lock(list_lock_);
462 // Make sure we are not out of date: if icon_urls_ is not empty, then
463 // another notification has been received since we processed this one
464 if (!icon_urls_.empty())
465 return;
467 // Make local copies of lists so we can release the lock.
468 local_most_visited_pages = most_visited_pages_;
469 local_recently_closed_pages = recently_closed_pages_;
472 // Delete the directory which contains old icon files, rename the current
473 // icon directory, and create a new directory which contains new JumpList
474 // icon files.
475 base::FilePath icon_dir_old(icon_dir_.value() + L"Old");
476 if (base::PathExists(icon_dir_old))
477 base::DeleteFile(icon_dir_old, true);
478 base::Move(icon_dir_, icon_dir_old);
479 base::CreateDirectory(icon_dir_);
481 // Create temporary icon files for shortcuts in the "Most Visited" category.
482 CreateIconFiles(local_most_visited_pages);
484 // Create temporary icon files for shortcuts in the "Recently Closed"
485 // category.
486 CreateIconFiles(local_recently_closed_pages);
488 // We finished collecting all resources needed for updating an application
489 // JumpList. So, create a new JumpList and replace the current JumpList
490 // with it.
491 UpdateJumpList(app_id_.c_str(),
492 local_most_visited_pages,
493 local_recently_closed_pages,
494 incognito_availability);
497 void JumpList::CreateIconFiles(const ShellLinkItemList& item_list) {
498 for (ShellLinkItemList::const_iterator item = item_list.begin();
499 item != item_list.end(); ++item) {
500 base::FilePath icon_path;
501 if (CreateIconFile((*item)->icon_data(), icon_dir_, &icon_path))
502 (*item)->set_icon(icon_path.value(), 0);
506 void JumpList::TopSitesLoaded(history::TopSites* top_sites) {
509 void JumpList::TopSitesChanged(history::TopSites* top_sites) {
510 top_sites->GetMostVisitedURLs(
511 base::Bind(&JumpList::OnMostVisitedURLsAvailable,
512 weak_ptr_factory_.GetWeakPtr()),
513 false);