Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / widget / windows / JumpListItem.cpp
blob941a479203c5a8003974d666412103823a554350
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "JumpListItem.h"
8 #include <shellapi.h>
9 #include <propvarutil.h>
10 #include <propkey.h>
12 #include "nsIFile.h"
13 #include "nsNetUtil.h"
14 #include "nsCRT.h"
15 #include "nsNetCID.h"
16 #include "nsCExternalHandlerService.h"
17 #include "nsCycleCollectionParticipant.h"
18 #include "mozilla/Preferences.h"
19 #include "JumpListBuilder.h"
20 #include "WinUtils.h"
22 namespace mozilla {
23 namespace widget {
25 // ISUPPORTS Impl's
26 NS_IMPL_ISUPPORTS(JumpListItem, nsIJumpListItem)
28 NS_INTERFACE_MAP_BEGIN(JumpListSeparator)
29 NS_INTERFACE_MAP_ENTRY(nsIJumpListSeparator)
30 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase)
31 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, JumpListItemBase)
32 NS_INTERFACE_MAP_END
33 NS_IMPL_ADDREF(JumpListSeparator)
34 NS_IMPL_RELEASE(JumpListSeparator)
36 NS_INTERFACE_MAP_BEGIN(JumpListLink)
37 NS_INTERFACE_MAP_ENTRY(nsIJumpListLink)
38 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase)
39 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, JumpListItemBase)
40 NS_INTERFACE_MAP_END
41 NS_IMPL_ADDREF(JumpListLink)
42 NS_IMPL_RELEASE(JumpListLink)
44 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JumpListShortcut)
45 NS_INTERFACE_MAP_ENTRY(nsIJumpListShortcut)
46 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIJumpListItem, JumpListItemBase)
47 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJumpListShortcut)
48 NS_INTERFACE_MAP_END
49 NS_IMPL_CYCLE_COLLECTING_ADDREF(JumpListShortcut)
50 NS_IMPL_CYCLE_COLLECTING_RELEASE(JumpListShortcut)
51 NS_IMPL_CYCLE_COLLECTION(JumpListShortcut, mHandlerApp)
53 NS_IMETHODIMP JumpListItemBase::GetType(int16_t* aType) {
54 NS_ENSURE_ARG_POINTER(aType);
56 *aType = mItemType;
58 return NS_OK;
61 NS_IMETHODIMP JumpListItemBase::Equals(nsIJumpListItem* aItem, bool* aResult) {
62 NS_ENSURE_ARG_POINTER(aItem);
64 *aResult = false;
66 int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
67 if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
69 // Make sure the types match.
70 if (Type() != theType) return NS_OK;
72 *aResult = true;
74 return NS_OK;
77 /* link impl. */
79 NS_IMETHODIMP JumpListLink::GetUri(nsIURI** aURI) {
80 NS_IF_ADDREF(*aURI = mURI);
82 return NS_OK;
85 NS_IMETHODIMP JumpListLink::SetUri(nsIURI* aURI) {
86 mURI = aURI;
88 return NS_OK;
91 NS_IMETHODIMP JumpListLink::SetUriTitle(const nsAString& aUriTitle) {
92 mUriTitle.Assign(aUriTitle);
94 return NS_OK;
97 NS_IMETHODIMP JumpListLink::GetUriTitle(nsAString& aUriTitle) {
98 aUriTitle.Assign(mUriTitle);
100 return NS_OK;
103 NS_IMETHODIMP JumpListLink::GetUriHash(nsACString& aUriHash) {
104 if (!mURI) return NS_ERROR_NOT_AVAILABLE;
106 return mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, aUriHash);
109 NS_IMETHODIMP JumpListLink::CompareHash(nsIURI* aUri, bool* aResult) {
110 nsresult rv;
112 if (!mURI) {
113 *aResult = !aUri;
114 return NS_OK;
117 NS_ENSURE_ARG_POINTER(aUri);
119 nsAutoCString hash1, hash2;
121 rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, mURI, hash1);
122 NS_ENSURE_SUCCESS(rv, rv);
123 rv = mozilla::widget::FaviconHelper::HashURI(mCryptoHash, aUri, hash2);
124 NS_ENSURE_SUCCESS(rv, rv);
126 *aResult = hash1.Equals(hash2);
128 return NS_OK;
131 NS_IMETHODIMP JumpListLink::Equals(nsIJumpListItem* aItem, bool* aResult) {
132 NS_ENSURE_ARG_POINTER(aItem);
134 nsresult rv;
136 *aResult = false;
138 int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
139 if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
141 // Make sure the types match.
142 if (Type() != theType) return NS_OK;
144 nsCOMPtr<nsIJumpListLink> link = do_QueryInterface(aItem, &rv);
145 if (NS_FAILED(rv)) return rv;
147 // Check the titles
148 nsAutoString title;
149 link->GetUriTitle(title);
150 if (!mUriTitle.Equals(title)) return NS_OK;
152 // Call the internal object's equals() method to check.
153 nsCOMPtr<nsIURI> theUri;
154 bool equals = false;
155 if (NS_SUCCEEDED(link->GetUri(getter_AddRefs(theUri)))) {
156 if (!theUri) {
157 if (!mURI) *aResult = true;
158 return NS_OK;
160 if (NS_SUCCEEDED(theUri->Equals(mURI, &equals)) && equals) {
161 *aResult = true;
165 return NS_OK;
168 /* shortcut impl. */
170 NS_IMETHODIMP JumpListShortcut::GetApp(nsILocalHandlerApp** aApp) {
171 NS_IF_ADDREF(*aApp = mHandlerApp);
173 return NS_OK;
176 NS_IMETHODIMP JumpListShortcut::SetApp(nsILocalHandlerApp* aApp) {
177 mHandlerApp = aApp;
178 return NS_OK;
181 NS_IMETHODIMP JumpListShortcut::GetIconIndex(int32_t* aIconIndex) {
182 NS_ENSURE_ARG_POINTER(aIconIndex);
184 *aIconIndex = mIconIndex;
185 return NS_OK;
188 NS_IMETHODIMP JumpListShortcut::SetIconIndex(int32_t aIconIndex) {
189 mIconIndex = aIconIndex;
190 return NS_OK;
193 NS_IMETHODIMP JumpListShortcut::GetFaviconPageUri(nsIURI** aFaviconPageURI) {
194 NS_IF_ADDREF(*aFaviconPageURI = mFaviconPageURI);
196 return NS_OK;
199 NS_IMETHODIMP JumpListShortcut::SetFaviconPageUri(nsIURI* aFaviconPageURI) {
200 mFaviconPageURI = aFaviconPageURI;
201 return NS_OK;
204 NS_IMETHODIMP JumpListShortcut::Equals(nsIJumpListItem* aItem, bool* aResult) {
205 NS_ENSURE_ARG_POINTER(aItem);
207 nsresult rv;
209 *aResult = false;
211 int16_t theType = nsIJumpListItem::JUMPLIST_ITEM_EMPTY;
212 if (NS_FAILED(aItem->GetType(&theType))) return NS_OK;
214 // Make sure the types match.
215 if (Type() != theType) return NS_OK;
217 nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(aItem, &rv);
218 if (NS_FAILED(rv)) return rv;
220 // Check the icon index
221 // int32_t idx;
222 // shortcut->GetIconIndex(&idx);
223 // if (mIconIndex != idx)
224 // return NS_OK;
225 // No need to check the icon page URI either
227 // Call the internal object's equals() method to check.
228 nsCOMPtr<nsILocalHandlerApp> theApp;
229 bool equals = false;
230 if (NS_SUCCEEDED(shortcut->GetApp(getter_AddRefs(theApp)))) {
231 if (!theApp) {
232 if (!mHandlerApp) *aResult = true;
233 return NS_OK;
235 if (NS_SUCCEEDED(theApp->Equals(mHandlerApp, &equals)) && equals) {
236 *aResult = true;
240 return NS_OK;
243 /* internal helpers */
245 // (static) Creates a ShellLink that encapsulate a separator.
246 nsresult JumpListSeparator::GetSeparator(RefPtr<IShellLinkW>& aShellLink) {
247 HRESULT hr;
248 IShellLinkW* psl;
250 // Create a IShellLink.
251 hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
252 IID_IShellLinkW, (LPVOID*)&psl);
253 if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
255 IPropertyStore* pPropStore = nullptr;
256 hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore);
257 if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
259 PROPVARIANT pv;
260 InitPropVariantFromBoolean(TRUE, &pv);
262 pPropStore->SetValue(PKEY_AppUserModel_IsDestListSeparator, pv);
263 pPropStore->Commit();
264 pPropStore->Release();
266 PropVariantClear(&pv);
268 aShellLink = dont_AddRef(psl);
270 return NS_OK;
273 // (static) Creates a ShellLink that encapsulate a shortcut to local apps.
274 nsresult JumpListShortcut::GetShellLink(nsCOMPtr<nsIJumpListItem>& item,
275 RefPtr<IShellLinkW>& aShellLink,
276 nsCOMPtr<nsIThread>& aIOThread) {
277 HRESULT hr;
278 IShellLinkW* psl;
279 nsresult rv;
281 // Shell links:
282 // http://msdn.microsoft.com/en-us/library/bb776891(VS.85).aspx
283 // http://msdn.microsoft.com/en-us/library/bb774950(VS.85).aspx
285 int16_t type;
286 if (NS_FAILED(item->GetType(&type))) return NS_ERROR_INVALID_ARG;
288 if (type != nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT)
289 return NS_ERROR_INVALID_ARG;
291 nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item, &rv);
292 NS_ENSURE_SUCCESS(rv, rv);
294 nsCOMPtr<nsILocalHandlerApp> handlerApp;
295 rv = shortcut->GetApp(getter_AddRefs(handlerApp));
296 NS_ENSURE_SUCCESS(rv, rv);
298 // Create a IShellLink
299 hr = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
300 IID_IShellLinkW, (LPVOID*)&psl);
301 if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
303 // Retrieve the app path, title, description and optional command line args.
304 nsAutoString appPath, appTitle, appDescription, appArgs;
305 int32_t appIconIndex = 0;
307 // Path
308 nsCOMPtr<nsIFile> executable;
309 handlerApp->GetExecutable(getter_AddRefs(executable));
311 rv = executable->GetPath(appPath);
312 NS_ENSURE_SUCCESS(rv, rv);
314 // Command line parameters
315 uint32_t count = 0;
316 handlerApp->GetParameterCount(&count);
317 for (uint32_t idx = 0; idx < count; idx++) {
318 if (idx > 0) appArgs.Append(' ');
319 nsAutoString param;
320 rv = handlerApp->GetParameter(idx, param);
321 if (NS_FAILED(rv)) return rv;
322 appArgs.Append(param);
325 handlerApp->GetName(appTitle);
326 handlerApp->GetDetailedDescription(appDescription);
328 bool useUriIcon = false; // if we want to use the URI icon
329 bool usedUriIcon = false; // if we did use the URI icon
330 shortcut->GetIconIndex(&appIconIndex);
332 nsCOMPtr<nsIURI> iconUri;
333 rv = shortcut->GetFaviconPageUri(getter_AddRefs(iconUri));
334 if (NS_SUCCEEDED(rv) && iconUri) {
335 useUriIcon = true;
338 // Store the title of the app
339 if (appTitle.Length() > 0) {
340 IPropertyStore* pPropStore = nullptr;
341 hr = psl->QueryInterface(IID_IPropertyStore, (LPVOID*)&pPropStore);
342 if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
344 PROPVARIANT pv;
345 InitPropVariantFromString(appTitle.get(), &pv);
347 pPropStore->SetValue(PKEY_Title, pv);
348 pPropStore->Commit();
349 pPropStore->Release();
351 PropVariantClear(&pv);
354 // Store the rest of the params
355 psl->SetPath(appPath.get());
356 psl->SetDescription(appDescription.get());
357 psl->SetArguments(appArgs.get());
359 if (useUriIcon) {
360 nsString icoFilePath;
361 rv = mozilla::widget::FaviconHelper::ObtainCachedIconFile(
362 iconUri, icoFilePath, aIOThread, false);
363 if (NS_SUCCEEDED(rv)) {
364 // Always use the first icon in the ICO file
365 // our encoded icon only has 1 resource
366 psl->SetIconLocation(icoFilePath.get(), 0);
367 usedUriIcon = true;
371 // We didn't use an ICO via URI so fall back to the app icon
372 if (!usedUriIcon) {
373 psl->SetIconLocation(appPath.get(), appIconIndex);
376 aShellLink = dont_AddRef(psl);
378 return NS_OK;
381 // If successful fills in the aSame parameter
382 // aSame will be true if the path is in our icon cache
383 static nsresult IsPathInOurIconCache(nsCOMPtr<nsIJumpListShortcut>& aShortcut,
384 wchar_t* aPath, bool* aSame) {
385 NS_ENSURE_ARG_POINTER(aPath);
386 NS_ENSURE_ARG_POINTER(aSame);
388 *aSame = false;
390 // Construct the path of our jump list cache
391 nsCOMPtr<nsIFile> jumpListCache;
392 nsresult rv =
393 NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCache));
394 NS_ENSURE_SUCCESS(rv, rv);
395 rv = jumpListCache->AppendNative(
396 nsDependentCString(FaviconHelper::kJumpListCacheDir));
397 NS_ENSURE_SUCCESS(rv, rv);
398 nsAutoString jumpListCachePath;
399 rv = jumpListCache->GetPath(jumpListCachePath);
400 NS_ENSURE_SUCCESS(rv, rv);
402 // Construct the parent path of the passed in path
403 nsCOMPtr<nsIFile> passedInFile =
404 do_CreateInstance("@mozilla.org/file/local;1");
405 NS_ENSURE_TRUE(passedInFile, NS_ERROR_FAILURE);
406 nsAutoString passedInPath(aPath);
407 rv = passedInFile->InitWithPath(passedInPath);
408 nsCOMPtr<nsIFile> passedInParentFile;
409 passedInFile->GetParent(getter_AddRefs(passedInParentFile));
410 nsAutoString passedInParentPath;
411 rv = jumpListCache->GetPath(passedInParentPath);
412 NS_ENSURE_SUCCESS(rv, rv);
414 *aSame = jumpListCachePath.Equals(passedInParentPath);
415 return NS_OK;
418 // (static) For a given IShellLink, create and return a populated
419 // nsIJumpListShortcut.
420 nsresult JumpListShortcut::GetJumpListShortcut(
421 IShellLinkW* pLink, nsCOMPtr<nsIJumpListShortcut>& aShortcut) {
422 NS_ENSURE_ARG_POINTER(pLink);
424 nsresult rv;
425 HRESULT hres;
427 nsCOMPtr<nsILocalHandlerApp> handlerApp =
428 do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
429 NS_ENSURE_SUCCESS(rv, rv);
431 wchar_t buf[MAX_PATH];
433 // Path
434 hres = pLink->GetPath(buf, MAX_PATH, nullptr, SLGP_UNCPRIORITY);
435 if (FAILED(hres)) return NS_ERROR_INVALID_ARG;
437 nsCOMPtr<nsIFile> file;
438 nsDependentString filepath(buf);
439 rv = NS_NewLocalFile(filepath, false, getter_AddRefs(file));
440 NS_ENSURE_SUCCESS(rv, rv);
442 rv = handlerApp->SetExecutable(file);
443 NS_ENSURE_SUCCESS(rv, rv);
445 // Parameters
446 hres = pLink->GetArguments(buf, MAX_PATH);
447 if (SUCCEEDED(hres)) {
448 LPWSTR* arglist;
449 int32_t numArgs;
450 int32_t idx;
452 arglist = ::CommandLineToArgvW(buf, &numArgs);
453 if (arglist) {
454 for (idx = 0; idx < numArgs; idx++) {
455 // szArglist[i] is null terminated
456 nsDependentString arg(arglist[idx]);
457 handlerApp->AppendParameter(arg);
459 ::LocalFree(arglist);
463 rv = aShortcut->SetApp(handlerApp);
464 NS_ENSURE_SUCCESS(rv, rv);
466 // Icon index or file location
467 int iconIdx = 0;
468 hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
469 if (SUCCEEDED(hres)) {
470 // XXX How do we handle converting local files to images here? Do we need
471 // to?
472 aShortcut->SetIconIndex(iconIdx);
474 // Obtain the local profile directory and construct the output icon file
475 // path We only set the Icon Uri if we're sure it was from our icon cache.
476 bool isInOurCache;
477 if (NS_SUCCEEDED(IsPathInOurIconCache(aShortcut, buf, &isInOurCache)) &&
478 isInOurCache) {
479 nsCOMPtr<nsIURI> iconUri;
480 nsAutoString path(buf);
481 rv = NS_NewURI(getter_AddRefs(iconUri), path);
482 if (NS_SUCCEEDED(rv)) {
483 aShortcut->SetFaviconPageUri(iconUri);
488 // Do we need the title and description? Probably not since handler app
489 // doesn't compare these in equals.
491 return NS_OK;
494 // (static) ShellItems are used to encapsulate links to things. We currently
495 // only support URI links, but more support could be added, such as local file
496 // and directory links.
497 nsresult JumpListLink::GetShellItem(nsCOMPtr<nsIJumpListItem>& item,
498 RefPtr<IShellItem2>& aShellItem) {
499 IShellItem2* psi = nullptr;
500 nsresult rv;
502 int16_t type;
503 if (NS_FAILED(item->GetType(&type))) return NS_ERROR_INVALID_ARG;
505 if (type != nsIJumpListItem::JUMPLIST_ITEM_LINK) return NS_ERROR_INVALID_ARG;
507 nsCOMPtr<nsIJumpListLink> link = do_QueryInterface(item, &rv);
508 NS_ENSURE_SUCCESS(rv, rv);
510 nsCOMPtr<nsIURI> uri;
511 rv = link->GetUri(getter_AddRefs(uri));
512 NS_ENSURE_SUCCESS(rv, rv);
514 nsAutoCString spec;
515 rv = uri->GetSpec(spec);
516 NS_ENSURE_SUCCESS(rv, rv);
518 // Create the IShellItem
519 if (FAILED(SHCreateItemFromParsingName(NS_ConvertASCIItoUTF16(spec).get(),
520 nullptr, IID_PPV_ARGS(&psi)))) {
521 return NS_ERROR_INVALID_ARG;
524 // Set the title
525 nsAutoString linkTitle;
526 link->GetUriTitle(linkTitle);
528 IPropertyStore* pPropStore = nullptr;
529 HRESULT hres = psi->GetPropertyStore(GPS_DEFAULT, IID_IPropertyStore,
530 (void**)&pPropStore);
531 if (FAILED(hres)) return NS_ERROR_UNEXPECTED;
533 PROPVARIANT pv;
534 InitPropVariantFromString(linkTitle.get(), &pv);
536 // May fail due to shell item access permissions.
537 pPropStore->SetValue(PKEY_ItemName, pv);
538 pPropStore->Commit();
539 pPropStore->Release();
541 PropVariantClear(&pv);
543 aShellItem = dont_AddRef(psi);
545 return NS_OK;
548 // (static) For a given IShellItem, create and return a populated
549 // nsIJumpListLink.
550 nsresult JumpListLink::GetJumpListLink(IShellItem* pItem,
551 nsCOMPtr<nsIJumpListLink>& aLink) {
552 NS_ENSURE_ARG_POINTER(pItem);
554 // We assume for now these are URI links, but through properties we could
555 // query and create other types.
556 nsresult rv;
557 LPWSTR lpstrName = nullptr;
559 if (SUCCEEDED(pItem->GetDisplayName(SIGDN_URL, &lpstrName))) {
560 nsCOMPtr<nsIURI> uri;
561 nsAutoString spec(lpstrName);
563 rv = NS_NewURI(getter_AddRefs(uri), NS_ConvertUTF16toUTF8(spec));
564 if (NS_FAILED(rv)) return NS_ERROR_INVALID_ARG;
566 aLink->SetUri(uri);
568 ::CoTaskMemFree(lpstrName);
571 return NS_OK;
574 } // namespace widget
575 } // namespace mozilla