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"
47 JumpListBuilder::JumpListBuilder() :
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;
76 /* readonly attribute short available; */
77 NS_IMETHODIMP
JumpListBuilder::GetAvailable(int16_t *aAvailable
)
87 /* readonly attribute boolean isListCommitted; */
88 NS_IMETHODIMP
JumpListBuilder::GetIsListCommitted(bool *aCommit
)
90 *aCommit
= mHasCommit
;
95 /* readonly attribute short maxItems; */
96 NS_IMETHODIMP
JumpListBuilder::GetMaxListItems(int16_t *aMaxItems
)
99 return NS_ERROR_NOT_AVAILABLE
;
104 *aMaxItems
= mMaxItems
;
108 IObjectArray
*objArray
;
109 if (SUCCEEDED(mJumpListMgr
->BeginList(&mMaxItems
, IID_PPV_ARGS(&objArray
)))) {
110 *aMaxItems
= mMaxItems
;
115 mJumpListMgr
->AbortList();
121 /* boolean initListBuild(in nsIMutableArray removedItems); */
122 NS_IMETHODIMP
JumpListBuilder::InitListBuild(nsIMutableArray
*removedItems
, bool *_retval
)
124 NS_ENSURE_ARG_POINTER(removedItems
);
129 return NS_ERROR_NOT_AVAILABLE
;
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
)))) {
141 TransferIObjectArrayToIMutableArray(objArray
, removedItems
);
145 RemoveIconCacheForItems(removedItems
);
147 sBuildingList
= true;
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
);
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
);
172 if (NS_FAILED(item
->GetType(&type
))) {
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
);
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
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);
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
225 bool hasMore
= false;
226 if (NS_FAILED(entries
->HasMoreElements(&hasMore
)) || !hasMore
)
229 nsCOMPtr
<nsISupports
> supp
;
230 if (NS_FAILED(entries
->GetNext(getter_AddRefs(supp
))))
233 nsCOMPtr
<nsIFile
> currFile(do_QueryInterface(supp
));
235 if (NS_FAILED(currFile
->GetPath(path
)))
238 int32_t len
= path
.Length();
239 if (StringTail(path
, 4).LowerCaseEqualsASCII(".ico")) {
240 // Check if the cached ICO file exists
242 if (NS_FAILED(currFile
->Exists(&exists
)) || !exists
)
245 // We found an ICO file that exists, so we should remove it
246 currFile
->Remove(false);
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
)
261 return NS_ERROR_NOT_AVAILABLE
;
264 case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS
:
266 NS_ENSURE_ARG_POINTER(items
);
269 nsRefPtr
<IObjectCollection
> collection
;
270 hr
= CoCreateInstance(CLSID_EnumerableObjectCollection
, nullptr,
271 CLSCTX_INPROC_SERVER
, IID_IObjectCollection
,
272 getter_AddRefs(collection
));
274 return NS_ERROR_UNEXPECTED
;
278 items
->GetLength(&length
);
279 for (uint32_t i
= 0; i
< length
; ++i
) {
280 nsCOMPtr
<nsIJumpListItem
> item
= do_QueryElementAt(items
, i
);
283 // Check for separators
284 if (IsSeparator(item
)) {
285 nsRefPtr
<IShellLinkW
> link
;
286 rv
= JumpListSeparator::GetSeparator(link
);
289 collection
->AddObject(link
);
292 // These should all be ShellLinks
293 nsRefPtr
<IShellLinkW
> link
;
294 rv
= JumpListShortcut::GetShellLink(item
, link
, mIOThread
);
297 collection
->AddObject(link
);
300 // We need IObjectArray to submit
301 nsRefPtr
<IObjectArray
> pArray
;
302 hr
= collection
->QueryInterface(IID_IObjectArray
, getter_AddRefs(pArray
));
304 return NS_ERROR_UNEXPECTED
;
307 hr
= mJumpListMgr
->AddUserTasks(pArray
);
313 case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT
:
315 if (SUCCEEDED(mJumpListMgr
->AppendKnownCategory(KDC_RECENT
)))
320 case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT
:
322 if (SUCCEEDED(mJumpListMgr
->AppendKnownCategory(KDC_FREQUENT
)))
327 case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST
:
329 NS_ENSURE_ARG_POINTER(items
);
331 if (catName
.IsEmpty())
332 return NS_ERROR_INVALID_ARG
;
335 nsRefPtr
<IObjectCollection
> collection
;
336 hr
= CoCreateInstance(CLSID_EnumerableObjectCollection
, nullptr,
337 CLSCTX_INPROC_SERVER
, IID_IObjectCollection
,
338 getter_AddRefs(collection
));
340 return NS_ERROR_UNEXPECTED
;
343 items
->GetLength(&length
);
344 for (uint32_t i
= 0; i
< length
; ++i
) {
345 nsCOMPtr
<nsIJumpListItem
> item
= do_QueryElementAt(items
, i
);
349 if (NS_FAILED(item
->GetType(&type
)))
352 case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR
:
354 nsRefPtr
<IShellLinkW
> shellItem
;
355 rv
= JumpListSeparator::GetSeparator(shellItem
);
358 collection
->AddObject(shellItem
);
361 case nsIJumpListItem::JUMPLIST_ITEM_LINK
:
363 nsRefPtr
<IShellItem2
> shellItem
;
364 rv
= JumpListLink::GetShellItem(item
, shellItem
);
367 collection
->AddObject(shellItem
);
370 case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT
:
372 nsRefPtr
<IShellLinkW
> shellItem
;
373 rv
= JumpListShortcut::GetShellLink(item
, shellItem
, mIOThread
);
376 collection
->AddObject(shellItem
);
382 // We need IObjectArray to submit
383 nsRefPtr
<IObjectArray
> pArray
;
384 hr
= collection
->QueryInterface(IID_IObjectArray
, (LPVOID
*)&pArray
);
386 return NS_ERROR_UNEXPECTED
;
389 hr
= mJumpListMgr
->AppendCategory(reinterpret_cast<const wchar_t*>(catName
.BeginReading()), pArray
);
393 // Get rid of the old icons
394 nsCOMPtr
<nsIRunnable
> event
=
395 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
396 mIOThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
405 /* void abortListBuild(); */
406 NS_IMETHODIMP
JumpListBuilder::AbortListBuild()
409 return NS_ERROR_NOT_AVAILABLE
;
411 mJumpListMgr
->AbortList();
412 sBuildingList
= false;
417 /* boolean commitListBuild(); */
418 NS_IMETHODIMP
JumpListBuilder::CommitListBuild(bool *_retval
)
423 return NS_ERROR_NOT_AVAILABLE
;
425 HRESULT hr
= mJumpListMgr
->CommitList();
426 sBuildingList
= false;
428 // XXX We might want some specific error data here.
437 /* boolean deleteActiveList(); */
438 NS_IMETHODIMP
JumpListBuilder::DeleteActiveList(bool *_retval
)
443 return NS_ERROR_NOT_AVAILABLE
;
449 if (!WinTaskbar::GetAppUserModelID(uid
))
452 if (SUCCEEDED(mJumpListMgr
->DeleteList(uid
.get())))
460 bool JumpListBuilder::IsSeparator(nsCOMPtr
<nsIJumpListItem
>& item
)
463 item
->GetType(&type
);
464 if (NS_FAILED(item
->GetType(&type
)))
467 if (type
== nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR
)
472 // TransferIObjectArrayToIMutableArray - used in converting removed items
474 nsresult
JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray
*objArray
, nsIMutableArray
*removedItems
)
476 NS_ENSURE_ARG_POINTER(objArray
);
477 NS_ENSURE_ARG_POINTER(removedItems
);
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
);
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
);
502 return NS_ERROR_UNEXPECTED
;
503 rv
= JumpListLink::GetJumpListLink(pItem
, link
);
504 item
= do_QueryInterface(link
);
512 if (NS_SUCCEEDED(rv
)) {
513 removedItems
->AppendElement(item
, false);
519 NS_IMETHODIMP
JumpListBuilder::Observe(nsISupports
* aSubject
,
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);
536 nsCOMPtr
<nsIRunnable
> event
=
537 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
538 mIOThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
544 } // namespace widget
545 } // namespace mozilla