|
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 "nsAutoPtr.h" |
|
12 #include "nsString.h" |
|
13 #include "nsArrayUtils.h" |
|
14 #include "nsIMutableArray.h" |
|
15 #include "nsWidgetsCID.h" |
|
16 #include "WinTaskbar.h" |
|
17 #include "nsDirectoryServiceUtils.h" |
|
18 #include "nsISimpleEnumerator.h" |
|
19 #include "mozilla/Preferences.h" |
|
20 #include "nsStringStream.h" |
|
21 #include "nsNetUtil.h" |
|
22 #include "nsThreadUtils.h" |
|
23 #include "mozilla/LazyIdleThread.h" |
|
24 |
|
25 #include "WinUtils.h" |
|
26 |
|
27 // The amount of time, in milliseconds, that our IO thread will stay alive after the last event it processes. |
|
28 #define DEFAULT_THREAD_TIMEOUT_MS 30000 |
|
29 |
|
30 namespace mozilla { |
|
31 namespace widget { |
|
32 |
|
33 static NS_DEFINE_CID(kJumpListItemCID, NS_WIN_JUMPLISTITEM_CID); |
|
34 static NS_DEFINE_CID(kJumpListLinkCID, NS_WIN_JUMPLISTLINK_CID); |
|
35 static NS_DEFINE_CID(kJumpListShortcutCID, NS_WIN_JUMPLISTSHORTCUT_CID); |
|
36 |
|
37 // defined in WinTaskbar.cpp |
|
38 extern const wchar_t *gMozillaJumpListIDGeneric; |
|
39 |
|
40 bool JumpListBuilder::sBuildingList = false; |
|
41 const char kPrefTaskbarEnabled[] = "browser.taskbar.lists.enabled"; |
|
42 |
|
43 NS_IMPL_ISUPPORTS(JumpListBuilder, nsIJumpListBuilder, nsIObserver) |
|
44 |
|
45 JumpListBuilder::JumpListBuilder() : |
|
46 mMaxItems(0), |
|
47 mHasCommit(false) |
|
48 { |
|
49 ::CoInitialize(nullptr); |
|
50 |
|
51 CoCreateInstance(CLSID_DestinationList, nullptr, CLSCTX_INPROC_SERVER, |
|
52 IID_ICustomDestinationList, getter_AddRefs(mJumpListMgr)); |
|
53 |
|
54 // Make a lazy thread for any IO |
|
55 mIOThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, |
|
56 NS_LITERAL_CSTRING("Jump List"), |
|
57 LazyIdleThread::ManualShutdown); |
|
58 Preferences::AddStrongObserver(this, kPrefTaskbarEnabled); |
|
59 } |
|
60 |
|
61 JumpListBuilder::~JumpListBuilder() |
|
62 { |
|
63 mIOThread->Shutdown(); |
|
64 Preferences::RemoveObserver(this, kPrefTaskbarEnabled); |
|
65 mJumpListMgr = nullptr; |
|
66 ::CoUninitialize(); |
|
67 } |
|
68 |
|
69 /* readonly attribute short available; */ |
|
70 NS_IMETHODIMP JumpListBuilder::GetAvailable(int16_t *aAvailable) |
|
71 { |
|
72 *aAvailable = false; |
|
73 |
|
74 if (mJumpListMgr) |
|
75 *aAvailable = true; |
|
76 |
|
77 return NS_OK; |
|
78 } |
|
79 |
|
80 /* readonly attribute boolean isListCommitted; */ |
|
81 NS_IMETHODIMP JumpListBuilder::GetIsListCommitted(bool *aCommit) |
|
82 { |
|
83 *aCommit = mHasCommit; |
|
84 |
|
85 return NS_OK; |
|
86 } |
|
87 |
|
88 /* readonly attribute short maxItems; */ |
|
89 NS_IMETHODIMP JumpListBuilder::GetMaxListItems(int16_t *aMaxItems) |
|
90 { |
|
91 if (!mJumpListMgr) |
|
92 return NS_ERROR_NOT_AVAILABLE; |
|
93 |
|
94 *aMaxItems = 0; |
|
95 |
|
96 if (sBuildingList) { |
|
97 *aMaxItems = mMaxItems; |
|
98 return NS_OK; |
|
99 } |
|
100 |
|
101 IObjectArray *objArray; |
|
102 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { |
|
103 *aMaxItems = mMaxItems; |
|
104 |
|
105 if (objArray) |
|
106 objArray->Release(); |
|
107 |
|
108 mJumpListMgr->AbortList(); |
|
109 } |
|
110 |
|
111 return NS_OK; |
|
112 } |
|
113 |
|
114 /* boolean initListBuild(in nsIMutableArray removedItems); */ |
|
115 NS_IMETHODIMP JumpListBuilder::InitListBuild(nsIMutableArray *removedItems, bool *_retval) |
|
116 { |
|
117 NS_ENSURE_ARG_POINTER(removedItems); |
|
118 |
|
119 *_retval = false; |
|
120 |
|
121 if (!mJumpListMgr) |
|
122 return NS_ERROR_NOT_AVAILABLE; |
|
123 |
|
124 if(sBuildingList) |
|
125 AbortListBuild(); |
|
126 |
|
127 IObjectArray *objArray; |
|
128 |
|
129 if (SUCCEEDED(mJumpListMgr->BeginList(&mMaxItems, IID_PPV_ARGS(&objArray)))) { |
|
130 if (objArray) { |
|
131 TransferIObjectArrayToIMutableArray(objArray, removedItems); |
|
132 objArray->Release(); |
|
133 } |
|
134 |
|
135 RemoveIconCacheForItems(removedItems); |
|
136 |
|
137 sBuildingList = true; |
|
138 *_retval = true; |
|
139 return NS_OK; |
|
140 } |
|
141 |
|
142 return NS_OK; |
|
143 } |
|
144 |
|
145 // Ensures that we don't have old ICO files that aren't in our jump lists |
|
146 // anymore left over in the cache. |
|
147 nsresult JumpListBuilder::RemoveIconCacheForItems(nsIMutableArray *items) |
|
148 { |
|
149 NS_ENSURE_ARG_POINTER(items); |
|
150 |
|
151 nsresult rv; |
|
152 uint32_t length; |
|
153 items->GetLength(&length); |
|
154 for (uint32_t i = 0; i < length; ++i) { |
|
155 |
|
156 //Obtain an IJumpListItem and get the type |
|
157 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); |
|
158 if (!item) { |
|
159 continue; |
|
160 } |
|
161 int16_t type; |
|
162 if (NS_FAILED(item->GetType(&type))) { |
|
163 continue; |
|
164 } |
|
165 |
|
166 // If the item is a shortcut, remove its associated icon if any |
|
167 if (type == nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT) { |
|
168 nsCOMPtr<nsIJumpListShortcut> shortcut = do_QueryInterface(item); |
|
169 if (shortcut) { |
|
170 nsCOMPtr<nsIURI> uri; |
|
171 rv = shortcut->GetFaviconPageUri(getter_AddRefs(uri)); |
|
172 if (NS_SUCCEEDED(rv) && uri) { |
|
173 |
|
174 // The local file path is stored inside the nsIURI |
|
175 // Get the nsIURI spec which stores the local path for the icon to remove |
|
176 nsAutoCString spec; |
|
177 nsresult rv = uri->GetSpec(spec); |
|
178 NS_ENSURE_SUCCESS(rv, rv); |
|
179 |
|
180 nsCOMPtr<nsIRunnable> event |
|
181 = new mozilla::widget::AsyncDeleteIconFromDisk(NS_ConvertUTF8toUTF16(spec)); |
|
182 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); |
|
183 |
|
184 // The shortcut was generated from an IShellLinkW so IShellLinkW can |
|
185 // only tell us what the original icon is and not the URI. |
|
186 // So this field was used only temporarily as the actual icon file |
|
187 // path. It should be cleared. |
|
188 shortcut->SetFaviconPageUri(nullptr); |
|
189 } |
|
190 } |
|
191 } |
|
192 |
|
193 } // end for |
|
194 |
|
195 return NS_OK; |
|
196 } |
|
197 |
|
198 // Ensures that we have no old ICO files left in the jump list cache |
|
199 nsresult JumpListBuilder::RemoveIconCacheForAllItems() |
|
200 { |
|
201 // Construct the path of our jump list cache |
|
202 nsCOMPtr<nsIFile> jumpListCacheDir; |
|
203 nsresult rv = NS_GetSpecialDirectory("ProfLDS", |
|
204 getter_AddRefs(jumpListCacheDir)); |
|
205 NS_ENSURE_SUCCESS(rv, rv); |
|
206 rv = jumpListCacheDir->AppendNative(nsDependentCString( |
|
207 mozilla::widget::FaviconHelper::kJumpListCacheDir)); |
|
208 NS_ENSURE_SUCCESS(rv, rv); |
|
209 nsCOMPtr<nsISimpleEnumerator> entries; |
|
210 rv = jumpListCacheDir->GetDirectoryEntries(getter_AddRefs(entries)); |
|
211 NS_ENSURE_SUCCESS(rv, rv); |
|
212 |
|
213 // Loop through each directory entry and remove all ICO files found |
|
214 do { |
|
215 bool hasMore = false; |
|
216 if (NS_FAILED(entries->HasMoreElements(&hasMore)) || !hasMore) |
|
217 break; |
|
218 |
|
219 nsCOMPtr<nsISupports> supp; |
|
220 if (NS_FAILED(entries->GetNext(getter_AddRefs(supp)))) |
|
221 break; |
|
222 |
|
223 nsCOMPtr<nsIFile> currFile(do_QueryInterface(supp)); |
|
224 nsAutoString path; |
|
225 if (NS_FAILED(currFile->GetPath(path))) |
|
226 continue; |
|
227 |
|
228 int32_t len = path.Length(); |
|
229 if (StringTail(path, 4).LowerCaseEqualsASCII(".ico")) { |
|
230 // Check if the cached ICO file exists |
|
231 bool exists; |
|
232 if (NS_FAILED(currFile->Exists(&exists)) || !exists) |
|
233 continue; |
|
234 |
|
235 // We found an ICO file that exists, so we should remove it |
|
236 currFile->Remove(false); |
|
237 } |
|
238 } while(true); |
|
239 |
|
240 return NS_OK; |
|
241 } |
|
242 |
|
243 /* boolean addListToBuild(in short aCatType, [optional] in nsIArray items, [optional] in AString catName); */ |
|
244 NS_IMETHODIMP JumpListBuilder::AddListToBuild(int16_t aCatType, nsIArray *items, const nsAString &catName, bool *_retval) |
|
245 { |
|
246 nsresult rv; |
|
247 |
|
248 *_retval = false; |
|
249 |
|
250 if (!mJumpListMgr) |
|
251 return NS_ERROR_NOT_AVAILABLE; |
|
252 |
|
253 switch(aCatType) { |
|
254 case nsIJumpListBuilder::JUMPLIST_CATEGORY_TASKS: |
|
255 { |
|
256 NS_ENSURE_ARG_POINTER(items); |
|
257 |
|
258 HRESULT hr; |
|
259 nsRefPtr<IObjectCollection> collection; |
|
260 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, |
|
261 CLSCTX_INPROC_SERVER, IID_IObjectCollection, |
|
262 getter_AddRefs(collection)); |
|
263 if (FAILED(hr)) |
|
264 return NS_ERROR_UNEXPECTED; |
|
265 |
|
266 // Build the list |
|
267 uint32_t length; |
|
268 items->GetLength(&length); |
|
269 for (uint32_t i = 0; i < length; ++i) { |
|
270 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); |
|
271 if (!item) |
|
272 continue; |
|
273 // Check for separators |
|
274 if (IsSeparator(item)) { |
|
275 nsRefPtr<IShellLinkW> link; |
|
276 rv = JumpListSeparator::GetSeparator(link); |
|
277 if (NS_FAILED(rv)) |
|
278 return rv; |
|
279 collection->AddObject(link); |
|
280 continue; |
|
281 } |
|
282 // These should all be ShellLinks |
|
283 nsRefPtr<IShellLinkW> link; |
|
284 rv = JumpListShortcut::GetShellLink(item, link, mIOThread); |
|
285 if (NS_FAILED(rv)) |
|
286 return rv; |
|
287 collection->AddObject(link); |
|
288 } |
|
289 |
|
290 // We need IObjectArray to submit |
|
291 nsRefPtr<IObjectArray> pArray; |
|
292 hr = collection->QueryInterface(IID_IObjectArray, getter_AddRefs(pArray)); |
|
293 if (FAILED(hr)) |
|
294 return NS_ERROR_UNEXPECTED; |
|
295 |
|
296 // Add the tasks |
|
297 hr = mJumpListMgr->AddUserTasks(pArray); |
|
298 if (SUCCEEDED(hr)) |
|
299 *_retval = true; |
|
300 return NS_OK; |
|
301 } |
|
302 break; |
|
303 case nsIJumpListBuilder::JUMPLIST_CATEGORY_RECENT: |
|
304 { |
|
305 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_RECENT))) |
|
306 *_retval = true; |
|
307 return NS_OK; |
|
308 } |
|
309 break; |
|
310 case nsIJumpListBuilder::JUMPLIST_CATEGORY_FREQUENT: |
|
311 { |
|
312 if (SUCCEEDED(mJumpListMgr->AppendKnownCategory(KDC_FREQUENT))) |
|
313 *_retval = true; |
|
314 return NS_OK; |
|
315 } |
|
316 break; |
|
317 case nsIJumpListBuilder::JUMPLIST_CATEGORY_CUSTOMLIST: |
|
318 { |
|
319 NS_ENSURE_ARG_POINTER(items); |
|
320 |
|
321 if (catName.IsEmpty()) |
|
322 return NS_ERROR_INVALID_ARG; |
|
323 |
|
324 HRESULT hr; |
|
325 nsRefPtr<IObjectCollection> collection; |
|
326 hr = CoCreateInstance(CLSID_EnumerableObjectCollection, nullptr, |
|
327 CLSCTX_INPROC_SERVER, IID_IObjectCollection, |
|
328 getter_AddRefs(collection)); |
|
329 if (FAILED(hr)) |
|
330 return NS_ERROR_UNEXPECTED; |
|
331 |
|
332 uint32_t length; |
|
333 items->GetLength(&length); |
|
334 for (uint32_t i = 0; i < length; ++i) { |
|
335 nsCOMPtr<nsIJumpListItem> item = do_QueryElementAt(items, i); |
|
336 if (!item) |
|
337 continue; |
|
338 int16_t type; |
|
339 if (NS_FAILED(item->GetType(&type))) |
|
340 continue; |
|
341 switch(type) { |
|
342 case nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR: |
|
343 { |
|
344 nsRefPtr<IShellLinkW> shellItem; |
|
345 rv = JumpListSeparator::GetSeparator(shellItem); |
|
346 if (NS_FAILED(rv)) |
|
347 return rv; |
|
348 collection->AddObject(shellItem); |
|
349 } |
|
350 break; |
|
351 case nsIJumpListItem::JUMPLIST_ITEM_LINK: |
|
352 { |
|
353 nsRefPtr<IShellItem2> shellItem; |
|
354 rv = JumpListLink::GetShellItem(item, shellItem); |
|
355 if (NS_FAILED(rv)) |
|
356 return rv; |
|
357 collection->AddObject(shellItem); |
|
358 } |
|
359 break; |
|
360 case nsIJumpListItem::JUMPLIST_ITEM_SHORTCUT: |
|
361 { |
|
362 nsRefPtr<IShellLinkW> shellItem; |
|
363 rv = JumpListShortcut::GetShellLink(item, shellItem, mIOThread); |
|
364 if (NS_FAILED(rv)) |
|
365 return rv; |
|
366 collection->AddObject(shellItem); |
|
367 } |
|
368 break; |
|
369 } |
|
370 } |
|
371 |
|
372 // We need IObjectArray to submit |
|
373 nsRefPtr<IObjectArray> pArray; |
|
374 hr = collection->QueryInterface(IID_IObjectArray, (LPVOID*)&pArray); |
|
375 if (FAILED(hr)) |
|
376 return NS_ERROR_UNEXPECTED; |
|
377 |
|
378 // Add the tasks |
|
379 hr = mJumpListMgr->AppendCategory(reinterpret_cast<const wchar_t*>(catName.BeginReading()), pArray); |
|
380 if (SUCCEEDED(hr)) |
|
381 *_retval = true; |
|
382 return NS_OK; |
|
383 } |
|
384 break; |
|
385 } |
|
386 return NS_OK; |
|
387 } |
|
388 |
|
389 /* void abortListBuild(); */ |
|
390 NS_IMETHODIMP JumpListBuilder::AbortListBuild() |
|
391 { |
|
392 if (!mJumpListMgr) |
|
393 return NS_ERROR_NOT_AVAILABLE; |
|
394 |
|
395 mJumpListMgr->AbortList(); |
|
396 sBuildingList = false; |
|
397 |
|
398 return NS_OK; |
|
399 } |
|
400 |
|
401 /* boolean commitListBuild(); */ |
|
402 NS_IMETHODIMP JumpListBuilder::CommitListBuild(bool *_retval) |
|
403 { |
|
404 *_retval = false; |
|
405 |
|
406 if (!mJumpListMgr) |
|
407 return NS_ERROR_NOT_AVAILABLE; |
|
408 |
|
409 HRESULT hr = mJumpListMgr->CommitList(); |
|
410 sBuildingList = false; |
|
411 |
|
412 // XXX We might want some specific error data here. |
|
413 if (SUCCEEDED(hr)) { |
|
414 *_retval = true; |
|
415 mHasCommit = true; |
|
416 } |
|
417 |
|
418 return NS_OK; |
|
419 } |
|
420 |
|
421 /* boolean deleteActiveList(); */ |
|
422 NS_IMETHODIMP JumpListBuilder::DeleteActiveList(bool *_retval) |
|
423 { |
|
424 *_retval = false; |
|
425 |
|
426 if (!mJumpListMgr) |
|
427 return NS_ERROR_NOT_AVAILABLE; |
|
428 |
|
429 if(sBuildingList) |
|
430 AbortListBuild(); |
|
431 |
|
432 nsAutoString uid; |
|
433 if (!WinTaskbar::GetAppUserModelID(uid)) |
|
434 return NS_OK; |
|
435 |
|
436 if (SUCCEEDED(mJumpListMgr->DeleteList(uid.get()))) |
|
437 *_retval = true; |
|
438 |
|
439 return NS_OK; |
|
440 } |
|
441 |
|
442 /* internal */ |
|
443 |
|
444 bool JumpListBuilder::IsSeparator(nsCOMPtr<nsIJumpListItem>& item) |
|
445 { |
|
446 int16_t type; |
|
447 item->GetType(&type); |
|
448 if (NS_FAILED(item->GetType(&type))) |
|
449 return false; |
|
450 |
|
451 if (type == nsIJumpListItem::JUMPLIST_ITEM_SEPARATOR) |
|
452 return true; |
|
453 return false; |
|
454 } |
|
455 |
|
456 // TransferIObjectArrayToIMutableArray - used in converting removed items |
|
457 // to our objects. |
|
458 nsresult JumpListBuilder::TransferIObjectArrayToIMutableArray(IObjectArray *objArray, nsIMutableArray *removedItems) |
|
459 { |
|
460 NS_ENSURE_ARG_POINTER(objArray); |
|
461 NS_ENSURE_ARG_POINTER(removedItems); |
|
462 |
|
463 nsresult rv; |
|
464 |
|
465 uint32_t count = 0; |
|
466 objArray->GetCount(&count); |
|
467 |
|
468 nsCOMPtr<nsIJumpListItem> item; |
|
469 |
|
470 for (uint32_t idx = 0; idx < count; idx++) { |
|
471 IShellLinkW * pLink = nullptr; |
|
472 IShellItem * pItem = nullptr; |
|
473 |
|
474 if (SUCCEEDED(objArray->GetAt(idx, IID_IShellLinkW, (LPVOID*)&pLink))) { |
|
475 nsCOMPtr<nsIJumpListShortcut> shortcut = |
|
476 do_CreateInstance(kJumpListShortcutCID, &rv); |
|
477 if (NS_FAILED(rv)) |
|
478 return NS_ERROR_UNEXPECTED; |
|
479 rv = JumpListShortcut::GetJumpListShortcut(pLink, shortcut); |
|
480 item = do_QueryInterface(shortcut); |
|
481 } |
|
482 else if (SUCCEEDED(objArray->GetAt(idx, IID_IShellItem, (LPVOID*)&pItem))) { |
|
483 nsCOMPtr<nsIJumpListLink> link = |
|
484 do_CreateInstance(kJumpListLinkCID, &rv); |
|
485 if (NS_FAILED(rv)) |
|
486 return NS_ERROR_UNEXPECTED; |
|
487 rv = JumpListLink::GetJumpListLink(pItem, link); |
|
488 item = do_QueryInterface(link); |
|
489 } |
|
490 |
|
491 if (pLink) |
|
492 pLink->Release(); |
|
493 if (pItem) |
|
494 pItem->Release(); |
|
495 |
|
496 if (NS_SUCCEEDED(rv)) { |
|
497 removedItems->AppendElement(item, false); |
|
498 } |
|
499 } |
|
500 return NS_OK; |
|
501 } |
|
502 |
|
503 NS_IMETHODIMP JumpListBuilder::Observe(nsISupports* aSubject, |
|
504 const char* aTopic, |
|
505 const char16_t* aData) |
|
506 { |
|
507 if (nsDependentString(aData).EqualsASCII(kPrefTaskbarEnabled)) { |
|
508 bool enabled = Preferences::GetBool(kPrefTaskbarEnabled, true); |
|
509 if (!enabled) { |
|
510 |
|
511 nsCOMPtr<nsIRunnable> event = |
|
512 new mozilla::widget::AsyncDeleteAllFaviconsFromDisk(); |
|
513 mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); |
|
514 } |
|
515 } |
|
516 return NS_OK; |
|
517 } |
|
518 |
|
519 } // namespace widget |
|
520 } // namespace mozilla |
|
521 |