jumplist_win.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 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. 4 5#include "chrome/browser/jumplist_win.h" 6 7#include <windows.h> 8#include <shobjidl.h> 9#include <propkey.h> 10#include <propvarutil.h> 11 12#include <string> 13#include <vector> 14 15#include "base/bind.h" 16#include "base/bind_helpers.h" 17#include "base/command_line.h" 18#include "base/file_util.h" 19#include "base/path_service.h" 20#include "base/string_util.h" 21#include "base/threading/thread.h" 22#include "base/utf_string_conversions.h" 23#include "base/win/scoped_comptr.h" 24#include "base/win/scoped_propvariant.h" 25#include "base/win/windows_version.h" 26#include "chrome/browser/favicon/favicon_service.h" 27#include "chrome/browser/favicon/favicon_service_factory.h" 28#include "chrome/browser/history/history_service.h" 29#include "chrome/browser/history/page_usage_data.h" 30#include "chrome/browser/history/top_sites.h" 31#include "chrome/browser/profiles/profile.h" 32#include "chrome/browser/sessions/session_types.h" 33#include "chrome/browser/sessions/tab_restore_service.h" 34#include "chrome/browser/sessions/tab_restore_service_factory.h" 35#include "chrome/browser/shell_integration.h" 36#include "chrome/common/chrome_constants.h" 37#include "chrome/common/chrome_notification_types.h" 38#include "chrome/common/chrome_switches.h" 39#include "chrome/common/url_constants.h" 40#include "content/public/browser/browser_thread.h" 41#include "content/public/browser/notification_source.h" 42#include "googleurl/src/gurl.h" 43#include "grit/chromium_strings.h" 44#include "grit/generated_resources.h" 45#include "third_party/skia/include/core/SkBitmap.h" 46#include "ui/base/l10n/l10n_util.h" 47#include "ui/gfx/codec/png_codec.h" 48#include "ui/gfx/favicon_size.h" 49#include "ui/gfx/icon_util.h" 50 51using content::BrowserThread; 52 53namespace { 54 55// COM interfaces used in this file. 56// These interface declarations are copied from Windows SDK 7.0. 57// TODO(hbono): Bug 16903: delete them when we use Windows SDK 7.0. 58#ifndef __IObjectArray_INTERFACE_DEFINED__ 59#define __IObjectArray_INTERFACE_DEFINED__ 60 61MIDL_INTERFACE("92CA9DCD-5622-4bba-A805-5E9F541BD8C9") 62IObjectArray : public IUnknown { 63 public: 64 virtual HRESULT STDMETHODCALLTYPE GetCount( 65 /* [out] */ __RPC__out UINT *pcObjects) = 0; 66 virtual HRESULT STDMETHODCALLTYPE GetAt( 67 /* [in] */ UINT uiIndex, 68 /* [in] */ __RPC__in REFIID riid, 69 /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0; 70}; 71 72#endif // __IObjectArray_INTERFACE_DEFINED__ 73 74#ifndef __IObjectCollection_INTERFACE_DEFINED__ 75#define __IObjectCollection_INTERFACE_DEFINED__ 76 77MIDL_INTERFACE("5632b1a4-e38a-400a-928a-d4cd63230295") 78IObjectCollection : public IObjectArray { 79 public: 80 virtual HRESULT STDMETHODCALLTYPE AddObject( 81 /* [in] */ __RPC__in_opt IUnknown *punk) = 0; 82 virtual HRESULT STDMETHODCALLTYPE AddFromArray( 83 /* [in] */ __RPC__in_opt IObjectArray *poaSource) = 0; 84 virtual HRESULT STDMETHODCALLTYPE RemoveObjectAt( 85 /* [in] */ UINT uiIndex) = 0; 86 virtual HRESULT STDMETHODCALLTYPE Clear(void) = 0; 87}; 88 89#endif // __IObjectCollection_INTERFACE_DEFINED__ 90 91#ifndef __ICustomDestinationList_INTERFACE_DEFINED__ 92#define __ICustomDestinationList_INTERFACE_DEFINED__ 93 94typedef /* [v1_enum] */ enum tagKNOWNDESTCATEGORY { 95 KDC_FREQUENT = 1, 96 KDC_RECENT = (KDC_FREQUENT + 1) 97} KNOWNDESTCATEGORY; 98 99MIDL_INTERFACE("6332debf-87b5-4670-90c0-5e57b408a49e") 100ICustomDestinationList : public IUnknown { 101 public: 102 virtual HRESULT STDMETHODCALLTYPE SetAppID( 103 /* [string][in] */__RPC__in_string LPCWSTR pszAppID) = 0; 104 virtual HRESULT STDMETHODCALLTYPE BeginList( 105 /* [out] */ __RPC__out UINT *pcMaxSlots, 106 /* [in] */ __RPC__in REFIID riid, 107 /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0; 108 virtual HRESULT STDMETHODCALLTYPE AppendCategory( 109 /* [string][in] */ __RPC__in_string LPCWSTR pszCategory, 110 /* [in] */ __RPC__in_opt IObjectArray *poa) = 0; 111 virtual HRESULT STDMETHODCALLTYPE AppendKnownCategory( 112 /* [in] */ KNOWNDESTCATEGORY category) = 0; 113 virtual HRESULT STDMETHODCALLTYPE AddUserTasks( 114 /* [in] */ __RPC__in_opt IObjectArray *poa) = 0; 115 virtual HRESULT STDMETHODCALLTYPE CommitList(void) = 0; 116 virtual HRESULT STDMETHODCALLTYPE GetRemovedDestinations( 117 /* [in] */ __RPC__in REFIID riid, 118 /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0; 119 virtual HRESULT STDMETHODCALLTYPE DeleteList( 120 /* [string][in] */ __RPC__in_string LPCWSTR pszAppID) = 0; 121 virtual HRESULT STDMETHODCALLTYPE AbortList(void) = 0; 122}; 123 124#endif // __ICustomDestinationList_INTERFACE_DEFINED__ 125 126// Class IDs used in this file. 127// These class IDs must be defined in an anonymous namespace to avoid 128// conflicts with ones defined in "shell32.lib" of Visual Studio 2008. 129// TODO(hbono): Bug 16903: delete them when we use Windows SDK 7.0. 130const CLSID CLSID_DestinationList = { 131 0x77f10cf0, 0x3db5, 0x4966, {0xb5, 0x20, 0xb7, 0xc5, 0x4f, 0xd3, 0x5e, 0xd6} 132}; 133 134const CLSID CLSID_EnumerableObjectCollection = { 135 0x2d3468c1, 0x36a7, 0x43b6, {0xac, 0x24, 0xd3, 0xf0, 0x2f, 0xd9, 0x60, 0x7a} 136}; 137 138}; // namespace 139 140// END OF WINDOWS 7 SDK DEFINITIONS 141 142namespace { 143 144// Creates an IShellLink object. 145// An IShellLink object is almost the same as an application shortcut, and it 146// requires three items: the absolute path to an application, an argument 147// string, and a title string. 148HRESULT AddShellLink(base::win::ScopedComPtr<IObjectCollection> collection, 149 const std::wstring& application, 150 const std::wstring& switches, 151 scoped_refptr<ShellLinkItem> item) { 152 // Create an IShellLink object. 153 base::win::ScopedComPtr<IShellLink> link; 154 HRESULT result = link.CreateInstance(CLSID_ShellLink, NULL, 155 CLSCTX_INPROC_SERVER); 156 if (FAILED(result)) 157 return result; 158 159 // Set the application path. 160 // We should exit this function when this call fails because it doesn't make 161 // any sense to add a shortcut that we cannot execute. 162 result = link->SetPath(application.c_str()); 163 if (FAILED(result)) 164 return result; 165 166 // Attach the command-line switches of this process before the given 167 // arguments and set it as the arguments of this IShellLink object. 168 // We also exit this function when this call fails because it isn't usuful to 169 // add a shortcut that cannot open the given page. 170 std::wstring arguments(switches); 171 if (!item->arguments().empty()) { 172 arguments.push_back(L' '); 173 arguments += item->arguments(); 174 } 175 result = link->SetArguments(arguments.c_str()); 176 if (FAILED(result)) 177 return result; 178 179 // Attach the given icon path to this IShellLink object. 180 // Since an icon is an optional item for an IShellLink object, so we don't 181 // have to exit even when it fails. 182 if (!item->icon().empty()) 183 link->SetIconLocation(item->icon().c_str(), item->index()); 184 185 // Set the title of the IShellLink object. 186 // The IShellLink interface does not have any functions which update its 187 // title because this interface is originally for creating an application 188 // shortcut which doesn't have titles. 189 // So, we should use the IPropertyStore interface to set its title as 190 // listed in the steps below: 191 // 1. Retrieve the IPropertyStore interface from the IShellLink object; 192 // 2. Start a transaction that changes a value of the object with the 193 // IPropertyStore interface; 194 // 3. Create a string PROPVARIANT, and; 195 // 4. Call the IPropertyStore::SetValue() function to Set the title property 196 // of the IShellLink object. 197 // 5. Commit the transaction. 198 base::win::ScopedComPtr<IPropertyStore> property_store; 199 result = link.QueryInterface(property_store.Receive()); 200 if (FAILED(result)) 201 return result; 202 203 base::win::ScopedPropVariant property_title; 204 // Call InitPropVariantFromString() to initialize |property_title|. Reading 205 // <propvarutil.h>, it seems InitPropVariantFromString() is an inline function 206 // that initializes a PROPVARIANT object and calls SHStrDupW() to set a copy 207 // of its input string. It is thus safe to call it without first creating a 208 // copy here. 209 result = InitPropVariantFromString(item->title().c_str(), 210 property_title.Receive()); 211 if (FAILED(result)) 212 return result; 213 214 result = property_store->SetValue(PKEY_Title, property_title.get()); 215 if (FAILED(result)) 216 return result; 217 218 result = property_store->Commit(); 219 if (FAILED(result)) 220 return result; 221 222 // Add this IShellLink object to the given collection. 223 return collection->AddObject(link); 224} 225 226// Creates a temporary icon file to be shown in JumpList. 227bool CreateIconFile(const SkBitmap& bitmap, 228 const base::FilePath& icon_dir, 229 base::FilePath* icon_path) { 230 // Retrieve the path to a temporary file. 231 // We don't have to care about the extension of this temporary file because 232 // JumpList does not care about it. 233 base::FilePath path; 234 if (!file_util::CreateTemporaryFileInDir(icon_dir, &path)) 235 return false; 236 237 // Create an icon file from the favicon attached to the given |page|, and 238 // save it as the temporary file. 239 if (!IconUtil::CreateIconFileFromSkBitmap(bitmap, SkBitmap(), path)) 240 return false; 241 242 // Add this icon file to the list and return its absolute path. 243 // The IShellLink::SetIcon() function needs the absolute path to an icon. 244 *icon_path = path; 245 return true; 246} 247 248// Updates a specified category of an application JumpList. 249// This function cannot update registered categories (such as "Tasks") because 250// special steps are required for updating them. 251// So, this function can be used only for adding an unregistered category. 252// Parameters: 253// * category_id (int) 254// A string ID which contains the category name. 255// * application (std::wstring) 256// An application name to be used for creating JumpList items. 257// Even though we can add command-line switches to this parameter, it is 258// better to use the |switches| parameter below. 259// * switches (std::wstring) 260// Command-lien switches for the application. This string is to be added 261// before the arguments of each ShellLinkItem object. If there aren't any 262// switches, use an empty string. 263// * data (ShellLinkItemList) 264// A list of ShellLinkItem objects to be added under the specified category. 265HRESULT UpdateCategory(base::win::ScopedComPtr<ICustomDestinationList> list, 266 int category_id, 267 const std::wstring& application, 268 const std::wstring& switches, 269 const ShellLinkItemList& data, 270 int max_slots) { 271 // Exit this function when the given vector does not contain any items 272 // because an ICustomDestinationList::AppendCategory() call fails in this 273 // case. 274 if (data.empty() || !max_slots) 275 return S_OK; 276 277 std::wstring category = UTF16ToWide(l10n_util::GetStringUTF16(category_id)); 278 279 // Create an EnumerableObjectCollection object. 280 // We once add the given items to this collection object and add this 281 // collection to the JumpList. 282 base::win::ScopedComPtr<IObjectCollection> collection; 283 HRESULT result = collection.CreateInstance(CLSID_EnumerableObjectCollection, 284 NULL, CLSCTX_INPROC_SERVER); 285 if (FAILED(result)) 286 return false; 287 288 for (ShellLinkItemList::const_iterator item = data.begin(); 289 item != data.end() && max_slots > 0; ++item, --max_slots) { 290 scoped_refptr<ShellLinkItem> link(*item); 291 AddShellLink(collection, application, switches, link); 292 } 293 294 // We can now add the new list to the JumpList. 295 // The ICustomDestinationList::AppendCategory() function needs the 296 // IObjectArray interface to retrieve each item in the list. So, we retrive 297 // the IObjectArray interface from the IEnumeratableObjectCollection object 298 // and use it. 299 // It seems the ICustomDestinationList::AppendCategory() function just 300 // replaces all items in the given category with the ones in the new list. 301 base::win::ScopedComPtr<IObjectArray> object_array; 302 result = collection.QueryInterface(object_array.Receive()); 303 if (FAILED(result)) 304 return false; 305 306 return list->AppendCategory(category.c_str(), object_array); 307} 308 309// Updates the "Tasks" category of the JumpList. 310// Even though this function is almost the same as UpdateCategory(), this 311// function has the following differences: 312// * The "Task" category is a registered category. 313// We should use AddUserTasks() instead of AppendCategory(). 314// * The items in the "Task" category are static. 315// We don't have to use a list. 316HRESULT UpdateTaskCategory(base::win::ScopedComPtr<ICustomDestinationList> list, 317 const std::wstring& chrome_path, 318 const std::wstring& chrome_switches) { 319 // Create an EnumerableObjectCollection object to be added items of the 320 // "Task" category. (We can also use this object for the "Task" category.) 321 base::win::ScopedComPtr<IObjectCollection> collection; 322 HRESULT result = collection.CreateInstance(CLSID_EnumerableObjectCollection, 323 NULL, CLSCTX_INPROC_SERVER); 324 if (FAILED(result)) 325 return result; 326 327 // Create an IShellLink object which launches Chrome, and add it to the 328 // collection. We use our application icon as the icon for this item. 329 // We remove '&' characters from this string so we can share it with our 330 // system menu. 331 scoped_refptr<ShellLinkItem> chrome(new ShellLinkItem); 332 std::wstring chrome_title = 333 UTF16ToWide(l10n_util::GetStringUTF16(IDS_NEW_WINDOW)); 334 ReplaceSubstringsAfterOffset(&chrome_title, 0, L"&", L""); 335 chrome->SetTitle(chrome_title); 336 chrome->SetIcon(chrome_path, 0, false); 337 AddShellLink(collection, chrome_path, chrome_switches, chrome); 338 339 // Create an IShellLink object which launches Chrome in incognito mode, and 340 // add it to the collection. We use our application icon as the icon for 341 // this item. 342 scoped_refptr<ShellLinkItem> incognito(new ShellLinkItem); 343 incognito->SetArguments( 344 ASCIIToWide(std::string("--") + switches::kIncognito)); 345 std::wstring incognito_title = 346 UTF16ToWide(l10n_util::GetStringUTF16(IDS_NEW_INCOGNITO_WINDOW)); 347 ReplaceSubstringsAfterOffset(&incognito_title, 0, L"&", L""); 348 incognito->SetTitle(incognito_title); 349 incognito->SetIcon(chrome_path, 0, false); 350 AddShellLink(collection, chrome_path, chrome_switches, incognito); 351 352 // We can now add the new list to the JumpList. 353 // ICustomDestinationList::AddUserTasks() also uses the IObjectArray 354 // interface to retrieve each item in the list. So, we retrieve the 355 // IObjectArray interface from the EnumerableObjectCollection object. 356 base::win::ScopedComPtr<IObjectArray> object_array; 357 result = collection.QueryInterface(object_array.Receive()); 358 if (FAILED(result)) 359 return result; 360 361 return list->AddUserTasks(object_array); 362} 363 364// Updates the application JumpList. 365// This function encapsulates all OS-specific operations required for updating 366// the Chromium JumpList, such as: 367// * Creating an ICustomDestinationList instance; 368// * Updating the categories of the ICustomDestinationList instance, and; 369// * Sending it to Taskbar of Windows 7. 370bool UpdateJumpList(const wchar_t* app_id, 371 const ShellLinkItemList& most_visited_pages, 372 const ShellLinkItemList& recently_closed_pages) { 373 // JumpList is implemented only on Windows 7 or later. 374 // So, we should return now when this function is called on earlier versions 375 // of Windows. 376 if (base::win::GetVersion() < base::win::VERSION_WIN7) 377 return true; 378 379 // Create an ICustomDestinationList object and attach it to our application. 380 base::win::ScopedComPtr<ICustomDestinationList> destination_list; 381 HRESULT result = destination_list.CreateInstance(CLSID_DestinationList, NULL, 382 CLSCTX_INPROC_SERVER); 383 if (FAILED(result)) 384 return false; 385 386 // Set the App ID for this JumpList. 387 destination_list->SetAppID(app_id); 388 389 // Start a transaction that updates the JumpList of this application. 390 // This implementation just replaces the all items in this JumpList, so 391 // we don't have to use the IObjectArray object returned from this call. 392 // It seems Windows 7 RC (Build 7100) automatically checks the items in this 393 // removed list and prevent us from adding the same item. 394 UINT max_slots; 395 base::win::ScopedComPtr<IObjectArray> removed; 396 result = destination_list->BeginList(&max_slots, __uuidof(*removed), 397 reinterpret_cast<void**>(&removed)); 398 if (FAILED(result)) 399 return false; 400 401 // Retrieve the absolute path to "chrome.exe". 402 base::FilePath chrome_path; 403 if (!PathService::Get(base::FILE_EXE, &chrome_path)) 404 return false; 405 406 // Retrieve the command-line switches of this process. 407 CommandLine command_line(CommandLine::NO_PROGRAM); 408 base::FilePath user_data_dir = CommandLine::ForCurrentProcess()-> 409 GetSwitchValuePath(switches::kUserDataDir); 410 if (!user_data_dir.empty()) 411 command_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir); 412 413 std::wstring chrome_switches = command_line.GetCommandLineString(); 414 415 // We allocate 60% of the given JumpList slots to "most-visited" items 416 // and 40% to "recently-closed" items, respectively. 417 // Nevertheless, if there are not so many items in |recently_closed_pages|, 418 // we give the remaining slots to "most-visited" items. 419 const int kMostVisited = 60; 420 const int kRecentlyClosed = 40; 421 const int kTotal = kMostVisited + kRecentlyClosed; 422 size_t most_visited_items = MulDiv(max_slots, kMostVisited, kTotal); 423 size_t recently_closed_items = max_slots - most_visited_items; 424 if (recently_closed_pages.size() < recently_closed_items) { 425 most_visited_items += recently_closed_items - recently_closed_pages.size(); 426 recently_closed_items = recently_closed_pages.size(); 427 } 428 429 // Update the "Most Visited" category of the JumpList. 430 // This update request is applied into the JumpList when we commit this 431 // transaction. 432 result = UpdateCategory(destination_list, IDS_NEW_TAB_MOST_VISITED, 433 chrome_path.value(), chrome_switches, 434 most_visited_pages, most_visited_items); 435 if (FAILED(result)) 436 return false; 437 438 // Update the "Recently Closed" category of the JumpList. 439 result = UpdateCategory(destination_list, IDS_NEW_TAB_RECENTLY_CLOSED, 440 chrome_path.value(), chrome_switches, 441 recently_closed_pages, recently_closed_items); 442 if (FAILED(result)) 443 return false; 444 445 // Update the "Tasks" category of the JumpList. 446 result = UpdateTaskCategory(destination_list, chrome_path.value(), 447 chrome_switches); 448 if (FAILED(result)) 449 return false; 450 451 // Commit this transaction and send the updated JumpList to Windows. 452 result = destination_list->CommitList(); 453 if (FAILED(result)) 454 return false; 455 456 return true; 457} 458 459} // namespace 460 461JumpList::JumpList() 462 : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), 463 profile_(NULL), 464 task_id_(CancelableTaskTracker::kBadTaskId) { 465} 466 467JumpList::~JumpList() { 468 Terminate(); 469} 470 471// static 472bool JumpList::Enabled() { 473 return (base::win::GetVersion() >= base::win::VERSION_WIN7 && 474 !CommandLine::ForCurrentProcess()->HasSwitch( 475 switches::kDisableCustomJumpList)); 476} 477 478bool JumpList::AddObserver(Profile* profile) { 479 // To update JumpList when a tab is added or removed, we add this object to 480 // the observer list of the TabRestoreService class. 481 // When we add this object to the observer list, we save the pointer to this 482 // TabRestoreService object. This pointer is used when we remove this object 483 // from the observer list. 484 if (base::win::GetVersion() < base::win::VERSION_WIN7 || !profile) 485 return false; 486 487 TabRestoreService* tab_restore_service = 488 TabRestoreServiceFactory::GetForProfile(profile); 489 if (!tab_restore_service) 490 return false; 491 492 app_id_ = ShellIntegration::GetChromiumModelIdForProfile(profile->GetPath()); 493 icon_dir_ = profile->GetPath().Append(chrome::kJumpListIconDirname); 494 profile_ = profile; 495 history::TopSites* top_sites = profile_->GetTopSites(); 496 if (top_sites) { 497 // TopSites updates itself after a delay. This is especially noticable when 498 // your profile is empty. Ask TopSites to update itself when jumplist is 499 // initialized. 500 top_sites->SyncWithHistory(); 501 registrar_.reset(new content::NotificationRegistrar); 502 // Register for notification when TopSites changes so that we can update 503 // ourself. 504 registrar_->Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED, 505 content::Source<history::TopSites>(top_sites)); 506 // Register for notification when profile is destroyed to ensure that all 507 // observers are detatched at that time. 508 registrar_->Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, 509 content::Source<Profile>(profile_)); 510 } 511 tab_restore_service->AddObserver(this); 512 return true; 513} 514 515void JumpList::Observe(int type, 516 const content::NotificationSource& source, 517 const content::NotificationDetails& details) { 518 switch (type) { 519 case chrome::NOTIFICATION_TOP_SITES_CHANGED: { 520 // Most visited urls changed, query again. 521 history::TopSites* top_sites = profile_->GetTopSites(); 522 if (top_sites) { 523 top_sites->GetMostVisitedURLs( 524 base::Bind(&JumpList::OnMostVisitedURLsAvailable, 525 weak_ptr_factory_.GetWeakPtr())); 526 } 527 break; 528 } 529 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 530 // Profile was destroyed, do clean-up. 531 Terminate(); 532 break; 533 } 534 default: 535 NOTREACHED() << "Unexpected notification type."; 536 } 537} 538 539void JumpList::RemoveObserver() { 540 if (profile_) { 541 TabRestoreService* tab_restore_service = 542 TabRestoreServiceFactory::GetForProfile(profile_); 543 if (tab_restore_service) 544 tab_restore_service->RemoveObserver(this); 545 registrar_.reset(); 546 } 547 profile_ = NULL; 548} 549 550void JumpList::CancelPendingUpdate() { 551 if (task_id_ != CancelableTaskTracker::kBadTaskId) { 552 cancelable_task_tracker_.TryCancel(task_id_); 553 task_id_ = CancelableTaskTracker::kBadTaskId; 554 } 555} 556 557void JumpList::Terminate() { 558 CancelPendingUpdate(); 559 RemoveObserver(); 560} 561 562void JumpList::OnMostVisitedURLsAvailable( 563 const history::MostVisitedURLList& data) { 564 565 // If we have a pending favicon request, cancel it here (it is out of date). 566 CancelPendingUpdate(); 567 568 { 569 base::AutoLock auto_lock(list_lock_); 570 most_visited_pages_.clear(); 571 for (size_t i = 0; i < data.size(); i++) { 572 const history::MostVisitedURL& url = data[i]; 573 scoped_refptr<ShellLinkItem> link(new ShellLinkItem); 574 std::string url_string = url.url.spec(); 575 link->SetArguments(UTF8ToWide(url_string)); 576 link->SetTitle(!url.title.empty()? url.title : link->arguments()); 577 most_visited_pages_.push_back(link); 578 icon_urls_.push_back(make_pair(url_string, link)); 579 } 580 } 581 582 // Send a query that retrieves the first favicon. 583 StartLoadingFavicon(); 584} 585 586void JumpList::TabRestoreServiceChanged(TabRestoreService* service) { 587 // if we have a pending handle request, cancel it here (it is out of date). 588 CancelPendingUpdate(); 589 590 // local list to pass to methods 591 ShellLinkItemList temp_list; 592 593 // Create a list of ShellLinkItems from the "Recently Closed" pages. 594 // As noted above, we create a ShellLinkItem objects with the following 595 // parameters. 596 // * arguments 597 // The last URL of the tab object. 598 // * title 599 // The title of the last URL. 600 // * icon 601 // An empty string. This value is to be updated in OnFaviconDataAvailable(). 602 // This code is copied from 603 // RecentlyClosedTabsHandler::TabRestoreServiceChanged() to emulate it. 604 const int kRecentlyClosedCount = 4; 605 TabRestoreService* tab_restore_service = 606 TabRestoreServiceFactory::GetForProfile(profile_); 607 const TabRestoreService::Entries& entries = tab_restore_service->entries(); 608 for (TabRestoreService::Entries::const_iterator it = entries.begin(); 609 it != entries.end(); ++it) { 610 const TabRestoreService::Entry* entry = *it; 611 if (entry->type == TabRestoreService::TAB) { 612 AddTab(static_cast<const TabRestoreService::Tab*>(entry), 613 &temp_list, kRecentlyClosedCount); 614 } else if (entry->type == TabRestoreService::WINDOW) { 615 AddWindow(static_cast<const TabRestoreService::Window*>(entry), 616 &temp_list, kRecentlyClosedCount); 617 } 618 } 619 // Lock recently_closed_pages and copy temp_list into it. 620 { 621 base::AutoLock auto_lock(list_lock_); 622 recently_closed_pages_ = temp_list; 623 } 624 625 // Send a query that retrieves the first favicon. 626 StartLoadingFavicon(); 627} 628 629void JumpList::TabRestoreServiceDestroyed(TabRestoreService* service) { 630} 631 632bool JumpList::AddTab(const TabRestoreService::Tab* tab, 633 ShellLinkItemList* list, 634 size_t max_items) { 635 // This code adds the URL and the title strings of the given tab to the 636 // specified list. 637 if (list->size() >= max_items) 638 return false; 639 640 scoped_refptr<ShellLinkItem> link(new ShellLinkItem); 641 const TabNavigation& current_navigation = 642 tab->navigations.at(tab->current_navigation_index); 643 std::string url = current_navigation.virtual_url().spec(); 644 link->SetArguments(UTF8ToWide(url)); 645 link->SetTitle(current_navigation.title()); 646 list->push_back(link); 647 icon_urls_.push_back(make_pair(url, link)); 648 return true; 649} 650 651void JumpList::AddWindow(const TabRestoreService::Window* window, 652 ShellLinkItemList* list, 653 size_t max_items) { 654 // This code enumerates al the tabs in the given window object and add their 655 // URLs and titles to the list. 656 DCHECK(!window->tabs.empty()); 657 658 for (size_t i = 0; i < window->tabs.size(); ++i) { 659 if (!AddTab(&window->tabs[i], list, max_items)) 660 return; 661 } 662} 663 664void JumpList::StartLoadingFavicon() { 665 GURL url; 666 { 667 base::AutoLock auto_lock(list_lock_); 668 if (icon_urls_.empty()) { 669 // No more favicons are needed by the application JumpList. Schedule a 670 // RunUpdate call. 671 BrowserThread::PostTask( 672 BrowserThread::FILE, FROM_HERE, 673 base::Bind(&JumpList::RunUpdate, this)); 674 return; 675 } 676 // Ask FaviconService if it has a favicon of a URL. 677 // When FaviconService has one, it will call OnFaviconDataAvailable(). 678 url = GURL(icon_urls_.front().first); 679 } 680 FaviconService* favicon_service = 681 FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS); 682 task_id_ = favicon_service->GetFaviconImageForURL( 683 FaviconService::FaviconForURLParams(profile_, 684 url, 685 history::FAVICON, 686 gfx::kFaviconSize), 687 base::Bind(&JumpList::OnFaviconDataAvailable, 688 base::Unretained(this)), 689 &cancelable_task_tracker_); 690} 691 692void JumpList::OnFaviconDataAvailable( 693 const history::FaviconImageResult& image_result) { 694 // If there is currently a favicon request in progress, it is now outdated, 695 // as we have received another, so nullify the handle from the old request. 696 task_id_ = CancelableTaskTracker::kBadTaskId; 697 // lock the list to set icon data and pop the url 698 { 699 base::AutoLock auto_lock(list_lock_); 700 // Attach the received data to the ShellLinkItem object. 701 // This data will be decoded by the RunUpdate method. 702 if (!image_result.image.IsEmpty()) { 703 if (!icon_urls_.empty() && icon_urls_.front().second) 704 icon_urls_.front().second->SetIconData(image_result.image.AsBitmap()); 705 } 706 707 if (!icon_urls_.empty()) 708 icon_urls_.pop_front(); 709 } 710 // Check whether we need to load more favicons. 711 StartLoadingFavicon(); 712} 713 714void JumpList::RunUpdate() { 715 ShellLinkItemList local_most_visited_pages; 716 ShellLinkItemList local_recently_closed_pages; 717 718 { 719 base::AutoLock auto_lock(list_lock_); 720 // Make sure we are not out of date: if icon_urls_ is not empty, then 721 // another notification has been received since we processed this one 722 if (!icon_urls_.empty()) 723 return; 724 725 // Make local copies of lists so we can release the lock. 726 local_most_visited_pages = most_visited_pages_; 727 local_recently_closed_pages = recently_closed_pages_; 728 } 729 730 // Delete the directory which contains old icon files, rename the current 731 // icon directory, and create a new directory which contains new JumpList 732 // icon files. 733 base::FilePath icon_dir_old(icon_dir_.value() + L"Old"); 734 if (file_util::PathExists(icon_dir_old)) 735 file_util::Delete(icon_dir_old, true); 736 file_util::Move(icon_dir_, icon_dir_old); 737 file_util::CreateDirectory(icon_dir_); 738 739 // Create temporary icon files for shortcuts in the "Most Visited" category. 740 CreateIconFiles(local_most_visited_pages); 741 742 // Create temporary icon files for shortcuts in the "Recently Closed" 743 // category. 744 CreateIconFiles(local_recently_closed_pages); 745 746 // We finished collecting all resources needed for updating an appliation 747 // JumpList. So, create a new JumpList and replace the current JumpList 748 // with it. 749 UpdateJumpList(app_id_.c_str(), local_most_visited_pages, 750 local_recently_closed_pages); 751} 752 753void JumpList::CreateIconFiles(const ShellLinkItemList& item_list) { 754 for (ShellLinkItemList::const_iterator item = item_list.begin(); 755 item != item_list.end(); ++item) { 756 base::FilePath icon_path; 757 if (CreateIconFile((*item)->data(), icon_dir_, &icon_path)) 758 (*item)->SetIcon(icon_path.value(), 0, true); 759 } 760} 761