diff options
Diffstat (limited to 'widget/windows/JumpListBuilder.cpp')
-rw-r--r-- | widget/windows/JumpListBuilder.cpp | 542 |
1 files changed, 542 insertions, 0 deletions
diff --git a/widget/windows/JumpListBuilder.cpp b/widget/windows/JumpListBuilder.cpp new file mode 100644 index 000000000..566c41d4a --- /dev/null +++ b/widget/windows/JumpListBuilder.cpp @@ -0,0 +1,542 @@ +/* -*- 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 "nsIMutableArray.h" +#include "nsWidgetsCID.h" +#include "WinTaskbar.h" +#include "nsDirectoryServiceUtils.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/Preferences.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" +#include "mozilla/LazyIdleThread.h" +#include "nsIObserverService.h" + +#include "WinUtils.h" + +// 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 { + +static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID); +static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID); +static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID); + +// defined in WinTaskbar.cpp +extern const wchar_t *gMozillaJumpListIDGeneric; + +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" + +JumpListBuilder::JumpListBuilder() : + mMaxItems(0), + mHasCommit(false) +{ + ::CoInitialize(nullptr); + + CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, + IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr)); + + // Make a lazy thread for any IO + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, + NS_LITERAL_CSTRING("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); + } +} + +JumpListBuilder::~JumpListBuilder() +{ + Preferences::RemoveObserver(this, kPrefTaskbarEnabled); + mJumpListMgr = nullptr; + ::CoUninitialize(); +} + +NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable) +{ + *aAvailable = false; + + 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) +{ + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + *aMaxItems = 0; + + if (sBuildingList) { + *aMaxItems = mMaxItems; + return NS_OK; + } + + IObjectArray *objArray; + if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { + *aMaxItems = mMaxItems; + + if (objArray) + objArray->Release(); + + mJumpListMgr->AbortList(); + } + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(removedItems); + + *_retval = false; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + if(sBuildingList) + AbortListBuild(); + + IObjectArray *objArray; + + // 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. + if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { + if (objArray) { + TransferIObjectArrayToIMutableArray(objArray, removedItems); + objArray->Release(); + } + + RemoveIconCacheForItems(removedItems); + + sBuildingList = true; + *_retval = true; + return NS_OK; + } + + return NS_OK; +} + +// Ensures that we don't have old ICO files that aren't in our jump lists +// anymore left over in the cache. +nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items) +{ + NS_ENSURE_ARG_POINTER(items); + + nsresult rv; + uint32_t length; + items->GetLength(&length); + for (uint32_t i = 0; i < length; ++i) { + + //Obtain an IJumpListItem and get the type + nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); + if (!item) { + continue; + } + int16_t type; + if (NS_FAILED(item->GetType(&type))) { + continue; + } + + // If the item is a shortcut, remove its associated icon if any + if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) { + nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item); + if (shortcut) { + nsCOMPtr<nsIURI> uri; + rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri)); + if (NS_SUCCEEDED(rv) && uri) { + + // The local file path is stored inside the nsIURI + // Get the nsIURI spec which stores the local path for the icon to remove + nsAutoCString spec; + nsresult rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIRunnable> event + = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec)); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + + // The shortcut was generated from an IShellLinkW so IShellLinkW can + // only tell us what the original icon is and not the URI. + // So this field was used only temporarily as the actual icon file + // path. It should be cleared. + shortcut->SetFaviconPageUri(nullptr); + } + } + } + + } // end for + + return NS_OK; +} + +// 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<nsISimpleEnumerator> entries; + rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through each directory entry and remove all ICO files found + do { + bool hasMore = false; + if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore) + break; + + nsCOMPtr<nsISupports> supp; + if (NS_FAILED(entries->GetNext(getter_AddRefs(supp)))) + break; + + nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp)); + 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; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + 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 = mJumpListMgr->AddUserTasks(pArray); + if (SUCCEEDED(hr)) + *_retval = true; + return NS_OK; + } + break; + case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: + { + if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT))) + *_retval = true; + return NS_OK; + } + break; + case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: + { + if (SUCCEEDED(mJumpListMgr->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 = mJumpListMgr->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() +{ + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + mJumpListMgr->AbortList(); + sBuildingList = false; + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval) +{ + *_retval = false; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + HRESULT hr = mJumpListMgr->CommitList(); + sBuildingList = false; + + // XXX We might want some specific error data here. + if (SUCCEEDED(hr)) { + *_retval = true; + mHasCommit = true; + } + + return NS_OK; +} + +NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval) +{ + *_retval = false; + + if (!mJumpListMgr) + return NS_ERROR_NOT_AVAILABLE; + + if(sBuildingList) + AbortListBuild(); + + nsAutoString uid; + if (!WinTaskbar::GetAppUserModelID(uid)) + return NS_OK; + + if (SUCCEEDED(mJumpListMgr->DeleteList(uid.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; +} + +// TransferIObjectArrayToIMutableArray - used in converting removed items +// to our objects. +nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems) +{ + NS_ENSURE_ARG_POINTER(objArray); + NS_ENSURE_ARG_POINTER(removedItems); + + nsresult rv; + + uint32_t count = 0; + objArray->GetCount(&count); + + nsCOMPtr<nsIJumpListItem> item; + + for (uint32_t idx = 0; idx < count; idx++) { + IShellLinkW * pLink = nullptr; + IShellItem * pItem = nullptr; + + if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) { + nsCOMPtr<nsIJumpListShortcut> shortcut = + do_CreateInstance(kJumpListShortcutCID, &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut); + item = do_QueryInterface(shortcut); + } + else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) { + nsCOMPtr<nsIJumpListLink> link = + do_CreateInstance(kJumpListLinkCID, &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + rv = JumpListLink::GetJumpListLink(pItem, link); + item = do_QueryInterface(link); + } + + if (pLink) + pLink->Release(); + if (pItem) + pItem->Release(); + + if (NS_SUCCEEDED(rv)) { + removedItems->AppendElement(item, false); + } + } + return NS_OK; +} + +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(); + } 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 + |