1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/modules/WindowsJumpLists.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,581 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.10 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.11 + 1.12 +/** 1.13 + * Constants 1.14 + */ 1.15 + 1.16 +const Cc = Components.classes; 1.17 +const Ci = Components.interfaces; 1.18 + 1.19 +// Stop updating jumplists after some idle time. 1.20 +const IDLE_TIMEOUT_SECONDS = 5 * 60; 1.21 + 1.22 +// Prefs 1.23 +const PREF_TASKBAR_BRANCH = "browser.taskbar.lists."; 1.24 +const PREF_TASKBAR_ENABLED = "enabled"; 1.25 +const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount"; 1.26 +const PREF_TASKBAR_FREQUENT = "frequent.enabled"; 1.27 +const PREF_TASKBAR_RECENT = "recent.enabled"; 1.28 +const PREF_TASKBAR_TASKS = "tasks.enabled"; 1.29 +const PREF_TASKBAR_REFRESH = "refreshInSeconds"; 1.30 + 1.31 +// Hash keys for pendingStatements. 1.32 +const LIST_TYPE = { 1.33 + FREQUENT: 0 1.34 +, RECENT: 1 1.35 +} 1.36 + 1.37 +/** 1.38 + * Exports 1.39 + */ 1.40 + 1.41 +this.EXPORTED_SYMBOLS = [ 1.42 + "WinTaskbarJumpList", 1.43 +]; 1.44 + 1.45 +/** 1.46 + * Smart getters 1.47 + */ 1.48 + 1.49 +XPCOMUtils.defineLazyGetter(this, "_prefs", function() { 1.50 + return Services.prefs.getBranch(PREF_TASKBAR_BRANCH); 1.51 +}); 1.52 + 1.53 +XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() { 1.54 + return Services.strings 1.55 + .createBundle("chrome://browser/locale/taskbar.properties"); 1.56 +}); 1.57 + 1.58 +XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() { 1.59 + Components.utils.import("resource://gre/modules/PlacesUtils.jsm"); 1.60 + return PlacesUtils; 1.61 +}); 1.62 + 1.63 +XPCOMUtils.defineLazyGetter(this, "NetUtil", function() { 1.64 + Components.utils.import("resource://gre/modules/NetUtil.jsm"); 1.65 + return NetUtil; 1.66 +}); 1.67 + 1.68 +XPCOMUtils.defineLazyServiceGetter(this, "_idle", 1.69 + "@mozilla.org/widget/idleservice;1", 1.70 + "nsIIdleService"); 1.71 + 1.72 +XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService", 1.73 + "@mozilla.org/windows-taskbar;1", 1.74 + "nsIWinTaskbar"); 1.75 + 1.76 +XPCOMUtils.defineLazyServiceGetter(this, "_winShellService", 1.77 + "@mozilla.org/browser/shell-service;1", 1.78 + "nsIWindowsShellService"); 1.79 + 1.80 +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", 1.81 + "resource://gre/modules/PrivateBrowsingUtils.jsm"); 1.82 + 1.83 +/** 1.84 + * Global functions 1.85 + */ 1.86 + 1.87 +function _getString(name) { 1.88 + return _stringBundle.GetStringFromName(name); 1.89 +} 1.90 + 1.91 +///////////////////////////////////////////////////////////////////////////// 1.92 +// Task list configuration data object. 1.93 + 1.94 +var tasksCfg = [ 1.95 + /** 1.96 + * Task configuration options: title, description, args, iconIndex, open, close. 1.97 + * 1.98 + * title - Task title displayed in the list. (strings in the table are temp fillers.) 1.99 + * description - Tooltip description on the list item. 1.100 + * args - Command line args to invoke the task. 1.101 + * iconIndex - Optional win icon index into the main application for the 1.102 + * list item. 1.103 + * open - Boolean indicates if the command should be visible after the browser opens. 1.104 + * close - Boolean indicates if the command should be visible after the browser closes. 1.105 + */ 1.106 + // Open new tab 1.107 + { 1.108 + get title() _getString("taskbar.tasks.newTab.label"), 1.109 + get description() _getString("taskbar.tasks.newTab.description"), 1.110 + args: "-new-tab about:blank", 1.111 + iconIndex: 3, // New window icon 1.112 + open: true, 1.113 + close: true, // The jump list already has an app launch icon, but 1.114 + // we don't always update the list on shutdown. 1.115 + // Thus true for consistency. 1.116 + }, 1.117 + 1.118 + // Open new window 1.119 + { 1.120 + get title() _getString("taskbar.tasks.newWindow.label"), 1.121 + get description() _getString("taskbar.tasks.newWindow.description"), 1.122 + args: "-browser", 1.123 + iconIndex: 2, // New tab icon 1.124 + open: true, 1.125 + close: true, // No point, but we don't always update the list on 1.126 + // shutdown. Thus true for consistency. 1.127 + }, 1.128 + 1.129 + // Open new private window 1.130 + { 1.131 + get title() _getString("taskbar.tasks.newPrivateWindow.label"), 1.132 + get description() _getString("taskbar.tasks.newPrivateWindow.description"), 1.133 + args: "-private-window", 1.134 + iconIndex: 4, // Private browsing mode icon 1.135 + open: true, 1.136 + close: true, // No point, but we don't always update the list on 1.137 + // shutdown. Thus true for consistency. 1.138 + }, 1.139 +]; 1.140 + 1.141 +///////////////////////////////////////////////////////////////////////////// 1.142 +// Implementation 1.143 + 1.144 +this.WinTaskbarJumpList = 1.145 +{ 1.146 + _builder: null, 1.147 + _tasks: null, 1.148 + _shuttingDown: false, 1.149 + 1.150 + /** 1.151 + * Startup, shutdown, and update 1.152 + */ 1.153 + 1.154 + startup: function WTBJL_startup() { 1.155 + // exit if this isn't win7 or higher. 1.156 + if (!this._initTaskbar()) 1.157 + return; 1.158 + 1.159 + // Win shell shortcut maintenance. If we've gone through an update, 1.160 + // this will update any pinned taskbar shortcuts. Not specific to 1.161 + // jump lists, but this was a convienent place to call it. 1.162 + try { 1.163 + // dev builds may not have helper.exe, ignore failures. 1.164 + this._shortcutMaintenance(); 1.165 + } catch (ex) { 1.166 + } 1.167 + 1.168 + // Store our task list config data 1.169 + this._tasks = tasksCfg; 1.170 + 1.171 + // retrieve taskbar related prefs. 1.172 + this._refreshPrefs(); 1.173 + 1.174 + // observer for private browsing and our prefs branch 1.175 + this._initObs(); 1.176 + 1.177 + // jump list refresh timer 1.178 + this._updateTimer(); 1.179 + }, 1.180 + 1.181 + update: function WTBJL_update() { 1.182 + // are we disabled via prefs? don't do anything! 1.183 + if (!this._enabled) 1.184 + return; 1.185 + 1.186 + // do what we came here to do, update the taskbar jumplist 1.187 + this._buildList(); 1.188 + }, 1.189 + 1.190 + _shutdown: function WTBJL__shutdown() { 1.191 + this._shuttingDown = true; 1.192 + 1.193 + // Correctly handle a clear history on shutdown. If there are no 1.194 + // entries be sure to empty all history lists. Luckily Places caches 1.195 + // this value, so it's a pretty fast call. 1.196 + if (!PlacesUtils.history.hasHistoryEntries) { 1.197 + this.update(); 1.198 + } 1.199 + 1.200 + this._free(); 1.201 + }, 1.202 + 1.203 + _shortcutMaintenance: function WTBJL__maintenace() { 1.204 + _winShellService.shortcutMaintenance(); 1.205 + }, 1.206 + 1.207 + /** 1.208 + * List building 1.209 + * 1.210 + * @note Async builders must add their mozIStoragePendingStatement to 1.211 + * _pendingStatements object, using a different LIST_TYPE entry for 1.212 + * each statement. Once finished they must remove it and call 1.213 + * commitBuild(). When there will be no more _pendingStatements, 1.214 + * commitBuild() will commit for real. 1.215 + */ 1.216 + 1.217 + _pendingStatements: {}, 1.218 + _hasPendingStatements: function WTBJL__hasPendingStatements() { 1.219 + return Object.keys(this._pendingStatements).length > 0; 1.220 + }, 1.221 + 1.222 + _buildList: function WTBJL__buildList() { 1.223 + if (this._hasPendingStatements()) { 1.224 + // We were requested to update the list while another update was in 1.225 + // progress, this could happen at shutdown, idle or privatebrowsing. 1.226 + // Abort the current list building. 1.227 + for (let listType in this._pendingStatements) { 1.228 + this._pendingStatements[listType].cancel(); 1.229 + delete this._pendingStatements[listType]; 1.230 + } 1.231 + this._builder.abortListBuild(); 1.232 + } 1.233 + 1.234 + // anything to build? 1.235 + if (!this._showFrequent && !this._showRecent && !this._showTasks) { 1.236 + // don't leave the last list hanging on the taskbar. 1.237 + this._deleteActiveJumpList(); 1.238 + return; 1.239 + } 1.240 + 1.241 + if (!this._startBuild()) 1.242 + return; 1.243 + 1.244 + if (this._showTasks) 1.245 + this._buildTasks(); 1.246 + 1.247 + // Space for frequent items takes priority over recent. 1.248 + if (this._showFrequent) 1.249 + this._buildFrequent(); 1.250 + 1.251 + if (this._showRecent) 1.252 + this._buildRecent(); 1.253 + 1.254 + this._commitBuild(); 1.255 + }, 1.256 + 1.257 + /** 1.258 + * Taskbar api wrappers 1.259 + */ 1.260 + 1.261 + _startBuild: function WTBJL__startBuild() { 1.262 + var removedItems = Cc["@mozilla.org/array;1"]. 1.263 + createInstance(Ci.nsIMutableArray); 1.264 + this._builder.abortListBuild(); 1.265 + if (this._builder.initListBuild(removedItems)) { 1.266 + // Prior to building, delete removed items from history. 1.267 + this._clearHistory(removedItems); 1.268 + return true; 1.269 + } 1.270 + return false; 1.271 + }, 1.272 + 1.273 + _commitBuild: function WTBJL__commitBuild() { 1.274 + if (!this._hasPendingStatements() && !this._builder.commitListBuild()) { 1.275 + this._builder.abortListBuild(); 1.276 + } 1.277 + }, 1.278 + 1.279 + _buildTasks: function WTBJL__buildTasks() { 1.280 + var items = Cc["@mozilla.org/array;1"]. 1.281 + createInstance(Ci.nsIMutableArray); 1.282 + this._tasks.forEach(function (task) { 1.283 + if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open)) 1.284 + return; 1.285 + var item = this._getHandlerAppItem(task.title, task.description, 1.286 + task.args, task.iconIndex, null); 1.287 + items.appendElement(item, false); 1.288 + }, this); 1.289 + 1.290 + if (items.length > 0) 1.291 + this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items); 1.292 + }, 1.293 + 1.294 + _buildCustom: function WTBJL__buildCustom(title, items) { 1.295 + if (items.length > 0) 1.296 + this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title); 1.297 + }, 1.298 + 1.299 + _buildFrequent: function WTBJL__buildFrequent() { 1.300 + // If history is empty, just bail out. 1.301 + if (!PlacesUtils.history.hasHistoryEntries) { 1.302 + return; 1.303 + } 1.304 + 1.305 + // Windows supports default frequent and recent lists, 1.306 + // but those depend on internal windows visit tracking 1.307 + // which we don't populate. So we build our own custom 1.308 + // frequent and recent lists using our nav history data. 1.309 + 1.310 + var items = Cc["@mozilla.org/array;1"]. 1.311 + createInstance(Ci.nsIMutableArray); 1.312 + // track frequent items so that we don't add them to 1.313 + // the recent list. 1.314 + this._frequentHashList = []; 1.315 + 1.316 + this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults( 1.317 + Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING, 1.318 + this._maxItemCount, 1.319 + function (aResult) { 1.320 + if (!aResult) { 1.321 + delete this._pendingStatements[LIST_TYPE.FREQUENT]; 1.322 + // The are no more results, build the list. 1.323 + this._buildCustom(_getString("taskbar.frequent.label"), items); 1.324 + this._commitBuild(); 1.325 + return; 1.326 + } 1.327 + 1.328 + let title = aResult.title || aResult.uri; 1.329 + let faviconPageUri = Services.io.newURI(aResult.uri, null, null); 1.330 + let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1, 1.331 + faviconPageUri); 1.332 + items.appendElement(shortcut, false); 1.333 + this._frequentHashList.push(aResult.uri); 1.334 + }, 1.335 + this 1.336 + ); 1.337 + }, 1.338 + 1.339 + _buildRecent: function WTBJL__buildRecent() { 1.340 + // If history is empty, just bail out. 1.341 + if (!PlacesUtils.history.hasHistoryEntries) { 1.342 + return; 1.343 + } 1.344 + 1.345 + var items = Cc["@mozilla.org/array;1"]. 1.346 + createInstance(Ci.nsIMutableArray); 1.347 + // Frequent items will be skipped, so we select a double amount of 1.348 + // entries and stop fetching results at _maxItemCount. 1.349 + var count = 0; 1.350 + 1.351 + this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults( 1.352 + Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING, 1.353 + this._maxItemCount * 2, 1.354 + function (aResult) { 1.355 + if (!aResult) { 1.356 + // The are no more results, build the list. 1.357 + this._buildCustom(_getString("taskbar.recent.label"), items); 1.358 + delete this._pendingStatements[LIST_TYPE.RECENT]; 1.359 + this._commitBuild(); 1.360 + return; 1.361 + } 1.362 + 1.363 + if (count >= this._maxItemCount) { 1.364 + return; 1.365 + } 1.366 + 1.367 + // Do not add items to recent that have already been added to frequent. 1.368 + if (this._frequentHashList && 1.369 + this._frequentHashList.indexOf(aResult.uri) != -1) { 1.370 + return; 1.371 + } 1.372 + 1.373 + let title = aResult.title || aResult.uri; 1.374 + let faviconPageUri = Services.io.newURI(aResult.uri, null, null); 1.375 + let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1, 1.376 + faviconPageUri); 1.377 + items.appendElement(shortcut, false); 1.378 + count++; 1.379 + }, 1.380 + this 1.381 + ); 1.382 + }, 1.383 + 1.384 + _deleteActiveJumpList: function WTBJL__deleteAJL() { 1.385 + this._builder.deleteActiveList(); 1.386 + }, 1.387 + 1.388 + /** 1.389 + * Jump list item creation helpers 1.390 + */ 1.391 + 1.392 + _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description, 1.393 + args, iconIndex, 1.394 + faviconPageUri) { 1.395 + var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile); 1.396 + 1.397 + var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]. 1.398 + createInstance(Ci.nsILocalHandlerApp); 1.399 + handlerApp.executable = file; 1.400 + // handlers default to the leaf name if a name is not specified 1.401 + if (name && name.length != 0) 1.402 + handlerApp.name = name; 1.403 + handlerApp.detailedDescription = description; 1.404 + handlerApp.appendParameter(args); 1.405 + 1.406 + var item = Cc["@mozilla.org/windows-jumplistshortcut;1"]. 1.407 + createInstance(Ci.nsIJumpListShortcut); 1.408 + item.app = handlerApp; 1.409 + item.iconIndex = iconIndex; 1.410 + item.faviconPageUri = faviconPageUri; 1.411 + return item; 1.412 + }, 1.413 + 1.414 + _getSeparatorItem: function WTBJL__getSeparatorItem() { 1.415 + var item = Cc["@mozilla.org/windows-jumplistseparator;1"]. 1.416 + createInstance(Ci.nsIJumpListSeparator); 1.417 + return item; 1.418 + }, 1.419 + 1.420 + /** 1.421 + * Nav history helpers 1.422 + */ 1.423 + 1.424 + _getHistoryResults: 1.425 + function WTBLJL__getHistoryResults(aSortingMode, aLimit, aCallback, aScope) { 1.426 + var options = PlacesUtils.history.getNewQueryOptions(); 1.427 + options.maxResults = aLimit; 1.428 + options.sortingMode = aSortingMode; 1.429 + var query = PlacesUtils.history.getNewQuery(); 1.430 + 1.431 + // Return the pending statement to the caller, to allow cancelation. 1.432 + return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) 1.433 + .asyncExecuteLegacyQueries([query], 1, options, { 1.434 + handleResult: function (aResultSet) { 1.435 + for (let row; (row = aResultSet.getNextRow());) { 1.436 + try { 1.437 + aCallback.call(aScope, 1.438 + { uri: row.getResultByIndex(1) 1.439 + , title: row.getResultByIndex(2) 1.440 + }); 1.441 + } catch (e) {} 1.442 + } 1.443 + }, 1.444 + handleError: function (aError) { 1.445 + Components.utils.reportError( 1.446 + "Async execution error (" + aError.result + "): " + aError.message); 1.447 + }, 1.448 + handleCompletion: function (aReason) { 1.449 + aCallback.call(WinTaskbarJumpList, null); 1.450 + }, 1.451 + }); 1.452 + }, 1.453 + 1.454 + _clearHistory: function WTBJL__clearHistory(items) { 1.455 + if (!items) 1.456 + return; 1.457 + var URIsToRemove = []; 1.458 + var e = items.enumerate(); 1.459 + while (e.hasMoreElements()) { 1.460 + let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut); 1.461 + if (oldItem) { 1.462 + try { // in case we get a bad uri 1.463 + let uriSpec = oldItem.app.getParameter(0); 1.464 + URIsToRemove.push(NetUtil.newURI(uriSpec)); 1.465 + } catch (err) { } 1.466 + } 1.467 + } 1.468 + if (URIsToRemove.length > 0) { 1.469 + PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true); 1.470 + } 1.471 + }, 1.472 + 1.473 + /** 1.474 + * Prefs utilities 1.475 + */ 1.476 + 1.477 + _refreshPrefs: function WTBJL__refreshPrefs() { 1.478 + this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED); 1.479 + this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT); 1.480 + this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT); 1.481 + this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS); 1.482 + this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT); 1.483 + }, 1.484 + 1.485 + /** 1.486 + * Init and shutdown utilities 1.487 + */ 1.488 + 1.489 + _initTaskbar: function WTBJL__initTaskbar() { 1.490 + this._builder = _taskbarService.createJumpListBuilder(); 1.491 + if (!this._builder || !this._builder.available) 1.492 + return false; 1.493 + 1.494 + return true; 1.495 + }, 1.496 + 1.497 + _initObs: function WTBJL__initObs() { 1.498 + // If the browser is closed while in private browsing mode, the "exit" 1.499 + // notification is fired on quit-application-granted. 1.500 + // History cleanup can happen at profile-change-teardown. 1.501 + Services.obs.addObserver(this, "profile-before-change", false); 1.502 + Services.obs.addObserver(this, "browser:purge-session-history", false); 1.503 + _prefs.addObserver("", this, false); 1.504 + }, 1.505 + 1.506 + _freeObs: function WTBJL__freeObs() { 1.507 + Services.obs.removeObserver(this, "profile-before-change"); 1.508 + Services.obs.removeObserver(this, "browser:purge-session-history"); 1.509 + _prefs.removeObserver("", this); 1.510 + }, 1.511 + 1.512 + _updateTimer: function WTBJL__updateTimer() { 1.513 + if (this._enabled && !this._shuttingDown && !this._timer) { 1.514 + this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.515 + this._timer.initWithCallback(this, 1.516 + _prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000, 1.517 + this._timer.TYPE_REPEATING_SLACK); 1.518 + } 1.519 + else if ((!this._enabled || this._shuttingDown) && this._timer) { 1.520 + this._timer.cancel(); 1.521 + delete this._timer; 1.522 + } 1.523 + }, 1.524 + 1.525 + _hasIdleObserver: false, 1.526 + _updateIdleObserver: function WTBJL__updateIdleObserver() { 1.527 + if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) { 1.528 + _idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS); 1.529 + this._hasIdleObserver = true; 1.530 + } 1.531 + else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) { 1.532 + _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); 1.533 + this._hasIdleObserver = false; 1.534 + } 1.535 + }, 1.536 + 1.537 + _free: function WTBJL__free() { 1.538 + this._freeObs(); 1.539 + this._updateTimer(); 1.540 + this._updateIdleObserver(); 1.541 + delete this._builder; 1.542 + }, 1.543 + 1.544 + /** 1.545 + * Notification handlers 1.546 + */ 1.547 + 1.548 + notify: function WTBJL_notify(aTimer) { 1.549 + // Add idle observer on the first notification so it doesn't hit startup. 1.550 + this._updateIdleObserver(); 1.551 + this.update(); 1.552 + }, 1.553 + 1.554 + observe: function WTBJL_observe(aSubject, aTopic, aData) { 1.555 + switch (aTopic) { 1.556 + case "nsPref:changed": 1.557 + if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED)) 1.558 + this._deleteActiveJumpList(); 1.559 + this._refreshPrefs(); 1.560 + this._updateTimer(); 1.561 + this._updateIdleObserver(); 1.562 + this.update(); 1.563 + break; 1.564 + 1.565 + case "profile-before-change": 1.566 + this._shutdown(); 1.567 + break; 1.568 + 1.569 + case "browser:purge-session-history": 1.570 + this.update(); 1.571 + break; 1.572 + case "idle": 1.573 + if (this._timer) { 1.574 + this._timer.cancel(); 1.575 + delete this._timer; 1.576 + } 1.577 + break; 1.578 + 1.579 + case "active": 1.580 + this._updateTimer(); 1.581 + break; 1.582 + } 1.583 + }, 1.584 +};