Bumping manifests a=b2g-bump
[gecko.git] / widget / windows / JumpListBuilder.cpp
blob0b719bdaf2572013cfbe4250161d9c5efabca232
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"
46 #define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
48 JumpListBuilder::JumpListBuilder() :
49 mMaxItems(0),
50 mHasCommit(false)
52 ::CoInitialize(nullptr);
54 CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
55 IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr));
57 // Make a lazy thread for any IO
58 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS,
59 NS_LITERAL_CSTRING("Jump List"),
60 LazyIdleThread::ManualShutdown);
61 Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
63 nsCOMPtr<nsIObserverService> observerService =
64 do_GetService("@mozilla.org/observer-service;1");
65 if (observerService) {
66 observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false);
67 observerService->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA, false);
71 JumpListBuilder::~JumpListBuilder()
73 Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
74 mJumpListMgr = nullptr;
75 ::CoUninitialize();
78 /* readonly attribute short available; */
79 NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable)
81 *aAvailable = false;
83 if (mJumpListMgr)
84 *aAvailable = true;
86 return NS_OK;
89 /* readonly attribute boolean isListCommitted; */
90 NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit)
92 *aCommit = mHasCommit;
94 return NS_OK;
97 /* readonly attribute short maxItems; */
98 NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems)
100 if (!mJumpListMgr)
101 return NS_ERROR_NOT_AVAILABLE;
103 *aMaxItems = 0;
105 if (sBuildingList) {
106 *aMaxItems = mMaxItems;
107 return NS_OK;
110 IObjectArray *objArray;
111 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
112 *aMaxItems = mMaxItems;
114 if (objArray)
115 objArray->Release();
117 mJumpListMgr->AbortList();
120 return NS_OK;
123 /* boolean initListBuild(in nsIMutableArray removedItems); */
124 NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval)
126 NS_ENSURE_ARG_POINTER(removedItems);
128 *_retval = false;
130 if (!mJumpListMgr)
131 return NS_ERROR_NOT_AVAILABLE;
133 if(sBuildingList)
134 AbortListBuild();
136 IObjectArray *objArray;
138 // The returned objArray of removed items are for manually removed items.
139 // This does not return items which are removed because they were previously
140 // part of the jump list but are no longer part of the jump list.
141 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
142 if (objArray) {
143 TransferIObjectArrayToIMutableArray(objArray, removedItems);
144 objArray->Release();
147 RemoveIconCacheForItems(removedItems);
149 sBuildingList = true;
150 *_retval = true;
151 return NS_OK;
154 return NS_OK;
157 // Ensures that we don't have old ICO files that aren't in our jump lists
158 // anymore left over in the cache.
159 nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items)
161 NS_ENSURE_ARG_POINTER(items);
163 nsresult rv;
164 uint32_t length;
165 items->GetLength(&length);
166 for (uint32_t i = 0; i < length; ++i) {
168 //Obtain an IJumpListItem and get the type
169 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
170 if (!item) {
171 continue;
173 int16_t type;
174 if (NS_FAILED(item->GetType(&type))) {
175 continue;
178 // If the item is a shortcut, remove its associated icon if any
179 if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) {
180 nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item);
181 if (shortcut) {
182 nsCOMPtr<nsIURI> uri;
183 rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri));
184 if (NS_SUCCEEDED(rv) && uri) {
186 // The local file path is stored inside the nsIURI
187 // Get the nsIURI spec which stores the local path for the icon to remove
188 nsAutoCString spec;
189 nsresult rv = uri->GetSpec(spec);
190 NS_ENSURE_SUCCESS(rv, rv);
192 nsCOMPtr<nsIRunnable> event
193 = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec));
194 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
196 // The shortcut was generated from an IShellLinkW so IShellLinkW can
197 // only tell us what the original icon is and not the URI.
198 // So this field was used only temporarily as the actual icon file
199 // path. It should be cleared.
200 shortcut->SetFaviconPageUri(nullptr);
205 } // end for
207 return NS_OK;
210 // Ensures that we have no old ICO files left in the jump list cache
211 nsresult JumpListBuilder::RemoveIconCacheForAllItems()
213 // Construct the path of our jump list cache
214 nsCOMPtr<nsIFile> jumpListCacheDir;
215 nsresult rv = NS_GetSpecialDirectory("ProfLDS",
216 getter_AddRefs(jumpListCacheDir));
217 NS_ENSURE_SUCCESS(rv, rv);
218 rv = jumpListCacheDir->AppendNative(nsDependentCString(
219 mozilla::widget::FaviconHelper::kJumpListCacheDir));
220 NS_ENSURE_SUCCESS(rv, rv);
221 nsCOMPtr<nsISimpleEnumerator> entries;
222 rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
223 NS_ENSURE_SUCCESS(rv, rv);
225 // Loop through each directory entry and remove all ICO files found
226 do {
227 bool hasMore = false;
228 if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore)
229 break;
231 nsCOMPtr<nsISupports> supp;
232 if (NS_FAILED(entries->GetNext(getter_AddRefs(supp))))
233 break;
235 nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp));
236 nsAutoString path;
237 if (NS_FAILED(currFile->GetPath(path)))
238 continue;
240 int32_t len = path.Length();
241 if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
242 // Check if the cached ICO file exists
243 bool exists;
244 if (NS_FAILED(currFile->Exists(&exists)) || !exists)
245 continue;
247 // We found an ICO file that exists, so we should remove it
248 currFile->Remove(false);
250 } while(true);
252 return NS_OK;
255 /* boolean addListToBuild(in short aCatType, [optional] in nsIArray items, [optional] in AString catName); */
256 NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval)
258 nsresult rv;
260 *_retval = false;
262 if (!mJumpListMgr)
263 return NS_ERROR_NOT_AVAILABLE;
265 switch(aCatType) {
266 case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS:
268 NS_ENSURE_ARG_POINTER(items);
270 HRESULT hr;
271 nsRefPtr<IObjectCollection> collection;
272 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
273 CLSCTX_INPROC_SERVER, IID_IObjectCollection,
274 getter_AddRefs(collection));
275 if (FAILED(hr))
276 return NS_ERROR_UNEXPECTED;
278 // Build the list
279 uint32_t length;
280 items->GetLength(&length);
281 for (uint32_t i = 0; i < length; ++i) {
282 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
283 if (!item)
284 continue;
285 // Check for separators
286 if (IsSeparator(item)) {
287 nsRefPtr<IShellLinkW> link;
288 rv = JumpListSeparator::GetSeparator(link);
289 if (NS_FAILED(rv))
290 return rv;
291 collection->AddObject(link);
292 continue;
294 // These should all be ShellLinks
295 nsRefPtr<IShellLinkW> link;
296 rv = JumpListShortcut::GetShellLink(item, link, mIOThread);
297 if (NS_FAILED(rv))
298 return rv;
299 collection->AddObject(link);
302 // We need IObjectArray to submit
303 nsRefPtr<IObjectArray> pArray;
304 hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
305 if (FAILED(hr))
306 return NS_ERROR_UNEXPECTED;
308 // Add the tasks
309 hr = mJumpListMgr->AddUserTasks(pArray);
310 if (SUCCEEDED(hr))
311 *_retval = true;
312 return NS_OK;
314 break;
315 case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT:
317 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT)))
318 *_retval = true;
319 return NS_OK;
321 break;
322 case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT:
324 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
325 *_retval = true;
326 return NS_OK;
328 break;
329 case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST:
331 NS_ENSURE_ARG_POINTER(items);
333 if (catName.IsEmpty())
334 return NS_ERROR_INVALID_ARG;
336 HRESULT hr;
337 nsRefPtr<IObjectCollection> collection;
338 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
339 CLSCTX_INPROC_SERVER, IID_IObjectCollection,
340 getter_AddRefs(collection));
341 if (FAILED(hr))
342 return NS_ERROR_UNEXPECTED;
344 uint32_t length;
345 items->GetLength(&length);
346 for (uint32_t i = 0; i < length; ++i) {
347 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
348 if (!item)
349 continue;
350 int16_t type;
351 if (NS_FAILED(item->GetType(&type)))
352 continue;
353 switch(type) {
354 case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR:
356 nsRefPtr<IShellLinkW> shellItem;
357 rv = JumpListSeparator::GetSeparator(shellItem);
358 if (NS_FAILED(rv))
359 return rv;
360 collection->AddObject(shellItem);
362 break;
363 case nsIJumpListItem::JUMPLIST_ITEM_LINK:
365 nsRefPtr<IShellItem2> shellItem;
366 rv = JumpListLink::GetShellItem(item, shellItem);
367 if (NS_FAILED(rv))
368 return rv;
369 collection->AddObject(shellItem);
371 break;
372 case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT:
374 nsRefPtr<IShellLinkW> shellItem;
375 rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread);
376 if (NS_FAILED(rv))
377 return rv;
378 collection->AddObject(shellItem);
380 break;
384 // We need IObjectArray to submit
385 nsRefPtr<IObjectArray> pArray;
386 hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
387 if (FAILED(hr))
388 return NS_ERROR_UNEXPECTED;
390 // Add the tasks
391 hr = mJumpListMgr->AppendCategory(reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
392 if (SUCCEEDED(hr))
393 *_retval = true;
395 // Get rid of the old icons
396 nsCOMPtr<nsIRunnable> event =
397 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
398 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
400 return NS_OK;
402 break;
404 return NS_OK;
407 /* void abortListBuild(); */
408 NS_IMETHODIMP JumpListBuilder::AbortListBuild()
410 if (!mJumpListMgr)
411 return NS_ERROR_NOT_AVAILABLE;
413 mJumpListMgr->AbortList();
414 sBuildingList = false;
416 return NS_OK;
419 /* boolean commitListBuild(); */
420 NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval)
422 *_retval = false;
424 if (!mJumpListMgr)
425 return NS_ERROR_NOT_AVAILABLE;
427 HRESULT hr = mJumpListMgr->CommitList();
428 sBuildingList = false;
430 // XXX We might want some specific error data here.
431 if (SUCCEEDED(hr)) {
432 *_retval = true;
433 mHasCommit = true;
436 return NS_OK;
439 /* boolean deleteActiveList(); */
440 NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval)
442 *_retval = false;
444 if (!mJumpListMgr)
445 return NS_ERROR_NOT_AVAILABLE;
447 if(sBuildingList)
448 AbortListBuild();
450 nsAutoString uid;
451 if (!WinTaskbar::GetAppUserModelID(uid))
452 return NS_OK;
454 if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get())))
455 *_retval = true;
457 return NS_OK;
460 /* internal */
462 bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item)
464 int16_t type;
465 item->GetType(&type);
466 if (NS_FAILED(item->GetType(&type)))
467 return false;
469 if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR)
470 return true;
471 return false;
474 // TransferIObjectArrayToIMutableArray - used in converting removed items
475 // to our objects.
476 nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems)
478 NS_ENSURE_ARG_POINTER(objArray);
479 NS_ENSURE_ARG_POINTER(removedItems);
481 nsresult rv;
483 uint32_t count = 0;
484 objArray->GetCount(&count);
486 nsCOMPtr<nsIJumpListItem> item;
488 for (uint32_t idx = 0; idx < count; idx++) {
489 IShellLinkW * pLink = nullptr;
490 IShellItem * pItem = nullptr;
492 if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) {
493 nsCOMPtr<nsIJumpListShortcut> shortcut =
494 do_CreateInstance(kJumpListShortcutCID, &rv);
495 if (NS_FAILED(rv))
496 return NS_ERROR_UNEXPECTED;
497 rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut);
498 item = do_QueryInterface(shortcut);
500 else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) {
501 nsCOMPtr<nsIJumpListLink> link =
502 do_CreateInstance(kJumpListLinkCID, &rv);
503 if (NS_FAILED(rv))
504 return NS_ERROR_UNEXPECTED;
505 rv = JumpListLink::GetJumpListLink(pItem, link);
506 item = do_QueryInterface(link);
509 if (pLink)
510 pLink->Release();
511 if (pItem)
512 pItem->Release();
514 if (NS_SUCCEEDED(rv)) {
515 removedItems->AppendElement(item, false);
518 return NS_OK;
521 NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject,
522 const char* aTopic,
523 const char16_t* aData)
525 NS_ENSURE_ARG_POINTER(aTopic);
526 if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
527 nsCOMPtr<nsIObserverService> observerService =
528 do_GetService("@mozilla.org/observer-service;1");
529 if (observerService) {
530 observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
532 mIOThread->Shutdown();
533 } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
534 nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
535 bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
536 if (!enabled) {
538 nsCOMPtr<nsIRunnable> event =
539 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
540 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
542 } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) {
543 // Delete JumpListCache icons from Disk, if any.
544 nsCOMPtr<nsIRunnable> event =
545 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
546 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
548 return NS_OK;
551 } // namespace widget
552 } // namespace mozilla