Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "JumpListBuilder.h"
8 #include "nsError.h"
9 #include "nsCOMPtr.h"
10 #include "nsServiceManagerUtils.h"
11 #include "nsAutoPtr.h"
12 #include "nsString.h"
13 #include "nsArrayUtils.h"
14 #include "nsIMutableArray.h"
15 #include "nsWidgetsCID.h"
16 #include "WinTaskbar.h"
17 #include "nsDirectoryServiceUtils.h"
18 #include "nsISimpleEnumerator.h"
19 #include "mozilla/Preferences.h"
20 #include "nsStringStream.h"
21 #include "nsNetUtil.h"
22 #include "nsThreadUtils.h"
23 #include "mozilla/LazyIdleThread.h"
25 #include "WinUtils.h"
27 // The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes.
28 #define DEFAULT_THREAD_TIMEOUT_MS 30000
30 namespace mozilla {
31 namespace widget {
33 static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID);
34 static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID);
35 static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID);
37 // defined in WinTaskbar.cpp
38 extern const wchar_t *gMozillaJumpListIDGeneric;
40 bool JumpListBuilder::sBuildingList = false;
41 const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
43 NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver)
45 JumpListBuilder::JumpListBuilder() :
46 mMaxItems(0),
47 mHasCommit(false)
48 {
49 ::CoInitialize(nullptr);
51 CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
52 IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr));
54 // Make a lazy thread for any IO
55 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
56 NS_LITERAL_CSTRING("Jump List"),
57 LazyIdleThread::ManualShutdown);
58 Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
59 }
61 JumpListBuilder::~JumpListBuilder()
62 {
63 mIOThread->Shutdown();
64 Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
65 mJumpListMgr = nullptr;
66 ::CoUninitialize();
67 }
69 /* readonly attribute short available; */
70 NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable)
71 {
72 *aAvailable = false;
74 if (mJumpListMgr)
75 *aAvailable = true;
77 return NS_OK;
78 }
80 /* readonly attribute boolean isListCommitted; */
81 NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit)
82 {
83 *aCommit = mHasCommit;
85 return NS_OK;
86 }
88 /* readonly attribute short maxItems; */
89 NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems)
90 {
91 if (!mJumpListMgr)
92 return NS_ERROR_NOT_AVAILABLE;
94 *aMaxItems = 0;
96 if (sBuildingList) {
97 *aMaxItems = mMaxItems;
98 return NS_OK;
99 }
101 IObjectArray *objArray;
102 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
103 *aMaxItems = mMaxItems;
105 if (objArray)
106 objArray->Release();
108 mJumpListMgr->AbortList();
109 }
111 return NS_OK;
112 }
114 /* boolean initListBuild(in nsIMutableArray removedItems); */
115 NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval)
116 {
117 NS_ENSURE_ARG_POINTER(removedItems);
119 *_retval = false;
121 if (!mJumpListMgr)
122 return NS_ERROR_NOT_AVAILABLE;
124 if(sBuildingList)
125 AbortListBuild();
127 IObjectArray *objArray;
129 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
130 if (objArray) {
131 TransferIObjectArrayToIMutableArray(objArray, removedItems);
132 objArray->Release();
133 }
135 RemoveIconCacheForItems(removedItems);
137 sBuildingList = true;
138 *_retval = true;
139 return NS_OK;
140 }
142 return NS_OK;
143 }
145 // Ensures that we don't have old ICO files that aren't in our jump lists
146 // anymore left over in the cache.
147 nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items)
148 {
149 NS_ENSURE_ARG_POINTER(items);
151 nsresult rv;
152 uint32_t length;
153 items->GetLength(&length);
154 for (uint32_t i = 0; i < length; ++i) {
156 //Obtain an IJumpListItem and get the type
157 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
158 if (!item) {
159 continue;
160 }
161 int16_t type;
162 if (NS_FAILED(item->GetType(&type))) {
163 continue;
164 }
166 // If the item is a shortcut, remove its associated icon if any
167 if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) {
168 nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item);
169 if (shortcut) {
170 nsCOMPtr<nsIURI> uri;
171 rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri));
172 if (NS_SUCCEEDED(rv) && uri) {
174 // The local file path is stored inside the nsIURI
175 // Get the nsIURI spec which stores the local path for the icon to remove
176 nsAutoCString spec;
177 nsresult rv = uri->GetSpec(spec);
178 NS_ENSURE_SUCCESS(rv, rv);
180 nsCOMPtr<nsIRunnable> event
181 = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec));
182 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
184 // The shortcut was generated from an IShellLinkW so IShellLinkW can
185 // only tell us what the original icon is and not the URI.
186 // So this field was used only temporarily as the actual icon file
187 // path. It should be cleared.
188 shortcut->SetFaviconPageUri(nullptr);
189 }
190 }
191 }
193 } // end for
195 return NS_OK;
196 }
198 // Ensures that we have no old ICO files left in the jump list cache
199 nsresult JumpListBuilder::RemoveIconCacheForAllItems()
200 {
201 // Construct the path of our jump list cache
202 nsCOMPtr<nsIFile> jumpListCacheDir;
203 nsresult rv = NS_GetSpecialDirectory("ProfLDS",
204 getter_AddRefs(jumpListCacheDir));
205 NS_ENSURE_SUCCESS(rv, rv);
206 rv = jumpListCacheDir->AppendNative(nsDependentCString(
207 mozilla::widget::FaviconHelper::kJumpListCacheDir));
208 NS_ENSURE_SUCCESS(rv, rv);
209 nsCOMPtr<nsISimpleEnumerator> entries;
210 rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
211 NS_ENSURE_SUCCESS(rv, rv);
213 // Loop through each directory entry and remove all ICO files found
214 do {
215 bool hasMore = false;
216 if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore)
217 break;
219 nsCOMPtr<nsISupports> supp;
220 if (NS_FAILED(entries->GetNext(getter_AddRefs(supp))))
221 break;
223 nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
224 nsAutoString path;
225 if (NS_FAILED(currFile->GetPath(path)))
226 continue;
228 int32_t len = path.Length();
229 if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
230 // Check if the cached ICO file exists
231 bool exists;
232 if (NS_FAILED(currFile->Exists(&exists)) || !exists)
233 continue;
235 // We found an ICO file that exists, so we should remove it
236 currFile->Remove(false);
237 }
238 } while(true);
240 return NS_OK;
241 }
243 /* boolean addListToBuild(in short aCatType, [optional] in nsIArray items, [optional] in AString catName); */
244 NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval)
245 {
246 nsresult rv;
248 *_retval = false;
250 if (!mJumpListMgr)
251 return NS_ERROR_NOT_AVAILABLE;
253 switch(aCatType) {
254 case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS:
255 {
256 NS_ENSURE_ARG_POINTER(items);
258 HRESULT hr;
259 nsRefPtr<IObjectCollection> collection;
260 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
261 CLSCTX_INPROC_SERVER, IID_IObjectCollection,
262 getter_AddRefs(collection));
263 if (FAILED(hr))
264 return NS_ERROR_UNEXPECTED;
266 // Build the list
267 uint32_t length;
268 items->GetLength(&length);
269 for (uint32_t i = 0; i < length; ++i) {
270 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
271 if (!item)
272 continue;
273 // Check for separators
274 if (IsSeparator(item)) {
275 nsRefPtr<IShellLinkW> link;
276 rv = JumpListSeparator::GetSeparator(link);
277 if (NS_FAILED(rv))
278 return rv;
279 collection->AddObject(link);
280 continue;
281 }
282 // These should all be ShellLinks
283 nsRefPtr<IShellLinkW> link;
284 rv = JumpListShortcut::GetShellLink(item, link, mIOThread);
285 if (NS_FAILED(rv))
286 return rv;
287 collection->AddObject(link);
288 }
290 // We need IObjectArray to submit
291 nsRefPtr<IObjectArray> pArray;
292 hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
293 if (FAILED(hr))
294 return NS_ERROR_UNEXPECTED;
296 // Add the tasks
297 hr = mJumpListMgr->AddUserTasks(pArray);
298 if (SUCCEEDED(hr))
299 *_retval = true;
300 return NS_OK;
301 }
302 break;
303 case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT:
304 {
305 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT)))
306 *_retval = true;
307 return NS_OK;
308 }
309 break;
310 case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT:
311 {
312 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
313 *_retval = true;
314 return NS_OK;
315 }
316 break;
317 case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST:
318 {
319 NS_ENSURE_ARG_POINTER(items);
321 if (catName.IsEmpty())
322 return NS_ERROR_INVALID_ARG;
324 HRESULT hr;
325 nsRefPtr<IObjectCollection> collection;
326 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
327 CLSCTX_INPROC_SERVER, IID_IObjectCollection,
328 getter_AddRefs(collection));
329 if (FAILED(hr))
330 return NS_ERROR_UNEXPECTED;
332 uint32_t length;
333 items->GetLength(&length);
334 for (uint32_t i = 0; i < length; ++i) {
335 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
336 if (!item)
337 continue;
338 int16_t type;
339 if (NS_FAILED(item->GetType(&type)))
340 continue;
341 switch(type) {
342 case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR:
343 {
344 nsRefPtr<IShellLinkW> shellItem;
345 rv = JumpListSeparator::GetSeparator(shellItem);
346 if (NS_FAILED(rv))
347 return rv;
348 collection->AddObject(shellItem);
349 }
350 break;
351 case nsIJumpListItem::JUMPLIST_ITEM_LINK:
352 {
353 nsRefPtr<IShellItem2> shellItem;
354 rv = JumpListLink::GetShellItem(item, shellItem);
355 if (NS_FAILED(rv))
356 return rv;
357 collection->AddObject(shellItem);
358 }
359 break;
360 case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT:
361 {
362 nsRefPtr<IShellLinkW> shellItem;
363 rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread);
364 if (NS_FAILED(rv))
365 return rv;
366 collection->AddObject(shellItem);
367 }
368 break;
369 }
370 }
372 // We need IObjectArray to submit
373 nsRefPtr<IObjectArray> pArray;
374 hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
375 if (FAILED(hr))
376 return NS_ERROR_UNEXPECTED;
378 // Add the tasks
379 hr = mJumpListMgr->AppendCategory(reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
380 if (SUCCEEDED(hr))
381 *_retval = true;
382 return NS_OK;
383 }
384 break;
385 }
386 return NS_OK;
387 }
389 /* void abortListBuild(); */
390 NS_IMETHODIMP JumpListBuilder::AbortListBuild()
391 {
392 if (!mJumpListMgr)
393 return NS_ERROR_NOT_AVAILABLE;
395 mJumpListMgr->AbortList();
396 sBuildingList = false;
398 return NS_OK;
399 }
401 /* boolean commitListBuild(); */
402 NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval)
403 {
404 *_retval = false;
406 if (!mJumpListMgr)
407 return NS_ERROR_NOT_AVAILABLE;
409 HRESULT hr = mJumpListMgr->CommitList();
410 sBuildingList = false;
412 // XXX We might want some specific error data here.
413 if (SUCCEEDED(hr)) {
414 *_retval = true;
415 mHasCommit = true;
416 }
418 return NS_OK;
419 }
421 /* boolean deleteActiveList(); */
422 NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval)
423 {
424 *_retval = false;
426 if (!mJumpListMgr)
427 return NS_ERROR_NOT_AVAILABLE;
429 if(sBuildingList)
430 AbortListBuild();
432 nsAutoString uid;
433 if (!WinTaskbar::GetAppUserModelID(uid))
434 return NS_OK;
436 if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get())))
437 *_retval = true;
439 return NS_OK;
440 }
442 /* internal */
444 bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item)
445 {
446 int16_t type;
447 item->GetType(&type);
448 if (NS_FAILED(item->GetType(&type)))
449 return false;
451 if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR)
452 return true;
453 return false;
454 }
456 // TransferIObjectArrayToIMutableArray - used in converting removed items
457 // to our objects.
458 nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems)
459 {
460 NS_ENSURE_ARG_POINTER(objArray);
461 NS_ENSURE_ARG_POINTER(removedItems);
463 nsresult rv;
465 uint32_t count = 0;
466 objArray->GetCount(&count);
468 nsCOMPtr<nsIJumpListItem> item;
470 for (uint32_t idx = 0; idx < count; idx++) {
471 IShellLinkW * pLink = nullptr;
472 IShellItem * pItem = nullptr;
474 if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) {
475 nsCOMPtr<nsIJumpListShortcut> shortcut =
476 do_CreateInstance(kJumpListShortcutCID, &rv);
477 if (NS_FAILED(rv))
478 return NS_ERROR_UNEXPECTED;
479 rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut);
480 item = do_QueryInterface(shortcut);
481 }
482 else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) {
483 nsCOMPtr<nsIJumpListLink> link =
484 do_CreateInstance(kJumpListLinkCID, &rv);
485 if (NS_FAILED(rv))
486 return NS_ERROR_UNEXPECTED;
487 rv = JumpListLink::GetJumpListLink(pItem, link);
488 item = do_QueryInterface(link);
489 }
491 if (pLink)
492 pLink->Release();
493 if (pItem)
494 pItem->Release();
496 if (NS_SUCCEEDED(rv)) {
497 removedItems->AppendElement(item, false);
498 }
499 }
500 return NS_OK;
501 }
503 NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject,
504 const char* aTopic,
505 const char16_t* aData)
506 {
507 if (nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
508 bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
509 if (!enabled) {
511 nsCOMPtr<nsIRunnable> event =
512 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
513 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
514 }
515 }
516 return NS_OK;
517 }
519 } // namespace widget
520 } // namespace mozilla