1 // Copyright 2014 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/win/jumplist_updater.h"
6 
7 #include <windows.h>
8 #include <objbase.h>
9 #include <propkey.h>
10 #include <shobjidl.h>
11 
12 #include "base/command_line.h"
13 #include "base/files/file_path.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/path_service.h"
16 #include "base/win/win_util.h"
17 #include "chrome/common/chrome_switches.h"
18 #include "content/public/common/content_switches.h"
19 
20 namespace {
21 
22 // Creates an IShellLink object.
23 // An IShellLink object is almost the same as an application shortcut, and it
24 // requires three items: the absolute path to an application, an argument
25 // string, and a title string.
AddShellLink(Microsoft::WRL::ComPtr<IObjectCollection> collection,const base::string16 & application_path,scoped_refptr<ShellLinkItem> item)26 bool AddShellLink(Microsoft::WRL::ComPtr<IObjectCollection> collection,
27                   const base::string16& application_path,
28                   scoped_refptr<ShellLinkItem> item) {
29   // Create an IShellLink object.
30   Microsoft::WRL::ComPtr<IShellLink> link;
31   HRESULT result = ::CoCreateInstance(
32       CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&link));
33   if (FAILED(result))
34     return false;
35 
36   // Set the application path.
37   // We should exit this function when this call fails because it doesn't make
38   // any sense to add a shortcut that we cannot execute.
39   result = link->SetPath(application_path.c_str());
40   if (FAILED(result))
41     return false;
42 
43   // Attach the command-line switches of this process before the given
44   // arguments and set it as the arguments of this IShellLink object.
45   // We also exit this function when this call fails because it isn't useful to
46   // add a shortcut that cannot open the given page.
47   base::string16 arguments(item->GetArguments());
48   if (!arguments.empty()) {
49     result = link->SetArguments(arguments.c_str());
50     if (FAILED(result))
51       return false;
52   }
53 
54   // Attach the given icon path to this IShellLink object.
55   // Since an icon is an optional item for an IShellLink object, so we don't
56   // have to exit even when it fails.
57   if (!item->icon_path().empty())
58     link->SetIconLocation(item->icon_path().c_str(), item->icon_index());
59 
60   // Set the title of the IShellLink object.
61   // The IShellLink interface does not have any functions which update its
62   // title because this interface is originally for creating an application
63   // shortcut which doesn't have titles.
64   // So, we should use the IPropertyStore interface to set its title.
65   Microsoft::WRL::ComPtr<IPropertyStore> property_store;
66   result = link.As(&property_store);
67   if (FAILED(result))
68     return false;
69 
70   if (!base::win::SetStringValueForPropertyStore(
71           property_store.Get(),
72           PKEY_Title,
73           item->title().c_str())) {
74     return false;
75   }
76 
77   // Add this IShellLink object to the given collection.
78   return SUCCEEDED(collection->AddObject(link.Get()));
79 }
80 
81 }  // namespace
82 
83 
84 // ShellLinkItem
85 
ShellLinkItem()86 ShellLinkItem::ShellLinkItem()
87     : command_line_(base::CommandLine::NO_PROGRAM), icon_index_(0) {
88 }
89 
~ShellLinkItem()90 ShellLinkItem::~ShellLinkItem() {}
91 
GetArguments() const92 base::string16 ShellLinkItem::GetArguments() const {
93   return command_line_.GetArgumentsString();
94 }
95 
GetCommandLine()96 base::CommandLine* ShellLinkItem::GetCommandLine() {
97   return &command_line_;
98 }
99 
100 
101 // JumpListUpdater
102 
JumpListUpdater(const base::string16 & app_user_model_id)103 JumpListUpdater::JumpListUpdater(const base::string16& app_user_model_id)
104     : app_user_model_id_(app_user_model_id), user_max_items_(0) {}
105 
~JumpListUpdater()106 JumpListUpdater::~JumpListUpdater() {
107 }
108 
109 // static
IsEnabled()110 bool JumpListUpdater::IsEnabled() {
111   // Do not create custom JumpLists in tests. See http://crbug.com/389375.
112   return !base::CommandLine::ForCurrentProcess()->HasSwitch(
113       switches::kTestType);
114 }
115 
BeginUpdate()116 bool JumpListUpdater::BeginUpdate() {
117   // This instance is expected to be one-time-use only.
118   DCHECK(!destination_list_.Get());
119 
120   // Check preconditions.
121   if (!JumpListUpdater::IsEnabled() || app_user_model_id_.empty())
122     return false;
123 
124   // Create an ICustomDestinationList object and attach it to our application.
125   HRESULT result =
126       ::CoCreateInstance(CLSID_DestinationList, NULL, CLSCTX_INPROC_SERVER,
127                          IID_PPV_ARGS(&destination_list_));
128   if (FAILED(result))
129     return false;
130 
131   // Set the App ID for this JumpList.
132   result = destination_list_->SetAppID(app_user_model_id_.c_str());
133   if (FAILED(result))
134     return false;
135 
136   // Start a transaction that updates the JumpList of this application.
137   // This implementation just replaces the all items in this JumpList, so
138   // we don't have to use the IObjectArray object returned from this call.
139   // It seems Windows 7 RC (Build 7100) automatically checks the items in this
140   // removed list and prevent us from adding the same item.
141   UINT max_slots;
142   Microsoft::WRL::ComPtr<IObjectArray> removed;
143   result = destination_list_->BeginList(&max_slots, IID_PPV_ARGS(&removed));
144   if (FAILED(result))
145     return false;
146 
147   user_max_items_ = max_slots;
148 
149   return true;
150 }
151 
CommitUpdate()152 bool JumpListUpdater::CommitUpdate() {
153   if (!destination_list_.Get())
154     return false;
155 
156   // Commit this transaction and send the updated JumpList to Windows.
157   return SUCCEEDED(destination_list_->CommitList());
158 }
159 
AddTasks(const ShellLinkItemList & link_items)160 bool JumpListUpdater::AddTasks(const ShellLinkItemList& link_items) {
161   if (!destination_list_.Get())
162     return false;
163 
164   // Retrieve the absolute path to "chrome.exe".
165   base::FilePath application_path;
166   if (!base::PathService::Get(base::FILE_EXE, &application_path))
167     return false;
168 
169   // Create an EnumerableObjectCollection object to be added items of the
170   // "Task" category.
171   Microsoft::WRL::ComPtr<IObjectCollection> collection;
172   HRESULT result =
173       ::CoCreateInstance(CLSID_EnumerableObjectCollection, NULL,
174                          CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&collection));
175   if (FAILED(result))
176     return false;
177 
178   // Add items to the "Task" category.
179   for (ShellLinkItemList::const_iterator it = link_items.begin();
180        it != link_items.end(); ++it) {
181     if (!AddShellLink(collection, application_path.value(), *it))
182       return false;
183   }
184 
185   // We can now add the new list to the JumpList.
186   // ICustomDestinationList::AddUserTasks() also uses the IObjectArray
187   // interface to retrieve each item in the list. So, we retrieve the
188   // IObjectArray interface from the EnumerableObjectCollection object.
189   Microsoft::WRL::ComPtr<IObjectArray> object_array;
190   result = collection.As(&object_array);
191   if (FAILED(result))
192     return false;
193 
194   return SUCCEEDED(destination_list_->AddUserTasks(object_array.Get()));
195 }
196 
AddCustomCategory(const base::string16 & category_name,const ShellLinkItemList & link_items,size_t max_items)197 bool JumpListUpdater::AddCustomCategory(const base::string16& category_name,
198                                         const ShellLinkItemList& link_items,
199                                         size_t max_items) {
200   if (!destination_list_.Get())
201     return false;
202 
203   // Retrieve the absolute path to "chrome.exe".
204   base::FilePath application_path;
205   if (!base::PathService::Get(base::FILE_EXE, &application_path))
206     return false;
207 
208   // Exit this function when the given vector does not contain any items
209   // because an ICustomDestinationList::AppendCategory() call fails in this
210   // case.
211   if (link_items.empty() || !max_items)
212     return true;
213 
214   // Create an EnumerableObjectCollection object.
215   // We once add the given items to this collection object and add this
216   // collection to the JumpList.
217   Microsoft::WRL::ComPtr<IObjectCollection> collection;
218   HRESULT result =
219       ::CoCreateInstance(CLSID_EnumerableObjectCollection, NULL,
220                          CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&collection));
221   if (FAILED(result))
222     return false;
223 
224   for (ShellLinkItemList::const_iterator item = link_items.begin();
225        item != link_items.end() && max_items > 0; ++item, --max_items) {
226     if (!AddShellLink(collection, application_path.value(), *item))
227       return false;
228   }
229 
230   // We can now add the new list to the JumpList.
231   // The ICustomDestinationList::AppendCategory() function needs the
232   // IObjectArray interface to retrieve each item in the list. So, we retrive
233   // the IObjectArray interface from the IEnumerableObjectCollection object
234   // and use it.
235   // It seems the ICustomDestinationList::AppendCategory() function just
236   // replaces all items in the given category with the ones in the new list.
237   Microsoft::WRL::ComPtr<IObjectArray> object_array;
238   result = collection.As(&object_array);
239   if (FAILED(result))
240     return false;
241 
242   return SUCCEEDED(destination_list_->AppendCategory(category_name.c_str(),
243                                                      object_array.Get()));
244 }
245 
246 // static
DeleteJumpList(const base::string16 & app_user_model_id)247 bool JumpListUpdater::DeleteJumpList(const base::string16& app_user_model_id) {
248   if (!JumpListUpdater::IsEnabled() || app_user_model_id.empty())
249     return false;
250 
251   Microsoft::WRL::ComPtr<ICustomDestinationList> destination_list;
252   return SUCCEEDED(::CoCreateInstance(CLSID_DestinationList, nullptr,
253                                       CLSCTX_INPROC_SERVER,
254                                       IID_PPV_ARGS(&destination_list))) &&
255          SUCCEEDED(destination_list->DeleteList(app_user_model_id.c_str()));
256 }
257