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/. */
5 
6 #include "JumpListBuilder.h"
7 
8 #include "nsError.h"
9 #include "nsCOMPtr.h"
10 #include "nsServiceManagerUtils.h"
11 #include "nsString.h"
12 #include "nsArrayUtils.h"
13 #include "nsWidgetsCID.h"
14 #include "WinTaskbar.h"
15 #include "nsDirectoryServiceUtils.h"
16 #include "mozilla/Preferences.h"
17 #include "nsStringStream.h"
18 #include "nsThreadUtils.h"
19 #include "mozilla/LazyIdleThread.h"
20 #include "nsIObserverService.h"
21 #include "mozilla/ScopeExit.h"
22 #include "mozilla/Unused.h"
23 #include "mozilla/dom/Promise.h"
24 #include "mozilla/mscom/ApartmentRegion.h"
25 #include "mozilla/mscom/EnsureMTA.h"
26 
27 #include <shellapi.h>
28 #include "WinUtils.h"
29 
30 using mozilla::dom::Promise;
31 
32 // The amount of time, in milliseconds, that our IO thread will stay alive after
33 // the last event it processes.
34 #define DEFAULT_THREAD_TIMEOUT_MS 30000
35 
36 namespace mozilla {
37 namespace widget {
38 
39 // defined in WinTaskbar.cpp
40 extern const wchar_t* gMozillaJumpListIDGeneric;
41 
42 Atomic<bool> JumpListBuilder::sBuildingList(false);
43 const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled";
44 
45 NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver)
46 #define TOPIC_PROFILE_BEFORE_CHANGE "profile-before-change"
47 #define TOPIC_CLEAR_PRIVATE_DATA "clear-private-data"
48 
49 namespace detail {
50 
51 class DoneCommitListBuildCallback final : public nsIRunnable {
52   NS_DECL_THREADSAFE_ISUPPORTS
53 
54  public:
DoneCommitListBuildCallback(nsIJumpListCommittedCallback * aCallback,JumpListBuilder * aBuilder)55   DoneCommitListBuildCallback(nsIJumpListCommittedCallback* aCallback,
56                               JumpListBuilder* aBuilder)
57       : mCallback(aCallback), mBuilder(aBuilder), mResult(false) {}
58 
Run()59   NS_IMETHOD Run() override {
60     MOZ_ASSERT(NS_IsMainThread());
61     if (mCallback) {
62       Unused << mCallback->Done(mResult);
63     }
64     // Ensure we are releasing on the main thread.
65     Destroy();
66     return NS_OK;
67   }
68 
SetResult(bool aResult)69   void SetResult(bool aResult) { mResult = aResult; }
70 
71  private:
~DoneCommitListBuildCallback()72   ~DoneCommitListBuildCallback() {
73     // Destructor does not always call on the main thread.
74     MOZ_ASSERT(!mCallback);
75     MOZ_ASSERT(!mBuilder);
76   }
77 
Destroy()78   void Destroy() {
79     MOZ_ASSERT(NS_IsMainThread());
80     mCallback = nullptr;
81     mBuilder = nullptr;
82   }
83 
84   // These two references MUST be released on the main thread.
85   RefPtr<nsIJumpListCommittedCallback> mCallback;
86   RefPtr<JumpListBuilder> mBuilder;
87   bool mResult;
88 };
89 
90 NS_IMPL_ISUPPORTS(DoneCommitListBuildCallback, nsIRunnable);
91 
92 }  // namespace detail
93 
JumpListBuilder()94 JumpListBuilder::JumpListBuilder()
95     : mMaxItems(0), mHasCommit(false), mMonitor("JumpListBuilderMonitor") {
96   MOZ_ASSERT(NS_IsMainThread());
97 
98   // Instantiate mJumpListMgr in the multithreaded apartment so that proxied
99   // calls on that object do not need to interact with the main thread's message
100   // pump.
101   mscom::EnsureMTA([&]() {
102     RefPtr<ICustomDestinationList> jumpListMgr;
103     HRESULT hr = ::CoCreateInstance(
104         CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER,
105         IID_ICustomDestinationList, getter_AddRefs(jumpListMgr));
106     if (FAILED(hr)) {
107       return;
108     }
109 
110     // Since we are accessing mJumpListMgr across different threads
111     // (ie, different apartments), mJumpListMgr must be an agile reference.
112     mJumpListMgr = jumpListMgr;
113   });
114 
115   if (!mJumpListMgr) {
116     return;
117   }
118 
119   // Make a lazy thread for any IO
120   mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, "Jump List"_ns,
121                                  LazyIdleThread::ManualShutdown);
122   Preferences::AddStrongObserver(this, kPrefTaskbarEnabled);
123 
124   nsCOMPtr<nsIObserverService> observerService =
125       do_GetService("@mozilla.org/observer-service;1");
126   if (observerService) {
127     observerService->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, false);
128     observerService->AddObserver(this, TOPIC_CLEAR_PRIVATE_DATA, false);
129   }
130 
131   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
132   if (!jumpListMgr) {
133     return;
134   }
135 }
136 
~JumpListBuilder()137 JumpListBuilder::~JumpListBuilder() {
138   Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
139 }
140 
SetAppUserModelID(const nsAString & aAppUserModelId)141 NS_IMETHODIMP JumpListBuilder::SetAppUserModelID(
142     const nsAString& aAppUserModelId) {
143   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
144 
145   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
146   if (!jumpListMgr) {
147     return NS_ERROR_NOT_AVAILABLE;
148   }
149 
150   mAppUserModelId.Assign(aAppUserModelId);
151   // MSIX packages explicitly do not support setting the appid from within
152   // the app, as it is set in the package manifest instead.
153   if (!mozilla::widget::WinUtils::HasPackageIdentity()) {
154     jumpListMgr->SetAppID(mAppUserModelId.get());
155   }
156 
157   return NS_OK;
158 }
159 
GetAvailable(int16_t * aAvailable)160 NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t* aAvailable) {
161   *aAvailable = false;
162 
163   ReentrantMonitorAutoEnter lock(mMonitor);
164   if (mJumpListMgr) *aAvailable = true;
165 
166   return NS_OK;
167 }
168 
GetIsListCommitted(bool * aCommit)169 NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool* aCommit) {
170   *aCommit = mHasCommit;
171 
172   return NS_OK;
173 }
174 
GetMaxListItems(int16_t * aMaxItems)175 NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t* aMaxItems) {
176   ReentrantMonitorAutoEnter lock(mMonitor);
177   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
178 
179   *aMaxItems = 0;
180 
181   if (sBuildingList) {
182     *aMaxItems = mMaxItems;
183     return NS_OK;
184   }
185 
186   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
187   if (!jumpListMgr) {
188     return NS_ERROR_UNEXPECTED;
189   }
190 
191   IObjectArray* objArray;
192   if (SUCCEEDED(jumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
193     *aMaxItems = mMaxItems;
194 
195     if (objArray) objArray->Release();
196 
197     jumpListMgr->AbortList();
198   }
199 
200   return NS_OK;
201 }
202 
InitListBuild(JSContext * aCx,Promise ** aPromise)203 NS_IMETHODIMP JumpListBuilder::InitListBuild(JSContext* aCx,
204                                              Promise** aPromise) {
205   ReentrantMonitorAutoEnter lock(mMonitor);
206   if (!mJumpListMgr) {
207     return NS_ERROR_NOT_AVAILABLE;
208   }
209 
210   nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
211   if (NS_WARN_IF(!globalObject)) {
212     return NS_ERROR_FAILURE;
213   }
214 
215   ErrorResult result;
216   RefPtr<Promise> promise = Promise::Create(globalObject, result);
217   if (NS_WARN_IF(result.Failed())) {
218     return result.StealNSResult();
219   }
220 
221   nsCOMPtr<nsIRunnable> runnable =
222       NewRunnableMethod<StoreCopyPassByRRef<RefPtr<Promise>>>(
223           "InitListBuild", this, &JumpListBuilder::DoInitListBuild, promise);
224   nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
225   if (NS_WARN_IF(NS_FAILED(rv))) {
226     return rv;
227   }
228 
229   promise.forget(aPromise);
230   return NS_OK;
231 }
232 
DoInitListBuild(RefPtr<Promise> && aPromise)233 void JumpListBuilder::DoInitListBuild(RefPtr<Promise>&& aPromise) {
234   // Since we're invoking COM interfaces to talk to the shell on a background
235   // thread, we need to be running inside a multithreaded apartment.
236   mscom::MTARegion mta;
237   MOZ_ASSERT(mta.IsValid());
238 
239   ReentrantMonitorAutoEnter lock(mMonitor);
240   MOZ_ASSERT(mJumpListMgr);
241 
242   if (sBuildingList) {
243     AbortListBuild();
244   }
245 
246   HRESULT hr = E_UNEXPECTED;
247   auto errorHandler = MakeScopeExit([&aPromise, &hr]() {
248     if (SUCCEEDED(hr)) {
249       return;
250     }
251 
252     NS_DispatchToMainThread(NS_NewRunnableFunction(
253         "InitListBuildReject", [promise = std::move(aPromise)]() {
254           promise->MaybeReject(NS_ERROR_FAILURE);
255         }));
256   });
257 
258   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
259   if (!jumpListMgr) {
260     return;
261   }
262 
263   nsTArray<nsString> urisToRemove;
264   RefPtr<IObjectArray> objArray;
265   hr = jumpListMgr->BeginList(
266       &mMaxItems,
267       IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
268   if (FAILED(hr)) {
269     return;
270   }
271 
272   // The returned objArray of removed items are for manually removed items.
273   // This does not return items which are removed because they were previously
274   // part of the jump list but are no longer part of the jump list.
275   sBuildingList = true;
276   RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove);
277 
278   NS_DispatchToMainThread(NS_NewRunnableFunction(
279       "InitListBuildResolve", [urisToRemove = std::move(urisToRemove),
280                                promise = std::move(aPromise)]() {
281         promise->MaybeResolve(urisToRemove);
282       }));
283 }
284 
285 // Ensures that we have no old ICO files left in the jump list cache
RemoveIconCacheForAllItems()286 nsresult JumpListBuilder::RemoveIconCacheForAllItems() {
287   // Construct the path of our jump list cache
288   nsCOMPtr<nsIFile> jumpListCacheDir;
289   nsresult rv =
290       NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCacheDir));
291   NS_ENSURE_SUCCESS(rv, rv);
292   rv = jumpListCacheDir->AppendNative(
293       nsDependentCString(mozilla::widget::FaviconHelper::kJumpListCacheDir));
294   NS_ENSURE_SUCCESS(rv, rv);
295 
296   nsCOMPtr<nsIDirectoryEnumerator> entries;
297   rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
298   NS_ENSURE_SUCCESS(rv, rv);
299 
300   // Loop through each directory entry and remove all ICO files found
301   do {
302     nsCOMPtr<nsIFile> currFile;
303     if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
304       break;
305 
306     nsAutoString path;
307     if (NS_FAILED(currFile->GetPath(path))) continue;
308 
309     if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
310       // Check if the cached ICO file exists
311       bool exists;
312       if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
313 
314       // We found an ICO file that exists, so we should remove it
315       currFile->Remove(false);
316     }
317   } while (true);
318 
319   return NS_OK;
320 }
321 
AddListToBuild(int16_t aCatType,nsIArray * items,const nsAString & catName,bool * _retval)322 NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray* items,
323                                               const nsAString& catName,
324                                               bool* _retval) {
325   nsresult rv;
326 
327   *_retval = false;
328 
329   ReentrantMonitorAutoEnter lock(mMonitor);
330   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
331 
332   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
333   if (!jumpListMgr) {
334     return NS_ERROR_UNEXPECTED;
335   }
336 
337   switch (aCatType) {
338     case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: {
339       NS_ENSURE_ARG_POINTER(items);
340 
341       HRESULT hr;
342       RefPtr<IObjectCollection> collection;
343       hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
344                             CLSCTX_INPROC_SERVER, IID_IObjectCollection,
345                             getter_AddRefs(collection));
346       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
347 
348       // Build the list
349       uint32_t length;
350       items->GetLength(&length);
351       for (uint32_t i = 0; i < length; ++i) {
352         nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
353         if (!item) continue;
354         // Check for separators
355         if (IsSeparator(item)) {
356           RefPtr<IShellLinkW> link;
357           rv = JumpListSeparator::GetSeparator(link);
358           if (NS_FAILED(rv)) return rv;
359           collection->AddObject(link);
360           continue;
361         }
362         // These should all be ShellLinks
363         RefPtr<IShellLinkW> link;
364         rv = JumpListShortcut::GetShellLink(item, link, mIOThread);
365         if (NS_FAILED(rv)) return rv;
366         collection->AddObject(link);
367       }
368 
369       // We need IObjectArray to submit
370       RefPtr<IObjectArray> pArray;
371       hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
372       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
373 
374       // Add the tasks
375       hr = jumpListMgr->AddUserTasks(pArray);
376       if (SUCCEEDED(hr)) *_retval = true;
377       return NS_OK;
378     } break;
379     case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: {
380       if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_RECENT)))
381         *_retval = true;
382       return NS_OK;
383     } break;
384     case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: {
385       if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
386         *_retval = true;
387       return NS_OK;
388     } break;
389     case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: {
390       NS_ENSURE_ARG_POINTER(items);
391 
392       if (catName.IsEmpty()) return NS_ERROR_INVALID_ARG;
393 
394       HRESULT hr;
395       RefPtr<IObjectCollection> collection;
396       hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
397                             CLSCTX_INPROC_SERVER, IID_IObjectCollection,
398                             getter_AddRefs(collection));
399       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
400 
401       uint32_t length;
402       items->GetLength(&length);
403       for (uint32_t i = 0; i < length; ++i) {
404         nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
405         if (!item) continue;
406         int16_t type;
407         if (NS_FAILED(item->GetType(&type))) continue;
408         switch (type) {
409           case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: {
410             RefPtr<IShellLinkW> shellItem;
411             rv = JumpListSeparator::GetSeparator(shellItem);
412             if (NS_FAILED(rv)) return rv;
413             collection->AddObject(shellItem);
414           } break;
415           case nsIJumpListItem::JUMPLIST_ITEM_LINK: {
416             RefPtr<IShellItem2> shellItem;
417             rv = JumpListLink::GetShellItem(item, shellItem);
418             if (NS_FAILED(rv)) return rv;
419             collection->AddObject(shellItem);
420           } break;
421           case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: {
422             RefPtr<IShellLinkW> shellItem;
423             rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread);
424             if (NS_FAILED(rv)) return rv;
425             collection->AddObject(shellItem);
426           } break;
427         }
428       }
429 
430       // We need IObjectArray to submit
431       RefPtr<IObjectArray> pArray;
432       hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
433       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
434 
435       // Add the tasks
436       hr = jumpListMgr->AppendCategory(
437           reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
438       if (SUCCEEDED(hr)) *_retval = true;
439 
440       // Get rid of the old icons
441       nsCOMPtr<nsIRunnable> event =
442           new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
443       mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
444 
445       return NS_OK;
446     } break;
447   }
448   return NS_OK;
449 }
450 
AbortListBuild()451 NS_IMETHODIMP JumpListBuilder::AbortListBuild() {
452   ReentrantMonitorAutoEnter lock(mMonitor);
453   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
454 
455   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
456   if (!jumpListMgr) {
457     return NS_ERROR_UNEXPECTED;
458   }
459 
460   jumpListMgr->AbortList();
461   sBuildingList = false;
462 
463   return NS_OK;
464 }
465 
CommitListBuild(nsIJumpListCommittedCallback * aCallback)466 NS_IMETHODIMP JumpListBuilder::CommitListBuild(
467     nsIJumpListCommittedCallback* aCallback) {
468   ReentrantMonitorAutoEnter lock(mMonitor);
469   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
470 
471   // Also holds a strong reference to this to prevent use-after-free.
472   RefPtr<detail::DoneCommitListBuildCallback> callback =
473       new detail::DoneCommitListBuildCallback(aCallback, this);
474 
475   // The builder has a strong reference in the callback already, so we do not
476   // need to do it for this runnable again.
477   RefPtr<nsIRunnable> event =
478       NewNonOwningRunnableMethod<RefPtr<detail::DoneCommitListBuildCallback>>(
479           "JumpListBuilder::DoCommitListBuild", this,
480           &JumpListBuilder::DoCommitListBuild, std::move(callback));
481   Unused << mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
482 
483   return NS_OK;
484 }
485 
DoCommitListBuild(RefPtr<detail::DoneCommitListBuildCallback> aCallback)486 void JumpListBuilder::DoCommitListBuild(
487     RefPtr<detail::DoneCommitListBuildCallback> aCallback) {
488   // Since we're invoking COM interfaces to talk to the shell on a background
489   // thread, we need to be running inside a multithreaded apartment.
490   mscom::MTARegion mta;
491   MOZ_ASSERT(mta.IsValid());
492 
493   ReentrantMonitorAutoEnter lock(mMonitor);
494   MOZ_ASSERT(mJumpListMgr);
495   MOZ_ASSERT(aCallback);
496 
497   HRESULT hr = E_UNEXPECTED;
498   auto onExit = MakeScopeExit([&hr, &aCallback]() {
499     // XXX We might want some specific error data here.
500     aCallback->SetResult(SUCCEEDED(hr));
501     Unused << NS_DispatchToMainThread(aCallback);
502   });
503 
504   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
505   if (!jumpListMgr) {
506     return;
507   }
508 
509   hr = jumpListMgr->CommitList();
510   sBuildingList = false;
511 
512   if (SUCCEEDED(hr)) {
513     mHasCommit = true;
514   }
515 }
516 
DeleteActiveList(bool * _retval)517 NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool* _retval) {
518   *_retval = false;
519 
520   ReentrantMonitorAutoEnter lock(mMonitor);
521   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
522 
523   if (sBuildingList) {
524     AbortListBuild();
525   }
526 
527   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
528   if (!jumpListMgr) {
529     return NS_ERROR_UNEXPECTED;
530   }
531 
532   if (SUCCEEDED(jumpListMgr->DeleteList(mAppUserModelId.get()))) {
533     *_retval = true;
534   }
535 
536   return NS_OK;
537 }
538 
539 /* internal */
540 
IsSeparator(nsCOMPtr<nsIJumpListItem> & item)541 bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item) {
542   int16_t type;
543   item->GetType(&type);
544   if (NS_FAILED(item->GetType(&type))) return false;
545 
546   if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) return true;
547   return false;
548 }
549 
550 // RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to
551 // avoid unnecessary extra XPCOM incantations. For each object in the input
552 // array,  if it's an IShellLinkW, this deletes the cached icon and adds the
553 // url param to a list of URLs to be removed from the places database.
RemoveIconCacheAndGetJumplistShortcutURIs(IObjectArray * aObjArray,nsTArray<nsString> & aURISpecs)554 void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs(
555     IObjectArray* aObjArray, nsTArray<nsString>& aURISpecs) {
556   MOZ_ASSERT(!NS_IsMainThread());
557 
558   // Early return here just in case some versions of Windows don't populate this
559   if (!aObjArray) {
560     return;
561   }
562 
563   uint32_t count = 0;
564   aObjArray->GetCount(&count);
565 
566   for (uint32_t idx = 0; idx < count; idx++) {
567     RefPtr<IShellLinkW> pLink;
568 
569     if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW,
570                                 static_cast<void**>(getter_AddRefs(pLink))))) {
571       continue;
572     }
573 
574     wchar_t buf[MAX_PATH];
575     HRESULT hres = pLink->GetArguments(buf, MAX_PATH);
576     if (SUCCEEDED(hres)) {
577       LPWSTR* arglist;
578       int32_t numArgs;
579 
580       arglist = ::CommandLineToArgvW(buf, &numArgs);
581       if (arglist && numArgs > 0) {
582         nsString spec(arglist[0]);
583         aURISpecs.AppendElement(std::move(spec));
584         ::LocalFree(arglist);
585       }
586     }
587 
588     int iconIdx = 0;
589     hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
590     if (SUCCEEDED(hres)) {
591       nsDependentString spec(buf);
592       DeleteIconFromDisk(spec);
593     }
594   }
595 }
596 
DeleteIconFromDisk(const nsAString & aPath)597 void JumpListBuilder::DeleteIconFromDisk(const nsAString& aPath) {
598   MOZ_ASSERT(!NS_IsMainThread());
599 
600   // Check that we aren't deleting some arbitrary file that is not an icon
601   if (StringTail(aPath, 4).LowerCaseEqualsASCII(".ico")) {
602     // Construct the parent path of the passed in path
603     nsCOMPtr<nsIFile> icoFile;
604     nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile));
605     if (NS_WARN_IF(NS_FAILED(rv))) {
606       return;
607     }
608 
609     icoFile->Remove(false);
610   }
611 }
612 
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)613 NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject,
614                                        const char* aTopic,
615                                        const char16_t* aData) {
616   NS_ENSURE_ARG_POINTER(aTopic);
617   if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
618     nsCOMPtr<nsIObserverService> observerService =
619         do_GetService("@mozilla.org/observer-service;1");
620     if (observerService) {
621       observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
622     }
623     mIOThread->Shutdown();
624     // Clear out mJumpListMgr, as MSCOM services won't be available soon.
625     ReentrantMonitorAutoEnter lock(mMonitor);
626     mJumpListMgr = nullptr;
627   } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
628              nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
629     bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
630     if (!enabled) {
631       nsCOMPtr<nsIRunnable> event =
632           new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
633       mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
634     }
635   } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) {
636     // Delete JumpListCache icons from Disk, if any.
637     nsCOMPtr<nsIRunnable> event =
638         new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
639     mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
640   }
641   return NS_OK;
642 }
643 
644 }  // namespace widget
645 }  // namespace mozilla
646