Bug 518666 - Add Firefox win7 jump list support. r=sdwilsh, r=dao.
[mozilla-central.git] / browser / components / wintaskbar / jumpLists.jsm
blob0f94a2143620789d2f33076098d541399073c324
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4  *
5  * The contents of this file are subject to the Mozilla Public License Version
6  * 1.1 (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  * http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the
13  * License.
14  *
15  * The Original Code is mozilla.org code.
16  *
17  * The Initial Developer of the Original Code is
18  * Mozilla Foundation.
19  * Portions created by the Initial Developer are Copyright (C) 2009
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  *   Jim Mathies <jmathies@mozilla.com>
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either the GNU General Public License Version 2 or later (the "GPL"), or
27  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the MPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the MPL, the GPL or the LGPL.
36  *
37  * ***** END LICENSE BLOCK ***** */
39 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
41 /**
42  * Constants
43  */
45 const Cc = Components.classes;
46 const Ci = Components.interfaces;
48 // Prefs
49 const PREF_TASKBAR_BRANCH    = "browser.taskbar.lists.";
50 const PREF_TASKBAR_ENABLED   = "enabled";
51 const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
52 const PREF_TASKBAR_FREQUENT  = "frequent.enabled";
53 const PREF_TASKBAR_RECENT    = "recent.enabled";
54 const PREF_TASKBAR_TASKS     = "tasks.enabled";
56 // The amount of time between updates for jump lists
57 const TIMER_TASKBAR_REFRESH = 1000*60*2; // 2 min.
59 /**
60  * Exports
61  */
63 let EXPORTED_SYMBOLS = [
64   "WinTaskbarJumpList",
67 /**
68  * Smart getters
69  */
71 XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
72   return Cc["@mozilla.org/preferences-service;1"]
73            .getService(Ci.nsIPrefService)
74            .getBranch(PREF_TASKBAR_BRANCH)
75            .QueryInterface(Ci.nsIPrefBranch2);
76 });
78 XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
79   return Cc["@mozilla.org/intl/stringbundle;1"]
80            .getService(Ci.nsIStringBundleService)
81            .createBundle("chrome://browser/locale/taskbar.properties");
82 });
84 XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
85                                    "@mozilla.org/windows-taskbar;1",
86                                    "nsIWinTaskbar");
88 XPCOMUtils.defineLazyServiceGetter(this, "_navHistoryService",
89                                    "@mozilla.org/browser/nav-history-service;1",
90                                    "nsINavHistoryService");
92 XPCOMUtils.defineLazyServiceGetter(this, "_observerService",
93                                    "@mozilla.org/observer-service;1",
94                                    "nsIObserverService");
96 XPCOMUtils.defineLazyServiceGetter(this, "_directoryService",
97                                    "@mozilla.org/file/directory_service;1",
98                                    "nsIProperties");
100 XPCOMUtils.defineLazyServiceGetter(this, "_ioService",
101                                    "@mozilla.org/network/io-service;1",
102                                    "nsIIOService");
105  * Global functions
106  */
108 function _getString(name) {
109   return _stringBundle.GetStringFromName(name);
112 /////////////////////////////////////////////////////////////////////////////
113 // Task list configuration data object.
115 var tasksCfg = [
116   /**
117    * Task configuration options: title, description, args, iconIndex, open, close.
118    *
119    * title       - Task title displayed in the list. (strings in the table are temp fillers.)
120    * description - Tooltip description on the list item.
121    * args        - Command line args to invoke the task.
122    * iconIndex   - Optional win icon index into the main application for the
123    *               list item.
124    * open        - Boolean indicates if the command should be visible after the browser opens.
125    * close       - Boolean indicates if the command should be visible after the browser closes.
126    */
127   // Open new window
128   {
129     get title()       _getString("taskbar.tasks.newTab.label"),
130     get description() _getString("taskbar.tasks.newTab.description"),
131     args:             "-new-tab about:blank",
132     iconIndex:        0, // Fx app icon
133     open:             true,
134     close:            false, // The jump list already has an app launch icon
135   },
137   // Open new tab
138   {
139     get title()       _getString("taskbar.tasks.newWindow.label"),
140     get description() _getString("taskbar.tasks.newWindow.description"),
141     args:             "-browser",
142     iconIndex:        0, // Fx app icon
143     open:             true,
144     close:            false, // no point
145   },
148 /////////////////////////////////////////////////////////////////////////////
149 // Implementation
151 var WinTaskbarJumpList =
153   _builder: null,
154   _tasks: null,
155   _shuttingDown: false,
157   /**
158    * Startup, shutdown, and update
159    */ 
161   startup: function WTBJL_startup() {
162     // exit if this isn't win7 or higher.
163     if (!this._initTaskbar())
164       return;
166     // Store our task list config data
167     this._tasks = tasksCfg;
169     // retrieve taskbar related prefs.
170     this._refreshPrefs();
171     
172     // observer for private browsing and our prefs branch
173     this._initObs();
175     // jump list refresh timer
176     this._initTimer();
178     // build the list
179     this.update();
180   },
182   update: function WTBJL_update() {
183     // are we disabled via prefs? don't do anything!
184     if (!this._enabled)
185       return;
187     // hide jump lists when we're enabled and in private browsing mode
188     if (this._inPrivateBrowsing) {
189       this._deleteActiveJumpList();
190       return;
191     }
193     // do what we came here to do, update the taskbar jumplist
194     this._buildList();
195   },
197   _shutdown: function WTBJL__shutdown() {
198     this._shuttingDown = true;
199     this.update();
200     this._free();
201   },
203   /**
204    * List building
205    */ 
207   _buildList: function WTBJL__buildList() {
208     // anything to build?
209     if (!this._showFrequent && !this._showRecent && !this._showTasks) {
210       // don't leave the last list hanging on the taskbar.
211       this._deleteActiveJumpList();
212       return;
213     }
215     if (!this._startBuild())
216       return;
218     if (this._showTasks && !this._buildTasks())
219       return;
221     // Space for frequent items takes priority over recent.
222     
223     if (this._showFrequent && !this._buildFrequent())
224       return;
226     if (this._showRecent && !this._buildRecent())
227       return;
229     this._commitBuild();
230   },
232   /**
233    * Taskbar api wrappers
234    */ 
236   _startBuild: function WTBJL__startBuild() {
237     var removedItems = Cc["@mozilla.org/array;1"].
238                        createInstance(Ci.nsIMutableArray);
239     this._builder.abortListBuild();
240     if (this._builder.initListBuild(removedItems)) { 
241       // Prior to building, delete removed items from history.
242       this._clearHistory(removedItems);
243       return true;
244     }
245     return false;
246   },
248   _commitBuild: function WTBJL__commitBuild() {
249     if (!this._builder.commitListBuild())
250       this._builder.abortListBuild();
251   },
253   _buildTasks: function WTBJL__buildTasks() {
254     var items = Cc["@mozilla.org/array;1"].
255                 createInstance(Ci.nsIMutableArray);
256     this._tasks.forEach(function (task) {
257       if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
258         return;
259       var item = this._getHandlerAppItem(task.title, task.description,
260                       task.args, task.iconIndex);
261       items.appendElement(item, false);
262     }, this);
263     
264     if (items.length == 0)
265       return true;
267     return this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
268   },
270   _buildCustom: function WTBJL__buildCustom(title, items) {
271     if (items.length == 0)
272       return true;
273     return this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
274   },
276   _buildFrequent: function WTBJL__buildFrequent() {
277     // Windows supports default frequent and recent lists,
278     // but those depend on internal windows visit tracking
279     // which we don't populate. So we build our own custom
280     // frequent and recent lists using our nav history data.
282     var items = Cc["@mozilla.org/array;1"].
283                 createInstance(Ci.nsIMutableArray);
284     var list = this._getNavFrequent(this._maxItemCount);
286     if (!list || list.length == 0)
287       return true;
289     // track frequent items so that we don't add them to
290     // the recent list.
291     this._frequentHashList = [];
292     
293     list.forEach(function (entry) {
294       let shortcut = this._getHandlerAppItem(entry.title, entry.title, entry.uri, 1);
295       items.appendElement(shortcut, false);
296       this._frequentHashList.push(entry.uri);
297     }, this);
298     return this._buildCustom(_getString("taskbar.frequent.label"), items);
299   },
301   _buildRecent: function WTBJL__buildRecent() {
302     var items = Cc["@mozilla.org/array;1"].
303                 createInstance(Ci.nsIMutableArray);
304     var list = this._getNavRecent(this._maxItemCount*2);
305     
306     if (!list || list.length == 0)
307       return true;
309     for (let idx = 0; idx < list.length; idx++) {
310       let entry = list[idx];
311       let shortcut = this._getHandlerAppItem(entry.title, entry.title, entry.uri, 1);
312       if (idx >= this._maxItemCount)
313         break;
314       // do not add items to recent that have already been added
315       // to frequent.
316       if (this._frequentHashList &&
317           this._frequentHashList.indexOf(entry.uri) != -1)
318         return;
319       items.appendElement(shortcut, false);
320     }
321     return this._buildCustom(_getString("taskbar.recent.label"), items);
322   },
324   _deleteActiveJumpList: function WTBJL__deleteAJL() {
325     return this._builder.deleteActiveList();
326   },
328   /**
329    * Jump list item creation helpers
330    */
332   _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description, args, icon) {
333     var file = _directoryService.get("XCurProcD", Ci.nsILocalFile);
335     // XXX where can we grab this from in the build? Do we need to?
336     file.append("firefox.exe");
338     var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
339                      createInstance(Ci.nsILocalHandlerApp);
340     handlerApp.executable = file;
341     // handlers default to the leaf name if a name is not specified
342     if (name.length != 0)
343       handlerApp.name = name;
344     handlerApp.detailedDescription = description;
345     handlerApp.appendParameter(args);
347     var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].
348                createInstance(Ci.nsIJumpListShortcut);
349     item.app = handlerApp;
350     item.iconIndex  = icon;
351     return item;
352   },
354   _getSeparatorItem: function WTBJL__getSeparatorItem() {
355     var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
356                createInstance(Ci.nsIJumpListSeparator);
357     return item;
358   },
360   /**
361    * Nav history helpers
362    */ 
364   _getNavFrequent: function WTBJL__getNavFrequent(depth) {
365     var options = _navHistoryService.getNewQueryOptions();
366     var query = _navHistoryService.getNewQuery();
367     
368     query.beginTimeReference = query.TIME_RELATIVE_NOW;
369     query.beginTime = -24 * 30 * 60 * 60 * 1000000; // one month
370     query.endTimeReference = query.TIME_RELATIVE_NOW;
372     options.maxResults = depth;
373     options.queryType = options.QUERY_TYPE_HISTORY;
374     options.sortingMode = options.SORT_BY_VISITCOUNT_DESCENDING;
375     options.resultType = options.RESULT_TYPE_URI;
377     var result = _navHistoryService.executeQuery(query, options);
379     var list = [];
381     var rootNode = result.root;
382     rootNode.containerOpen = true;
384     for (let idx = 0; idx < rootNode.childCount; idx++) {
385       let node = rootNode.getChild(idx);
386       list.push({uri: node.uri, title: node.title});
387     }
388     rootNode.containerOpen = false;
390     return list;
391   },
392   
393   _getNavRecent: function WTBJL__getNavRecent(depth) {
394     var options = _navHistoryService.getNewQueryOptions();
395     var query = _navHistoryService.getNewQuery();
396     
397     query.beginTimeReference = query.TIME_RELATIVE_NOW;
398     query.beginTime = -48 * 60 * 60 * 1000000; // two days
399     query.endTimeReference = query.TIME_RELATIVE_NOW;
401     options.maxResults = depth;
402     options.queryType = options.QUERY_TYPE_HISTORY;
403     options.sortingMode = options.SORT_BY_LASTMODIFIED_DESCENDING;
404     options.resultType = options.RESULT_TYPE_URI;
406     var result = _navHistoryService.executeQuery(query, options);
408     var list = [];
410     var rootNode = result.root;
411     rootNode.containerOpen = true;
413     for (var idx = 0; idx < rootNode.childCount; idx++) {
414       var node = rootNode.getChild(idx);
415       list.push({uri: node.uri, title: node.title});
416     }
417     rootNode.containerOpen = false;
419     return list;
420   },
421   
422   _clearHistory: function WTBJL__clearHistory(items) {
423     if (!items)
424       return;
425     var enum = items.enumerate();
426     while (enum.hasMoreElements()) {
427       let oldItem = enum.getNext().QueryInterface(Ci.nsIJumpListShortcut);
428       if (oldItem) {
429         try { // in case we get a bad uri
430           let uriSpec = oldItem.app.getParameter(0);
431           _navHistoryService.QueryInterface(Ci.nsIBrowserHistory).removePage(
432             _ioService.newURI(uriSpec));
433         } catch (err) { }
434       }
435     }
436   },
438   /**
439    * Prefs utilities
440    */ 
442   _refreshPrefs: function WTBJL__refreshPrefs() {
443     this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
444     this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
445     this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
446     this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
447     this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
449     // retrieve the initial status of the Private Browsing mode.
450     this._inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
451                               getService(Ci.nsIPrivateBrowsingService).
452                               privateBrowsingEnabled;
453   },
455   /**
456    * Init and shutdown utilities
457    */ 
459   _initTaskbar: function WTBJL__initTaskbar() {
460     this._builder = _taskbarService.createJumpListBuilder();
461     if (!this._builder || !this._builder.available)
462       return false;
464     return true;
465   },
467   _initObs: function WTBJL__initObs() {
468     _observerService.addObserver(this, "private-browsing", false);
469     _observerService.addObserver(this, "quit-application-granted", false);
470     _prefs.addObserver("", this, false);
471   },
473   _freeObs: function WTBJL__freeObs() {
474     _observerService.removeObserver(this, "private-browsing");
475     _observerService.removeObserver(this, "quit-application-granted");
476     _prefs.removeObserver("", this);
477   },
479   _initTimer: function WTBJL__initTimer(aTimer) {
480     this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
481     this._timer.initWithCallback(this, TIMER_TASKBAR_REFRESH, this._timer.TYPE_REPEATING_SLACK);
482   },
484   _free: function WTBJL__free() {
485     this._freeObs();
486     delete this._builder;
487     delete this._timer;
488   },
490   /**
491    * Notification handlers
492    */
494   notify: function WTBJL_notify(aTimer) {
495     this.update();
496   },
498   observe: function WTBJL_observe(aSubject, aTopic, aData) {
499     switch (aTopic) {
500       case "nsPref:changed":
501         this._refreshPrefs();
502         this.update();
503       break;
505       case "quit-application-granted":
506         this._shutdown();
507       break;
509       case "browser:purge-session-history":
510         this.update();
511       break;
513       case "private-browsing":
514         switch (aData) {
515           case "enter":
516             this._inPrivateBrowsing = true;
517             break;
518           case "exit":
519             this._inPrivateBrowsing = false;
520             break;
521         }
522         this.update();
523       break;
524     }
525   },