gecko/browser/components/wintaskbar/WindowsJumpLists.jsm

529 lines
16 KiB
JavaScript

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jim Mathies <jmathies@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
/**
* Constants
*/
const Cc = Components.classes;
const Ci = Components.interfaces;
// Prefs
const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
const PREF_TASKBAR_ENABLED = "enabled";
const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
const PREF_TASKBAR_FREQUENT = "frequent.enabled";
const PREF_TASKBAR_RECENT = "recent.enabled";
const PREF_TASKBAR_TASKS = "tasks.enabled";
// The amount of time between updates for jump lists
const TIMER_TASKBAR_REFRESH = 1000*60*2; // 2 min.
/**
* Exports
*/
let EXPORTED_SYMBOLS = [
"WinTaskbarJumpList",
];
/**
* Smart getters
*/
XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
return Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.getBranch(PREF_TASKBAR_BRANCH)
.QueryInterface(Ci.nsIPrefBranch2);
});
XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
return Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Ci.nsIStringBundleService)
.createBundle("chrome://browser/locale/taskbar.properties");
});
XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
"@mozilla.org/windows-taskbar;1",
"nsIWinTaskbar");
XPCOMUtils.defineLazyServiceGetter(this, "_navHistoryService",
"@mozilla.org/browser/nav-history-service;1",
"nsINavHistoryService");
XPCOMUtils.defineLazyServiceGetter(this, "_observerService",
"@mozilla.org/observer-service;1",
"nsIObserverService");
XPCOMUtils.defineLazyServiceGetter(this, "_directoryService",
"@mozilla.org/file/directory_service;1",
"nsIProperties");
XPCOMUtils.defineLazyServiceGetter(this, "_ioService",
"@mozilla.org/network/io-service;1",
"nsIIOService");
/**
* Global functions
*/
function _getString(name) {
return _stringBundle.GetStringFromName(name);
}
/////////////////////////////////////////////////////////////////////////////
// Task list configuration data object.
var tasksCfg = [
/**
* Task configuration options: title, description, args, iconIndex, open, close.
*
* title - Task title displayed in the list. (strings in the table are temp fillers.)
* description - Tooltip description on the list item.
* args - Command line args to invoke the task.
* iconIndex - Optional win icon index into the main application for the
* list item.
* open - Boolean indicates if the command should be visible after the browser opens.
* close - Boolean indicates if the command should be visible after the browser closes.
*/
// Open new window
{
get title() _getString("taskbar.tasks.newTab.label"),
get description() _getString("taskbar.tasks.newTab.description"),
args: "-new-tab about:blank",
iconIndex: 0, // Fx app icon
open: true,
close: false, // The jump list already has an app launch icon
},
// Open new tab
{
get title() _getString("taskbar.tasks.newWindow.label"),
get description() _getString("taskbar.tasks.newWindow.description"),
args: "-browser",
iconIndex: 0, // Fx app icon
open: true,
close: false, // no point
},
];
/////////////////////////////////////////////////////////////////////////////
// Implementation
var WinTaskbarJumpList =
{
_builder: null,
_tasks: null,
_shuttingDown: false,
/**
* Startup, shutdown, and update
*/
startup: function WTBJL_startup() {
// exit if this isn't win7 or higher.
if (!this._initTaskbar())
return;
// Store our task list config data
this._tasks = tasksCfg;
// retrieve taskbar related prefs.
this._refreshPrefs();
// observer for private browsing and our prefs branch
this._initObs();
// jump list refresh timer
this._initTimer();
// build the list
this.update();
},
update: function WTBJL_update() {
// are we disabled via prefs? don't do anything!
if (!this._enabled)
return;
// hide jump lists when we're enabled and in private browsing mode
if (this._inPrivateBrowsing) {
this._deleteActiveJumpList();
return;
}
// do what we came here to do, update the taskbar jumplist
this._buildList();
},
_shutdown: function WTBJL__shutdown() {
this._shuttingDown = true;
this.update();
this._free();
},
/**
* List building
*/
_buildList: function WTBJL__buildList() {
// anything to build?
if (!this._showFrequent && !this._showRecent && !this._showTasks) {
// don't leave the last list hanging on the taskbar.
this._deleteActiveJumpList();
return;
}
if (!this._startBuild())
return;
if (this._showTasks && !this._buildTasks())
return;
// Space for frequent items takes priority over recent.
if (this._showFrequent && !this._buildFrequent())
return;
if (this._showRecent && !this._buildRecent())
return;
this._commitBuild();
},
/**
* Taskbar api wrappers
*/
_startBuild: function WTBJL__startBuild() {
var removedItems = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
this._builder.abortListBuild();
if (this._builder.initListBuild(removedItems)) {
// Prior to building, delete removed items from history.
this._clearHistory(removedItems);
return true;
}
return false;
},
_commitBuild: function WTBJL__commitBuild() {
if (!this._builder.commitListBuild())
this._builder.abortListBuild();
},
_buildTasks: function WTBJL__buildTasks() {
var items = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
this._tasks.forEach(function (task) {
if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
return;
var item = this._getHandlerAppItem(task.title, task.description,
task.args, task.iconIndex);
items.appendElement(item, false);
}, this);
if (items.length == 0)
return true;
return this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
},
_buildCustom: function WTBJL__buildCustom(title, items) {
if (items.length == 0)
return true;
return this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
},
_buildFrequent: function WTBJL__buildFrequent() {
// Windows supports default frequent and recent lists,
// but those depend on internal windows visit tracking
// which we don't populate. So we build our own custom
// frequent and recent lists using our nav history data.
var items = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
var list = this._getNavFrequent(this._maxItemCount);
if (!list || list.length == 0)
return true;
// track frequent items so that we don't add them to
// the recent list.
this._frequentHashList = [];
list.forEach(function (entry) {
let shortcut = this._getHandlerAppItem(entry.title, entry.title, entry.uri, 1);
items.appendElement(shortcut, false);
this._frequentHashList.push(entry.uri);
}, this);
return this._buildCustom(_getString("taskbar.frequent.label"), items);
},
_buildRecent: function WTBJL__buildRecent() {
var items = Cc["@mozilla.org/array;1"].
createInstance(Ci.nsIMutableArray);
var list = this._getNavRecent(this._maxItemCount*2);
if (!list || list.length == 0)
return true;
let count = 0;
for (let idx = 0; idx < list.length; idx++) {
if (count >= this._maxItemCount)
break;
let entry = list[idx];
// do not add items to recent that have already been added
// to frequent.
if (this._frequentHashList &&
this._frequentHashList.indexOf(entry.uri) != -1)
continue;
let shortcut = this._getHandlerAppItem(entry.title, entry.title, entry.uri, 1);
items.appendElement(shortcut, false);
count++;
}
return this._buildCustom(_getString("taskbar.recent.label"), items);
},
_deleteActiveJumpList: function WTBJL__deleteAJL() {
return this._builder.deleteActiveList();
},
/**
* Jump list item creation helpers
*/
_getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description, args, icon) {
var file = _directoryService.get("XCurProcD", Ci.nsILocalFile);
// XXX where can we grab this from in the build? Do we need to?
file.append("firefox.exe");
var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
createInstance(Ci.nsILocalHandlerApp);
handlerApp.executable = file;
// handlers default to the leaf name if a name is not specified
if (name && name.length != 0)
handlerApp.name = name;
handlerApp.detailedDescription = description;
handlerApp.appendParameter(args);
var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].
createInstance(Ci.nsIJumpListShortcut);
item.app = handlerApp;
item.iconIndex = icon;
return item;
},
_getSeparatorItem: function WTBJL__getSeparatorItem() {
var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
createInstance(Ci.nsIJumpListSeparator);
return item;
},
/**
* Nav history helpers
*/
_getNavFrequent: function WTBJL__getNavFrequent(depth) {
var options = _navHistoryService.getNewQueryOptions();
var query = _navHistoryService.getNewQuery();
query.beginTimeReference = query.TIME_RELATIVE_NOW;
query.beginTime = -24 * 30 * 60 * 60 * 1000000; // one month
query.endTimeReference = query.TIME_RELATIVE_NOW;
options.maxResults = depth;
options.queryType = options.QUERY_TYPE_HISTORY;
options.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING;
options.resultType = options.RESULT_TYPE_URI;
var result = _navHistoryService.executeQuery(query, options);
var list = [];
var rootNode = result.root;
rootNode.containerOpen = true;
for (let idx = 0; idx < rootNode.childCount; idx++) {
let node = rootNode.getChild(idx);
list.push({uri: node.uri, title: node.title});
}
rootNode.containerOpen = false;
return list;
},
_getNavRecent: function WTBJL__getNavRecent(depth) {
var options = _navHistoryService.getNewQueryOptions();
var query = _navHistoryService.getNewQuery();
query.beginTimeReference = query.TIME_RELATIVE_NOW;
query.beginTime = -48 * 60 * 60 * 1000000; // two days
query.endTimeReference = query.TIME_RELATIVE_NOW;
options.maxResults = depth;
options.queryType = options.QUERY_TYPE_HISTORY;
options.sortingMode = options.SORT_BY_LASTMODIFIED_DESCENDING;
options.resultType = options.RESULT_TYPE_URI;
var result = _navHistoryService.executeQuery(query, options);
var list = [];
var rootNode = result.root;
rootNode.containerOpen = true;
for (var idx = 0; idx < rootNode.childCount; idx++) {
var node = rootNode.getChild(idx);
list.push({uri: node.uri, title: node.title});
}
rootNode.containerOpen = false;
return list;
},
_clearHistory: function WTBJL__clearHistory(items) {
if (!items)
return;
var enum = items.enumerate();
while (enum.hasMoreElements()) {
let oldItem = enum.getNext().QueryInterface(Ci.nsIJumpListShortcut);
if (oldItem) {
try { // in case we get a bad uri
let uriSpec = oldItem.app.getParameter(0);
_navHistoryService.QueryInterface(Ci.nsIBrowserHistory).removePage(
_ioService.newURI(uriSpec));
} catch (err) { }
}
}
},
/**
* Prefs utilities
*/
_refreshPrefs: function WTBJL__refreshPrefs() {
this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
// retrieve the initial status of the Private Browsing mode.
this._inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
getService(Ci.nsIPrivateBrowsingService).
privateBrowsingEnabled;
},
/**
* Init and shutdown utilities
*/
_initTaskbar: function WTBJL__initTaskbar() {
this._builder = _taskbarService.createJumpListBuilder();
if (!this._builder || !this._builder.available)
return false;
return true;
},
_initObs: function WTBJL__initObs() {
_observerService.addObserver(this, "private-browsing", false);
_observerService.addObserver(this, "quit-application-granted", false);
_prefs.addObserver("", this, false);
},
_freeObs: function WTBJL__freeObs() {
_observerService.removeObserver(this, "private-browsing");
_observerService.removeObserver(this, "quit-application-granted");
_prefs.removeObserver("", this);
},
_initTimer: function WTBJL__initTimer(aTimer) {
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._timer.initWithCallback(this, TIMER_TASKBAR_REFRESH, this._timer.TYPE_REPEATING_SLACK);
},
_free: function WTBJL__free() {
this._freeObs();
delete this._builder;
delete this._timer;
},
/**
* Notification handlers
*/
notify: function WTBJL_notify(aTimer) {
this.update();
},
observe: function WTBJL_observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "nsPref:changed":
this._refreshPrefs();
this.update();
break;
case "quit-application-granted":
this._shutdown();
break;
case "browser:purge-session-history":
this.update();
break;
case "private-browsing":
switch (aData) {
case "enter":
this._inPrivateBrowsing = true;
break;
case "exit":
this._inPrivateBrowsing = false;
break;
}
this.update();
break;
}
},
};