1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/windows/JumpListBuilder.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,521 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "JumpListBuilder.h" 1.10 + 1.11 +#include "nsError.h" 1.12 +#include "nsCOMPtr.h" 1.13 +#include "nsServiceManagerUtils.h" 1.14 +#include "nsAutoPtr.h" 1.15 +#include "nsString.h" 1.16 +#include "nsArrayUtils.h" 1.17 +#include "nsIMutableArray.h" 1.18 +#include "nsWidgetsCID.h" 1.19 +#include "WinTaskbar.h" 1.20 +#include "nsDirectoryServiceUtils.h" 1.21 +#include "nsISimpleEnumerator.h" 1.22 +#include "mozilla/Preferences.h" 1.23 +#include "nsStringStream.h" 1.24 +#include "nsNetUtil.h" 1.25 +#include "nsThreadUtils.h" 1.26 +#include "mozilla/LazyIdleThread.h" 1.27 + 1.28 +#include "WinUtils.h" 1.29 + 1.30 +// The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes. 1.31 +#define DEFAULT_THREAD_TIMEOUT_MS 30000 1.32 + 1.33 +namespace mozilla { 1.34 +namespace widget { 1.35 + 1.36 +static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID); 1.37 +static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID); 1.38 +static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID); 1.39 + 1.40 +// defined in WinTaskbar.cpp 1.41 +extern const wchar_t *gMozillaJumpListIDGeneric; 1.42 + 1.43 +bool JumpListBuilder::sBuildingList = false; 1.44 +const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled"; 1.45 + 1.46 +NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver) 1.47 + 1.48 +JumpListBuilder::JumpListBuilder() : 1.49 + mMaxItems(0), 1.50 + mHasCommit(false) 1.51 +{ 1.52 + ::CoInitialize(nullptr); 1.53 + 1.54 + CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, 1.55 + IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr)); 1.56 + 1.57 + // Make a lazy thread for any IO 1.58 + mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, 1.59 + NS_LITERAL_CSTRING("Jump List"), 1.60 + LazyIdleThread::ManualShutdown); 1.61 + Preferences::AddStrongObserver(this, kPrefTaskbarEnabled); 1.62 +} 1.63 + 1.64 +JumpListBuilder::~JumpListBuilder() 1.65 +{ 1.66 + mIOThread->Shutdown(); 1.67 + Preferences::RemoveObserver(this, kPrefTaskbarEnabled); 1.68 + mJumpListMgr = nullptr; 1.69 + ::CoUninitialize(); 1.70 +} 1.71 + 1.72 +/* readonly attribute short available; */ 1.73 +NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable) 1.74 +{ 1.75 + *aAvailable = false; 1.76 + 1.77 + if (mJumpListMgr) 1.78 + *aAvailable = true; 1.79 + 1.80 + return NS_OK; 1.81 +} 1.82 + 1.83 +/* readonly attribute boolean isListCommitted; */ 1.84 +NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit) 1.85 +{ 1.86 + *aCommit = mHasCommit; 1.87 + 1.88 + return NS_OK; 1.89 +} 1.90 + 1.91 +/* readonly attribute short maxItems; */ 1.92 +NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems) 1.93 +{ 1.94 + if (!mJumpListMgr) 1.95 + return NS_ERROR_NOT_AVAILABLE; 1.96 + 1.97 + *aMaxItems = 0; 1.98 + 1.99 + if (sBuildingList) { 1.100 + *aMaxItems = mMaxItems; 1.101 + return NS_OK; 1.102 + } 1.103 + 1.104 + IObjectArray *objArray; 1.105 + if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { 1.106 + *aMaxItems = mMaxItems; 1.107 + 1.108 + if (objArray) 1.109 + objArray->Release(); 1.110 + 1.111 + mJumpListMgr->AbortList(); 1.112 + } 1.113 + 1.114 + return NS_OK; 1.115 +} 1.116 + 1.117 +/* boolean initListBuild(in nsIMutableArray removedItems); */ 1.118 +NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval) 1.119 +{ 1.120 + NS_ENSURE_ARG_POINTER(removedItems); 1.121 + 1.122 + *_retval = false; 1.123 + 1.124 + if (!mJumpListMgr) 1.125 + return NS_ERROR_NOT_AVAILABLE; 1.126 + 1.127 + if(sBuildingList) 1.128 + AbortListBuild(); 1.129 + 1.130 + IObjectArray *objArray; 1.131 + 1.132 + if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { 1.133 + if (objArray) { 1.134 + TransferIObjectArrayToIMutableArray(objArray, removedItems); 1.135 + objArray->Release(); 1.136 + } 1.137 + 1.138 + RemoveIconCacheForItems(removedItems); 1.139 + 1.140 + sBuildingList = true; 1.141 + *_retval = true; 1.142 + return NS_OK; 1.143 + } 1.144 + 1.145 + return NS_OK; 1.146 +} 1.147 + 1.148 +// Ensures that we don't have old ICO files that aren't in our jump lists 1.149 +// anymore left over in the cache. 1.150 +nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items) 1.151 +{ 1.152 + NS_ENSURE_ARG_POINTER(items); 1.153 + 1.154 + nsresult rv; 1.155 + uint32_t length; 1.156 + items->GetLength(&length); 1.157 + for (uint32_t i = 0; i < length; ++i) { 1.158 + 1.159 + //Obtain an IJumpListItem and get the type 1.160 + nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); 1.161 + if (!item) { 1.162 + continue; 1.163 + } 1.164 + int16_t type; 1.165 + if (NS_FAILED(item->GetType(&type))) { 1.166 + continue; 1.167 + } 1.168 + 1.169 + // If the item is a shortcut, remove its associated icon if any 1.170 + if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) { 1.171 + nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item); 1.172 + if (shortcut) { 1.173 + nsCOMPtr<nsIURI> uri; 1.174 + rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri)); 1.175 + if (NS_SUCCEEDED(rv) && uri) { 1.176 + 1.177 + // The local file path is stored inside the nsIURI 1.178 + // Get the nsIURI spec which stores the local path for the icon to remove 1.179 + nsAutoCString spec; 1.180 + nsresult rv = uri->GetSpec(spec); 1.181 + NS_ENSURE_SUCCESS(rv, rv); 1.182 + 1.183 + nsCOMPtr<nsIRunnable> event 1.184 + = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec)); 1.185 + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.186 + 1.187 + // The shortcut was generated from an IShellLinkW so IShellLinkW can 1.188 + // only tell us what the original icon is and not the URI. 1.189 + // So this field was used only temporarily as the actual icon file 1.190 + // path. It should be cleared. 1.191 + shortcut->SetFaviconPageUri(nullptr); 1.192 + } 1.193 + } 1.194 + } 1.195 + 1.196 + } // end for 1.197 + 1.198 + return NS_OK; 1.199 +} 1.200 + 1.201 +// Ensures that we have no old ICO files left in the jump list cache 1.202 +nsresult JumpListBuilder::RemoveIconCacheForAllItems() 1.203 +{ 1.204 + // Construct the path of our jump list cache 1.205 + nsCOMPtr<nsIFile> jumpListCacheDir; 1.206 + nsresult rv = NS_GetSpecialDirectory("ProfLDS", 1.207 + getter_AddRefs(jumpListCacheDir)); 1.208 + NS_ENSURE_SUCCESS(rv, rv); 1.209 + rv = jumpListCacheDir->AppendNative(nsDependentCString( 1.210 + mozilla::widget::FaviconHelper::kJumpListCacheDir)); 1.211 + NS_ENSURE_SUCCESS(rv, rv); 1.212 + nsCOMPtr<nsISimpleEnumerator> entries; 1.213 + rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); 1.214 + NS_ENSURE_SUCCESS(rv, rv); 1.215 + 1.216 + // Loop through each directory entry and remove all ICO files found 1.217 + do { 1.218 + bool hasMore = false; 1.219 + if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore) 1.220 + break; 1.221 + 1.222 + nsCOMPtr<nsISupports> supp; 1.223 + if (NS_FAILED(entries->GetNext(getter_AddRefs(supp)))) 1.224 + break; 1.225 + 1.226 + nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp)); 1.227 + nsAutoString path; 1.228 + if (NS_FAILED(currFile->GetPath(path))) 1.229 + continue; 1.230 + 1.231 + int32_t len = path.Length(); 1.232 + if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) { 1.233 + // Check if the cached ICO file exists 1.234 + bool exists; 1.235 + if (NS_FAILED(currFile->Exists(&exists)) || !exists) 1.236 + continue; 1.237 + 1.238 + // We found an ICO file that exists, so we should remove it 1.239 + currFile->Remove(false); 1.240 + } 1.241 + } while(true); 1.242 + 1.243 + return NS_OK; 1.244 +} 1.245 + 1.246 +/* boolean addListToBuild(in short aCatType, [optional] in nsIArray items, [optional] in AString catName); */ 1.247 +NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval) 1.248 +{ 1.249 + nsresult rv; 1.250 + 1.251 + *_retval = false; 1.252 + 1.253 + if (!mJumpListMgr) 1.254 + return NS_ERROR_NOT_AVAILABLE; 1.255 + 1.256 + switch(aCatType) { 1.257 + case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: 1.258 + { 1.259 + NS_ENSURE_ARG_POINTER(items); 1.260 + 1.261 + HRESULT hr; 1.262 + nsRefPtr<IObjectCollection> collection; 1.263 + hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, 1.264 + CLSCTX_INPROC_SERVER, IID_IObjectCollection, 1.265 + getter_AddRefs(collection)); 1.266 + if (FAILED(hr)) 1.267 + return NS_ERROR_UNEXPECTED; 1.268 + 1.269 + // Build the list 1.270 + uint32_t length; 1.271 + items->GetLength(&length); 1.272 + for (uint32_t i = 0; i < length; ++i) { 1.273 + nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); 1.274 + if (!item) 1.275 + continue; 1.276 + // Check for separators 1.277 + if (IsSeparator(item)) { 1.278 + nsRefPtr<IShellLinkW> link; 1.279 + rv = JumpListSeparator::GetSeparator(link); 1.280 + if (NS_FAILED(rv)) 1.281 + return rv; 1.282 + collection->AddObject(link); 1.283 + continue; 1.284 + } 1.285 + // These should all be ShellLinks 1.286 + nsRefPtr<IShellLinkW> link; 1.287 + rv = JumpListShortcut::GetShellLink(item, link, mIOThread); 1.288 + if (NS_FAILED(rv)) 1.289 + return rv; 1.290 + collection->AddObject(link); 1.291 + } 1.292 + 1.293 + // We need IObjectArray to submit 1.294 + nsRefPtr<IObjectArray> pArray; 1.295 + hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray)); 1.296 + if (FAILED(hr)) 1.297 + return NS_ERROR_UNEXPECTED; 1.298 + 1.299 + // Add the tasks 1.300 + hr = mJumpListMgr->AddUserTasks(pArray); 1.301 + if (SUCCEEDED(hr)) 1.302 + *_retval = true; 1.303 + return NS_OK; 1.304 + } 1.305 + break; 1.306 + case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: 1.307 + { 1.308 + if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT))) 1.309 + *_retval = true; 1.310 + return NS_OK; 1.311 + } 1.312 + break; 1.313 + case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: 1.314 + { 1.315 + if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT))) 1.316 + *_retval = true; 1.317 + return NS_OK; 1.318 + } 1.319 + break; 1.320 + case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: 1.321 + { 1.322 + NS_ENSURE_ARG_POINTER(items); 1.323 + 1.324 + if (catName.IsEmpty()) 1.325 + return NS_ERROR_INVALID_ARG; 1.326 + 1.327 + HRESULT hr; 1.328 + nsRefPtr<IObjectCollection> collection; 1.329 + hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, 1.330 + CLSCTX_INPROC_SERVER, IID_IObjectCollection, 1.331 + getter_AddRefs(collection)); 1.332 + if (FAILED(hr)) 1.333 + return NS_ERROR_UNEXPECTED; 1.334 + 1.335 + uint32_t length; 1.336 + items->GetLength(&length); 1.337 + for (uint32_t i = 0; i < length; ++i) { 1.338 + nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); 1.339 + if (!item) 1.340 + continue; 1.341 + int16_t type; 1.342 + if (NS_FAILED(item->GetType(&type))) 1.343 + continue; 1.344 + switch(type) { 1.345 + case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: 1.346 + { 1.347 + nsRefPtr<IShellLinkW> shellItem; 1.348 + rv = JumpListSeparator::GetSeparator(shellItem); 1.349 + if (NS_FAILED(rv)) 1.350 + return rv; 1.351 + collection->AddObject(shellItem); 1.352 + } 1.353 + break; 1.354 + case nsIJumpListItem::JUMPLIST_ITEM_LINK: 1.355 + { 1.356 + nsRefPtr<IShellItem2> shellItem; 1.357 + rv = JumpListLink::GetShellItem(item, shellItem); 1.358 + if (NS_FAILED(rv)) 1.359 + return rv; 1.360 + collection->AddObject(shellItem); 1.361 + } 1.362 + break; 1.363 + case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: 1.364 + { 1.365 + nsRefPtr<IShellLinkW> shellItem; 1.366 + rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread); 1.367 + if (NS_FAILED(rv)) 1.368 + return rv; 1.369 + collection->AddObject(shellItem); 1.370 + } 1.371 + break; 1.372 + } 1.373 + } 1.374 + 1.375 + // We need IObjectArray to submit 1.376 + nsRefPtr<IObjectArray> pArray; 1.377 + hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray); 1.378 + if (FAILED(hr)) 1.379 + return NS_ERROR_UNEXPECTED; 1.380 + 1.381 + // Add the tasks 1.382 + hr = mJumpListMgr->AppendCategory(reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray); 1.383 + if (SUCCEEDED(hr)) 1.384 + *_retval = true; 1.385 + return NS_OK; 1.386 + } 1.387 + break; 1.388 + } 1.389 + return NS_OK; 1.390 +} 1.391 + 1.392 +/* void abortListBuild(); */ 1.393 +NS_IMETHODIMP JumpListBuilder::AbortListBuild() 1.394 +{ 1.395 + if (!mJumpListMgr) 1.396 + return NS_ERROR_NOT_AVAILABLE; 1.397 + 1.398 + mJumpListMgr->AbortList(); 1.399 + sBuildingList = false; 1.400 + 1.401 + return NS_OK; 1.402 +} 1.403 + 1.404 +/* boolean commitListBuild(); */ 1.405 +NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval) 1.406 +{ 1.407 + *_retval = false; 1.408 + 1.409 + if (!mJumpListMgr) 1.410 + return NS_ERROR_NOT_AVAILABLE; 1.411 + 1.412 + HRESULT hr = mJumpListMgr->CommitList(); 1.413 + sBuildingList = false; 1.414 + 1.415 + // XXX We might want some specific error data here. 1.416 + if (SUCCEEDED(hr)) { 1.417 + *_retval = true; 1.418 + mHasCommit = true; 1.419 + } 1.420 + 1.421 + return NS_OK; 1.422 +} 1.423 + 1.424 +/* boolean deleteActiveList(); */ 1.425 +NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval) 1.426 +{ 1.427 + *_retval = false; 1.428 + 1.429 + if (!mJumpListMgr) 1.430 + return NS_ERROR_NOT_AVAILABLE; 1.431 + 1.432 + if(sBuildingList) 1.433 + AbortListBuild(); 1.434 + 1.435 + nsAutoString uid; 1.436 + if (!WinTaskbar::GetAppUserModelID(uid)) 1.437 + return NS_OK; 1.438 + 1.439 + if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get()))) 1.440 + *_retval = true; 1.441 + 1.442 + return NS_OK; 1.443 +} 1.444 + 1.445 +/* internal */ 1.446 + 1.447 +bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item) 1.448 +{ 1.449 + int16_t type; 1.450 + item->GetType(&type); 1.451 + if (NS_FAILED(item->GetType(&type))) 1.452 + return false; 1.453 + 1.454 + if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) 1.455 + return true; 1.456 + return false; 1.457 +} 1.458 + 1.459 +// TransferIObjectArrayToIMutableArray - used in converting removed items 1.460 +// to our objects. 1.461 +nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems) 1.462 +{ 1.463 + NS_ENSURE_ARG_POINTER(objArray); 1.464 + NS_ENSURE_ARG_POINTER(removedItems); 1.465 + 1.466 + nsresult rv; 1.467 + 1.468 + uint32_t count = 0; 1.469 + objArray->GetCount(&count); 1.470 + 1.471 + nsCOMPtr<nsIJumpListItem> item; 1.472 + 1.473 + for (uint32_t idx = 0; idx < count; idx++) { 1.474 + IShellLinkW * pLink = nullptr; 1.475 + IShellItem * pItem = nullptr; 1.476 + 1.477 + if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) { 1.478 + nsCOMPtr<nsIJumpListShortcut> shortcut = 1.479 + do_CreateInstance(kJumpListShortcutCID, &rv); 1.480 + if (NS_FAILED(rv)) 1.481 + return NS_ERROR_UNEXPECTED; 1.482 + rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut); 1.483 + item = do_QueryInterface(shortcut); 1.484 + } 1.485 + else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) { 1.486 + nsCOMPtr<nsIJumpListLink> link = 1.487 + do_CreateInstance(kJumpListLinkCID, &rv); 1.488 + if (NS_FAILED(rv)) 1.489 + return NS_ERROR_UNEXPECTED; 1.490 + rv = JumpListLink::GetJumpListLink(pItem, link); 1.491 + item = do_QueryInterface(link); 1.492 + } 1.493 + 1.494 + if (pLink) 1.495 + pLink->Release(); 1.496 + if (pItem) 1.497 + pItem->Release(); 1.498 + 1.499 + if (NS_SUCCEEDED(rv)) { 1.500 + removedItems->AppendElement(item, false); 1.501 + } 1.502 + } 1.503 + return NS_OK; 1.504 +} 1.505 + 1.506 +NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject, 1.507 + const char* aTopic, 1.508 + const char16_t* aData) 1.509 +{ 1.510 + if (nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) { 1.511 + bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true); 1.512 + if (!enabled) { 1.513 + 1.514 + nsCOMPtr<nsIRunnable> event = 1.515 + new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(); 1.516 + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); 1.517 + } 1.518 + } 1.519 + return NS_OK; 1.520 +} 1.521 + 1.522 +} // namespace widget 1.523 +} // namespace mozilla 1.524 +