browser/modules/WindowsJumpLists.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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
    32 }
    34 /**
    35  * Exports
    36  */
    38 this.EXPORTED_SYMBOLS = [
    39   "WinTaskbarJumpList",
    40 ];
    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);
    86 }
    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   },
   136 ];
   138 /////////////////////////////////////////////////////////////////////////////
   139 // Implementation
   141 this.WinTaskbarJumpList =
   142 {
   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);
   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   },
   581 };

mercurial