michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "JumpListBuilder.h" michael@0: michael@0: #include "nsError.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsArrayUtils.h" michael@0: #include "nsIMutableArray.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "WinTaskbar.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsStringStream.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "mozilla/LazyIdleThread.h" michael@0: michael@0: #include "WinUtils.h" michael@0: michael@0: // The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes. michael@0: #define DEFAULT_THREAD_TIMEOUT_MS 30000 michael@0: michael@0: namespace mozilla { michael@0: namespace widget { michael@0: michael@0: static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID); michael@0: static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID); michael@0: static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID); michael@0: michael@0: // defined in WinTaskbar.cpp michael@0: extern const wchar_t *gMozillaJumpListIDGeneric; michael@0: michael@0: bool JumpListBuilder::sBuildingList = false; michael@0: const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled"; michael@0: michael@0: NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver) michael@0: michael@0: JumpListBuilder::JumpListBuilder() : michael@0: mMaxItems(0), michael@0: mHasCommit(false) michael@0: { michael@0: ::CoInitialize(nullptr); michael@0: michael@0: CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, michael@0: IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr)); michael@0: michael@0: // Make a lazy thread for any IO michael@0: mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, michael@0: NS_LITERAL_CSTRING("Jump List"), michael@0: LazyIdleThread::ManualShutdown); michael@0: Preferences::AddStrongObserver(this, kPrefTaskbarEnabled); michael@0: } michael@0: michael@0: JumpListBuilder::~JumpListBuilder() michael@0: { michael@0: mIOThread->Shutdown(); michael@0: Preferences::RemoveObserver(this, kPrefTaskbarEnabled); michael@0: mJumpListMgr = nullptr; michael@0: ::CoUninitialize(); michael@0: } michael@0: michael@0: /* readonly attribute short available; */ michael@0: NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable) michael@0: { michael@0: *aAvailable = false; michael@0: michael@0: if (mJumpListMgr) michael@0: *aAvailable = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute boolean isListCommitted; */ michael@0: NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit) michael@0: { michael@0: *aCommit = mHasCommit; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute short maxItems; */ michael@0: NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems) michael@0: { michael@0: if (!mJumpListMgr) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: *aMaxItems = 0; michael@0: michael@0: if (sBuildingList) { michael@0: *aMaxItems = mMaxItems; michael@0: return NS_OK; michael@0: } michael@0: michael@0: IObjectArray *objArray; michael@0: if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { michael@0: *aMaxItems = mMaxItems; michael@0: michael@0: if (objArray) michael@0: objArray->Release(); michael@0: michael@0: mJumpListMgr->AbortList(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* boolean initListBuild(in nsIMutableArray removedItems); */ michael@0: NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(removedItems); michael@0: michael@0: *_retval = false; michael@0: michael@0: if (!mJumpListMgr) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if(sBuildingList) michael@0: AbortListBuild(); michael@0: michael@0: IObjectArray *objArray; michael@0: michael@0: if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { michael@0: if (objArray) { michael@0: TransferIObjectArrayToIMutableArray(objArray, removedItems); michael@0: objArray->Release(); michael@0: } michael@0: michael@0: RemoveIconCacheForItems(removedItems); michael@0: michael@0: sBuildingList = true; michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Ensures that we don't have old ICO files that aren't in our jump lists michael@0: // anymore left over in the cache. michael@0: nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(items); michael@0: michael@0: nsresult rv; michael@0: uint32_t length; michael@0: items->GetLength(&length); michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: michael@0: //Obtain an IJumpListItem and get the type michael@0: nsCOMPtr item = do_QueryElementAt(items, i); michael@0: if (!item) { michael@0: continue; michael@0: } michael@0: int16_t type; michael@0: if (NS_FAILED(item->GetType(&type))) { michael@0: continue; michael@0: } michael@0: michael@0: // If the item is a shortcut, remove its associated icon if any michael@0: if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) { michael@0: nsCOMPtr shortcut = do_QueryInterface(item); michael@0: if (shortcut) { michael@0: nsCOMPtr uri; michael@0: rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri)); michael@0: if (NS_SUCCEEDED(rv) && uri) { michael@0: michael@0: // The local file path is stored inside the nsIURI michael@0: // Get the nsIURI spec which stores the local path for the icon to remove michael@0: nsAutoCString spec; michael@0: nsresult rv = uri->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr event michael@0: = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec)); michael@0: mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: michael@0: // The shortcut was generated from an IShellLinkW so IShellLinkW can michael@0: // only tell us what the original icon is and not the URI. michael@0: // So this field was used only temporarily as the actual icon file michael@0: // path. It should be cleared. michael@0: shortcut->SetFaviconPageUri(nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: } // end for michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Ensures that we have no old ICO files left in the jump list cache michael@0: nsresult JumpListBuilder::RemoveIconCacheForAllItems() michael@0: { michael@0: // Construct the path of our jump list cache michael@0: nsCOMPtr jumpListCacheDir; michael@0: nsresult rv = NS_GetSpecialDirectory("ProfLDS", michael@0: getter_AddRefs(jumpListCacheDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = jumpListCacheDir->AppendNative(nsDependentCString( michael@0: mozilla::widget::FaviconHelper::kJumpListCacheDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr entries; michael@0: rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Loop through each directory entry and remove all ICO files found michael@0: do { michael@0: bool hasMore = false; michael@0: if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore) michael@0: break; michael@0: michael@0: nsCOMPtr supp; michael@0: if (NS_FAILED(entries->GetNext(getter_AddRefs(supp)))) michael@0: break; michael@0: michael@0: nsCOMPtr currFile(do_QueryInterface(supp)); michael@0: nsAutoString path; michael@0: if (NS_FAILED(currFile->GetPath(path))) michael@0: continue; michael@0: michael@0: int32_t len = path.Length(); michael@0: if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) { michael@0: // Check if the cached ICO file exists michael@0: bool exists; michael@0: if (NS_FAILED(currFile->Exists(&exists)) || !exists) michael@0: continue; michael@0: michael@0: // We found an ICO file that exists, so we should remove it michael@0: currFile->Remove(false); michael@0: } michael@0: } while(true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* boolean addListToBuild(in short aCatType, [optional] in nsIArray items, [optional] in AString catName); */ michael@0: NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval) michael@0: { michael@0: nsresult rv; michael@0: michael@0: *_retval = false; michael@0: michael@0: if (!mJumpListMgr) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: switch(aCatType) { michael@0: case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: michael@0: { michael@0: NS_ENSURE_ARG_POINTER(items); michael@0: michael@0: HRESULT hr; michael@0: nsRefPtr collection; michael@0: hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, michael@0: CLSCTX_INPROC_SERVER, IID_IObjectCollection, michael@0: getter_AddRefs(collection)); michael@0: if (FAILED(hr)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // Build the list michael@0: uint32_t length; michael@0: items->GetLength(&length); michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: nsCOMPtr item = do_QueryElementAt(items, i); michael@0: if (!item) michael@0: continue; michael@0: // Check for separators michael@0: if (IsSeparator(item)) { michael@0: nsRefPtr link; michael@0: rv = JumpListSeparator::GetSeparator(link); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: collection->AddObject(link); michael@0: continue; michael@0: } michael@0: // These should all be ShellLinks michael@0: nsRefPtr link; michael@0: rv = JumpListShortcut::GetShellLink(item, link, mIOThread); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: collection->AddObject(link); michael@0: } michael@0: michael@0: // We need IObjectArray to submit michael@0: nsRefPtr pArray; michael@0: hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray)); michael@0: if (FAILED(hr)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // Add the tasks michael@0: hr = mJumpListMgr->AddUserTasks(pArray); michael@0: if (SUCCEEDED(hr)) michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: break; michael@0: case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: michael@0: { michael@0: if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT))) michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: break; michael@0: case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: michael@0: { michael@0: if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT))) michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: break; michael@0: case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: michael@0: { michael@0: NS_ENSURE_ARG_POINTER(items); michael@0: michael@0: if (catName.IsEmpty()) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: HRESULT hr; michael@0: nsRefPtr collection; michael@0: hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, michael@0: CLSCTX_INPROC_SERVER, IID_IObjectCollection, michael@0: getter_AddRefs(collection)); michael@0: if (FAILED(hr)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: uint32_t length; michael@0: items->GetLength(&length); michael@0: for (uint32_t i = 0; i < length; ++i) { michael@0: nsCOMPtr item = do_QueryElementAt(items, i); michael@0: if (!item) michael@0: continue; michael@0: int16_t type; michael@0: if (NS_FAILED(item->GetType(&type))) michael@0: continue; michael@0: switch(type) { michael@0: case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: michael@0: { michael@0: nsRefPtr shellItem; michael@0: rv = JumpListSeparator::GetSeparator(shellItem); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: collection->AddObject(shellItem); michael@0: } michael@0: break; michael@0: case nsIJumpListItem::JUMPLIST_ITEM_LINK: michael@0: { michael@0: nsRefPtr shellItem; michael@0: rv = JumpListLink::GetShellItem(item, shellItem); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: collection->AddObject(shellItem); michael@0: } michael@0: break; michael@0: case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: michael@0: { michael@0: nsRefPtr shellItem; michael@0: rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: collection->AddObject(shellItem); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // We need IObjectArray to submit michael@0: nsRefPtr pArray; michael@0: hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray); michael@0: if (FAILED(hr)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: // Add the tasks michael@0: hr = mJumpListMgr->AppendCategory(reinterpret_cast(catName.BeginReading()), pArray); michael@0: if (SUCCEEDED(hr)) michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: break; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void abortListBuild(); */ michael@0: NS_IMETHODIMP JumpListBuilder::AbortListBuild() michael@0: { michael@0: if (!mJumpListMgr) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: mJumpListMgr->AbortList(); michael@0: sBuildingList = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* boolean commitListBuild(); */ michael@0: NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval) michael@0: { michael@0: *_retval = false; michael@0: michael@0: if (!mJumpListMgr) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: HRESULT hr = mJumpListMgr->CommitList(); michael@0: sBuildingList = false; michael@0: michael@0: // XXX We might want some specific error data here. michael@0: if (SUCCEEDED(hr)) { michael@0: *_retval = true; michael@0: mHasCommit = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* boolean deleteActiveList(); */ michael@0: NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval) michael@0: { michael@0: *_retval = false; michael@0: michael@0: if (!mJumpListMgr) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if(sBuildingList) michael@0: AbortListBuild(); michael@0: michael@0: nsAutoString uid; michael@0: if (!WinTaskbar::GetAppUserModelID(uid)) michael@0: return NS_OK; michael@0: michael@0: if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get()))) michael@0: *_retval = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* internal */ michael@0: michael@0: bool JumpListBuilder::IsSeparator(nsCOMPtr& item) michael@0: { michael@0: int16_t type; michael@0: item->GetType(&type); michael@0: if (NS_FAILED(item->GetType(&type))) michael@0: return false; michael@0: michael@0: if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: // TransferIObjectArrayToIMutableArray - used in converting removed items michael@0: // to our objects. michael@0: nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(objArray); michael@0: NS_ENSURE_ARG_POINTER(removedItems); michael@0: michael@0: nsresult rv; michael@0: michael@0: uint32_t count = 0; michael@0: objArray->GetCount(&count); michael@0: michael@0: nsCOMPtr item; michael@0: michael@0: for (uint32_t idx = 0; idx < count; idx++) { michael@0: IShellLinkW * pLink = nullptr; michael@0: IShellItem * pItem = nullptr; michael@0: michael@0: if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) { michael@0: nsCOMPtr shortcut = michael@0: do_CreateInstance(kJumpListShortcutCID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut); michael@0: item = do_QueryInterface(shortcut); michael@0: } michael@0: else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) { michael@0: nsCOMPtr link = michael@0: do_CreateInstance(kJumpListLinkCID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: rv = JumpListLink::GetJumpListLink(pItem, link); michael@0: item = do_QueryInterface(link); michael@0: } michael@0: michael@0: if (pLink) michael@0: pLink->Release(); michael@0: if (pItem) michael@0: pItem->Release(); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: removedItems->AppendElement(item, false); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject, michael@0: const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: if (nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) { michael@0: bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true); michael@0: if (!enabled) { michael@0: michael@0: nsCOMPtr event = michael@0: new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(); michael@0: mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace widget michael@0: } // namespace mozilla michael@0: