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