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 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
7 Components.utils.import("resource://gre/modules/Services.jsm");
13 const Cc = Components.classes;
14 const Ci = Components.interfaces;
16 // Stop updating jumplists after some idle time.
17 const IDLE_TIMEOUT_SECONDS = 5 * 60;
20 const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
21 const PREF_TASKBAR_ENABLED = "enabled";
22 const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
23 const PREF_TASKBAR_FREQUENT = "frequent.enabled";
24 const PREF_TASKBAR_RECENT = "recent.enabled";
25 const PREF_TASKBAR_TASKS = "tasks.enabled";
26 const PREF_TASKBAR_REFRESH = "refreshInSeconds";
28 // Hash keys for pendingStatements.
38 this.EXPORTED_SYMBOLS = [
46 XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
47 return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
50 XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
51 return Services.strings
52 .createBundle("chrome://browser/locale/taskbar.properties");
55 XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
56 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
60 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
61 Components.utils.import("resource://gre/modules/NetUtil.jsm");
65 XPCOMUtils.defineLazyServiceGetter(this, "_idle",
66 "@mozilla.org/widget/idleservice;1",
69 XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
70 "@mozilla.org/windows-taskbar;1",
73 XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
74 "@mozilla.org/browser/shell-service;1",
75 "nsIWindowsShellService");
77 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
78 "resource://gre/modules/PrivateBrowsingUtils.jsm");
84 function _getString(name) {
85 return _stringBundle.GetStringFromName(name);
88 /////////////////////////////////////////////////////////////////////////////
89 // Task list configuration data object.
93 * Task configuration options: title, description, args, iconIndex, open, close.
95 * title - Task title displayed in the list. (strings in the table are temp fillers.)
96 * description - Tooltip description on the list item.
97 * args - Command line args to invoke the task.
98 * iconIndex - Optional win icon index into the main application for the
100 * open - Boolean indicates if the command should be visible after the browser opens.
101 * close - Boolean indicates if the command should be visible after the browser closes.
105 get title() _getString("taskbar.tasks.newTab.label"),
106 get description() _getString("taskbar.tasks.newTab.description"),
107 args: "-new-tab about:blank",
108 iconIndex: 3, // New window icon
110 close: true, // The jump list already has an app launch icon, but
111 // we don't always update the list on shutdown.
112 // Thus true for consistency.
117 get title() _getString("taskbar.tasks.newWindow.label"),
118 get description() _getString("taskbar.tasks.newWindow.description"),
120 iconIndex: 2, // New tab icon
122 close: true, // No point, but we don't always update the list on
123 // shutdown. Thus true for consistency.
126 // Open new private window
128 get title() _getString("taskbar.tasks.newPrivateWindow.label"),
129 get description() _getString("taskbar.tasks.newPrivateWindow.description"),
130 args: "-private-window",
131 iconIndex: 4, // Private browsing mode icon
133 close: true, // No point, but we don't always update the list on
134 // shutdown. Thus true for consistency.
138 /////////////////////////////////////////////////////////////////////////////
141 this.WinTaskbarJumpList =
145 _shuttingDown: false,
148 * Startup, shutdown, and update
151 startup: function WTBJL_startup() {
152 // exit if this isn't win7 or higher.
153 if (!this._initTaskbar())
156 // Win shell shortcut maintenance. If we've gone through an update,
157 // this will update any pinned taskbar shortcuts. Not specific to
158 // jump lists, but this was a convienent place to call it.
160 // dev builds may not have helper.exe, ignore failures.
161 this._shortcutMaintenance();
165 // Store our task list config data
166 this._tasks = tasksCfg;
168 // retrieve taskbar related prefs.
169 this._refreshPrefs();
171 // observer for private browsing and our prefs branch
174 // jump list refresh timer
178 update: function WTBJL_update() {
179 // are we disabled via prefs? don't do anything!
183 // do what we came here to do, update the taskbar jumplist
187 _shutdown: function WTBJL__shutdown() {
188 this._shuttingDown = true;
190 // Correctly handle a clear history on shutdown. If there are no
191 // entries be sure to empty all history lists. Luckily Places caches
192 // this value, so it's a pretty fast call.
193 if (!PlacesUtils.history.hasHistoryEntries) {
200 _shortcutMaintenance: function WTBJL__maintenace() {
201 _winShellService.shortcutMaintenance();
207 * @note Async builders must add their mozIStoragePendingStatement to
208 * _pendingStatements object, using a different LIST_TYPE entry for
209 * each statement. Once finished they must remove it and call
210 * commitBuild(). When there will be no more _pendingStatements,
211 * commitBuild() will commit for real.
214 _pendingStatements: {},
215 _hasPendingStatements: function WTBJL__hasPendingStatements() {
216 return Object.keys(this._pendingStatements).length > 0;
219 _buildList: function WTBJL__buildList() {
220 if (this._hasPendingStatements()) {
221 // We were requested to update the list while another update was in
222 // progress, this could happen at shutdown, idle or privatebrowsing.
223 // Abort the current list building.
224 for (let listType in this._pendingStatements) {
225 this._pendingStatements[listType].cancel();
226 delete this._pendingStatements[listType];
228 this._builder.abortListBuild();
231 // anything to build?
232 if (!this._showFrequent && !this._showRecent && !this._showTasks) {
233 // don't leave the last list hanging on the taskbar.
234 this._deleteActiveJumpList();
238 if (!this._startBuild())
244 // Space for frequent items takes priority over recent.
245 if (this._showFrequent)
246 this._buildFrequent();
248 if (this._showRecent)
255 * Taskbar api wrappers
258 _startBuild: function WTBJL__startBuild() {
259 var removedItems = Cc["@mozilla.org/array;1"].
260 createInstance(Ci.nsIMutableArray);
261 this._builder.abortListBuild();
262 if (this._builder.initListBuild(removedItems)) {
263 // Prior to building, delete removed items from history.
264 this._clearHistory(removedItems);
270 _commitBuild: function WTBJL__commitBuild() {
271 if (!this._hasPendingStatements() && !this._builder.commitListBuild()) {
272 this._builder.abortListBuild();
276 _buildTasks: function WTBJL__buildTasks() {
277 var items = Cc["@mozilla.org/array;1"].
278 createInstance(Ci.nsIMutableArray);
279 this._tasks.forEach(function (task) {
280 if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
282 var item = this._getHandlerAppItem(task.title, task.description,
283 task.args, task.iconIndex, null);
284 items.appendElement(item, false);
287 if (items.length > 0)
288 this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
291 _buildCustom: function WTBJL__buildCustom(title, items) {
292 if (items.length > 0)
293 this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
296 _buildFrequent: function WTBJL__buildFrequent() {
297 // If history is empty, just bail out.
298 if (!PlacesUtils.history.hasHistoryEntries) {
302 // Windows supports default frequent and recent lists,
303 // but those depend on internal windows visit tracking
304 // which we don't populate. So we build our own custom
305 // frequent and recent lists using our nav history data.
307 var items = Cc["@mozilla.org/array;1"].
308 createInstance(Ci.nsIMutableArray);
309 // track frequent items so that we don't add them to
311 this._frequentHashList = [];
313 this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
314 Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
318 delete this._pendingStatements[LIST_TYPE.FREQUENT];
319 // The are no more results, build the list.
320 this._buildCustom(_getString("taskbar.frequent.label"), items);
325 let title = aResult.title || aResult.uri;
326 let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
327 let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
329 items.appendElement(shortcut, false);
330 this._frequentHashList.push(aResult.uri);
336 _buildRecent: function WTBJL__buildRecent() {
337 // If history is empty, just bail out.
338 if (!PlacesUtils.history.hasHistoryEntries) {
342 var items = Cc["@mozilla.org/array;1"].
343 createInstance(Ci.nsIMutableArray);
344 // Frequent items will be skipped, so we select a double amount of
345 // entries and stop fetching results at _maxItemCount.
348 this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
349 Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
350 this._maxItemCount * 2,
353 // The are no more results, build the list.
354 this._buildCustom(_getString("taskbar.recent.label"), items);
355 delete this._pendingStatements[LIST_TYPE.RECENT];
360 if (count >= this._maxItemCount) {
364 // Do not add items to recent that have already been added to frequent.
365 if (this._frequentHashList &&
366 this._frequentHashList.indexOf(aResult.uri) != -1) {
370 let title = aResult.title || aResult.uri;
371 let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
372 let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
374 items.appendElement(shortcut, false);
381 _deleteActiveJumpList: function WTBJL__deleteAJL() {
382 this._builder.deleteActiveList();
386 * Jump list item creation helpers
389 _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
392 var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile);
394 var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
395 createInstance(Ci.nsILocalHandlerApp);
396 handlerApp.executable = file;
397 // handlers default to the leaf name if a name is not specified
398 if (name && name.length != 0)
399 handlerApp.name = name;
400 handlerApp.detailedDescription = description;
401 handlerApp.appendParameter(args);
403 var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].
404 createInstance(Ci.nsIJumpListShortcut);
405 item.app = handlerApp;
406 item.iconIndex = iconIndex;
407 item.faviconPageUri = faviconPageUri;
411 _getSeparatorItem: function WTBJL__getSeparatorItem() {
412 var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
413 createInstance(Ci.nsIJumpListSeparator);
418 * Nav history helpers
422 function WTBLJL__getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
423 var options = PlacesUtils.history.getNewQueryOptions();
424 options.maxResults = aLimit;
425 options.sortingMode = aSortingMode;
426 var query = PlacesUtils.history.getNewQuery();
428 // Return the pending statement to the caller, to allow cancelation.
429 return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
430 .asyncExecuteLegacyQueries([query], 1, options, {
431 handleResult: function (aResultSet) {
432 for (let row; (row = aResultSet.getNextRow());) {
434 aCallback.call(aScope,
435 { uri: row.getResultByIndex(1)
436 , title: row.getResultByIndex(2)
441 handleError: function (aError) {
442 Components.utils.reportError(
443 "Async execution error (" + aError.result + "): " + aError.message);
445 handleCompletion: function (aReason) {
446 aCallback.call(WinTaskbarJumpList, null);
451 _clearHistory: function WTBJL__clearHistory(items) {
454 var URIsToRemove = [];
455 var e = items.enumerate();
456 while (e.hasMoreElements()) {
457 let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut);
459 try { // in case we get a bad uri
460 let uriSpec = oldItem.app.getParameter(0);
461 URIsToRemove.push(NetUtil.newURI(uriSpec));
465 if (URIsToRemove.length > 0) {
466 PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
474 _refreshPrefs: function WTBJL__refreshPrefs() {
475 this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
476 this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
477 this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
478 this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
479 this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
483 * Init and shutdown utilities
486 _initTaskbar: function WTBJL__initTaskbar() {
487 this._builder = _taskbarService.createJumpListBuilder();
488 if (!this._builder || !this._builder.available)
494 _initObs: function WTBJL__initObs() {
495 // If the browser is closed while in private browsing mode, the "exit"
496 // notification is fired on quit-application-granted.
497 // History cleanup can happen at profile-change-teardown.
498 Services.obs.addObserver(this, "profile-before-change", false);
499 Services.obs.addObserver(this, "browser:purge-session-history", false);
500 _prefs.addObserver("", this, false);
503 _freeObs: function WTBJL__freeObs() {
504 Services.obs.removeObserver(this, "profile-before-change");
505 Services.obs.removeObserver(this, "browser:purge-session-history");
506 _prefs.removeObserver("", this);
509 _updateTimer: function WTBJL__updateTimer() {
510 if (this._enabled && !this._shuttingDown && !this._timer) {
511 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
512 this._timer.initWithCallback(this,
513 _prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
514 this._timer.TYPE_REPEATING_SLACK);
516 else if ((!this._enabled || this._shuttingDown) && this._timer) {
517 this._timer.cancel();
522 _hasIdleObserver: false,
523 _updateIdleObserver: function WTBJL__updateIdleObserver() {
524 if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
525 _idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
526 this._hasIdleObserver = true;
528 else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) {
529 _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
530 this._hasIdleObserver = false;
534 _free: function WTBJL__free() {
537 this._updateIdleObserver();
538 delete this._builder;
542 * Notification handlers
545 notify: function WTBJL_notify(aTimer) {
546 // Add idle observer on the first notification so it doesn't hit startup.
547 this._updateIdleObserver();
551 observe: function WTBJL_observe(aSubject, aTopic, aData) {
553 case "nsPref:changed":
554 if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED))
555 this._deleteActiveJumpList();
556 this._refreshPrefs();
558 this._updateIdleObserver();
562 case "profile-before-change":
566 case "browser:purge-session-history":
571 this._timer.cancel();