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 const { XPCOMUtils } = ChromeUtils.import(
7 "resource://gre/modules/XPCOMUtils.jsm"
9 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
11 // Stop updating jumplists after some idle time.
12 const IDLE_TIMEOUT_SECONDS = 5 * 60;
15 const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
16 const PREF_TASKBAR_ENABLED = "enabled";
17 const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
18 const PREF_TASKBAR_FREQUENT = "frequent.enabled";
19 const PREF_TASKBAR_RECENT = "recent.enabled";
20 const PREF_TASKBAR_TASKS = "tasks.enabled";
21 const PREF_TASKBAR_REFRESH = "refreshInSeconds";
23 // Hash keys for pendingStatements.
33 var EXPORTED_SYMBOLS = ["WinTaskbarJumpList"];
39 XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
40 return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
43 XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
44 return Services.strings.createBundle(
45 "chrome://browser/locale/taskbar.properties"
49 XPCOMUtils.defineLazyServiceGetter(
52 "@mozilla.org/widget/useridleservice;1",
55 XPCOMUtils.defineLazyServiceGetter(
58 "@mozilla.org/windows-taskbar;1",
62 ChromeUtils.defineModuleGetter(
65 "resource://gre/modules/PlacesUtils.jsm"
67 ChromeUtils.defineModuleGetter(
69 "PrivateBrowsingUtils",
70 "resource://gre/modules/PrivateBrowsingUtils.jsm"
73 XPCOMUtils.defineLazyGetter(this, "gHistoryObserver", function() {
74 return Object.freeze({
76 WinTaskbarJumpList.update();
78 onBeginUpdateBatch() {},
79 onEndUpdateBatch() {},
82 onFrecencyChanged() {},
83 onManyFrecenciesChanged() {},
87 QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
95 function _getString(name) {
96 return _stringBundle.GetStringFromName(name);
99 // Task list configuration data object.
103 * Task configuration options: title, description, args, iconIndex, open, close.
105 * title - Task title displayed in the list. (strings in the table are temp fillers.)
106 * description - Tooltip description on the list item.
107 * args - Command line args to invoke the task.
108 * iconIndex - Optional win icon index into the main application for the
110 * open - Boolean indicates if the command should be visible after the browser opens.
111 * close - Boolean indicates if the command should be visible after the browser closes.
116 return _getString("taskbar.tasks.newTab.label");
119 return _getString("taskbar.tasks.newTab.description");
121 args: "-new-tab about:blank",
122 iconIndex: 3, // New window icon
124 close: true, // The jump list already has an app launch icon, but
125 // we don't always update the list on shutdown.
126 // Thus true for consistency.
132 return _getString("taskbar.tasks.newWindow.label");
135 return _getString("taskbar.tasks.newWindow.description");
138 iconIndex: 2, // New tab icon
140 close: true, // No point, but we don't always update the list on
141 // shutdown. Thus true for consistency.
145 // Open new private window
146 let privateWindowTask = {
148 return _getString("taskbar.tasks.newPrivateWindow.label");
151 return _getString("taskbar.tasks.newPrivateWindow.description");
153 args: "-private-window",
154 iconIndex: 4, // Private browsing mode icon
156 close: true, // No point, but we don't always update the list on
157 // shutdown. Thus true for consistency.
162 var WinTaskbarJumpList = {
165 _shuttingDown: false,
168 * Startup, shutdown, and update
171 startup: function WTBJL_startup() {
172 // exit if this isn't win7 or higher.
173 if (!this._initTaskbar()) {
177 // Store our task list config data
178 this._tasks = tasksCfg;
180 if (PrivateBrowsingUtils.enabled) {
181 tasksCfg.push(privateWindowTask);
184 // retrieve taskbar related prefs.
185 this._refreshPrefs();
187 // observer for private browsing and our prefs branch
190 // jump list refresh timer
194 update: function WTBJL_update() {
195 // are we disabled via prefs? don't do anything!
196 if (!this._enabled) {
200 // do what we came here to do, update the taskbar jumplist
204 _shutdown: function WTBJL__shutdown() {
205 this._shuttingDown = true;
212 * @note Async builders must add their mozIStoragePendingStatement to
213 * _pendingStatements object, using a different LIST_TYPE entry for
214 * each statement. Once finished they must remove it and call
215 * commitBuild(). When there will be no more _pendingStatements,
216 * commitBuild() will commit for real.
219 _pendingStatements: {},
220 _hasPendingStatements: function WTBJL__hasPendingStatements() {
221 return !!Object.keys(this._pendingStatements).length;
225 if (this._hasPendingStatements()) {
226 // We were requested to update the list while another update was in
227 // progress, this could happen at shutdown, idle or privatebrowsing.
228 // Abort the current list building.
229 for (let listType in this._pendingStatements) {
230 this._pendingStatements[listType].cancel();
231 delete this._pendingStatements[listType];
233 this._builder.abortListBuild();
236 // anything to build?
237 if (!this._showFrequent && !this._showRecent && !this._showTasks) {
238 // don't leave the last list hanging on the taskbar.
239 this._deleteActiveJumpList();
243 await this._startBuild();
245 if (this._showTasks) {
249 // Space for frequent items takes priority over recent.
250 if (this._showFrequent) {
251 this._buildFrequent();
254 if (this._showRecent) {
262 * Taskbar api wrappers
265 async _startBuild() {
266 this._builder.abortListBuild();
267 let URIsToRemove = await this._builder.initListBuild();
268 if (URIsToRemove.length) {
269 // Prior to building, delete removed items from history.
270 this._clearHistory(URIsToRemove);
274 _commitBuild: function WTBJL__commitBuild() {
275 if (this._hasPendingStatements()) {
279 this._builder.commitListBuild(succeed => {
281 this._builder.abortListBuild();
286 _buildTasks: function WTBJL__buildTasks() {
287 var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
288 this._tasks.forEach(function(task) {
290 (this._shuttingDown && !task.close) ||
291 (!this._shuttingDown && !task.open)
295 var item = this._getHandlerAppItem(
302 items.appendElement(item);
306 this._builder.addListToBuild(
307 this._builder.JUMPLIST_CATEGORY_TASKS,
313 _buildCustom: function WTBJL__buildCustom(title, items) {
315 this._builder.addListToBuild(
316 this._builder.JUMPLIST_CATEGORY_CUSTOMLIST,
323 _buildFrequent: function WTBJL__buildFrequent() {
324 // Windows supports default frequent and recent lists,
325 // but those depend on internal windows visit tracking
326 // which we don't populate. So we build our own custom
327 // frequent and recent lists using our nav history data.
329 var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
330 // track frequent items so that we don't add them to
332 this._frequentHashList = [];
334 this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
335 Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
339 delete this._pendingStatements[LIST_TYPE.FREQUENT];
340 // The are no more results, build the list.
341 this._buildCustom(_getString("taskbar.frequent.label"), items);
346 let title = aResult.title || aResult.uri;
347 let faviconPageUri = Services.io.newURI(aResult.uri);
348 let shortcut = this._getHandlerAppItem(
355 items.appendElement(shortcut);
356 this._frequentHashList.push(aResult.uri);
362 _buildRecent: function WTBJL__buildRecent() {
363 var items = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
364 // Frequent items will be skipped, so we select a double amount of
365 // entries and stop fetching results at _maxItemCount.
368 this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
369 Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
370 this._maxItemCount * 2,
373 // The are no more results, build the list.
374 this._buildCustom(_getString("taskbar.recent.label"), items);
375 delete this._pendingStatements[LIST_TYPE.RECENT];
380 if (count >= this._maxItemCount) {
384 // Do not add items to recent that have already been added to frequent.
386 this._frequentHashList &&
387 this._frequentHashList.includes(aResult.uri)
392 let title = aResult.title || aResult.uri;
393 let faviconPageUri = Services.io.newURI(aResult.uri);
394 let shortcut = this._getHandlerAppItem(
401 items.appendElement(shortcut);
408 _deleteActiveJumpList: function WTBJL__deleteAJL() {
409 this._builder.deleteActiveList();
413 * Jump list item creation helpers
416 _getHandlerAppItem: function WTBJL__getHandlerAppItem(
423 var file = Services.dirsvc.get("XREExeF", Ci.nsIFile);
426 "@mozilla.org/uriloader/local-handler-app;1"
427 ].createInstance(Ci.nsILocalHandlerApp);
428 handlerApp.executable = file;
429 // handlers default to the leaf name if a name is not specified
430 if (name && name.length) {
431 handlerApp.name = name;
433 handlerApp.detailedDescription = description;
434 handlerApp.appendParameter(args);
436 var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].createInstance(
437 Ci.nsIJumpListShortcut
439 item.app = handlerApp;
440 item.iconIndex = iconIndex;
441 item.faviconPageUri = faviconPageUri;
445 _getSeparatorItem: function WTBJL__getSeparatorItem() {
446 var item = Cc["@mozilla.org/windows-jumplistseparator;1"].createInstance(
447 Ci.nsIJumpListSeparator
453 * Nav history helpers
456 _getHistoryResults: function WTBLJL__getHistoryResults(
462 var options = PlacesUtils.history.getNewQueryOptions();
463 options.maxResults = aLimit;
464 options.sortingMode = aSortingMode;
465 var query = PlacesUtils.history.getNewQuery();
467 // Return the pending statement to the caller, to allow cancelation.
468 return PlacesUtils.history.asyncExecuteLegacyQuery(query, options, {
469 handleResult(aResultSet) {
470 for (let row; (row = aResultSet.getNextRow()); ) {
472 aCallback.call(aScope, {
473 uri: row.getResultByIndex(1),
474 title: row.getResultByIndex(2),
479 handleError(aError) {
481 "Async execution error (" + aError.result + "): " + aError.message
484 handleCompletion(aReason) {
485 aCallback.call(WinTaskbarJumpList, null);
490 _clearHistory: function WTBJL__clearHistory(uriSpecsToRemove) {
491 let URIsToRemove = uriSpecsToRemove
494 // in case we get a bad uri
495 return Services.io.newURI(spec);
500 .filter(uri => !!uri);
502 if (URIsToRemove.length) {
503 PlacesUtils.history.remove(URIsToRemove).catch(Cu.reportError);
511 _refreshPrefs: function WTBJL__refreshPrefs() {
512 this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
513 this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
514 this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
515 this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
516 this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
520 * Init and shutdown utilities
523 _initTaskbar: function WTBJL__initTaskbar() {
524 this._builder = _taskbarService.createJumpListBuilder();
525 if (!this._builder || !this._builder.available) {
532 _initObs: function WTBJL__initObs() {
533 // If the browser is closed while in private browsing mode, the "exit"
534 // notification is fired on quit-application-granted.
535 // History cleanup can happen at profile-change-teardown.
536 Services.obs.addObserver(this, "profile-before-change");
537 Services.obs.addObserver(this, "browser:purge-session-history");
538 _prefs.addObserver("", this);
539 PlacesUtils.history.addObserver(gHistoryObserver, false);
542 _freeObs: function WTBJL__freeObs() {
543 Services.obs.removeObserver(this, "profile-before-change");
544 Services.obs.removeObserver(this, "browser:purge-session-history");
545 _prefs.removeObserver("", this);
546 PlacesUtils.history.removeObserver(gHistoryObserver);
549 _updateTimer: function WTBJL__updateTimer() {
550 if (this._enabled && !this._shuttingDown && !this._timer) {
551 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
552 this._timer.initWithCallback(
554 _prefs.getIntPref(PREF_TASKBAR_REFRESH) * 1000,
555 this._timer.TYPE_REPEATING_SLACK
557 } else if ((!this._enabled || this._shuttingDown) && this._timer) {
558 this._timer.cancel();
563 _hasIdleObserver: false,
564 _updateIdleObserver: function WTBJL__updateIdleObserver() {
565 if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
566 _idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
567 this._hasIdleObserver = true;
569 (!this._enabled || this._shuttingDown) &&
570 this._hasIdleObserver
572 _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
573 this._hasIdleObserver = false;
577 _free: function WTBJL__free() {
580 this._updateIdleObserver();
581 delete this._builder;
585 * Notification handlers
588 notify: function WTBJL_notify(aTimer) {
589 // Add idle observer on the first notification so it doesn't hit startup.
590 this._updateIdleObserver();
591 Services.tm.idleDispatchToMainThread(() => {
596 observe: function WTBJL_observe(aSubject, aTopic, aData) {
598 case "nsPref:changed":
599 if (this._enabled && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED)) {
600 this._deleteActiveJumpList();
602 this._refreshPrefs();
604 this._updateIdleObserver();
605 Services.tm.idleDispatchToMainThread(() => {
610 case "profile-before-change":
614 case "browser:purge-session-history":
619 this._timer.cancel();