Bumping gaia.json for 1 gaia revision(s) a=gaia-bump
[gecko.git] / browser / modules / WindowsJumpLists.jsm
blob30b6529cebd7fa412e7a93bc3d1332489568bd38
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");
9 /**
10  * Constants
11  */
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;
19 // Prefs
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.
29 const LIST_TYPE = {
30   FREQUENT: 0
31 , RECENT: 1
34 /**
35  * Exports
36  */
38 this.EXPORTED_SYMBOLS = [
39   "WinTaskbarJumpList",
42 /**
43  * Smart getters
44  */
46 XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
47   return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
48 });
50 XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
51   return Services.strings
52                  .createBundle("chrome://browser/locale/taskbar.properties");
53 });
55 XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
56   Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
57   return PlacesUtils;
58 });
60 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
61   Components.utils.import("resource://gre/modules/NetUtil.jsm");
62   return NetUtil;
63 });
65 XPCOMUtils.defineLazyServiceGetter(this, "_idle",
66                                    "@mozilla.org/widget/idleservice;1",
67                                    "nsIIdleService");
69 XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
70                                    "@mozilla.org/windows-taskbar;1",
71                                    "nsIWinTaskbar");
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");
80 /**
81  * Global functions
82  */
84 function _getString(name) {
85   return _stringBundle.GetStringFromName(name);
88 /////////////////////////////////////////////////////////////////////////////
89 // Task list configuration data object.
91 var tasksCfg = [
92   /**
93    * Task configuration options: title, description, args, iconIndex, open, close.
94    *
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
99    *               list item.
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.
102    */
103   // Open new tab
104   {
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
109     open:             true,
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.
113   },
115   // Open new window
116   {
117     get title()       _getString("taskbar.tasks.newWindow.label"),
118     get description() _getString("taskbar.tasks.newWindow.description"),
119     args:             "-browser",
120     iconIndex:        2, // New tab icon
121     open:             true,
122     close:            true, // No point, but we don't always update the list on
123                             // shutdown. Thus true for consistency.
124   },
126   // Open new private window
127   {
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
132     open:             true,
133     close:            true, // No point, but we don't always update the list on
134                             // shutdown. Thus true for consistency.
135   },
138 /////////////////////////////////////////////////////////////////////////////
139 // Implementation
141 this.WinTaskbarJumpList =
143   _builder: null,
144   _tasks: null,
145   _shuttingDown: false,
147   /**
148    * Startup, shutdown, and update
149    */ 
151   startup: function WTBJL_startup() {
152     // exit if this isn't win7 or higher.
153     if (!this._initTaskbar())
154       return;
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. 
159     try {
160       // dev builds may not have helper.exe, ignore failures.
161       this._shortcutMaintenance();
162     } catch (ex) {
163     }
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
172     this._initObs();
174     // jump list refresh timer
175     this._updateTimer();
176   },
178   update: function WTBJL_update() {
179     // are we disabled via prefs? don't do anything!
180     if (!this._enabled)
181       return;
183     // do what we came here to do, update the taskbar jumplist
184     this._buildList();
185   },
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) {
194       this.update();
195     }
197     this._free();
198   },
200   _shortcutMaintenance: function WTBJL__maintenace() {
201     _winShellService.shortcutMaintenance();
202   },
204   /**
205    * List building
206    *
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.
212    */
214   _pendingStatements: {},
215   _hasPendingStatements: function WTBJL__hasPendingStatements() {
216     return Object.keys(this._pendingStatements).length > 0;
217   },
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];
227       }
228       this._builder.abortListBuild();
229     }
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();
235       return;
236     }
238     if (!this._startBuild())
239       return;
241     if (this._showTasks)
242       this._buildTasks();
244     // Space for frequent items takes priority over recent.
245     if (this._showFrequent)
246       this._buildFrequent();
248     if (this._showRecent)
249       this._buildRecent();
251     this._commitBuild();
252   },
254   /**
255    * Taskbar api wrappers
256    */ 
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);
265       return true;
266     }
267     return false;
268   },
270   _commitBuild: function WTBJL__commitBuild() {
271     if (!this._hasPendingStatements() && !this._builder.commitListBuild()) {
272       this._builder.abortListBuild();
273     }
274   },
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))
281         return;
282       var item = this._getHandlerAppItem(task.title, task.description,
283                                          task.args, task.iconIndex, null);
284       items.appendElement(item, false);
285     }, this);
286     
287     if (items.length > 0)
288       this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
289   },
291   _buildCustom: function WTBJL__buildCustom(title, items) {
292     if (items.length > 0)
293       this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
294   },
296   _buildFrequent: function WTBJL__buildFrequent() {
297     // If history is empty, just bail out.
298     if (!PlacesUtils.history.hasHistoryEntries) {
299       return;
300     }
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
310     // the recent list.
311     this._frequentHashList = [];
313     this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
314       Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
315       this._maxItemCount,
316       function (aResult) {
317         if (!aResult) {
318           delete this._pendingStatements[LIST_TYPE.FREQUENT];
319           // The are no more results, build the list.
320           this._buildCustom(_getString("taskbar.frequent.label"), items);
321           this._commitBuild();
322           return;
323         }
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, 
328                                                faviconPageUri);
329         items.appendElement(shortcut, false);
330         this._frequentHashList.push(aResult.uri);
331       },
332       this
333     );
334   },
336   _buildRecent: function WTBJL__buildRecent() {
337     // If history is empty, just bail out.
338     if (!PlacesUtils.history.hasHistoryEntries) {
339       return;
340     }
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.
346     var count = 0;
348     this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
349       Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
350       this._maxItemCount * 2,
351       function (aResult) {
352         if (!aResult) {
353           // The are no more results, build the list.
354           this._buildCustom(_getString("taskbar.recent.label"), items);
355           delete this._pendingStatements[LIST_TYPE.RECENT];
356           this._commitBuild();
357           return;
358         }
360         if (count >= this._maxItemCount) {
361           return;
362         }
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) {
367           return;
368         }
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,
373                                                faviconPageUri);
374         items.appendElement(shortcut, false);
375         count++;
376       },
377       this
378     );
379   },
381   _deleteActiveJumpList: function WTBJL__deleteAJL() {
382     this._builder.deleteActiveList();
383   },
385   /**
386    * Jump list item creation helpers
387    */
389   _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description, 
390                                                         args, iconIndex, 
391                                                         faviconPageUri) {
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;
408     return item;
409   },
411   _getSeparatorItem: function WTBJL__getSeparatorItem() {
412     var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
413                createInstance(Ci.nsIJumpListSeparator);
414     return item;
415   },
417   /**
418    * Nav history helpers
419    */
421   _getHistoryResults:
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());) {
433           try {
434             aCallback.call(aScope,
435                            { uri: row.getResultByIndex(1)
436                            , title: row.getResultByIndex(2)
437                            });
438           } catch (e) {}
439         }
440       },
441       handleError: function (aError) {
442         Components.utils.reportError(
443           "Async execution error (" + aError.result + "): " + aError.message);
444       },
445       handleCompletion: function (aReason) {
446         aCallback.call(WinTaskbarJumpList, null);
447       },
448     });
449   },
451   _clearHistory: function WTBJL__clearHistory(items) {
452     if (!items)
453       return;
454     var URIsToRemove = [];
455     var e = items.enumerate();
456     while (e.hasMoreElements()) {
457       let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut);
458       if (oldItem) {
459         try { // in case we get a bad uri
460           let uriSpec = oldItem.app.getParameter(0);
461           URIsToRemove.push(NetUtil.newURI(uriSpec));
462         } catch (err) { }
463       }
464     }
465     if (URIsToRemove.length > 0) {
466       PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
467     }
468   },
470   /**
471    * Prefs utilities
472    */ 
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);
480   },
482   /**
483    * Init and shutdown utilities
484    */ 
486   _initTaskbar: function WTBJL__initTaskbar() {
487     this._builder = _taskbarService.createJumpListBuilder();
488     if (!this._builder || !this._builder.available)
489       return false;
491     return true;
492   },
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);
501   },
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);
507   },
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);
515     }
516     else if ((!this._enabled || this._shuttingDown) && this._timer) {
517       this._timer.cancel();
518       delete this._timer;
519     }
520   },
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;
527     }
528     else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) {
529       _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
530       this._hasIdleObserver = false;
531     }
532   },
534   _free: function WTBJL__free() {
535     this._freeObs();
536     this._updateTimer();
537     this._updateIdleObserver();
538     delete this._builder;
539   },
541   /**
542    * Notification handlers
543    */
545   notify: function WTBJL_notify(aTimer) {
546     // Add idle observer on the first notification so it doesn't hit startup.
547     this._updateIdleObserver();
548     this.update();
549   },
551   observe: function WTBJL_observe(aSubject, aTopic, aData) {
552     switch (aTopic) {
553       case "nsPref:changed":
554         if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED))
555           this._deleteActiveJumpList();
556         this._refreshPrefs();
557         this._updateTimer();
558         this._updateIdleObserver();
559         this.update();
560       break;
562       case "profile-before-change":
563         this._shutdown();
564       break;
566       case "browser:purge-session-history":
567         this.update();
568       break;
569       case "idle":
570         if (this._timer) {
571           this._timer.cancel();
572           delete this._timer;
573         }
574       break;
576       case "active":
577         this._updateTimer();
578       break;
579     }
580   },