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/. */
9 #include <propvarutil.h>
11 #include "JumpListBuilder.h"
12 #include "mozilla/Preferences.h"
13 #include "mozilla/dom/Promise.h"
14 #include "mozilla/dom/WindowsJumpListShortcutDescriptionBinding.h"
15 #include "mozilla/mscom/EnsureMTA.h"
17 #include "nsIObserverService.h"
18 #include "nsServiceManagerUtils.h"
21 using mozilla::dom::Promise
;
22 using mozilla::dom::WindowsJumpListShortcutDescription
;
27 NS_IMPL_ISUPPORTS(JumpListBuilder
, nsIJumpListBuilder
, nsIObserver
)
29 #define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
30 #define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
32 // The amount of time, in milliseconds, that our IO thread will stay alive after
33 // the last event it processes.
34 #define DEFAULT_THREAD_TIMEOUT_MS 30000
36 const char kPrefTaskbarEnabled
[] = "browser.taskbar.lists.enabled";
39 * A wrapper around a ICustomDestinationList that implements the JumpListBackend
40 * interface. This is an implementation of JumpListBackend that actually causes
41 * items to appear in a Windows jump list.
43 class NativeJumpListBackend
: public JumpListBackend
{
44 // We use NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET because this
45 // class might be destroyed on a different thread than the one it
46 // was created on, since it's maintained by a LazyIdleThread.
48 // This is a workaround for bug 1648031.
49 NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(JumpListBackend
, override
)
51 NativeJumpListBackend() {
52 MOZ_ASSERT(!NS_IsMainThread());
54 mscom::EnsureMTA([&]() {
55 RefPtr
<ICustomDestinationList
> destList
;
56 HRESULT hr
= ::CoCreateInstance(
57 CLSID_DestinationList
, nullptr, CLSCTX_INPROC_SERVER
,
58 IID_ICustomDestinationList
, getter_AddRefs(destList
));
63 mWindowsDestList
= destList
;
67 virtual bool IsAvailable() override
{
68 MOZ_ASSERT(!NS_IsMainThread());
69 return mWindowsDestList
!= nullptr;
72 virtual HRESULT
SetAppID(LPCWSTR pszAppID
) override
{
73 MOZ_ASSERT(!NS_IsMainThread());
74 MOZ_ASSERT(mWindowsDestList
);
76 return mWindowsDestList
->SetAppID(pszAppID
);
79 virtual HRESULT
BeginList(UINT
* pcMinSlots
, REFIID riid
,
80 void** ppv
) override
{
81 MOZ_ASSERT(!NS_IsMainThread());
82 MOZ_ASSERT(mWindowsDestList
);
84 return mWindowsDestList
->BeginList(pcMinSlots
, riid
, ppv
);
87 virtual HRESULT
AddUserTasks(IObjectArray
* poa
) override
{
88 MOZ_ASSERT(!NS_IsMainThread());
89 MOZ_ASSERT(mWindowsDestList
);
91 return mWindowsDestList
->AddUserTasks(poa
);
94 virtual HRESULT
AppendCategory(LPCWSTR pszCategory
,
95 IObjectArray
* poa
) override
{
96 MOZ_ASSERT(!NS_IsMainThread());
97 MOZ_ASSERT(mWindowsDestList
);
99 return mWindowsDestList
->AppendCategory(pszCategory
, poa
);
102 virtual HRESULT
CommitList() override
{
103 MOZ_ASSERT(!NS_IsMainThread());
104 MOZ_ASSERT(mWindowsDestList
);
106 return mWindowsDestList
->CommitList();
109 virtual HRESULT
AbortList() override
{
110 MOZ_ASSERT(!NS_IsMainThread());
111 MOZ_ASSERT(mWindowsDestList
);
113 return mWindowsDestList
->AbortList();
116 virtual HRESULT
DeleteList(LPCWSTR pszAppID
) override
{
117 MOZ_ASSERT(!NS_IsMainThread());
118 MOZ_ASSERT(mWindowsDestList
);
120 return mWindowsDestList
->DeleteList(pszAppID
);
123 virtual HRESULT
AppendKnownCategory(KNOWNDESTCATEGORY category
) override
{
124 MOZ_ASSERT(!NS_IsMainThread());
125 MOZ_ASSERT(mWindowsDestList
);
127 return mWindowsDestList
->AppendKnownCategory(category
);
131 virtual ~NativeJumpListBackend() override
{};
134 RefPtr
<ICustomDestinationList
> mWindowsDestList
;
137 JumpListBuilder::JumpListBuilder(const nsAString
& aAppUserModelId
,
138 RefPtr
<JumpListBackend
> aTestingBackend
) {
139 MOZ_ASSERT(NS_IsMainThread());
141 mAppUserModelId
.Assign(aAppUserModelId
);
143 Preferences::AddStrongObserver(this, kPrefTaskbarEnabled
);
145 // Make a lazy thread for any IO.
146 mIOThread
= new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS
, "Jump List",
147 LazyIdleThread::ManualShutdown
);
149 nsCOMPtr
<nsIObserverService
> observerService
=
150 do_GetService("@mozilla.org/observer-service;1");
151 if (observerService
) {
152 observerService
->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE
, false);
153 observerService
->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA
, false);
156 nsCOMPtr
<nsIRunnable
> runnable
;
158 if (aTestingBackend
) {
159 // Dispatch a task that hands a reference to the testing backend
160 // to the background thread. The testing backend was probably
161 // constructed on the main thread, and is responsible for doing
162 // any locking as well as cleanup.
163 runnable
= NewRunnableMethod
<RefPtr
<JumpListBackend
>>(
164 "SetupTestingBackend", this, &JumpListBuilder::DoSetupTestingBackend
,
168 // Dispatch a task that constructs the native jump list backend.
169 runnable
= NewRunnableMethod("SetupBackend", this,
170 &JumpListBuilder::DoSetupBackend
);
173 mIOThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
175 // MSIX packages explicitly do not support setting the appid from within
176 // the app, as it is set in the package manifest instead.
177 if (!mozilla::widget::WinUtils::HasPackageIdentity()) {
179 NewRunnableMethod
<nsString
>("SetAppID", this,
180 &JumpListBuilder::DoSetAppIDIfAvailable
,
186 JumpListBuilder::~JumpListBuilder() {
187 Preferences::RemoveObserver(this, kPrefTaskbarEnabled
);
190 void JumpListBuilder::DoSetupTestingBackend(
191 RefPtr
<JumpListBackend
> aTestingBackend
) {
192 MOZ_ASSERT(!NS_IsMainThread());
193 mJumpListBackend
= aTestingBackend
;
196 void JumpListBuilder::DoSetupBackend() {
197 MOZ_ASSERT(!NS_IsMainThread());
198 MOZ_ASSERT(!mJumpListBackend
);
199 mJumpListBackend
= new NativeJumpListBackend();
202 void JumpListBuilder::DoShutdownBackend() {
203 MOZ_ASSERT(!NS_IsMainThread());
204 mJumpListBackend
= nullptr;
207 void JumpListBuilder::DoSetAppIDIfAvailable(nsString aAppUserModelID
) {
208 MOZ_ASSERT(!NS_IsMainThread());
209 MOZ_ASSERT(mJumpListBackend
);
211 if (mJumpListBackend
->IsAvailable()) {
212 mJumpListBackend
->SetAppID(aAppUserModelID
.get());
217 JumpListBuilder::ObtainAndCacheFavicon(nsIURI
* aFaviconURI
,
218 nsAString
& aCachedIconPath
) {
219 MOZ_ASSERT(NS_IsMainThread());
220 nsString iconFilePath
;
221 nsresult rv
= mozilla::widget::FaviconHelper::ObtainCachedIconFile(
222 aFaviconURI
, iconFilePath
, mIOThread
, false);
223 NS_ENSURE_SUCCESS(rv
, rv
);
225 aCachedIconPath
= iconFilePath
;
230 JumpListBuilder::IsAvailable(JSContext
* aCx
, Promise
** aPromise
) {
231 MOZ_ASSERT(NS_IsMainThread());
232 MOZ_ASSERT(aPromise
);
233 MOZ_ASSERT(mIOThread
);
236 RefPtr
<Promise
> promise
=
237 Promise::Create(xpc::CurrentNativeGlobal(aCx
), result
);
239 if (MOZ_UNLIKELY(result
.Failed())) {
240 return result
.StealNSResult();
243 nsMainThreadPtrHandle
<Promise
> promiseHolder(
244 new nsMainThreadPtrHolder
<Promise
>("JumpListBuilder::IsAvailable promise",
247 nsCOMPtr
<nsIRunnable
> runnable
=
248 NewRunnableMethod
<nsMainThreadPtrHandle
<Promise
>>(
249 "IsAvailable", this, &JumpListBuilder::DoIsAvailable
,
250 std::move(promiseHolder
));
251 nsresult rv
= mIOThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
252 if (NS_WARN_IF(NS_FAILED(rv
))) {
256 promise
.forget(aPromise
);
261 JumpListBuilder::CheckForRemovals(JSContext
* aCx
, Promise
** aPromise
) {
262 MOZ_ASSERT(NS_IsMainThread());
263 MOZ_ASSERT(aPromise
);
264 MOZ_ASSERT(mIOThread
);
267 RefPtr
<Promise
> promise
=
268 Promise::Create(xpc::CurrentNativeGlobal(aCx
), result
);
270 if (MOZ_UNLIKELY(result
.Failed())) {
271 return result
.StealNSResult();
274 nsMainThreadPtrHandle
<Promise
> promiseHolder(
275 new nsMainThreadPtrHolder
<Promise
>(
276 "JumpListBuilder::CheckForRemovals promise", promise
));
278 nsCOMPtr
<nsIRunnable
> runnable
=
279 NewRunnableMethod
<nsMainThreadPtrHandle
<Promise
>>(
280 "CheckForRemovals", this, &JumpListBuilder::DoCheckForRemovals
,
281 std::move(promiseHolder
));
283 nsresult rv
= mIOThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
284 if (NS_WARN_IF(NS_FAILED(rv
))) {
288 promise
.forget(aPromise
);
294 JumpListBuilder::PopulateJumpList(
295 const nsTArray
<JS::Value
>& aTaskDescriptions
, const nsAString
& aCustomTitle
,
296 const nsTArray
<JS::Value
>& aCustomDescriptions
, JSContext
* aCx
,
297 Promise
** aPromise
) {
298 MOZ_ASSERT(NS_IsMainThread());
299 MOZ_ASSERT(aPromise
);
300 MOZ_ASSERT(mIOThread
);
302 if (aCustomDescriptions
.Length() && aCustomTitle
.IsEmpty()) {
303 return NS_ERROR_INVALID_ARG
;
306 // Get rid of the old icons
307 nsCOMPtr
<nsIRunnable
> event
=
308 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
309 mIOThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
311 nsTArray
<WindowsJumpListShortcutDescription
> taskDescs
;
312 for (auto& jsval
: aTaskDescriptions
) {
313 JS::Rooted
<JS::Value
> rootedVal(aCx
);
314 if (NS_WARN_IF(!dom::ToJSValue(aCx
, jsval
, &rootedVal
))) {
315 return NS_ERROR_INVALID_ARG
;
318 WindowsJumpListShortcutDescription desc
;
319 if (!desc
.Init(aCx
, rootedVal
)) {
320 return NS_ERROR_INVALID_ARG
;
323 taskDescs
.AppendElement(std::move(desc
));
326 nsTArray
<WindowsJumpListShortcutDescription
> customDescs
;
327 for (auto& jsval
: aCustomDescriptions
) {
328 JS::Rooted
<JS::Value
> rootedVal(aCx
);
329 if (NS_WARN_IF(!dom::ToJSValue(aCx
, jsval
, &rootedVal
))) {
330 return NS_ERROR_INVALID_ARG
;
333 WindowsJumpListShortcutDescription desc
;
334 if (!desc
.Init(aCx
, rootedVal
)) {
335 return NS_ERROR_INVALID_ARG
;
338 customDescs
.AppendElement(std::move(desc
));
342 RefPtr
<Promise
> promise
=
343 Promise::Create(xpc::CurrentNativeGlobal(aCx
), result
);
345 if (MOZ_UNLIKELY(result
.Failed())) {
346 return result
.StealNSResult();
349 nsMainThreadPtrHandle
<Promise
> promiseHolder(
350 new nsMainThreadPtrHolder
<Promise
>(
351 "JumpListBuilder::PopulateJumpList promise", promise
));
353 nsCOMPtr
<nsIRunnable
> runnable
= NewRunnableMethod
<
354 StoreCopyPassByRRef
<nsTArray
<WindowsJumpListShortcutDescription
>>,
356 StoreCopyPassByRRef
<nsTArray
<WindowsJumpListShortcutDescription
>>,
357 nsMainThreadPtrHandle
<Promise
>>(
358 "PopulateJumpList", this, &JumpListBuilder::DoPopulateJumpList
,
359 std::move(taskDescs
), aCustomTitle
, std::move(customDescs
),
360 std::move(promiseHolder
));
361 nsresult rv
= mIOThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
362 if (NS_WARN_IF(NS_FAILED(rv
))) {
366 promise
.forget(aPromise
);
372 JumpListBuilder::ClearJumpList(JSContext
* aCx
, Promise
** aPromise
) {
373 MOZ_ASSERT(NS_IsMainThread());
374 MOZ_ASSERT(aPromise
);
375 MOZ_ASSERT(mIOThread
);
378 RefPtr
<Promise
> promise
=
379 Promise::Create(xpc::CurrentNativeGlobal(aCx
), result
);
381 if (MOZ_UNLIKELY(result
.Failed())) {
382 return result
.StealNSResult();
385 nsMainThreadPtrHandle
<Promise
> promiseHolder(
386 new nsMainThreadPtrHolder
<Promise
>(
387 "JumpListBuilder::ClearJumpList promise", promise
));
389 nsCOMPtr
<nsIRunnable
> runnable
=
390 NewRunnableMethod
<nsMainThreadPtrHandle
<Promise
>>(
391 "ClearJumpList", this, &JumpListBuilder::DoClearJumpList
,
392 std::move(promiseHolder
));
393 nsresult rv
= mIOThread
->Dispatch(runnable
, NS_DISPATCH_NORMAL
);
394 if (NS_WARN_IF(NS_FAILED(rv
))) {
398 promise
.forget(aPromise
);
402 void JumpListBuilder::DoIsAvailable(
403 const nsMainThreadPtrHandle
<Promise
>& aPromiseHolder
) {
404 MOZ_ASSERT(!NS_IsMainThread());
405 MOZ_ASSERT(aPromiseHolder
);
407 if (!mJumpListBackend
) {
408 NS_DispatchToMainThread(NS_NewRunnableFunction(
409 "DoIsAvailable", [promiseHolder
= std::move(aPromiseHolder
)]() {
410 promiseHolder
.get()->MaybeResolve(false);
415 bool isAvailable
= mJumpListBackend
->IsAvailable();
417 NS_DispatchToMainThread(NS_NewRunnableFunction(
419 [promiseHolder
= std::move(aPromiseHolder
), isAvailable
]() {
420 promiseHolder
.get()->MaybeResolve(isAvailable
);
424 void JumpListBuilder::DoCheckForRemovals(
425 const nsMainThreadPtrHandle
<Promise
>& aPromiseHolder
) {
426 MOZ_ASSERT(!NS_IsMainThread());
427 MOZ_ASSERT(aPromiseHolder
);
429 nsresult rv
= NS_ERROR_UNEXPECTED
;
431 auto errorHandler
= MakeScopeExit([&aPromiseHolder
, &rv
]() {
432 NS_DispatchToMainThread(NS_NewRunnableFunction(
433 "DoCheckForRemovals",
434 [promiseHolder
= std::move(aPromiseHolder
), rv
]() {
435 promiseHolder
.get()->MaybeReject(rv
);
439 MOZ_ASSERT(mJumpListBackend
);
440 if (!mJumpListBackend
) {
444 // Abort any ongoing list building that might not have been committed,
445 // otherwise BeginList will give us problems.
446 Unused
<< mJumpListBackend
->AbortList();
448 nsTArray
<nsString
> urisToRemove
;
449 RefPtr
<IObjectArray
> objArray
;
450 uint32_t maxItems
= 0;
452 HRESULT hr
= mJumpListBackend
->BeginList(
454 IID_PPV_ARGS(static_cast<IObjectArray
**>(getter_AddRefs(objArray
))));
457 rv
= NS_ERROR_UNEXPECTED
;
461 RemoveIconCacheAndGetJumplistShortcutURIs(objArray
, urisToRemove
);
463 // We began a list in order to get the removals, which we can now abort.
464 Unused
<< mJumpListBackend
->AbortList();
466 errorHandler
.release();
468 NS_DispatchToMainThread(NS_NewRunnableFunction(
469 "DoCheckForRemovals", [urisToRemove
= std::move(urisToRemove
),
470 promiseHolder
= std::move(aPromiseHolder
)]() {
471 promiseHolder
.get()->MaybeResolve(urisToRemove
);
475 void JumpListBuilder::DoPopulateJumpList(
476 const nsTArray
<WindowsJumpListShortcutDescription
>&& aTaskDescriptions
,
477 const nsAString
& aCustomTitle
,
478 const nsTArray
<WindowsJumpListShortcutDescription
>&& aCustomDescriptions
,
479 const nsMainThreadPtrHandle
<Promise
>& aPromiseHolder
) {
480 MOZ_ASSERT(!NS_IsMainThread());
481 MOZ_ASSERT(aPromiseHolder
);
483 nsresult rv
= NS_ERROR_UNEXPECTED
;
485 auto errorHandler
= MakeScopeExit([&aPromiseHolder
, &rv
]() {
486 NS_DispatchToMainThread(NS_NewRunnableFunction(
487 "DoPopulateJumpList",
488 [promiseHolder
= std::move(aPromiseHolder
), rv
]() {
489 promiseHolder
.get()->MaybeReject(rv
);
493 MOZ_ASSERT(mJumpListBackend
);
494 if (!mJumpListBackend
) {
498 // Abort any ongoing list building that might not have been committed,
499 // otherwise BeginList will give us problems.
500 Unused
<< mJumpListBackend
->AbortList();
502 nsTArray
<nsString
> urisToRemove
;
503 RefPtr
<IObjectArray
> objArray
;
504 uint32_t maxItems
= 0;
506 HRESULT hr
= mJumpListBackend
->BeginList(
508 IID_PPV_ARGS(static_cast<IObjectArray
**>(getter_AddRefs(objArray
))));
511 rv
= NS_ERROR_UNEXPECTED
;
515 if (urisToRemove
.Length()) {
516 // It'd be nice if we could return a more descriptive error here so that
517 // the caller knows that they should have called checkForRemovals first.
518 rv
= NS_ERROR_UNEXPECTED
;
522 if (aTaskDescriptions
.Length()) {
523 RefPtr
<IObjectCollection
> taskCollection
;
524 hr
= CoCreateInstance(CLSID_EnumerableObjectCollection
, nullptr,
525 CLSCTX_INPROC_SERVER
, IID_IObjectCollection
,
526 getter_AddRefs(taskCollection
));
528 rv
= NS_ERROR_UNEXPECTED
;
532 // Start by building the task list
533 for (auto& desc
: aTaskDescriptions
) {
534 // These should all be ShellLinks
535 RefPtr
<IShellLinkW
> link
;
536 rv
= GetShellLinkFromDescription(desc
, link
);
538 // Let the errorHandler deal with this.
541 taskCollection
->AddObject(link
);
544 RefPtr
<IObjectArray
> pTaskArray
;
545 hr
= taskCollection
->QueryInterface(IID_IObjectArray
,
546 getter_AddRefs(pTaskArray
));
549 rv
= NS_ERROR_UNEXPECTED
;
553 hr
= mJumpListBackend
->AddUserTasks(pTaskArray
);
556 rv
= NS_ERROR_FAILURE
;
561 if (aCustomDescriptions
.Length()) {
562 // Then build the custom list
563 RefPtr
<IObjectCollection
> customCollection
;
564 hr
= CoCreateInstance(CLSID_EnumerableObjectCollection
, nullptr,
565 CLSCTX_INPROC_SERVER
, IID_IObjectCollection
,
566 getter_AddRefs(customCollection
));
568 rv
= NS_ERROR_UNEXPECTED
;
572 for (auto& desc
: aCustomDescriptions
) {
573 // These should all be ShellLinks
574 RefPtr
<IShellLinkW
> link
;
575 rv
= GetShellLinkFromDescription(desc
, link
);
577 // Let the errorHandler deal with this.
580 customCollection
->AddObject(link
);
583 RefPtr
<IObjectArray
> pCustomArray
;
584 hr
= customCollection
->QueryInterface(IID_IObjectArray
,
585 getter_AddRefs(pCustomArray
));
587 rv
= NS_ERROR_UNEXPECTED
;
591 hr
= mJumpListBackend
->AppendCategory(
592 reinterpret_cast<const wchar_t*>(aCustomTitle
.BeginReading()),
596 rv
= NS_ERROR_UNEXPECTED
;
601 hr
= mJumpListBackend
->CommitList();
603 rv
= NS_ERROR_FAILURE
;
607 errorHandler
.release();
609 NS_DispatchToMainThread(NS_NewRunnableFunction(
610 "DoPopulateJumpList", [promiseHolder
= std::move(aPromiseHolder
)]() {
611 promiseHolder
.get()->MaybeResolveWithUndefined();
615 void JumpListBuilder::DoClearJumpList(
616 const nsMainThreadPtrHandle
<Promise
>& aPromiseHolder
) {
617 MOZ_ASSERT(!NS_IsMainThread());
618 MOZ_ASSERT(aPromiseHolder
);
620 if (!mJumpListBackend
) {
621 NS_DispatchToMainThread(NS_NewRunnableFunction(
622 "DoClearJumpList", [promiseHolder
= std::move(aPromiseHolder
)]() {
623 promiseHolder
.get()->MaybeReject(NS_ERROR_UNEXPECTED
);
628 if (SUCCEEDED(mJumpListBackend
->DeleteList(mAppUserModelId
.get()))) {
629 NS_DispatchToMainThread(NS_NewRunnableFunction(
630 "DoClearJumpList", [promiseHolder
= std::move(aPromiseHolder
)]() {
631 promiseHolder
.get()->MaybeResolveWithUndefined();
634 NS_DispatchToMainThread(NS_NewRunnableFunction(
635 "DoClearJumpList", [promiseHolder
= std::move(aPromiseHolder
)]() {
636 promiseHolder
.get()->MaybeReject(NS_ERROR_FAILURE
);
641 // RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to
642 // avoid unnecessary extra XPCOM incantations. For each object in the input
643 // array, if it's an IShellLinkW, this deletes the cached icon and adds the
644 // url param to a list of URLs to be removed from the places database.
645 void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs(
646 IObjectArray
* aObjArray
, nsTArray
<nsString
>& aURISpecs
) {
647 MOZ_ASSERT(!NS_IsMainThread());
649 // Early return here just in case some versions of Windows don't populate this
655 aObjArray
->GetCount(&count
);
657 for (uint32_t idx
= 0; idx
< count
; idx
++) {
658 RefPtr
<IShellLinkW
> pLink
;
660 if (FAILED(aObjArray
->GetAt(idx
, IID_IShellLinkW
,
661 static_cast<void**>(getter_AddRefs(pLink
))))) {
665 wchar_t buf
[MAX_PATH
];
666 HRESULT hres
= pLink
->GetArguments(buf
, MAX_PATH
);
667 if (SUCCEEDED(hres
)) {
671 arglist
= ::CommandLineToArgvW(buf
, &numArgs
);
672 if (arglist
&& numArgs
> 0) {
673 nsString
spec(arglist
[0]);
674 aURISpecs
.AppendElement(std::move(spec
));
675 ::LocalFree(arglist
);
680 hres
= pLink
->GetIconLocation(buf
, MAX_PATH
, &iconIdx
);
681 if (SUCCEEDED(hres
)) {
682 nsDependentString
spec(buf
);
683 DeleteIconFromDisk(spec
);
688 void JumpListBuilder::DeleteIconFromDisk(const nsAString
& aPath
) {
689 MOZ_ASSERT(!NS_IsMainThread());
691 // Check that we aren't deleting some arbitrary file that is not an icon
692 if (StringTail(aPath
, 4).LowerCaseEqualsASCII(".ico")) {
693 // Construct the parent path of the passed in path
694 nsCOMPtr
<nsIFile
> icoFile
;
695 nsresult rv
= NS_NewLocalFile(aPath
, true, getter_AddRefs(icoFile
));
696 if (NS_WARN_IF(NS_FAILED(rv
))) {
700 icoFile
->Remove(false);
704 // Converts a WindowsJumpListShortcutDescription into a IShellLinkW
705 nsresult
JumpListBuilder::GetShellLinkFromDescription(
706 const WindowsJumpListShortcutDescription
& aDesc
,
707 RefPtr
<IShellLinkW
>& aShellLink
) {
708 MOZ_ASSERT(!NS_IsMainThread());
714 // http://msdn.microsoft.com/en-us/library/bb776891(VS.85).aspx
715 // http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx
717 // Create a IShellLink
718 hr
= CoCreateInstance(CLSID_ShellLink
, nullptr, CLSCTX_INPROC_SERVER
,
719 IID_IShellLinkW
, (LPVOID
*)&psl
);
721 return NS_ERROR_UNEXPECTED
;
724 // Store the title of the app
725 if (!aDesc
.mTitle
.IsEmpty()) {
726 IPropertyStore
* pPropStore
= nullptr;
727 hr
= psl
->QueryInterface(IID_IPropertyStore
, (LPVOID
*)&pPropStore
);
729 return NS_ERROR_UNEXPECTED
;
733 ::InitPropVariantFromString(aDesc
.mTitle
.get(), &pv
);
735 pPropStore
->SetValue(PKEY_Title
, pv
);
736 pPropStore
->Commit();
737 pPropStore
->Release();
739 PropVariantClear(&pv
);
742 // Store the rest of the params
743 hr
= psl
->SetPath(aDesc
.mPath
.get());
745 // According to the documentation at [1], the maximum description length is
746 // INFOTIPSIZE, so we copy the const string from the description into a buffer
747 // of that maximum size. However, testing reveals that INFOTIPSIZE is still
748 // sometimes too large. MAX_PATH seems to work instead.
750 // We truncate to MAX_PATH - 1, since nsAutoString's don't include the null
751 // character in their capacity calculations, but the string for IShellLinkW
752 // description does. So by truncating to MAX_PATH - 1, the full contents of
753 // the truncated nsAutoString will be copied into the IShellLink description
757 // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishelllinka-setdescription
758 nsAutoString
descriptionCopy(aDesc
.mDescription
.get());
759 if (descriptionCopy
.Length() >= MAX_PATH
) {
760 descriptionCopy
.Truncate(MAX_PATH
- 1);
763 hr
= psl
->SetDescription(descriptionCopy
.get());
765 if (aDesc
.mArguments
.WasPassed() && !aDesc
.mArguments
.Value().IsEmpty()) {
766 hr
= psl
->SetArguments(aDesc
.mArguments
.Value().get());
768 hr
= psl
->SetArguments(L
"");
771 // Set up the fallback icon in the event that a valid icon URI has
772 // not been supplied.
773 hr
= psl
->SetIconLocation(aDesc
.mPath
.get(), aDesc
.mFallbackIconIndex
);
775 if (aDesc
.mIconPath
.WasPassed() && !aDesc
.mIconPath
.Value().IsEmpty()) {
776 // Always use the first icon in the ICO file, as our encoded icon only has 1
778 hr
= psl
->SetIconLocation(aDesc
.mIconPath
.Value().get(), 0);
781 aShellLink
= dont_AddRef(psl
);
787 JumpListBuilder::Observe(nsISupports
* aSubject
, const char* aTopic
,
788 const char16_t
* aData
) {
789 MOZ_ASSERT(NS_IsMainThread());
790 NS_ENSURE_ARG_POINTER(aTopic
);
791 if (strcmp(aTopic
, TOPIC_PROFILE_BEFORE_CHANGE
) == 0) {
792 nsCOMPtr
<nsIObserverService
> observerService
=
793 do_GetService("@mozilla.org/observer-service;1");
794 if (observerService
) {
795 observerService
->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE
);
796 observerService
->RemoveObserver(this, TOPIC_CLEAR_PRIVATE_DATA
);
799 mIOThread
->Dispatch(NewRunnableMethod("ShutdownBackend", this,
800 &JumpListBuilder::DoShutdownBackend
),
803 mIOThread
->Shutdown();
804 } else if (strcmp(aTopic
, "nsPref:changed") == 0 &&
805 nsDependentString(aData
).EqualsASCII(kPrefTaskbarEnabled
)) {
806 bool enabled
= Preferences::GetBool(kPrefTaskbarEnabled
, true);
808 nsCOMPtr
<nsIRunnable
> event
=
809 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
810 mIOThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
812 } else if (strcmp(aTopic
, TOPIC_CLEAR_PRIVATE_DATA
) == 0) {
813 // Delete JumpListCache icons from Disk, if any.
814 nsCOMPtr
<nsIRunnable
> event
=
815 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
816 mIOThread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
821 } // namespace widget
822 } // namespace mozilla