Merge mozilla-beta to b2g34. a=merge
[gecko.git] / widget / windows / JumpListBuilder.cpp
blob1f1e293c9f19f5c2ca863efe22d3d6b2e0477c64
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"
24 #include "nsIObserverService.h"
26 #include "WinUtils.h"
28 // The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes.
29 #define DEFAULT_THREAD_TIMEOUT_MS 30000
31 namespace mozilla {
32 namespace widget {
34 static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID);
35 static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID);
36 static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID);
38 // defined in WinTaskbar.cpp
39 extern const wchar_t *gMozillaJumpListIDGeneric;
41 bool JumpListBuilder::sBuildingList = false;
42 const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
44 NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver)
45 #define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
47 JumpListBuilder::JumpListBuilder() :
48 mMaxItems(0),
49 mHasCommit(false)
51 ::CoInitialize(nullptr);
53 CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
54 IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr));
56 // Make a lazy thread for any IO
57 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
58 NS_LITERAL_CSTRING("Jump List"),
59 LazyIdleThread::ManualShutdown);
60 Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
62 nsCOMPtr<nsIObserverService> observerService =
63 do_GetService("@mozilla.org/observer-service;1");
64 if (observerService) {
65 observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false);
69 JumpListBuilder::~JumpListBuilder()
71 Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
72 mJumpListMgr = nullptr;
73 ::CoUninitialize();
76 /* readonly attribute short available; */
77 NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable)
79 *aAvailable = false;
81 if (mJumpListMgr)
82 *aAvailable = true;
84 return NS_OK;
87 /* readonly attribute boolean isListCommitted; */
88 NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit)
90 *aCommit = mHasCommit;
92 return NS_OK;
95 /* readonly attribute short maxItems; */
96 NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems)
98 if (!mJumpListMgr)
99 return NS_ERROR_NOT_AVAILABLE;
101 *aMaxItems = 0;
103 if (sBuildingList) {
104 *aMaxItems = mMaxItems;
105 return NS_OK;
108 IObjectArray *objArray;
109 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
110 *aMaxItems = mMaxItems;
112 if (objArray)
113 objArray->Release();
115 mJumpListMgr->AbortList();
118 return NS_OK;
121 /* boolean initListBuild(in nsIMutableArray removedItems); */
122 NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval)
124 NS_ENSURE_ARG_POINTER(removedItems);
126 *_retval = false;
128 if (!mJumpListMgr)
129 return NS_ERROR_NOT_AVAILABLE;
131 if(sBuildingList)
132 AbortListBuild();
134 IObjectArray *objArray;
136 // The returned objArray of removed items are for manually removed items.
137 // This does not return items which are removed because they were previously
138 // part of the jump list but are no longer part of the jump list.
139 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
140 if (objArray) {
141 TransferIObjectArrayToIMutableArray(objArray, removedItems);
142 objArray->Release();
145 RemoveIconCacheForItems(removedItems);
147 sBuildingList = true;
148 *_retval = true;
149 return NS_OK;
152 return NS_OK;
155 // Ensures that we don't have old ICO files that aren't in our jump lists
156 // anymore left over in the cache.
157 nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items)
159 NS_ENSURE_ARG_POINTER(items);
161 nsresult rv;
162 uint32_t length;
163 items->GetLength(&length);
164 for (uint32_t i = 0; i < length; ++i) {
166 //Obtain an IJumpListItem and get the type
167 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
168 if (!item) {
169 continue;
171 int16_t type;
172 if (NS_FAILED(item->GetType(&type))) {
173 continue;
176 // If the item is a shortcut, remove its associated icon if any
177 if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) {
178 nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item);
179 if (shortcut) {
180 nsCOMPtr<nsIURI> uri;
181 rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri));
182 if (NS_SUCCEEDED(rv) && uri) {
184 // The local file path is stored inside the nsIURI
185 // Get the nsIURI spec which stores the local path for the icon to remove
186 nsAutoCString spec;
187 nsresult rv = uri->GetSpec(spec);
188 NS_ENSURE_SUCCESS(rv, rv);
190 nsCOMPtr<nsIRunnable> event
191 = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec));
192 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
194 // The shortcut was generated from an IShellLinkW so IShellLinkW can
195 // only tell us what the original icon is and not the URI.
196 // So this field was used only temporarily as the actual icon file
197 // path. It should be cleared.
198 shortcut->SetFaviconPageUri(nullptr);
203 } // end for
205 return NS_OK;
208 // Ensures that we have no old ICO files left in the jump list cache
209 nsresult JumpListBuilder::RemoveIconCacheForAllItems()
211 // Construct the path of our jump list cache
212 nsCOMPtr<nsIFile> jumpListCacheDir;
213 nsresult rv = NS_GetSpecialDirectory("ProfLDS",
214 getter_AddRefs(jumpListCacheDir));
215 NS_ENSURE_SUCCESS(rv, rv);
216 rv = jumpListCacheDir->AppendNative(nsDependentCString(
217 mozilla::widget::FaviconHelper::kJumpListCacheDir));
218 NS_ENSURE_SUCCESS(rv, rv);
219 nsCOMPtr<nsISimpleEnumerator> entries;
220 rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
221 NS_ENSURE_SUCCESS(rv, rv);
223 // Loop through each directory entry and remove all ICO files found
224 do {
225 bool hasMore = false;
226 if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore)
227 break;
229 nsCOMPtr<nsISupports> supp;
230 if (NS_FAILED(entries->GetNext(getter_AddRefs(supp))))
231 break;
233 nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
234 nsAutoString path;
235 if (NS_FAILED(currFile->GetPath(path)))
236 continue;
238 int32_t len = path.Length();
239 if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
240 // Check if the cached ICO file exists
241 bool exists;
242 if (NS_FAILED(currFile->Exists(&exists)) || !exists)
243 continue;
245 // We found an ICO file that exists, so we should remove it
246 currFile->Remove(false);
248 } while(true);
250 return NS_OK;
253 /* boolean addListToBuild(in short aCatType, [optional] in nsIArray items, [optional] in AString catName); */
254 NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval)
256 nsresult rv;
258 *_retval = false;
260 if (!mJumpListMgr)
261 return NS_ERROR_NOT_AVAILABLE;
263 switch(aCatType) {
264 case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS:
266 NS_ENSURE_ARG_POINTER(items);
268 HRESULT hr;
269 nsRefPtr<IObjectCollection> collection;
270 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
271 CLSCTX_INPROC_SERVER, IID_IObjectCollection,
272 getter_AddRefs(collection));
273 if (FAILED(hr))
274 return NS_ERROR_UNEXPECTED;
276 // Build the list
277 uint32_t length;
278 items->GetLength(&length);
279 for (uint32_t i = 0; i < length; ++i) {
280 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
281 if (!item)
282 continue;
283 // Check for separators
284 if (IsSeparator(item)) {
285 nsRefPtr<IShellLinkW> link;
286 rv = JumpListSeparator::GetSeparator(link);
287 if (NS_FAILED(rv))
288 return rv;
289 collection->AddObject(link);
290 continue;
292 // These should all be ShellLinks
293 nsRefPtr<IShellLinkW> link;
294 rv = JumpListShortcut::GetShellLink(item, link, mIOThread);
295 if (NS_FAILED(rv))
296 return rv;
297 collection->AddObject(link);
300 // We need IObjectArray to submit
301 nsRefPtr<IObjectArray> pArray;
302 hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
303 if (FAILED(hr))
304 return NS_ERROR_UNEXPECTED;
306 // Add the tasks
307 hr = mJumpListMgr->AddUserTasks(pArray);
308 if (SUCCEEDED(hr))
309 *_retval = true;
310 return NS_OK;
312 break;
313 case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT:
315 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT)))
316 *_retval = true;
317 return NS_OK;
319 break;
320 case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT:
322 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
323 *_retval = true;
324 return NS_OK;
326 break;
327 case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST:
329 NS_ENSURE_ARG_POINTER(items);
331 if (catName.IsEmpty())
332 return NS_ERROR_INVALID_ARG;
334 HRESULT hr;
335 nsRefPtr<IObjectCollection> collection;
336 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
337 CLSCTX_INPROC_SERVER, IID_IObjectCollection,
338 getter_AddRefs(collection));
339 if (FAILED(hr))
340 return NS_ERROR_UNEXPECTED;
342 uint32_t length;
343 items->GetLength(&length);
344 for (uint32_t i = 0; i < length; ++i) {
345 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
346 if (!item)
347 continue;
348 int16_t type;
349 if (NS_FAILED(item->GetType(&type)))
350 continue;
351 switch(type) {
352 case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR:
354 nsRefPtr<IShellLinkW> shellItem;
355 rv = JumpListSeparator::GetSeparator(shellItem);
356 if (NS_FAILED(rv))
357 return rv;
358 collection->AddObject(shellItem);
360 break;
361 case nsIJumpListItem::JUMPLIST_ITEM_LINK:
363 nsRefPtr<IShellItem2> shellItem;
364 rv = JumpListLink::GetShellItem(item, shellItem);
365 if (NS_FAILED(rv))
366 return rv;
367 collection->AddObject(shellItem);
369 break;
370 case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT:
372 nsRefPtr<IShellLinkW> shellItem;
373 rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread);
374 if (NS_FAILED(rv))
375 return rv;
376 collection->AddObject(shellItem);
378 break;
382 // We need IObjectArray to submit
383 nsRefPtr<IObjectArray> pArray;
384 hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
385 if (FAILED(hr))
386 return NS_ERROR_UNEXPECTED;
388 // Add the tasks
389 hr = mJumpListMgr->AppendCategory(reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
390 if (SUCCEEDED(hr))
391 *_retval = true;
393 // Get rid of the old icons
394 nsCOMPtr<nsIRunnable> event =
395 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
396 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
398 return NS_OK;
400 break;
402 return NS_OK;
405 /* void abortListBuild(); */
406 NS_IMETHODIMP JumpListBuilder::AbortListBuild()
408 if (!mJumpListMgr)
409 return NS_ERROR_NOT_AVAILABLE;
411 mJumpListMgr->AbortList();
412 sBuildingList = false;
414 return NS_OK;
417 /* boolean commitListBuild(); */
418 NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval)
420 *_retval = false;
422 if (!mJumpListMgr)
423 return NS_ERROR_NOT_AVAILABLE;
425 HRESULT hr = mJumpListMgr->CommitList();
426 sBuildingList = false;
428 // XXX We might want some specific error data here.
429 if (SUCCEEDED(hr)) {
430 *_retval = true;
431 mHasCommit = true;
434 return NS_OK;
437 /* boolean deleteActiveList(); */
438 NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval)
440 *_retval = false;
442 if (!mJumpListMgr)
443 return NS_ERROR_NOT_AVAILABLE;
445 if(sBuildingList)
446 AbortListBuild();
448 nsAutoString uid;
449 if (!WinTaskbar::GetAppUserModelID(uid))
450 return NS_OK;
452 if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get())))
453 *_retval = true;
455 return NS_OK;
458 /* internal */
460 bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item)
462 int16_t type;
463 item->GetType(&type);
464 if (NS_FAILED(item->GetType(&type)))
465 return false;
467 if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR)
468 return true;
469 return false;
472 // TransferIObjectArrayToIMutableArray - used in converting removed items
473 // to our objects.
474 nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems)
476 NS_ENSURE_ARG_POINTER(objArray);
477 NS_ENSURE_ARG_POINTER(removedItems);
479 nsresult rv;
481 uint32_t count = 0;
482 objArray->GetCount(&count);
484 nsCOMPtr<nsIJumpListItem> item;
486 for (uint32_t idx = 0; idx < count; idx++) {
487 IShellLinkW * pLink = nullptr;
488 IShellItem * pItem = nullptr;
490 if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) {
491 nsCOMPtr<nsIJumpListShortcut> shortcut =
492 do_CreateInstance(kJumpListShortcutCID, &rv);
493 if (NS_FAILED(rv))
494 return NS_ERROR_UNEXPECTED;
495 rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut);
496 item = do_QueryInterface(shortcut);
498 else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) {
499 nsCOMPtr<nsIJumpListLink> link =
500 do_CreateInstance(kJumpListLinkCID, &rv);
501 if (NS_FAILED(rv))
502 return NS_ERROR_UNEXPECTED;
503 rv = JumpListLink::GetJumpListLink(pItem, link);
504 item = do_QueryInterface(link);
507 if (pLink)
508 pLink->Release();
509 if (pItem)
510 pItem->Release();
512 if (NS_SUCCEEDED(rv)) {
513 removedItems->AppendElement(item, false);
516 return NS_OK;
519 NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject,
520 const char* aTopic,
521 const char16_t* aData)
523 NS_ENSURE_ARG_POINTER(aTopic);
524 if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
525 nsCOMPtr<nsIObserverService> observerService =
526 do_GetService("@mozilla.org/observer-service;1");
527 if (observerService) {
528 observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
530 mIOThread->Shutdown();
531 } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
532 nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
533 bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
534 if (!enabled) {
536 nsCOMPtr<nsIRunnable> event =
537 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
538 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
541 return NS_OK;
544 } // namespace widget
545 } // namespace mozilla