Bug 1890689 Don't pretend to pre-buffer with DynamicResampler r=pehrsons
[gecko.git] / browser / modules / WindowsJumpLists.sys.mjs
bloba4493fc5914a77eb3a20fbcad9d0dcb27f6d1f61
1 /* -*- indent-tabs-mode: nil; js-indent-level: 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 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
8 // Stop updating jumplists after some idle time.
9 const IDLE_TIMEOUT_SECONDS = 5 * 60;
11 // Prefs
12 const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
13 const PREF_TASKBAR_LEGACY_BACKEND = "legacyBackend";
14 const PREF_TASKBAR_ENABLED = "enabled";
15 const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
16 const PREF_TASKBAR_FREQUENT = "frequent.enabled";
17 const PREF_TASKBAR_RECENT = "recent.enabled";
18 const PREF_TASKBAR_TASKS = "tasks.enabled";
19 const PREF_TASKBAR_REFRESH = "refreshInSeconds";
21 // Hash keys for pendingStatements.
22 const LIST_TYPE = {
23   FREQUENT: 0,
24   RECENT: 1,
27 /**
28  * Exports
29  */
31 const lazy = {};
33 /**
34  * Smart getters
35  */
37 ChromeUtils.defineLazyGetter(lazy, "_prefs", function () {
38   return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
39 });
41 ChromeUtils.defineLazyGetter(lazy, "_stringBundle", function () {
42   return Services.strings.createBundle(
43     "chrome://browser/locale/taskbar.properties"
44   );
45 });
47 XPCOMUtils.defineLazyServiceGetter(
48   lazy,
49   "_idle",
50   "@mozilla.org/widget/useridleservice;1",
51   "nsIUserIdleService"
53 XPCOMUtils.defineLazyServiceGetter(
54   lazy,
55   "_taskbarService",
56   "@mozilla.org/windows-taskbar;1",
57   "nsIWinTaskbar"
60 ChromeUtils.defineESModuleGetters(lazy, {
61   PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
62   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
63 });
65 /**
66  * Global functions
67  */
69 function _getString(name) {
70   return lazy._stringBundle.GetStringFromName(name);
73 // Task list configuration data object.
75 var tasksCfg = [
76   /**
77    * Task configuration options: title, description, args, iconIndex, open, close.
78    *
79    * title       - Task title displayed in the list. (strings in the table are temp fillers.)
80    * description - Tooltip description on the list item.
81    * args        - Command line args to invoke the task.
82    * iconIndex   - Optional win icon index into the main application for the
83    *               list item.
84    * open        - Boolean indicates if the command should be visible after the browser opens.
85    * close       - Boolean indicates if the command should be visible after the browser closes.
86    */
87   // Open new tab
88   {
89     get title() {
90       return _getString("taskbar.tasks.newTab.label");
91     },
92     get description() {
93       return _getString("taskbar.tasks.newTab.description");
94     },
95     args: "-new-tab about:blank",
96     iconIndex: 3, // New window icon
97     open: true,
98     close: true, // The jump list already has an app launch icon, but
99     // we don't always update the list on shutdown.
100     // Thus true for consistency.
101   },
103   // Open new window
104   {
105     get title() {
106       return _getString("taskbar.tasks.newWindow.label");
107     },
108     get description() {
109       return _getString("taskbar.tasks.newWindow.description");
110     },
111     args: "-browser",
112     iconIndex: 2, // New tab icon
113     open: true,
114     close: true, // No point, but we don't always update the list on
115     // shutdown. Thus true for consistency.
116   },
119 // Open new private window
120 let privateWindowTask = {
121   get title() {
122     return _getString("taskbar.tasks.newPrivateWindow.label");
123   },
124   get description() {
125     return _getString("taskbar.tasks.newPrivateWindow.description");
126   },
127   args: "-private-window",
128   iconIndex: 4, // Private browsing mode icon
129   open: true,
130   close: true, // No point, but we don't always update the list on
131   // shutdown. Thus true for consistency.
134 // Implementation
136 var Builder = class {
137   constructor(builder) {
138     this._builder = builder;
139     this._tasks = null;
140     this._pendingStatements = {};
141     this._shuttingDown = false;
142     // These are ultimately controlled by prefs, so we disable
143     // everything until is read from there
144     this._showTasks = false;
145     this._showFrequent = false;
146     this._showRecent = false;
147     this._maxItemCount = 0;
148     this._isBuilding = false;
149   }
151   refreshPrefs(showTasks, showFrequent, showRecent, maxItemCount) {
152     this._showTasks = showTasks;
153     this._showFrequent = showFrequent;
154     this._showRecent = showRecent;
155     this._maxItemCount = maxItemCount;
156   }
158   updateShutdownState(shuttingDown) {
159     this._shuttingDown = shuttingDown;
160   }
162   delete() {
163     delete this._builder;
164   }
166   /**
167    * Constructs the tasks and recent history items to display in the JumpList,
168    * and then sends those lists to the nsIJumpListBuilder to be written.
169    *
170    * @returns {Promise<undefined>}
171    *   The Promise resolves once the JumpList has been written, and any
172    *   items that the user remove from the recent history list have been
173    *   removed from Places. The Promise may reject if any part of constructing
174    *   the tasks or sending them to the builder thread failed.
175    */
176   async buildList() {
177     if (!(this._builder instanceof Ci.nsIJumpListBuilder)) {
178       console.error(
179         "Expected nsIJumpListBuilder. The builder is of the wrong type."
180       );
181       return;
182     }
184     // anything to build?
185     if (!this._showFrequent && !this._showRecent && !this._showTasks) {
186       // don't leave the last list hanging on the taskbar.
187       this._deleteActiveJumpList();
188       return;
189     }
191     // Are we in the midst of building an earlier iteration of this list? If
192     // so, bail out. Same if we're shutting down.
193     if (this._isBuilding || this._shuttingDown) {
194       return;
195     }
197     this._isBuilding = true;
199     try {
200       let removedURLs = await this._builder.checkForRemovals();
201       if (removedURLs.length) {
202         await this._clearHistory(removedURLs);
203       }
205       let selfPath = Services.dirsvc.get("XREExeF", Ci.nsIFile).path;
207       let taskDescriptions = [];
209       if (this._showTasks) {
210         taskDescriptions = this._tasks.map(task => {
211           return {
212             title: task.title,
213             description: task.description,
214             path: selfPath,
215             arguments: task.args,
216             fallbackIconIndex: task.iconIndex,
217           };
218         });
219       }
221       let customTitle = "";
222       let customDescriptions = [];
224       if (this._showFrequent) {
225         let conn = await lazy.PlacesUtils.promiseDBConnection();
226         let rows = await conn.executeCached(
227           "SELECT p.url, IFNULL(p.title, p.url) as title " +
228             "FROM moz_places p WHERE p.hidden = 0 " +
229             "AND EXISTS (" +
230             "SELECT id FROM moz_historyvisits WHERE " +
231             "place_id = p.id AND " +
232             "visit_type NOT IN (" +
233             "0, " +
234             `${Ci.nsINavHistoryService.TRANSITION_EMBED}, ` +
235             `${Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK}` +
236             ")" +
237             "LIMIT 1" +
238             ") " +
239             "ORDER BY p.visit_count DESC LIMIT :limit",
240           {
241             limit: this._maxItemCount,
242           }
243         );
245         customDescriptions = rows.map(row => {
246           let uri = Services.io.newURI(row.getResultByName("url"));
247           let iconPath = "";
248           try {
249             iconPath = this._builder.obtainAndCacheFavicon(uri);
250           } catch (e) {
251             // obtainAndCacheFavicon may throw NS_ERROR_NOT_AVAILABLE if the
252             // icon doesn't yet exist on the disk, but has been requested.
253             // That's not fatal, so we'll just let it pass. Any other errors,
254             // however, we'll abort on.
255             if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
256               throw e;
257             }
258           }
260           return {
261             title: row.getResultByName("title"),
262             description: row.getResultByName("title"),
263             path: selfPath,
264             arguments: row.getResultByName("url"),
265             fallbackIconIndex: 1,
266             iconPath,
267           };
268         });
270         customTitle = _getString("taskbar.frequent.label");
271       }
273       if (!this._shuttingDown) {
274         await this._builder.populateJumpList(
275           taskDescriptions,
276           customTitle,
277           customDescriptions
278         );
279       }
280     } catch (e) {
281       console.error("buildList failed: ", e);
282     } finally {
283       this._isBuilding = false;
284     }
285   }
287   /**
288    * Legacy list building
289    *
290    * @note Async builders must add their mozIStoragePendingStatement to
291    *       _pendingStatements object, using a different LIST_TYPE entry for
292    *       each statement. Once finished they must remove it and call
293    *       commitBuild().  When there will be no more _pendingStatements,
294    *       commitBuild() will commit for real.
295    */
297   _hasPendingStatements() {
298     return !!Object.keys(this._pendingStatements).length;
299   }
301   async buildListLegacy() {
302     if (!(this._builder instanceof Ci.nsILegacyJumpListBuilder)) {
303       console.error(
304         "Expected nsILegacyJumpListBuilder. The builder is of the wrong type."
305       );
306       return;
307     }
309     if (
310       (this._showFrequent || this._showRecent) &&
311       this._hasPendingStatements()
312     ) {
313       // We were requested to update the list while another update was in
314       // progress, this could happen at shutdown, idle or privatebrowsing.
315       // Abort the current list building.
316       for (let listType in this._pendingStatements) {
317         this._pendingStatements[listType].cancel();
318         delete this._pendingStatements[listType];
319       }
320       this._builder.abortListBuild();
321     }
323     // anything to build?
324     if (!this._showFrequent && !this._showRecent && !this._showTasks) {
325       // don't leave the last list hanging on the taskbar.
326       this._deleteActiveJumpList();
327       return;
328     }
330     await this._startBuild();
332     if (this._showTasks) {
333       this._buildTasks();
334     }
336     // Space for frequent items takes priority over recent.
337     if (this._showFrequent) {
338       this._buildFrequent();
339     }
341     if (this._showRecent) {
342       this._buildRecent();
343     }
345     this._commitBuild();
346   }
348   /**
349    * Taskbar api wrappers
350    */
352   async _startBuild() {
353     this._builder.abortListBuild();
354     let URIsToRemove = await this._builder.initListBuild();
355     if (URIsToRemove.length) {
356       // Prior to building, delete removed items from history.
357       this._clearHistory(URIsToRemove);
358     }
359   }
361   _commitBuild() {
362     if (
363       (this._showFrequent || this._showRecent) &&
364       this._hasPendingStatements()
365     ) {
366       return;
367     }
369     this._builder.commitListBuild(succeed => {
370       if (!succeed) {
371         this._builder.abortListBuild();
372       }
373     });
374   }
376   _buildTasks() {
377     var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
378     this._tasks.forEach(function (task) {
379       if (
380         (this._shuttingDown && !task.close) ||
381         (!this._shuttingDown && !task.open)
382       ) {
383         return;
384       }
385       var item = this._getHandlerAppItem(
386         task.title,
387         task.description,
388         task.args,
389         task.iconIndex,
390         null
391       );
392       items.appendElement(item);
393     }, this);
395     if (items.length) {
396       this._builder.addListToBuild(
397         this._builder.JUMPLIST_CATEGORY_TASKS,
398         items
399       );
400     }
401   }
403   _buildCustom(title, items) {
404     if (items.length) {
405       this._builder.addListToBuild(
406         this._builder.JUMPLIST_CATEGORY_CUSTOMLIST,
407         items,
408         title
409       );
410     }
411   }
413   _buildFrequent() {
414     // Windows supports default frequent and recent lists,
415     // but those depend on internal windows visit tracking
416     // which we don't populate. So we build our own custom
417     // frequent and recent lists using our nav history data.
419     var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
420     // track frequent items so that we don't add them to
421     // the recent list.
422     this._frequentHashList = [];
424     this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
425       Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
426       this._maxItemCount,
427       function (aResult) {
428         if (!aResult) {
429           delete this._pendingStatements[LIST_TYPE.FREQUENT];
430           // The are no more results, build the list.
431           this._buildCustom(_getString("taskbar.frequent.label"), items);
432           this._commitBuild();
433           return;
434         }
436         let title = aResult.title || aResult.uri;
437         let faviconPageUri = Services.io.newURI(aResult.uri);
438         let shortcut = this._getHandlerAppItem(
439           title,
440           title,
441           aResult.uri,
442           1,
443           faviconPageUri
444         );
445         items.appendElement(shortcut);
446         this._frequentHashList.push(aResult.uri);
447       },
448       this
449     );
450   }
452   _buildRecent() {
453     var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
454     // Frequent items will be skipped, so we select a double amount of
455     // entries and stop fetching results at _maxItemCount.
456     var count = 0;
458     this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
459       Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
460       this._maxItemCount * 2,
461       function (aResult) {
462         if (!aResult) {
463           // The are no more results, build the list.
464           this._buildCustom(_getString("taskbar.recent.label"), items);
465           delete this._pendingStatements[LIST_TYPE.RECENT];
466           this._commitBuild();
467           return;
468         }
470         if (count >= this._maxItemCount) {
471           return;
472         }
474         // Do not add items to recent that have already been added to frequent.
475         if (
476           this._frequentHashList &&
477           this._frequentHashList.includes(aResult.uri)
478         ) {
479           return;
480         }
482         let title = aResult.title || aResult.uri;
483         let faviconPageUri = Services.io.newURI(aResult.uri);
484         let shortcut = this._getHandlerAppItem(
485           title,
486           title,
487           aResult.uri,
488           1,
489           faviconPageUri
490         );
491         items.appendElement(shortcut);
492         count++;
493       },
494       this
495     );
496   }
498   _deleteActiveJumpList() {
499     if (this._builder instanceof Ci.nsIJumpListBuilder) {
500       this._builder.clearJumpList();
501     } else {
502       this._builder.deleteActiveList();
503     }
504   }
506   /**
507    * Jump list item creation helpers
508    */
510   _getHandlerAppItem(name, description, args, iconIndex, faviconPageUri) {
511     var file = Services.dirsvc.get("XREExeF", Ci.nsIFile);
513     var handlerApp = Cc[
514       "@mozilla.org/uriloader/local-handler-app;1"
515     ].createInstance(Ci.nsILocalHandlerApp);
516     handlerApp.executable = file;
517     // handlers default to the leaf name if a name is not specified
518     if (name && name.length) {
519       handlerApp.name = name;
520     }
521     handlerApp.detailedDescription = description;
522     handlerApp.appendParameter(args);
524     var item = Cc[
525       "@mozilla.org/windows-legacyjumplistshortcut;1"
526     ].createInstance(Ci.nsILegacyJumpListShortcut);
527     item.app = handlerApp;
528     item.iconIndex = iconIndex;
529     item.faviconPageUri = faviconPageUri;
530     return item;
531   }
533   /**
534    * Nav history helpers
535    */
537   _getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
538     var options = lazy.PlacesUtils.history.getNewQueryOptions();
539     options.maxResults = aLimit;
540     options.sortingMode = aSortingMode;
541     var query = lazy.PlacesUtils.history.getNewQuery();
543     // Return the pending statement to the caller, to allow cancelation.
544     return lazy.PlacesUtils.history.asyncExecuteLegacyQuery(query, options, {
545       handleResult(aResultSet) {
546         for (let row; (row = aResultSet.getNextRow()); ) {
547           try {
548             aCallback.call(aScope, {
549               uri: row.getResultByIndex(1),
550               title: row.getResultByIndex(2),
551             });
552           } catch (e) {}
553         }
554       },
555       handleError(aError) {
556         console.error(
557           "Async execution error (",
558           aError.result,
559           "): ",
560           aError.message
561         );
562       },
563       handleCompletion() {
564         aCallback.call(aScope, null);
565       },
566     });
567   }
569   /**
570    * Removes URLs from history in Places that the user has requested to clear
571    * from their Jump List. We must do this before recomputing which history
572    * to put into the Jump List, because if we ever include items that have
573    * recently been removed, Windows will not allow us to proceed.
574    * Please see
575    * https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-icustomdestinationlist-beginlist
576    * for more details.
577    *
578    * The returned Promise never rejects, but may report console errors in the
579    * event of removal failure.
580    *
581    * @param {string[]} uriSpecsToRemove
582    *   The URLs to be removed from Places history.
583    * @returns {Promise<undefined>}
584    */
585   _clearHistory(uriSpecsToRemove) {
586     let URIsToRemove = uriSpecsToRemove
587       .map(spec => {
588         try {
589           // in case we get a bad uri
590           return Services.io.newURI(spec);
591         } catch (e) {
592           return null;
593         }
594       })
595       .filter(uri => !!uri);
597     if (URIsToRemove.length) {
598       return lazy.PlacesUtils.history.remove(URIsToRemove).catch(console.error);
599     }
600     return Promise.resolve();
601   }
604 export var WinTaskbarJumpList = {
605   // We build two separate jump lists -- one for the regular Firefox icon
606   // and one for the Private Browsing icon
607   _builder: null,
608   _pbBuilder: null,
609   _builtPb: false,
610   _shuttingDown: false,
611   _useLegacyBackend: true,
613   /**
614    * Startup, shutdown, and update
615    */
617   startup: async function WTBJL_startup() {
618     // We do a one-time startup read of the backend pref here because
619     // we don't want to consider any bugs that occur if the pref is flipped
620     // at runtime. We want the pref flip to only take effect on a restart.
621     this._useLegacyBackend = lazy._prefs.getBoolPref(
622       PREF_TASKBAR_LEGACY_BACKEND
623     );
625     // exit if initting the taskbar failed for some reason.
626     if (!(await this._initTaskbar())) {
627       return;
628     }
630     if (lazy.PrivateBrowsingUtils.enabled) {
631       tasksCfg.push(privateWindowTask);
632     }
633     // Store our task list config data
634     this._builder._tasks = tasksCfg;
635     this._pbBuilder._tasks = tasksCfg;
637     // retrieve taskbar related prefs.
638     this._refreshPrefs();
640     // observer for private browsing and our prefs branch
641     this._initObs();
643     // jump list refresh timer
644     this._updateTimer();
645   },
647   update: function WTBJL_update() {
648     // are we disabled via prefs? don't do anything!
649     if (!this._enabled) {
650       return;
651     }
653     if (this._shuttingDown) {
654       return;
655     }
657     if (this._useLegacyBackend) {
658       // we only need to do this once, but we do it here
659       // to avoid main thread io on startup
660       if (!this._builtPb) {
661         this._pbBuilder.buildListLegacy();
662         this._builtPb = true;
663       }
665       // do what we came here to do, update the taskbar jumplist
666       this._builder.buildListLegacy();
667     } else {
668       this._builder.buildList();
670       // We only ever need to do this once because the private browsing window
671       // jumplist only ever shows the static task list, which never changes,
672       // so it doesn't need to be updated over time.
673       if (!this._builtPb) {
674         this._pbBuilder.buildList();
675         this._builtPb = true;
676       }
677     }
678   },
680   _shutdown: function WTBJL__shutdown() {
681     this._builder.updateShutdownState(true);
682     this._pbBuilder.updateShutdownState(true);
683     this._shuttingDown = true;
684     this._free();
685   },
687   /**
688    * Prefs utilities
689    */
691   _refreshPrefs: function WTBJL__refreshPrefs() {
692     this._enabled = lazy._prefs.getBoolPref(PREF_TASKBAR_ENABLED);
693     var showTasks = lazy._prefs.getBoolPref(PREF_TASKBAR_TASKS);
694     this._builder.refreshPrefs(
695       showTasks,
696       lazy._prefs.getBoolPref(PREF_TASKBAR_FREQUENT),
697       lazy._prefs.getBoolPref(PREF_TASKBAR_RECENT),
698       lazy._prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT)
699     );
700     // showTasks is the only relevant pref for the Private Browsing Jump List
701     // the others are are related to frequent/recent entries, which are
702     // explicitly disabled for it
703     this._pbBuilder.refreshPrefs(showTasks, false, false, 0);
704   },
706   /**
707    * Init and shutdown utilities
708    */
710   _initTaskbar: async function WTBJL__initTaskbar() {
711     let builder;
712     let pbBuilder;
714     if (this._useLegacyBackend) {
715       builder = lazy._taskbarService.createLegacyJumpListBuilder(false);
716       pbBuilder = lazy._taskbarService.createLegacyJumpListBuilder(true);
717       if (
718         !builder ||
719         !builder.available ||
720         !pbBuilder ||
721         !pbBuilder.available
722       ) {
723         return false;
724       }
725     } else {
726       builder = lazy._taskbarService.createJumpListBuilder(false);
727       pbBuilder = lazy._taskbarService.createJumpListBuilder(true);
728       if (!builder || !pbBuilder) {
729         return false;
730       }
731       let [builderAvailable, pbBuilderAvailable] = await Promise.all([
732         builder.isAvailable(),
733         pbBuilder.isAvailable(),
734       ]);
735       if (!builderAvailable || !pbBuilderAvailable) {
736         return false;
737       }
738     }
740     this._builder = new Builder(builder);
741     this._pbBuilder = new Builder(pbBuilder);
743     return true;
744   },
746   _initObs: function WTBJL__initObs() {
747     // If the browser is closed while in private browsing mode, the "exit"
748     // notification is fired on quit-application-granted.
749     // History cleanup can happen at profile-change-teardown.
750     Services.obs.addObserver(this, "profile-before-change");
751     Services.obs.addObserver(this, "browser:purge-session-history");
752     lazy._prefs.addObserver("", this);
753     this._placesObserver = new PlacesWeakCallbackWrapper(
754       this.update.bind(this)
755     );
756     lazy.PlacesUtils.observers.addListener(
757       ["history-cleared"],
758       this._placesObserver
759     );
760   },
762   _freeObs: function WTBJL__freeObs() {
763     Services.obs.removeObserver(this, "profile-before-change");
764     Services.obs.removeObserver(this, "browser:purge-session-history");
765     lazy._prefs.removeObserver("", this);
766     if (this._placesObserver) {
767       lazy.PlacesUtils.observers.removeListener(
768         ["history-cleared"],
769         this._placesObserver
770       );
771     }
772   },
774   _updateTimer: function WTBJL__updateTimer() {
775     if (this._enabled && !this._shuttingDown && !this._timer) {
776       this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
777       this._timer.initWithCallback(
778         this,
779         lazy._prefs.getIntPref(PREF_TASKBAR_REFRESH) * 1000,
780         this._timer.TYPE_REPEATING_SLACK
781       );
782     } else if ((!this._enabled || this._shuttingDown) && this._timer) {
783       this._timer.cancel();
784       delete this._timer;
785     }
786   },
788   _hasIdleObserver: false,
789   _updateIdleObserver: function WTBJL__updateIdleObserver() {
790     if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
791       lazy._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
792       this._hasIdleObserver = true;
793     } else if (
794       (!this._enabled || this._shuttingDown) &&
795       this._hasIdleObserver
796     ) {
797       lazy._idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
798       this._hasIdleObserver = false;
799     }
800   },
802   _free: function WTBJL__free() {
803     this._freeObs();
804     this._updateTimer();
805     this._updateIdleObserver();
806     this._builder.delete();
807     this._pbBuilder.delete();
808   },
810   QueryInterface: ChromeUtils.generateQI([
811     "nsINamed",
812     "nsIObserver",
813     "nsITimerCallback",
814   ]),
816   name: "WinTaskbarJumpList",
818   notify: function WTBJL_notify() {
819     // Add idle observer on the first notification so it doesn't hit startup.
820     this._updateIdleObserver();
821     Services.tm.idleDispatchToMainThread(() => {
822       this.update();
823     });
824   },
826   observe: function WTBJL_observe(aSubject, aTopic) {
827     switch (aTopic) {
828       case "nsPref:changed":
829         if (this._enabled && !lazy._prefs.getBoolPref(PREF_TASKBAR_ENABLED)) {
830           this._deleteActiveJumpList();
831         }
832         this._refreshPrefs();
833         this._updateTimer();
834         this._updateIdleObserver();
835         Services.tm.idleDispatchToMainThread(() => {
836           this.update();
837         });
838         break;
840       case "profile-before-change":
841         this._shutdown();
842         break;
844       case "browser:purge-session-history":
845         this.update();
846         break;
847       case "idle":
848         if (this._timer) {
849           this._timer.cancel();
850           delete this._timer;
851         }
852         break;
854       case "active":
855         this._updateTimer();
856         break;
857     }
858   },