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