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