Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / jumplist_win.cc
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 (!base::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 =
282       base::UTF16ToWide(l10n_util::GetStringUTF16(category_id));
283
284   // Create an EnumerableObjectCollection object.
285   // We once add the given items to this collection object and add this
286   // collection to the JumpList.
287   base::win::ScopedComPtr<IObjectCollection> collection;
288   HRESULT result = collection.CreateInstance(CLSID_EnumerableObjectCollection,
289                                              NULL, CLSCTX_INPROC_SERVER);
290   if (FAILED(result))
291     return false;
292
293   for (ShellLinkItemList::const_iterator item = data.begin();
294        item != data.end() && max_slots > 0; ++item, --max_slots) {
295     scoped_refptr<ShellLinkItem> link(*item);
296     AddShellLink(collection, application, switches, link);
297   }
298
299   // We can now add the new list to the JumpList.
300   // The ICustomDestinationList::AppendCategory() function needs the
301   // IObjectArray interface to retrieve each item in the list. So, we retrive
302   // the IObjectArray interface from the IEnumeratableObjectCollection object
303   // and use it.
304   // It seems the ICustomDestinationList::AppendCategory() function just
305   // replaces all items in the given category with the ones in the new list.
306   base::win::ScopedComPtr<IObjectArray> object_array;
307   result = collection.QueryInterface(object_array.Receive());
308   if (FAILED(result))
309     return false;
310
311   return list->AppendCategory(category.c_str(), object_array);
312 }
313
314 // Updates the "Tasks" category of the JumpList.
315 // Even though this function is almost the same as UpdateCategory(), this
316 // function has the following differences:
317 // * The "Task" category is a registered category.
318 //   We should use AddUserTasks() instead of AppendCategory().
319 // * The items in the "Task" category are static.
320 //   We don't have to use a list.
321 HRESULT UpdateTaskCategory(base::win::ScopedComPtr<ICustomDestinationList> list,
322                            const std::wstring& chrome_path,
323                            const std::wstring& chrome_switches) {
324   // Create an EnumerableObjectCollection object to be added items of the
325   // "Task" category. (We can also use this object for the "Task" category.)
326   base::win::ScopedComPtr<IObjectCollection> collection;
327   HRESULT result = collection.CreateInstance(CLSID_EnumerableObjectCollection,
328                                              NULL, CLSCTX_INPROC_SERVER);
329   if (FAILED(result))
330     return result;
331
332   // Create an IShellLink object which launches Chrome, and add it to the
333   // collection. We use our application icon as the icon for this item.
334   // We remove '&' characters from this string so we can share it with our
335   // system menu.
336   scoped_refptr<ShellLinkItem> chrome(new ShellLinkItem);
337   std::wstring chrome_title =
338       base::UTF16ToWide(l10n_util::GetStringUTF16(IDS_NEW_WINDOW));
339   ReplaceSubstringsAfterOffset(&chrome_title, 0, L"&", L"");
340   chrome->SetTitle(chrome_title);
341   chrome->SetIcon(chrome_path, 0, false);
342   AddShellLink(collection, chrome_path, chrome_switches, chrome);
343
344   // Create an IShellLink object which launches Chrome in incognito mode, and
345   // add it to the collection. We use our application icon as the icon for
346   // this item.
347   scoped_refptr<ShellLinkItem> incognito(new ShellLinkItem);
348   incognito->SetArguments(
349       base::ASCIIToWide(std::string("--") + switches::kIncognito));
350   std::wstring incognito_title =
351       base::UTF16ToWide(l10n_util::GetStringUTF16(IDS_NEW_INCOGNITO_WINDOW));
352   ReplaceSubstringsAfterOffset(&incognito_title, 0, L"&", L"");
353   incognito->SetTitle(incognito_title);
354   incognito->SetIcon(chrome_path, 0, false);
355   AddShellLink(collection, chrome_path, chrome_switches, incognito);
356
357   // We can now add the new list to the JumpList.
358   // ICustomDestinationList::AddUserTasks() also uses the IObjectArray
359   // interface to retrieve each item in the list. So, we retrieve the
360   // IObjectArray interface from the EnumerableObjectCollection object.
361   base::win::ScopedComPtr<IObjectArray> object_array;
362   result = collection.QueryInterface(object_array.Receive());
363   if (FAILED(result))
364     return result;
365
366   return list->AddUserTasks(object_array);
367 }
368
369 // Updates the application JumpList.
370 // This function encapsulates all OS-specific operations required for updating
371 // the Chromium JumpList, such as:
372 // * Creating an ICustomDestinationList instance;
373 // * Updating the categories of the ICustomDestinationList instance, and;
374 // * Sending it to Taskbar of Windows 7.
375 bool UpdateJumpList(const wchar_t* app_id,
376                     const ShellLinkItemList& most_visited_pages,
377                     const ShellLinkItemList& recently_closed_pages) {
378   // JumpList is implemented only on Windows 7 or later.
379   // So, we should return now when this function is called on earlier versions
380   // of Windows.
381   if (base::win::GetVersion() < base::win::VERSION_WIN7)
382     return true;
383
384   // Create an ICustomDestinationList object and attach it to our application.
385   base::win::ScopedComPtr<ICustomDestinationList> destination_list;
386   HRESULT result = destination_list.CreateInstance(CLSID_DestinationList, NULL,
387                                                    CLSCTX_INPROC_SERVER);
388   if (FAILED(result))
389     return false;
390
391   // Set the App ID for this JumpList.
392   destination_list->SetAppID(app_id);
393
394   // Start a transaction that updates the JumpList of this application.
395   // This implementation just replaces the all items in this JumpList, so
396   // we don't have to use the IObjectArray object returned from this call.
397   // It seems Windows 7 RC (Build 7100) automatically checks the items in this
398   // removed list and prevent us from adding the same item.
399   UINT max_slots;
400   base::win::ScopedComPtr<IObjectArray> removed;
401   result = destination_list->BeginList(&max_slots, __uuidof(*removed),
402                                        reinterpret_cast<void**>(&removed));
403   if (FAILED(result))
404     return false;
405
406   // Retrieve the absolute path to "chrome.exe".
407   base::FilePath chrome_path;
408   if (!PathService::Get(base::FILE_EXE, &chrome_path))
409     return false;
410
411   // Retrieve the command-line switches of this process.
412   CommandLine command_line(CommandLine::NO_PROGRAM);
413   base::FilePath user_data_dir = CommandLine::ForCurrentProcess()->
414       GetSwitchValuePath(switches::kUserDataDir);
415   if (!user_data_dir.empty())
416     command_line.AppendSwitchPath(switches::kUserDataDir, user_data_dir);
417
418   std::wstring chrome_switches = command_line.GetCommandLineString();
419
420   // We allocate 60% of the given JumpList slots to "most-visited" items
421   // and 40% to "recently-closed" items, respectively.
422   // Nevertheless, if there are not so many items in |recently_closed_pages|,
423   // we give the remaining slots to "most-visited" items.
424   const int kMostVisited = 60;
425   const int kRecentlyClosed = 40;
426   const int kTotal = kMostVisited + kRecentlyClosed;
427   size_t most_visited_items = MulDiv(max_slots, kMostVisited, kTotal);
428   size_t recently_closed_items = max_slots - most_visited_items;
429   if (recently_closed_pages.size() < recently_closed_items) {
430     most_visited_items += recently_closed_items - recently_closed_pages.size();
431     recently_closed_items = recently_closed_pages.size();
432   }
433
434   // Update the "Most Visited" category of the JumpList.
435   // This update request is applied into the JumpList when we commit this
436   // transaction.
437   result = UpdateCategory(destination_list, IDS_NEW_TAB_MOST_VISITED,
438                           chrome_path.value(), chrome_switches,
439                           most_visited_pages, most_visited_items);
440   if (FAILED(result))
441     return false;
442
443   // Update the "Recently Closed" category of the JumpList.
444   result = UpdateCategory(destination_list, IDS_NEW_TAB_RECENTLY_CLOSED,
445                           chrome_path.value(), chrome_switches,
446                           recently_closed_pages, recently_closed_items);
447   if (FAILED(result))
448     return false;
449
450   // Update the "Tasks" category of the JumpList.
451   result = UpdateTaskCategory(destination_list, chrome_path.value(),
452                               chrome_switches);
453   if (FAILED(result))
454     return false;
455
456   // Commit this transaction and send the updated JumpList to Windows.
457   result = destination_list->CommitList();
458   if (FAILED(result))
459     return false;
460
461   return true;
462 }
463
464 }  // namespace
465
466 JumpList::JumpList()
467     : weak_ptr_factory_(this),
468       profile_(NULL),
469       task_id_(base::CancelableTaskTracker::kBadTaskId) {}
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()), false);
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_ != base::CancelableTaskTracker::kBadTaskId) {
556     cancelable_task_tracker_.TryCancel(task_id_);
557     task_id_ = base::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(base::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(base::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(url,
688                                           chrome::FAVICON,
689                                           gfx::kFaviconSize),
690       base::Bind(&JumpList::OnFaviconDataAvailable,
691                  base::Unretained(this)),
692       &cancelable_task_tracker_);
693 }
694
695 void JumpList::OnFaviconDataAvailable(
696     const chrome::FaviconImageResult& image_result) {
697   // If there is currently a favicon request in progress, it is now outdated,
698   // as we have received another, so nullify the handle from the old request.
699   task_id_ = base::CancelableTaskTracker::kBadTaskId;
700   // lock the list to set icon data and pop the url
701   {
702     base::AutoLock auto_lock(list_lock_);
703     // Attach the received data to the ShellLinkItem object.
704     // This data will be decoded by the RunUpdate method.
705     if (!image_result.image.IsEmpty()) {
706       if (!icon_urls_.empty() && icon_urls_.front().second)
707         icon_urls_.front().second->SetIconData(image_result.image.AsBitmap());
708     }
709
710     if (!icon_urls_.empty())
711       icon_urls_.pop_front();
712   }
713   // Check whether we need to load more favicons.
714   StartLoadingFavicon();
715 }
716
717 void JumpList::RunUpdate() {
718   ShellLinkItemList local_most_visited_pages;
719   ShellLinkItemList local_recently_closed_pages;
720
721   {
722     base::AutoLock auto_lock(list_lock_);
723     // Make sure we are not out of date: if icon_urls_ is not empty, then
724     // another notification has been received since we processed this one
725     if (!icon_urls_.empty())
726       return;
727
728     // Make local copies of lists so we can release the lock.
729     local_most_visited_pages = most_visited_pages_;
730     local_recently_closed_pages = recently_closed_pages_;
731   }
732
733   // Delete the directory which contains old icon files, rename the current
734   // icon directory, and create a new directory which contains new JumpList
735   // icon files.
736   base::FilePath icon_dir_old(icon_dir_.value() + L"Old");
737   if (base::PathExists(icon_dir_old))
738     base::DeleteFile(icon_dir_old, true);
739   base::Move(icon_dir_, icon_dir_old);
740   base::CreateDirectory(icon_dir_);
741
742   // Create temporary icon files for shortcuts in the "Most Visited" category.
743   CreateIconFiles(local_most_visited_pages);
744
745   // Create temporary icon files for shortcuts in the "Recently Closed"
746   // category.
747   CreateIconFiles(local_recently_closed_pages);
748
749   // We finished collecting all resources needed for updating an appliation
750   // JumpList. So, create a new JumpList and replace the current JumpList
751   // with it.
752   UpdateJumpList(app_id_.c_str(), local_most_visited_pages,
753                  local_recently_closed_pages);
754 }
755
756 void JumpList::CreateIconFiles(const ShellLinkItemList& item_list) {
757   for (ShellLinkItemList::const_iterator item = item_list.begin();
758       item != item_list.end(); ++item) {
759     base::FilePath icon_path;
760     if (CreateIconFile((*item)->data(), icon_dir_, &icon_path))
761       (*item)->SetIcon(icon_path.value(), 0, true);
762   }
763 }