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   // GetAppUserModelID can only be called once we're back on the main thread.
137   nsString modelId;
138   if (mozilla::widget::WinTaskbar::GetAppUserModelID(modelId)) {
139     jumpListMgr->SetAppID(modelId.get());
140   }
141 }
142 
~JumpListBuilder()143 JumpListBuilder::~JumpListBuilder() {
144   Preferences::RemoveObserver(this, kPrefTaskbarEnabled);
145 }
146 
GetAvailable(int16_t * aAvailable)147 NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t* aAvailable) {
148   *aAvailable = false;
149 
150   ReentrantMonitorAutoEnter lock(mMonitor);
151   if (mJumpListMgr) *aAvailable = true;
152 
153   return NS_OK;
154 }
155 
GetIsListCommitted(bool * aCommit)156 NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool* aCommit) {
157   *aCommit = mHasCommit;
158 
159   return NS_OK;
160 }
161 
GetMaxListItems(int16_t * aMaxItems)162 NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t* aMaxItems) {
163   ReentrantMonitorAutoEnter lock(mMonitor);
164   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
165 
166   *aMaxItems = 0;
167 
168   if (sBuildingList) {
169     *aMaxItems = mMaxItems;
170     return NS_OK;
171   }
172 
173   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
174   if (!jumpListMgr) {
175     return NS_ERROR_UNEXPECTED;
176   }
177 
178   IObjectArray* objArray;
179   if (SUCCEEDED(jumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) {
180     *aMaxItems = mMaxItems;
181 
182     if (objArray) objArray->Release();
183 
184     jumpListMgr->AbortList();
185   }
186 
187   return NS_OK;
188 }
189 
InitListBuild(JSContext * aCx,Promise ** aPromise)190 NS_IMETHODIMP JumpListBuilder::InitListBuild(JSContext* aCx,
191                                              Promise** aPromise) {
192   ReentrantMonitorAutoEnter lock(mMonitor);
193   if (!mJumpListMgr) {
194     return NS_ERROR_NOT_AVAILABLE;
195   }
196 
197   nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
198   if (NS_WARN_IF(!globalObject)) {
199     return NS_ERROR_FAILURE;
200   }
201 
202   ErrorResult result;
203   RefPtr<Promise> promise = Promise::Create(globalObject, result);
204   if (NS_WARN_IF(result.Failed())) {
205     return result.StealNSResult();
206   }
207 
208   nsCOMPtr<nsIRunnable> runnable =
209       NewRunnableMethod<StoreCopyPassByRRef<RefPtr<Promise>>>(
210           "InitListBuild", this, &JumpListBuilder::DoInitListBuild, promise);
211   nsresult rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
212   if (NS_WARN_IF(NS_FAILED(rv))) {
213     return rv;
214   }
215 
216   promise.forget(aPromise);
217   return NS_OK;
218 }
219 
DoInitListBuild(RefPtr<Promise> && aPromise)220 void JumpListBuilder::DoInitListBuild(RefPtr<Promise>&& aPromise) {
221   // Since we're invoking COM interfaces to talk to the shell on a background
222   // thread, we need to be running inside a multithreaded apartment.
223   mscom::MTARegion mta;
224   MOZ_ASSERT(mta.IsValid());
225 
226   ReentrantMonitorAutoEnter lock(mMonitor);
227   MOZ_ASSERT(mJumpListMgr);
228 
229   if (sBuildingList) {
230     AbortListBuild();
231   }
232 
233   HRESULT hr = E_UNEXPECTED;
234   auto errorHandler = MakeScopeExit([&aPromise, &hr]() {
235     if (SUCCEEDED(hr)) {
236       return;
237     }
238 
239     NS_DispatchToMainThread(NS_NewRunnableFunction(
240         "InitListBuildReject", [promise = std::move(aPromise)]() {
241           promise->MaybeReject(NS_ERROR_FAILURE);
242         }));
243   });
244 
245   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
246   if (!jumpListMgr) {
247     return;
248   }
249 
250   nsTArray<nsString> urisToRemove;
251   RefPtr<IObjectArray> objArray;
252   hr = jumpListMgr->BeginList(
253       &mMaxItems,
254       IID_PPV_ARGS(static_cast<IObjectArray**>(getter_AddRefs(objArray))));
255   if (FAILED(hr)) {
256     return;
257   }
258 
259   // The returned objArray of removed items are for manually removed items.
260   // This does not return items which are removed because they were previously
261   // part of the jump list but are no longer part of the jump list.
262   sBuildingList = true;
263   RemoveIconCacheAndGetJumplistShortcutURIs(objArray, urisToRemove);
264 
265   NS_DispatchToMainThread(NS_NewRunnableFunction(
266       "InitListBuildResolve", [urisToRemove = std::move(urisToRemove),
267                                promise = std::move(aPromise)]() {
268         promise->MaybeResolve(urisToRemove);
269       }));
270 }
271 
272 // Ensures that we have no old ICO files left in the jump list cache
RemoveIconCacheForAllItems()273 nsresult JumpListBuilder::RemoveIconCacheForAllItems() {
274   // Construct the path of our jump list cache
275   nsCOMPtr<nsIFile> jumpListCacheDir;
276   nsresult rv =
277       NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(jumpListCacheDir));
278   NS_ENSURE_SUCCESS(rv, rv);
279   rv = jumpListCacheDir->AppendNative(
280       nsDependentCString(mozilla::widget::FaviconHelper::kJumpListCacheDir));
281   NS_ENSURE_SUCCESS(rv, rv);
282 
283   nsCOMPtr<nsIDirectoryEnumerator> entries;
284   rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries));
285   NS_ENSURE_SUCCESS(rv, rv);
286 
287   // Loop through each directory entry and remove all ICO files found
288   do {
289     nsCOMPtr<nsIFile> currFile;
290     if (NS_FAILED(entries->GetNextFile(getter_AddRefs(currFile))) || !currFile)
291       break;
292 
293     nsAutoString path;
294     if (NS_FAILED(currFile->GetPath(path))) continue;
295 
296     if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) {
297       // Check if the cached ICO file exists
298       bool exists;
299       if (NS_FAILED(currFile->Exists(&exists)) || !exists) continue;
300 
301       // We found an ICO file that exists, so we should remove it
302       currFile->Remove(false);
303     }
304   } while (true);
305 
306   return NS_OK;
307 }
308 
AddListToBuild(int16_t aCatType,nsIArray * items,const nsAString & catName,bool * _retval)309 NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray* items,
310                                               const nsAString& catName,
311                                               bool* _retval) {
312   nsresult rv;
313 
314   *_retval = false;
315 
316   ReentrantMonitorAutoEnter lock(mMonitor);
317   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
318 
319   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
320   if (!jumpListMgr) {
321     return NS_ERROR_UNEXPECTED;
322   }
323 
324   switch (aCatType) {
325     case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: {
326       NS_ENSURE_ARG_POINTER(items);
327 
328       HRESULT hr;
329       RefPtr<IObjectCollection> collection;
330       hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
331                             CLSCTX_INPROC_SERVER, IID_IObjectCollection,
332                             getter_AddRefs(collection));
333       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
334 
335       // Build the list
336       uint32_t length;
337       items->GetLength(&length);
338       for (uint32_t i = 0; i < length; ++i) {
339         nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
340         if (!item) continue;
341         // Check for separators
342         if (IsSeparator(item)) {
343           RefPtr<IShellLinkW> link;
344           rv = JumpListSeparator::GetSeparator(link);
345           if (NS_FAILED(rv)) return rv;
346           collection->AddObject(link);
347           continue;
348         }
349         // These should all be ShellLinks
350         RefPtr<IShellLinkW> link;
351         rv = JumpListShortcut::GetShellLink(item, link, mIOThread);
352         if (NS_FAILED(rv)) return rv;
353         collection->AddObject(link);
354       }
355 
356       // We need IObjectArray to submit
357       RefPtr<IObjectArray> pArray;
358       hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray));
359       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
360 
361       // Add the tasks
362       hr = jumpListMgr->AddUserTasks(pArray);
363       if (SUCCEEDED(hr)) *_retval = true;
364       return NS_OK;
365     } break;
366     case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: {
367       if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_RECENT)))
368         *_retval = true;
369       return NS_OK;
370     } break;
371     case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: {
372       if (SUCCEEDED(jumpListMgr->AppendKnownCategory(KDC_FREQUENT)))
373         *_retval = true;
374       return NS_OK;
375     } break;
376     case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: {
377       NS_ENSURE_ARG_POINTER(items);
378 
379       if (catName.IsEmpty()) return NS_ERROR_INVALID_ARG;
380 
381       HRESULT hr;
382       RefPtr<IObjectCollection> collection;
383       hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr,
384                             CLSCTX_INPROC_SERVER, IID_IObjectCollection,
385                             getter_AddRefs(collection));
386       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
387 
388       uint32_t length;
389       items->GetLength(&length);
390       for (uint32_t i = 0; i < length; ++i) {
391         nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i);
392         if (!item) continue;
393         int16_t type;
394         if (NS_FAILED(item->GetType(&type))) continue;
395         switch (type) {
396           case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: {
397             RefPtr<IShellLinkW> shellItem;
398             rv = JumpListSeparator::GetSeparator(shellItem);
399             if (NS_FAILED(rv)) return rv;
400             collection->AddObject(shellItem);
401           } break;
402           case nsIJumpListItem::JUMPLIST_ITEM_LINK: {
403             RefPtr<IShellItem2> shellItem;
404             rv = JumpListLink::GetShellItem(item, shellItem);
405             if (NS_FAILED(rv)) return rv;
406             collection->AddObject(shellItem);
407           } break;
408           case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: {
409             RefPtr<IShellLinkW> shellItem;
410             rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread);
411             if (NS_FAILED(rv)) return rv;
412             collection->AddObject(shellItem);
413           } break;
414         }
415       }
416 
417       // We need IObjectArray to submit
418       RefPtr<IObjectArray> pArray;
419       hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray);
420       if (FAILED(hr)) return NS_ERROR_UNEXPECTED;
421 
422       // Add the tasks
423       hr = jumpListMgr->AppendCategory(
424           reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray);
425       if (SUCCEEDED(hr)) *_retval = true;
426 
427       // Get rid of the old icons
428       nsCOMPtr<nsIRunnable> event =
429           new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(true);
430       mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
431 
432       return NS_OK;
433     } break;
434   }
435   return NS_OK;
436 }
437 
AbortListBuild()438 NS_IMETHODIMP JumpListBuilder::AbortListBuild() {
439   ReentrantMonitorAutoEnter lock(mMonitor);
440   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
441 
442   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
443   if (!jumpListMgr) {
444     return NS_ERROR_UNEXPECTED;
445   }
446 
447   jumpListMgr->AbortList();
448   sBuildingList = false;
449 
450   return NS_OK;
451 }
452 
CommitListBuild(nsIJumpListCommittedCallback * aCallback)453 NS_IMETHODIMP JumpListBuilder::CommitListBuild(
454     nsIJumpListCommittedCallback* aCallback) {
455   ReentrantMonitorAutoEnter lock(mMonitor);
456   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
457 
458   // Also holds a strong reference to this to prevent use-after-free.
459   RefPtr<detail::DoneCommitListBuildCallback> callback =
460       new detail::DoneCommitListBuildCallback(aCallback, this);
461 
462   // The builder has a strong reference in the callback already, so we do not
463   // need to do it for this runnable again.
464   RefPtr<nsIRunnable> event =
465       NewNonOwningRunnableMethod<RefPtr<detail::DoneCommitListBuildCallback>>(
466           "JumpListBuilder::DoCommitListBuild", this,
467           &JumpListBuilder::DoCommitListBuild, std::move(callback));
468   Unused << mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
469 
470   return NS_OK;
471 }
472 
DoCommitListBuild(RefPtr<detail::DoneCommitListBuildCallback> aCallback)473 void JumpListBuilder::DoCommitListBuild(
474     RefPtr<detail::DoneCommitListBuildCallback> aCallback) {
475   // Since we're invoking COM interfaces to talk to the shell on a background
476   // thread, we need to be running inside a multithreaded apartment.
477   mscom::MTARegion mta;
478   MOZ_ASSERT(mta.IsValid());
479 
480   ReentrantMonitorAutoEnter lock(mMonitor);
481   MOZ_ASSERT(mJumpListMgr);
482   MOZ_ASSERT(aCallback);
483 
484   HRESULT hr = E_UNEXPECTED;
485   auto onExit = MakeScopeExit([&hr, &aCallback]() {
486     // XXX We might want some specific error data here.
487     aCallback->SetResult(SUCCEEDED(hr));
488     Unused << NS_DispatchToMainThread(aCallback);
489   });
490 
491   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
492   if (!jumpListMgr) {
493     return;
494   }
495 
496   hr = jumpListMgr->CommitList();
497   sBuildingList = false;
498 
499   if (SUCCEEDED(hr)) {
500     mHasCommit = true;
501   }
502 }
503 
DeleteActiveList(bool * _retval)504 NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool* _retval) {
505   *_retval = false;
506 
507   ReentrantMonitorAutoEnter lock(mMonitor);
508   if (!mJumpListMgr) return NS_ERROR_NOT_AVAILABLE;
509 
510   if (sBuildingList) {
511     AbortListBuild();
512   }
513 
514   nsAutoString uid;
515   if (!WinTaskbar::GetAppUserModelID(uid)) return NS_OK;
516 
517   RefPtr<ICustomDestinationList> jumpListMgr = mJumpListMgr;
518   if (!jumpListMgr) {
519     return NS_ERROR_UNEXPECTED;
520   }
521 
522   if (SUCCEEDED(jumpListMgr->DeleteList(uid.get()))) {
523     *_retval = true;
524   }
525 
526   return NS_OK;
527 }
528 
529 /* internal */
530 
IsSeparator(nsCOMPtr<nsIJumpListItem> & item)531 bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item) {
532   int16_t type;
533   item->GetType(&type);
534   if (NS_FAILED(item->GetType(&type))) return false;
535 
536   if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) return true;
537   return false;
538 }
539 
540 // RemoveIconCacheAndGetJumplistShortcutURIs - does multiple things to
541 // avoid unnecessary extra XPCOM incantations. For each object in the input
542 // array,  if it's an IShellLinkW, this deletes the cached icon and adds the
543 // url param to a list of URLs to be removed from the places database.
RemoveIconCacheAndGetJumplistShortcutURIs(IObjectArray * aObjArray,nsTArray<nsString> & aURISpecs)544 void JumpListBuilder::RemoveIconCacheAndGetJumplistShortcutURIs(
545     IObjectArray* aObjArray, nsTArray<nsString>& aURISpecs) {
546   MOZ_ASSERT(!NS_IsMainThread());
547 
548   // Early return here just in case some versions of Windows don't populate this
549   if (!aObjArray) {
550     return;
551   }
552 
553   uint32_t count = 0;
554   aObjArray->GetCount(&count);
555 
556   for (uint32_t idx = 0; idx < count; idx++) {
557     RefPtr<IShellLinkW> pLink;
558 
559     if (FAILED(aObjArray->GetAt(idx, IID_IShellLinkW,
560                                 static_cast<void**>(getter_AddRefs(pLink))))) {
561       continue;
562     }
563 
564     wchar_t buf[MAX_PATH];
565     HRESULT hres = pLink->GetArguments(buf, MAX_PATH);
566     if (SUCCEEDED(hres)) {
567       LPWSTR* arglist;
568       int32_t numArgs;
569 
570       arglist = ::CommandLineToArgvW(buf, &numArgs);
571       if (arglist && numArgs > 0) {
572         nsString spec(arglist[0]);
573         aURISpecs.AppendElement(std::move(spec));
574         ::LocalFree(arglist);
575       }
576     }
577 
578     int iconIdx = 0;
579     hres = pLink->GetIconLocation(buf, MAX_PATH, &iconIdx);
580     if (SUCCEEDED(hres)) {
581       nsDependentString spec(buf);
582       DeleteIconFromDisk(spec);
583     }
584   }
585 }
586 
DeleteIconFromDisk(const nsAString & aPath)587 void JumpListBuilder::DeleteIconFromDisk(const nsAString& aPath) {
588   MOZ_ASSERT(!NS_IsMainThread());
589 
590   // Check that we aren't deleting some arbitrary file that is not an icon
591   if (StringTail(aPath, 4).LowerCaseEqualsASCII(".ico")) {
592     // Construct the parent path of the passed in path
593     nsCOMPtr<nsIFile> icoFile;
594     nsresult rv = NS_NewLocalFile(aPath, true, getter_AddRefs(icoFile));
595     if (NS_WARN_IF(NS_FAILED(rv))) {
596       return;
597     }
598 
599     icoFile->Remove(false);
600   }
601 }
602 
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)603 NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject,
604                                        const char* aTopic,
605                                        const char16_t* aData) {
606   NS_ENSURE_ARG_POINTER(aTopic);
607   if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) {
608     nsCOMPtr<nsIObserverService> observerService =
609         do_GetService("@mozilla.org/observer-service;1");
610     if (observerService) {
611       observerService->RemoveObserver(this, TOPIC_PROFILE_BEFORE_CHANGE);
612     }
613     mIOThread->Shutdown();
614     // Clear out mJumpListMgr, as MSCOM services won't be available soon.
615     ReentrantMonitorAutoEnter lock(mMonitor);
616     mJumpListMgr = nullptr;
617   } else if (strcmp(aTopic, "nsPref:changed") == 0 &&
618              nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) {
619     bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true);
620     if (!enabled) {
621       nsCOMPtr<nsIRunnable> event =
622           new mozilla::widget::AsyncDeleteAllFaviconsFromDisk();
623       mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
624     }
625   } else if (strcmp(aTopic, TOPIC_CLEAR_PRIVATE_DATA) == 0) {
626     // Delete JumpListCache icons from Disk, if any.
627     nsCOMPtr<nsIRunnable> event =
628         new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(false);
629     mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
630   }
631   return NS_OK;
632 }
633 
634 }  // namespace widget
635 }  // namespace mozilla
636