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