Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / windows / JumpListBuilder.cpp
blobe25c8c038fb4f0efda62a2eda73595051a92aba0
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 <windows.h>
7 #include <shobjidl.h>
8 #include <propkey.h>
9 #include <propvarutil.h>
10 #include <shellapi.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"
16 #include "nsIFile.h"
17 #include "nsIObserverService.h"
18 #include "nsServiceManagerUtils.h"
19 #include "WinUtils.h"
21 using mozilla::dom::Promise;
22 using mozilla::dom::WindowsJumpListShortcutDescription;
24 namespace mozilla {
25 namespace widget {
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";
38 /**
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));
59 if (FAILED(hr)) {
60 return;
63 mWindowsDestList = destList;
64 });
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);
130 protected:
131 virtual ~NativeJumpListBackend() override{};
133 private:
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,
165 aTestingBackend);
167 } else {
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()) {
178 mIOThread->Dispatch(
179 NewRunnableMethod<nsString>("SetAppID", this,
180 &JumpListBuilder::DoSetAppIDIfAvailable,
181 aAppUserModelId),
182 NS_DISPATCH_NORMAL);
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());
216 NS_IMETHODIMP
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;
226 return NS_OK;
229 NS_IMETHODIMP
230 JumpListBuilder::IsAvailable(JSContext* aCx, Promise** aPromise) {
231 MOZ_ASSERT(NS_IsMainThread());
232 MOZ_ASSERT(aPromise);
233 MOZ_ASSERT(mIOThread);
235 ErrorResult result;
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",
245 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))) {
253 return rv;
256 promise.forget(aPromise);
257 return NS_OK;
260 NS_IMETHODIMP
261 JumpListBuilder::CheckForRemovals(JSContext* aCx, Promise** aPromise) {
262 MOZ_ASSERT(NS_IsMainThread());
263 MOZ_ASSERT(aPromise);
264 MOZ_ASSERT(mIOThread);
266 ErrorResult result;
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))) {
285 return rv;
288 promise.forget(aPromise);
290 return NS_OK;
293 NS_IMETHODIMP
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));
341 ErrorResult result;
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>>,
355 nsString,
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))) {
363 return rv;
366 promise.forget(aPromise);
368 return NS_OK;
371 NS_IMETHODIMP
372 JumpListBuilder::ClearJumpList(JSContext* aCx, Promise** aPromise) {
373 MOZ_ASSERT(NS_IsMainThread());
374 MOZ_ASSERT(aPromise);
375 MOZ_ASSERT(mIOThread);
377 ErrorResult result;
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))) {
395 return rv;
398 promise.forget(aPromise);
399 return NS_OK;
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);
411 }));
412 return;
415 bool isAvailable = mJumpListBackend->IsAvailable();
417 NS_DispatchToMainThread(NS_NewRunnableFunction(
418 "DoIsAvailable",
419 [promiseHolder = std::move(aPromiseHolder), isAvailable]() {
420 promiseHolder.get()->MaybeResolve(isAvailable);
421 }));
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);
436 }));
439 MOZ_ASSERT(mJumpListBackend);
440 if (!mJumpListBackend) {
441 return;
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(
453 &maxItems,
454 IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
456 if (FAILED(hr)) {
457 rv = NS_ERROR_UNEXPECTED;
458 return;
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);
472 }));
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);
490 }));
493 MOZ_ASSERT(mJumpListBackend);
494 if (!mJumpListBackend) {
495 return;
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(
507 &maxItems,
508 IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
510 if (FAILED(hr)) {
511 rv = NS_ERROR_UNEXPECTED;
512 return;
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;
519 return;
522 if (aTaskDescriptions.Length()) {
523 RefPtr<IObjectCollection> taskCollection;
524 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
525 CLSCTX_INPROC_SERVER, IID_IObjectCollection,
526 getter_AddRefs(taskCollection));
527 if (FAILED(hr)) {
528 rv = NS_ERROR_UNEXPECTED;
529 return;
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);
537 if (NS_FAILED(rv)) {
538 // Let the errorHandler deal with this.
539 return;
541 taskCollection->AddObject(link);
544 RefPtr<IObjectArray> pTaskArray;
545 hr = taskCollection->QueryInterface(IID_IObjectArray,
546 getter_AddRefs(pTaskArray));
548 if (FAILED(hr)) {
549 rv = NS_ERROR_UNEXPECTED;
550 return;
553 hr = mJumpListBackend->AddUserTasks(pTaskArray);
555 if (FAILED(hr)) {
556 rv = NS_ERROR_FAILURE;
557 return;
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));
567 if (FAILED(hr)) {
568 rv = NS_ERROR_UNEXPECTED;
569 return;
572 for (auto& desc : aCustomDescriptions) {
573 // These should all be ShellLinks
574 RefPtr<IShellLinkW> link;
575 rv = GetShellLinkFromDescription(desc, link);
576 if (NS_FAILED(rv)) {
577 // Let the errorHandler deal with this.
578 return;
580 customCollection->AddObject(link);
583 RefPtr<IObjectArray> pCustomArray;
584 hr = customCollection->QueryInterface(IID_IObjectArray,
585 getter_AddRefs(pCustomArray));
586 if (FAILED(hr)) {
587 rv = NS_ERROR_UNEXPECTED;
588 return;
591 hr = mJumpListBackend->AppendCategory(
592 reinterpret_cast<const wchar_t*>(aCustomTitle.BeginReading()),
593 pCustomArray);
595 if (FAILED(hr)) {
596 rv = NS_ERROR_UNEXPECTED;
597 return;
601 hr = mJumpListBackend->CommitList();
602 if (FAILED(hr)) {
603 rv = NS_ERROR_FAILURE;
604 return;
607 errorHandler.release();
609 NS_DispatchToMainThread(NS_NewRunnableFunction(
610 "DoPopulateJumpList", [promiseHolder = std::move(aPromiseHolder)]() {
611 promiseHolder.get()->MaybeResolveWithUndefined();
612 }));
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);
624 }));
625 return;
628 if (SUCCEEDED(mJumpListBackend->DeleteList(mAppUserModelId.get()))) {
629 NS_DispatchToMainThread(NS_NewRunnableFunction(
630 "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() {
631 promiseHolder.get()->MaybeResolveWithUndefined();
632 }));
633 } else {
634 NS_DispatchToMainThread(NS_NewRunnableFunction(
635 "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() {
636 promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE);
637 }));
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
650 if (!aObjArray) {
651 return;
654 uint32_t count = 0;
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))))) {
662 continue;
665 wchar_t buf[MAX_PATH];
666 HRESULT hres = pLink->GetArguments(buf, MAX_PATH);
667 if (SUCCEEDED(hres)) {
668 LPWSTR* arglist;
669 int32_t numArgs;
671 arglist = ::CommandLineToArgvW(buf, &numArgs);
672 if (arglist && numArgs > 0) {
673 nsString spec(arglist[0]);
674 aURISpecs.AppendElement(std::move(spec));
675 ::LocalFree(arglist);
679 int iconIdx = 0;
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))) {
697 return;
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());
710 HRESULT hr;
711 IShellLinkW* psl;
713 // Shell links:
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);
720 if (FAILED(hr)) {
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);
728 if (FAILED(hr)) {
729 return NS_ERROR_UNEXPECTED;
732 PROPVARIANT pv;
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
754 // buffer.
756 // [1]:
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());
767 } else {
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
777 // resource
778 hr = psl->SetIconLocation(aDesc.mIconPath.Value().get(), 0);
781 aShellLink = dont_AddRef(psl);
783 return NS_OK;
786 NS_IMETHODIMP
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),
801 NS_DISPATCH_NORMAL);
803 mIOThread->Shutdown();
804 } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
805 nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
806 bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
807 if (!enabled) {
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);
818 return NS_OK;
821 } // namespace widget
822 } // namespace mozilla