/* -*- 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 #include #include #include #include #include "JumpListBuilder.h" #include "mozilla/Preferences.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/WindowsJumpListShortcutDescriptionBinding.h" #include "mozilla/mscom/EnsureMTA.h" #include "nsIFile.h" #include "nsIObserverService.h" #include "nsServiceManagerUtils.h" #include "WinUtils.h" using mozilla::dom::Promise; using mozilla::dom::WindowsJumpListShortcutDescription; namespace mozilla { namespace widget { NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver) #define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change" #define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data" // 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 const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled"; /** * A wrapper around a ICustomDestinationList that implements the JumpListBackend * interface. This is an implementation of JumpListBackend that actually causes * items to appear in a Windows jump list. */ class NativeJumpListBackend : public JumpListBackend { // We use NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET because this // class might be destroyed on a different thread than the one it // was created on, since it's maintained by a LazyIdleThread. // // This is a workaround for bug 1648031. NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(JumpListBackend, override) NativeJumpListBackend() { MOZ_ASSERT(!NS_IsMainThread()); mscom::EnsureMTA([&]() { RefPtr destList; HRESULT hr = ::CoCreateInstance( CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, IID_ICustomDestinationList, getter_AddRefs(destList)); if (FAILED(hr)) { return; } mWindowsDestList = destList; }); } virtual bool IsAvailable() override { MOZ_ASSERT(!NS_IsMainThread()); return mWindowsDestList != nullptr; } virtual HRESULT SetAppID(LPCWSTR pszAppID) override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->SetAppID(pszAppID); } virtual HRESULT BeginList(UINT* pcMinSlots, REFIID riid, void** ppv) override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->BeginList(pcMinSlots, riid, ppv); } virtual HRESULT AddUserTasks(IObjectArray* poa) override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->AddUserTasks(poa); } virtual HRESULT AppendCategory(LPCWSTR pszCategory, IObjectArray* poa) override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->AppendCategory(pszCategory, poa); } virtual HRESULT CommitList() override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->CommitList(); } virtual HRESULT AbortList() override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->AbortList(); } virtual HRESULT DeleteList(LPCWSTR pszAppID) override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->DeleteList(pszAppID); } virtual HRESULT AppendKnownCategory(KNOWNDESTCATEGORY category) override { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mWindowsDestList); return mWindowsDestList->AppendKnownCategory(category); } protected: virtual ~NativeJumpListBackend() override{}; private: RefPtr mWindowsDestList; }; JumpListBuilder::JumpListBuilder(const nsAString& aAppUserModelId, RefPtr aTestingBackend) { MOZ_ASSERT(NS_IsMainThread()); mAppUserModelId.Assign(aAppUserModelId); Preferences::AddStrongObserver(this, kPrefTaskbarEnabled); // Make a lazy thread for any IO. mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List", LazyIdleThread::ManualShutdown); nsCOMPtr 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); } nsCOMPtr runnable; if (aTestingBackend) { // Dispatch a task that hands a reference to the testing backend // to the background thread. The testing backend was probably // constructed on the main thread, and is responsible for doing // any locking as well as cleanup. runnable = NewRunnableMethod>( "SetupTestingBackend", this, &JumpListBuilder::DoSetupTestingBackend, aTestingBackend); } else { // Dispatch a task that constructs the native jump list backend. runnable = NewRunnableMethod("SetupBackend", this, &JumpListBuilder::DoSetupBackend); } mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); // 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()) { mIOThread->Dispatch( NewRunnableMethod("SetAppID", this, &JumpListBuilder::DoSetAppIDIfAvailable, aAppUserModelId), NS_DISPATCH_NORMAL); } } JumpListBuilder::~JumpListBuilder() { Preferences::RemoveObserver(this, kPrefTaskbarEnabled); } void JumpListBuilder::DoSetupTestingBackend( RefPtr aTestingBackend) { MOZ_ASSERT(!NS_IsMainThread()); mJumpListBackend = aTestingBackend; } void JumpListBuilder::DoSetupBackend() { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!mJumpListBackend); mJumpListBackend = new NativeJumpListBackend(); } void JumpListBuilder::DoShutdownBackend() { MOZ_ASSERT(!NS_IsMainThread()); mJumpListBackend = nullptr; } void JumpListBuilder::DoSetAppIDIfAvailable(nsString aAppUserModelID) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mJumpListBackend); if (mJumpListBackend->IsAvailable()) { mJumpListBackend->SetAppID(aAppUserModelID.get()); } } NS_IMETHODIMP JumpListBuilder::ObtainAndCacheFavicon(nsIURI* aFaviconURI, nsAString& aCachedIconPath) { MOZ_ASSERT(NS_IsMainThread()); nsString iconFilePath; nsresult rv = mozilla::widget::FaviconHelper::ObtainCachedIconFile( aFaviconURI, iconFilePath, mIOThread, false); NS_ENSURE_SUCCESS(rv, rv); aCachedIconPath = iconFilePath; return NS_OK; } NS_IMETHODIMP JumpListBuilder::IsAvailable(JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPromise); MOZ_ASSERT(mIOThread); ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (MOZ_UNLIKELY(result.Failed())) { return result.StealNSResult(); } nsMainThreadPtrHandle promiseHolder( new nsMainThreadPtrHolder("JumpListBuilder::IsAvailable promise", promise)); nsCOMPtr runnable = NewRunnableMethod>( "IsAvailable", this, &JumpListBuilder::DoIsAvailable, std::move(promiseHolder)); nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } promise.forget(aPromise); return NS_OK; } NS_IMETHODIMP JumpListBuilder::CheckForRemovals(JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPromise); MOZ_ASSERT(mIOThread); ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (MOZ_UNLIKELY(result.Failed())) { return result.StealNSResult(); } nsMainThreadPtrHandle promiseHolder( new nsMainThreadPtrHolder( "JumpListBuilder::CheckForRemovals promise", promise)); nsCOMPtr runnable = NewRunnableMethod>( "CheckForRemovals", this, &JumpListBuilder::DoCheckForRemovals, std::move(promiseHolder)); nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } promise.forget(aPromise); return NS_OK; } NS_IMETHODIMP JumpListBuilder::PopulateJumpList( const nsTArray& aTaskDescriptions, const nsAString& aCustomTitle, const nsTArray& aCustomDescriptions, JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPromise); MOZ_ASSERT(mIOThread); if (aCustomDescriptions.Length() && aCustomTitle.IsEmpty()) { return NS_ERROR_INVALID_ARG; } // Get rid of the old icons nsCOMPtr event = new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true); mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); nsTArray taskDescs; for (auto& jsval : aTaskDescriptions) { JS::Rooted rootedVal(aCx); if (NS_WARN_IF(!dom::ToJSValue(aCx, jsval, &rootedVal))) { return NS_ERROR_INVALID_ARG; } WindowsJumpListShortcutDescription desc; if (!desc.Init(aCx, rootedVal)) { return NS_ERROR_INVALID_ARG; } taskDescs.AppendElement(std::move(desc)); } nsTArray customDescs; for (auto& jsval : aCustomDescriptions) { JS::Rooted rootedVal(aCx); if (NS_WARN_IF(!dom::ToJSValue(aCx, jsval, &rootedVal))) { return NS_ERROR_INVALID_ARG; } WindowsJumpListShortcutDescription desc; if (!desc.Init(aCx, rootedVal)) { return NS_ERROR_INVALID_ARG; } customDescs.AppendElement(std::move(desc)); } ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (MOZ_UNLIKELY(result.Failed())) { return result.StealNSResult(); } nsMainThreadPtrHandle promiseHolder( new nsMainThreadPtrHolder( "JumpListBuilder::PopulateJumpList promise", promise)); nsCOMPtr runnable = NewRunnableMethod< StoreCopyPassByRRef>, nsString, StoreCopyPassByRRef>, nsMainThreadPtrHandle>( "PopulateJumpList", this, &JumpListBuilder::DoPopulateJumpList, std::move(taskDescs), aCustomTitle, std::move(customDescs), std::move(promiseHolder)); nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } promise.forget(aPromise); return NS_OK; } NS_IMETHODIMP JumpListBuilder::ClearJumpList(JSContext* aCx, Promise** aPromise) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPromise); MOZ_ASSERT(mIOThread); ErrorResult result; RefPtr promise = Promise::Create(xpc::CurrentNativeGlobal(aCx), result); if (MOZ_UNLIKELY(result.Failed())) { return result.StealNSResult(); } nsMainThreadPtrHandle promiseHolder( new nsMainThreadPtrHolder( "JumpListBuilder::ClearJumpList promise", promise)); nsCOMPtr runnable = NewRunnableMethod>( "ClearJumpList", this, &JumpListBuilder::DoClearJumpList, std::move(promiseHolder)); 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::DoIsAvailable( const nsMainThreadPtrHandle& aPromiseHolder) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aPromiseHolder); if (!mJumpListBackend) { NS_DispatchToMainThread(NS_NewRunnableFunction( "DoIsAvailable", [promiseHolder = std::move(aPromiseHolder)]() { promiseHolder.get()->MaybeResolve(false); })); return; } bool isAvailable = mJumpListBackend->IsAvailable(); NS_DispatchToMainThread(NS_NewRunnableFunction( "DoIsAvailable", [promiseHolder = std::move(aPromiseHolder), isAvailable]() { promiseHolder.get()->MaybeResolve(isAvailable); })); } void JumpListBuilder::DoCheckForRemovals( const nsMainThreadPtrHandle& aPromiseHolder) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aPromiseHolder); nsresult rv = NS_ERROR_UNEXPECTED; auto errorHandler = MakeScopeExit([&aPromiseHolder, &rv]() { NS_DispatchToMainThread(NS_NewRunnableFunction( "DoCheckForRemovals", [promiseHolder = std::move(aPromiseHolder), rv]() { promiseHolder.get()->MaybeReject(rv); })); }); MOZ_ASSERT(mJumpListBackend); if (!mJumpListBackend) { return; } // Abort any ongoing list building that might not have been committed, // otherwise BeginList will give us problems. Unused << mJumpListBackend->AbortList(); nsTArray urisToRemove; RefPtr objArray; uint32_t maxItems = 0; HRESULT hr = mJumpListBackend->BeginList( &maxItems, IID_PPV_ARGS(static_cast(getter_AddRefs(objArray)))); if (FAILED(hr)) { rv = NS_ERROR_UNEXPECTED; return; } RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove); // We began a list in order to get the removals, which we can now abort. Unused << mJumpListBackend->AbortList(); errorHandler.release(); NS_DispatchToMainThread(NS_NewRunnableFunction( "DoCheckForRemovals", [urisToRemove = std::move(urisToRemove), promiseHolder = std::move(aPromiseHolder)]() { promiseHolder.get()->MaybeResolve(urisToRemove); })); } void JumpListBuilder::DoPopulateJumpList( const nsTArray&& aTaskDescriptions, const nsAString& aCustomTitle, const nsTArray&& aCustomDescriptions, const nsMainThreadPtrHandle& aPromiseHolder) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aPromiseHolder); nsresult rv = NS_ERROR_UNEXPECTED; auto errorHandler = MakeScopeExit([&aPromiseHolder, &rv]() { NS_DispatchToMainThread(NS_NewRunnableFunction( "DoPopulateJumpList", [promiseHolder = std::move(aPromiseHolder), rv]() { promiseHolder.get()->MaybeReject(rv); })); }); MOZ_ASSERT(mJumpListBackend); if (!mJumpListBackend) { return; } // Abort any ongoing list building that might not have been committed, // otherwise BeginList will give us problems. Unused << mJumpListBackend->AbortList(); nsTArray urisToRemove; RefPtr objArray; uint32_t maxItems = 0; HRESULT hr = mJumpListBackend->BeginList( &maxItems, IID_PPV_ARGS(static_cast(getter_AddRefs(objArray)))); if (FAILED(hr)) { rv = NS_ERROR_UNEXPECTED; return; } if (urisToRemove.Length()) { // It'd be nice if we could return a more descriptive error here so that // the caller knows that they should have called checkForRemovals first. rv = NS_ERROR_UNEXPECTED; return; } if (aTaskDescriptions.Length()) { RefPtr taskCollection; hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, IID_IObjectCollection, getter_AddRefs(taskCollection)); if (FAILED(hr)) { rv = NS_ERROR_UNEXPECTED; return; } // Start by building the task list for (auto& desc : aTaskDescriptions) { // These should all be ShellLinks RefPtr link; rv = GetShellLinkFromDescription(desc, link); if (NS_FAILED(rv)) { // Let the errorHandler deal with this. return; } taskCollection->AddObject(link); } RefPtr pTaskArray; hr = taskCollection->QueryInterface(IID_IObjectArray, getter_AddRefs(pTaskArray)); if (FAILED(hr)) { rv = NS_ERROR_UNEXPECTED; return; } hr = mJumpListBackend->AddUserTasks(pTaskArray); if (FAILED(hr)) { rv = NS_ERROR_FAILURE; return; } } if (aCustomDescriptions.Length()) { // Then build the custom list RefPtr customCollection; hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, CLSCTX_INPROC_SERVER, IID_IObjectCollection, getter_AddRefs(customCollection)); if (FAILED(hr)) { rv = NS_ERROR_UNEXPECTED; return; } for (auto& desc : aCustomDescriptions) { // These should all be ShellLinks RefPtr link; rv = GetShellLinkFromDescription(desc, link); if (NS_FAILED(rv)) { // Let the errorHandler deal with this. return; } customCollection->AddObject(link); } RefPtr pCustomArray; hr = customCollection->QueryInterface(IID_IObjectArray, getter_AddRefs(pCustomArray)); if (FAILED(hr)) { rv = NS_ERROR_UNEXPECTED; return; } hr = mJumpListBackend->AppendCategory( reinterpret_cast(aCustomTitle.BeginReading()), pCustomArray); if (FAILED(hr)) { rv = NS_ERROR_UNEXPECTED; return; } } hr = mJumpListBackend->CommitList(); if (FAILED(hr)) { rv = NS_ERROR_FAILURE; return; } errorHandler.release(); NS_DispatchToMainThread(NS_NewRunnableFunction( "DoPopulateJumpList", [promiseHolder = std::move(aPromiseHolder)]() { promiseHolder.get()->MaybeResolveWithUndefined(); })); } void JumpListBuilder::DoClearJumpList( const nsMainThreadPtrHandle& aPromiseHolder) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aPromiseHolder); if (!mJumpListBackend) { NS_DispatchToMainThread(NS_NewRunnableFunction( "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() { promiseHolder.get()->MaybeReject(NS_ERROR_UNEXPECTED); })); return; } if (SUCCEEDED(mJumpListBackend->DeleteList(mAppUserModelId.get()))) { NS_DispatchToMainThread(NS_NewRunnableFunction( "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() { promiseHolder.get()->MaybeResolveWithUndefined(); })); } else { NS_DispatchToMainThread(NS_NewRunnableFunction( "DoClearJumpList", [promiseHolder = std::move(aPromiseHolder)]() { promiseHolder.get()->MaybeReject(NS_ERROR_FAILURE); })); } } // 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& 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 pLink; if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW, static_cast(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 icoFile; nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } icoFile->Remove(false); } } // Converts a WindowsJumpListShortcutDescription into a IShellLinkW nsresult JumpListBuilder::GetShellLinkFromDescription( const WindowsJumpListShortcutDescription& aDesc, RefPtr& aShellLink) { MOZ_ASSERT(!NS_IsMainThread()); HRESULT hr; IShellLinkW* psl; // Shell links: // http://msdn.microsoft.com/en-us/library/bb776891(VS.85).aspx // http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx // Create a IShellLink hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&psl); if (FAILED(hr)) { return NS_ERROR_UNEXPECTED; } // Store the title of the app if (!aDesc.mTitle.IsEmpty()) { IPropertyStore* pPropStore = nullptr; hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore); if (FAILED(hr)) { return NS_ERROR_UNEXPECTED; } PROPVARIANT pv; ::InitPropVariantFromString(aDesc.mTitle.get(), &pv); pPropStore->SetValue(PKEY_Title, pv); pPropStore->Commit(); pPropStore->Release(); PropVariantClear(&pv); } // Store the rest of the params hr = psl->SetPath(aDesc.mPath.get()); // According to the documentation at [1], the maximum description length is // INFOTIPSIZE, so we copy the const string from the description into a buffer // of that maximum size. However, testing reveals that INFOTIPSIZE is still // sometimes too large. MAX_PATH seems to work instead. // // We truncate to MAX_PATH - 1, since nsAutoString's don't include the null // character in their capacity calculations, but the string for IShellLinkW // description does. So by truncating to MAX_PATH - 1, the full contents of // the truncated nsAutoString will be copied into the IShellLink description // buffer. // // [1]: // https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishelllinka-setdescription nsAutoString descriptionCopy(aDesc.mDescription.get()); if (descriptionCopy.Length() >= MAX_PATH) { descriptionCopy.Truncate(MAX_PATH - 1); } hr = psl->SetDescription(descriptionCopy.get()); if (aDesc.mArguments.WasPassed() && !aDesc.mArguments.Value().IsEmpty()) { hr = psl->SetArguments(aDesc.mArguments.Value().get()); } else { hr = psl->SetArguments(L""); } // Set up the fallback icon in the event that a valid icon URI has // not been supplied. hr = psl->SetIconLocation(aDesc.mPath.get(), aDesc.mFallbackIconIndex); if (aDesc.mIconPath.WasPassed() && !aDesc.mIconPath.Value().IsEmpty()) { // Always use the first icon in the ICO file, as our encoded icon only has 1 // resource hr = psl->SetIconLocation(aDesc.mIconPath.Value().get(), 0); } aShellLink = dont_AddRef(psl); return NS_OK; } NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_ARG_POINTER(aTopic); if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) { nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1"); if (observerService) { observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE); observerService->RemoveObserver(this, TOPIC_CLEAR_PRIVATE_DATA); } mIOThread->Dispatch(NewRunnableMethod("ShutdownBackend", this, &JumpListBuilder::DoShutdownBackend), NS_DISPATCH_NORMAL); mIOThread->Shutdown(); } else if (strcmp(aTopic, "nsPref:changed") == 0 && nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) { bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true); if (!enabled) { nsCOMPtr 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 event = new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false); mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); } return NS_OK; } } // namespace widget } // namespace mozilla