summaryrefslogtreecommitdiffstats
path: root/widget/windows/JumpListBuilder.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /widget/windows/JumpListBuilder.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/windows/JumpListBuilder.cpp')
-rw-r--r--widget/windows/JumpListBuilder.cpp647
1 files changed, 647 insertions, 0 deletions
diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp
new file mode 100644
index 0000000000..133bcd2586
--- /dev/null
+++ b/widget/windows/JumpListBuilder.cpp
@@ -0,0 +1,647 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JumpListBuilder.h"
+
+#include "nsError.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+#include "nsArrayUtils.h"
+#include "nsWidgetsCID.h"
+#include "WinTaskbar.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsStringStream.h"
+#include "nsThreadUtils.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsIObserverService.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/mscom/ApartmentRegion.h"
+#include "mozilla/mscom/EnsureMTA.h"
+
+#include <shellapi.h>
+#include "WinUtils.h"
+
+using mozilla::dom::Promise;
+
+// The amount of time, in milliseconds, that our IO thread will stay alive after
+// the last event it processes.
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
+namespace mozilla {
+namespace widget {
+
+// defined in WinTaskbar.cpp
+extern const wchar_t* gMozillaJumpListIDGeneric;
+
+Atomic<bool> JumpListBuilder::sBuildingList(false);
+const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
+
+NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver)
+#define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
+#define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
+
+namespace detail {
+
+class DoneCommitListBuildCallback final : public nsIRunnable {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ public:
+ DoneCommitListBuildCallback(nsIJumpListCommittedCallback* aCallback,
+ JumpListBuilder* aBuilder)
+ : mCallback(aCallback), mBuilder(aBuilder), mResult(false) {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mCallback) {
+ Unused << mCallback->Done(mResult);
+ }
+ // Ensure we are releasing on the main thread.
+ Destroy();
+ return NS_OK;
+ }
+
+ void SetResult(bool aResult) { mResult = aResult; }
+
+ private:
+ ~DoneCommitListBuildCallback() {
+ // Destructor does not always call on the main thread.
+ MOZ_ASSERT(!mCallback);
+ MOZ_ASSERT(!mBuilder);
+ }
+
+ void Destroy() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mCallback = nullptr;
+ mBuilder = nullptr;
+ }
+
+ // These two references MUST be released on the main thread.
+ RefPtr<nsIJumpListCommittedCallback> mCallback;
+ RefPtr<JumpListBuilder> mBuilder;
+ bool mResult;
+};
+
+NS_IMPL_ISUPPORTS(DoneCommitListBuildCallback, nsIRunnable);
+
+} // namespace detail
+
+JumpListBuilder::JumpListBuilder()
+ : mMaxItems(0), mHasCommit(false), mMonitor("JumpListBuilderMonitor") {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Instantiate mJumpListMgr in the multithreaded apartment so that proxied
+ // calls on that object do not need to interact with the main thread's message
+ // pump.
+ mscom::EnsureMTA([&]() {
+ RefPtr<ICustomDestinationList> jumpListMgr;
+ HRESULT hr = ::CoCreateInstance(
+ CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
+ IID_ICustomDestinationList, getter_AddRefs(jumpListMgr));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ // Since we are accessing mJumpListMgr across different threads
+ // (ie, different apartments), mJumpListMgr must be an agile reference.
+ mJumpListMgr = jumpListMgr;
+ });
+
+ if (!mJumpListMgr) {
+ return;
+ }
+
+ // Make a lazy thread for any IO
+ mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List",
+ LazyIdleThread::ManualShutdown);
+ Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false);
+ observerService->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA, false);
+ }
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return;
+ }
+}
+
+JumpListBuilder::~JumpListBuilder() {
+ Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
+}
+
+NS_IMETHODIMP JumpListBuilder::SetAppUserModelID(
+ const nsAString& aAppUserModelId) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mAppUserModelId.Assign(aAppUserModelId);
+ // MSIX packages explicitly do not support setting the appid from within
+ // the app, as it is set in the package manifest instead.
+ if (!mozilla::widget::WinUtils::HasPackageIdentity()) {
+ jumpListMgr->SetAppID(mAppUserModelId.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t* aAvailable) {
+ *aAvailable = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (mJumpListMgr) *aAvailable = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool* aCommit) {
+ *aCommit = mHasCommit;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t* aMaxItems) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ *aMaxItems = 0;
+
+ if (sBuildingList) {
+ *aMaxItems = mMaxItems;
+ return NS_OK;
+ }
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ IObjectArray* objArray;
+ if (SUCCEEDED(jumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
+ *aMaxItems = mMaxItems;
+
+ if (objArray) objArray->Release();
+
+ jumpListMgr->AbortList();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::InitListBuild(JSContext* aCx,
+ Promise** aPromise) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod<StoreCopyPassByRRef<RefPtr<Promise>>>(
+ "InitListBuild", this, &JumpListBuilder::DoInitListBuild, promise);
+ nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ promise.forget(aPromise);
+ return NS_OK;
+}
+
+void JumpListBuilder::DoInitListBuild(RefPtr<Promise>&& aPromise) {
+ // Since we're invoking COM interfaces to talk to the shell on a background
+ // thread, we need to be running inside a multithreaded apartment.
+ mscom::MTARegion mta;
+ MOZ_ASSERT(mta.IsValid());
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ MOZ_ASSERT(mJumpListMgr);
+
+ if (sBuildingList) {
+ AbortListBuild();
+ }
+
+ HRESULT hr = E_UNEXPECTED;
+ auto errorHandler = MakeScopeExit([&aPromise, &hr]() {
+ if (SUCCEEDED(hr)) {
+ return;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "InitListBuildReject", [promise = std::move(aPromise)]() {
+ promise->MaybeReject(NS_ERROR_FAILURE);
+ }));
+ });
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return;
+ }
+
+ nsTArray<nsString> urisToRemove;
+ RefPtr<IObjectArray> objArray;
+ hr = jumpListMgr->BeginList(
+ &mMaxItems,
+ IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ // The returned objArray of removed items are for manually removed items.
+ // This does not return items which are removed because they were previously
+ // part of the jump list but are no longer part of the jump list.
+ sBuildingList = true;
+ RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove);
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "InitListBuildResolve", [urisToRemove = std::move(urisToRemove),
+ promise = std::move(aPromise)]() {
+ promise->MaybeResolve(urisToRemove);
+ }));
+}
+
+// Ensures that we have no old ICO files left in the jump list cache
+nsresult JumpListBuilder::RemoveIconCacheForAllItems() {
+ // Construct the path of our jump list cache
+ nsCOMPtr<nsIFile> jumpListCacheDir;
+ nsresult rv =
+ NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = jumpListCacheDir->AppendNative(
+ nsDependentCString(mozilla::widget::FaviconHelper::kJumpListCacheDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Loop through each directory entry and remove all ICO files found
+ do {
+ nsCOMPtr<nsIFile> currFile;
+ if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
+ break;
+
+ nsAutoString path;
+ if (NS_FAILED(currFile->GetPath(path))) continue;
+
+ if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
+ // Check if the cached ICO file exists
+ bool exists;
+ if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
+
+ // We found an ICO file that exists, so we should remove it
+ currFile->Remove(false);
+ }
+ } while (true);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray* items,
+ const nsAString& catName,
+ bool* _retval) {
+ nsresult rv;
+
+ *_retval = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ switch (aCatType) {
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: {
+ NS_ENSURE_ARG_POINTER(items);
+
+ HRESULT hr;
+ RefPtr<IObjectCollection> collection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(collection));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Build the list
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) continue;
+ // Check for separators
+ if (IsSeparator(item)) {
+ RefPtr<IShellLinkW> link;
+ rv = JumpListSeparator::GetSeparator(link);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(link);
+ continue;
+ }
+ // These should all be ShellLinks
+ RefPtr<IShellLinkW> link;
+ rv = JumpListShortcut::GetShellLink(item, link, mIOThread);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(link);
+ }
+
+ // We need IObjectArray to submit
+ RefPtr<IObjectArray> pArray;
+ hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Add the tasks
+ hr = jumpListMgr->AddUserTasks(pArray);
+ if (SUCCEEDED(hr)) *_retval = true;
+ return NS_OK;
+ } break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: {
+ if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_RECENT)))
+ *_retval = true;
+ return NS_OK;
+ } break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: {
+ if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
+ *_retval = true;
+ return NS_OK;
+ } break;
+ case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: {
+ NS_ENSURE_ARG_POINTER(items);
+
+ if (catName.IsEmpty()) return NS_ERROR_INVALID_ARG;
+
+ HRESULT hr;
+ RefPtr<IObjectCollection> collection;
+ hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
+ CLSCTX_INPROC_SERVER, IID_IObjectCollection,
+ getter_AddRefs(collection));
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ uint32_t length;
+ items->GetLength(&length);
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
+ if (!item) continue;
+ int16_t type;
+ if (NS_FAILED(item->GetType(&type))) continue;
+ switch (type) {
+ case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: {
+ RefPtr<IShellLinkW> shellItem;
+ rv = JumpListSeparator::GetSeparator(shellItem);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ case nsIJumpListItem::JUMPLIST_ITEM_LINK: {
+ RefPtr<IShellItem2> shellItem;
+ rv = JumpListLink::GetShellItem(item, shellItem);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: {
+ RefPtr<IShellLinkW> shellItem;
+ rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread);
+ if (NS_FAILED(rv)) return rv;
+ collection->AddObject(shellItem);
+ } break;
+ }
+ }
+
+ // We need IObjectArray to submit
+ RefPtr<IObjectArray> pArray;
+ hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
+ if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
+
+ // Add the tasks
+ hr = jumpListMgr->AppendCategory(
+ reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
+ if (SUCCEEDED(hr)) *_retval = true;
+
+ // Get rid of the old icons
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+ } break;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::AbortListBuild() {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ jumpListMgr->AbortList();
+ sBuildingList = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP JumpListBuilder::CommitListBuild(
+ nsIJumpListCommittedCallback* aCallback) {
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ // Also holds a strong reference to this to prevent use-after-free.
+ RefPtr<detail::DoneCommitListBuildCallback> callback =
+ new detail::DoneCommitListBuildCallback(aCallback, this);
+
+ // The builder has a strong reference in the callback already, so we do not
+ // need to do it for this runnable again.
+ RefPtr<nsIRunnable> event =
+ NewNonOwningRunnableMethod<RefPtr<detail::DoneCommitListBuildCallback>>(
+ "JumpListBuilder::DoCommitListBuild", this,
+ &JumpListBuilder::DoCommitListBuild, std::move(callback));
+ Unused << mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+
+ return NS_OK;
+}
+
+void JumpListBuilder::DoCommitListBuild(
+ RefPtr<detail::DoneCommitListBuildCallback> aCallback) {
+ // Since we're invoking COM interfaces to talk to the shell on a background
+ // thread, we need to be running inside a multithreaded apartment.
+ mscom::MTARegion mta;
+ MOZ_ASSERT(mta.IsValid());
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ MOZ_ASSERT(mJumpListMgr);
+ MOZ_ASSERT(aCallback);
+
+ HRESULT hr = E_UNEXPECTED;
+ auto onExit = MakeScopeExit([&hr, &aCallback]() {
+ // XXX We might want some specific error data here.
+ aCallback->SetResult(SUCCEEDED(hr));
+ Unused << NS_DispatchToMainThread(aCallback);
+ });
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return;
+ }
+
+ hr = jumpListMgr->CommitList();
+ sBuildingList = false;
+
+ if (SUCCEEDED(hr)) {
+ mHasCommit = true;
+ }
+}
+
+NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool* _retval) {
+ *_retval = false;
+
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
+
+ if (sBuildingList) {
+ AbortListBuild();
+ }
+
+ RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
+ if (!jumpListMgr) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (SUCCEEDED(jumpListMgr->DeleteList(mAppUserModelId.get()))) {
+ *_retval = true;
+ }
+
+ return NS_OK;
+}
+
+/* internal */
+
+bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item) {
+ int16_t type;
+ item->GetType(&type);
+ if (NS_FAILED(item->GetType(&type))) return false;
+
+ if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) return true;
+ return false;
+}
+
+// RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to
+// avoid unnecessary extra XPCOM incantations. For each object in the input
+// array, if it's an IShellLinkW, this deletes the cached icon and adds the
+// url param to a list of URLs to be removed from the places database.
+void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs(
+ IObjectArray* aObjArray, nsTArray<nsString>& aURISpecs) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Early return here just in case some versions of Windows don't populate this
+ if (!aObjArray) {
+ return;
+ }
+
+ uint32_t count = 0;
+ aObjArray->GetCount(&count);
+
+ for (uint32_t idx = 0; idx < count; idx++) {
+ RefPtr<IShellLinkW> pLink;
+
+ if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW,
+ static_cast<void**>(getter_AddRefs(pLink))))) {
+ continue;
+ }
+
+ wchar_t buf[MAX_PATH];
+ HRESULT hres = pLink->GetArguments(buf, MAX_PATH);
+ if (SUCCEEDED(hres)) {
+ LPWSTR* arglist;
+ int32_t numArgs;
+
+ arglist = ::CommandLineToArgvW(buf, &numArgs);
+ if (arglist && numArgs > 0) {
+ nsString spec(arglist[0]);
+ aURISpecs.AppendElement(std::move(spec));
+ ::LocalFree(arglist);
+ }
+ }
+
+ int iconIdx = 0;
+ hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
+ if (SUCCEEDED(hres)) {
+ nsDependentString spec(buf);
+ DeleteIconFromDisk(spec);
+ }
+ }
+}
+
+void JumpListBuilder::DeleteIconFromDisk(const nsAString& aPath) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Check that we aren't deleting some arbitrary file that is not an icon
+ if (StringTail(aPath, 4).LowerCaseEqualsASCII(".ico")) {
+ // Construct the parent path of the passed in path
+ nsCOMPtr<nsIFile> icoFile;
+ nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ icoFile->Remove(false);
+ }
+}
+
+NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ NS_ENSURE_ARG_POINTER(aTopic);
+ if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (observerService) {
+ observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
+ }
+ mIOThread->Shutdown();
+ // Clear out mJumpListMgr, as MSCOM services won't be available soon.
+ ReentrantMonitorAutoEnter lock(mMonitor);
+ mJumpListMgr = nullptr;
+ } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
+ nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
+ bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
+ if (!enabled) {
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) {
+ // Delete JumpListCache icons from Disk, if any.
+ nsCOMPtr<nsIRunnable> event =
+ new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
+ mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+ }
+ return NS_OK;
+}
+
+} // namespace widget
+} // namespace mozilla