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"
10 #include "nsServiceManagerUtils.h"
11 #include "nsAutoPtr.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"
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
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() :
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;
78 /* readonly attribute short available; */
79 NS_IMETHODIMP
JumpListBuilder::GetAvailable(int16_t *aAvailable
)
89 /* readonly attribute boolean isListCommitted; */
90 NS_IMETHODIMP
JumpListBuilder::GetIsListCommitted(bool *aCommit
)
92 *aCommit
= mHasCommit
;
97 /* readonly attribute short maxItems; */
98 NS_IMETHODIMP
JumpListBuilder::GetMaxListItems(int16_t *aMaxItems
)
101 return NS_ERROR_NOT_AVAILABLE
;
106 *aMaxItems
= mMaxItems
;
110 IObjectArray
*objArray
;
111 if (SUCCEEDED(mJumpListMgr
->BeginList(&mMaxItems
, IID_PPV_ARGS(&objArray
)))) {
112 *aMaxItems
= mMaxItems
;
117 mJumpListMgr
->AbortList();
123 /* boolean initListBuild(in nsIMutableArray removedItems); */
124 NS_IMETHODIMP
JumpListBuilder::InitListBuild(nsIMutableArray
*removedItems
, bool *_retval
)
126 NS_ENSURE_ARG_POINTER(removedItems
);
131 return NS_ERROR_NOT_AVAILABLE
;
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
)))) {
143 TransferIObjectArrayToIMutableArray(objArray
, removedItems
);
147 RemoveIconCacheForItems(removedItems
);
149 sBuildingList
= true;
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
);
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
);
174 if (NS_FAILED(item
->GetType(&type
))) {
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
);
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
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);
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
227 bool hasMore
= false;
228 if (NS_FAILED(entries
->HasMoreElements(&hasMore
)) || !hasMore
)
231 nsCOMPtr
<nsISupports
> supp
;
232 if (NS_FAILED(entries
->GetNext(getter_AddRefs(supp
))))
235 nsCOMPtr
<nsIFile
> currFile(do_QueryInterface(supp
));
237 if (NS_FAILED(currFile
->GetPath(path
)))
240 int32_t len
= path
.Length();
241 if (StringTail(path
, 4).LowerCaseEqualsASCII(".ico")) {
242 // Check if the cached ICO file exists
244 if (NS_FAILED(currFile
->Exists(&exists
)) || !exists
)
247 // We found an ICO file that exists, so we should remove it
248 currFile
->Remove(false);
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
)
263 return NS_ERROR_NOT_AVAILABLE
;
266 case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS
:
268 NS_ENSURE_ARG_POINTER(items
);
271 nsRefPtr
<IObjectCollection
> collection
;
272 hr
= CoCreateInstance(CLSID_EnumerableObjectCollection
, nullptr,
273 CLSCTX_INPROC_SERVER
, IID_IObjectCollection
,
274 getter_AddRefs(collection
));
276 return NS_ERROR_UNEXPECTED
;
280 items
->GetLength(&length
);
281 for (uint32_t i
= 0; i
< length
; ++i
) {
282 nsCOMPtr
<nsIJumpListItem
> item
= do_QueryElementAt(items
, i
);
285 // Check for separators
286 if (IsSeparator(item
)) {
287 nsRefPtr
<IShellLinkW
> link
;
288 rv
= JumpListSeparator::GetSeparator(link
);
291 collection
->AddObject(link
);
294 // These should all be ShellLinks
295 nsRefPtr
<IShellLinkW
> link
;
296 rv
= JumpListShortcut::GetShellLink(item
, link
, mIOThread
);
299 collection
->AddObject(link
);
302 // We need IObjectArray to submit
303 nsRefPtr
<IObjectArray
> pArray
;
304 hr
= collection
->QueryInterface(IID_IObjectArray
, getter_AddRefs(pArray
));
306 return NS_ERROR_UNEXPECTED
;
309 hr
= mJumpListMgr
->AddUserTasks(pArray
);
315 case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT
:
317 if (SUCCEEDED(mJumpListMgr
->AppendKnownCategory(KDC_RECENT
)))
322 case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT
:
324 if (SUCCEEDED(mJumpListMgr
->AppendKnownCategory(KDC_FREQUENT
)))
329 case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST
:
331 NS_ENSURE_ARG_POINTER(items
);
333 if (catName
.IsEmpty())
334 return NS_ERROR_INVALID_ARG
;
337 nsRefPtr
<IObjectCollection
> collection
;
338 hr
= CoCreateInstance(CLSID_EnumerableObjectCollection
, nullptr,
339 CLSCTX_INPROC_SERVER
, IID_IObjectCollection
,
340 getter_AddRefs(collection
));
342 return NS_ERROR_UNEXPECTED
;
345 items
->GetLength(&length
);
346 for (uint32_t i
= 0; i
< length
; ++i
) {
347 nsCOMPtr
<nsIJumpListItem
> item
= do_QueryElementAt(items
, i
);
351 if (NS_FAILED(item
->GetType(&type
)))
354 case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR
:
356 nsRefPtr
<IShellLinkW
> shellItem
;
357 rv
= JumpListSeparator::GetSeparator(shellItem
);
360 collection
->AddObject(shellItem
);
363 case nsIJumpListItem::JUMPLIST_ITEM_LINK
:
365 nsRefPtr
<IShellItem2
> shellItem
;
366 rv
= JumpListLink::GetShellItem(item
, shellItem
);
369 collection
->AddObject(shellItem
);
372 case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT
:
374 nsRefPtr
<IShellLinkW
> shellItem
;
375 rv
= JumpListShortcut::GetShellLink(item
, shellItem
, mIOThread
);
378 collection
->AddObject(shellItem
);
384 // We need IObjectArray to submit
385 nsRefPtr
<IObjectArray
> pArray
;
386 hr
= collection
->QueryInterface(IID_IObjectArray
, (LPVOID
*)&pArray
);
388 return NS_ERROR_UNEXPECTED
;
391 hr
= mJumpListMgr
->AppendCategory(reinterpret_cast<const wchar_t*>(catName
.BeginReading()), pArray
);
395 // Get rid of the old icons
396 nsCOMPtr
<nsIRunnable
> event
=
397 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
398 mIOThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
407 /* void abortListBuild(); */
408 NS_IMETHODIMP
JumpListBuilder::AbortListBuild()
411 return NS_ERROR_NOT_AVAILABLE
;
413 mJumpListMgr
->AbortList();
414 sBuildingList
= false;
419 /* boolean commitListBuild(); */
420 NS_IMETHODIMP
JumpListBuilder::CommitListBuild(bool *_retval
)
425 return NS_ERROR_NOT_AVAILABLE
;
427 HRESULT hr
= mJumpListMgr
->CommitList();
428 sBuildingList
= false;
430 // XXX We might want some specific error data here.
439 /* boolean deleteActiveList(); */
440 NS_IMETHODIMP
JumpListBuilder::DeleteActiveList(bool *_retval
)
445 return NS_ERROR_NOT_AVAILABLE
;
451 if (!WinTaskbar::GetAppUserModelID(uid
))
454 if (SUCCEEDED(mJumpListMgr
->DeleteList(uid
.get())))
462 bool JumpListBuilder::IsSeparator(nsCOMPtr
<nsIJumpListItem
>& item
)
465 item
->GetType(&type
);
466 if (NS_FAILED(item
->GetType(&type
)))
469 if (type
== nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR
)
474 // TransferIObjectArrayToIMutableArray - used in converting removed items
476 nsresult
JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray
*objArray
, nsIMutableArray
*removedItems
)
478 NS_ENSURE_ARG_POINTER(objArray
);
479 NS_ENSURE_ARG_POINTER(removedItems
);
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
);
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
);
504 return NS_ERROR_UNEXPECTED
;
505 rv
= JumpListLink::GetJumpListLink(pItem
, link
);
506 item
= do_QueryInterface(link
);
514 if (NS_SUCCEEDED(rv
)) {
515 removedItems
->AppendElement(item
, false);
521 NS_IMETHODIMP
JumpListBuilder::Observe(nsISupports
* aSubject
,
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);
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
);
551 } // namespace widget
552 } // namespace mozilla