toolkit/mozapps/extensions/content/extensions.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const Cc = Components.classes;
     8 const Ci = Components.interfaces;
     9 const Cu = Components.utils;
    10 const Cr = Components.results;
    12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    13 Cu.import("resource://gre/modules/Services.jsm");
    14 Cu.import("resource://gre/modules/DownloadUtils.jsm");
    15 Cu.import("resource://gre/modules/AddonManager.jsm");
    16 Cu.import("resource://gre/modules/addons/AddonRepository.jsm");
    18 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
    19                                   "resource://gre/modules/PluralForm.jsm");
    21 XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () {
    22   return Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {}).
    23          BrowserToolboxProcess;
    24 });
    25 XPCOMUtils.defineLazyModuleGetter(this, "Experiments",
    26   "resource:///modules/experiments/Experiments.jsm");
    28 const PREF_DISCOVERURL = "extensions.webservice.discoverURL";
    29 const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
    30 const PREF_XPI_ENABLED = "xpinstall.enabled";
    31 const PREF_MAXRESULTS = "extensions.getAddons.maxResults";
    32 const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";
    33 const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled";
    34 const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden";
    35 const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";
    36 const PREF_ADDON_DEBUGGING_ENABLED = "devtools.chrome.enabled";
    37 const PREF_REMOTE_DEBUGGING_ENABLED = "devtools.debugger.remote-enabled";
    39 const LOADING_MSG_DELAY = 100;
    41 const SEARCH_SCORE_MULTIPLIER_NAME = 2;
    42 const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2;
    44 // Use integers so search scores are sortable by nsIXULSortService
    45 const SEARCH_SCORE_MATCH_WHOLEWORD = 10;
    46 const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6;
    47 const SEARCH_SCORE_MATCH_SUBSTRING = 3;
    49 const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)
    50 const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl";
    52 const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
    54 const VIEW_DEFAULT = "addons://discover/";
    56 var gStrings = {};
    57 XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc",
    58                                    "@mozilla.org/intl/stringbundle;1",
    59                                    "nsIStringBundleService");
    61 XPCOMUtils.defineLazyGetter(gStrings, "brand", function brandLazyGetter() {
    62   return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties");
    63 });
    64 XPCOMUtils.defineLazyGetter(gStrings, "ext", function extLazyGetter() {
    65   return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties");
    66 });
    67 XPCOMUtils.defineLazyGetter(gStrings, "dl", function dlLazyGetter() {
    68   return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties");
    69 });
    71 XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function brandShortNameLazyGetter() {
    72   return this.brand.GetStringFromName("brandShortName");
    73 });
    74 XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function appVersionLazyGetter() {
    75   return Services.appinfo.version;
    76 });
    78 document.addEventListener("load", initialize, true);
    79 window.addEventListener("unload", shutdown, false);
    81 var gPendingInitializations = 1;
    82 this.__defineGetter__("gIsInitializing", function gIsInitializingGetter() gPendingInitializations > 0);
    84 function initialize(event) {
    85   // XXXbz this listener gets _all_ load events for all nodes in the
    86   // document... but relies on not being called "too early".
    87   if (event.target instanceof XMLStylesheetProcessingInstruction) {
    88     return;
    89   }
    90   document.removeEventListener("load", initialize, true);
    92   let globalCommandSet = document.getElementById("globalCommandSet");
    93   globalCommandSet.addEventListener("command", function(event) {
    94     gViewController.doCommand(event.target.id);
    95   });
    97   let viewCommandSet = document.getElementById("viewCommandSet");
    98   viewCommandSet.addEventListener("commandupdate", function(event) {
    99     gViewController.updateCommands();
   100   });
   101   viewCommandSet.addEventListener("command", function(event) {
   102     gViewController.doCommand(event.target.id);
   103   });
   105   let detailScreenshot = document.getElementById("detail-screenshot");
   106   detailScreenshot.addEventListener("load", function(event) {
   107     this.removeAttribute("loading");
   108   });
   109   detailScreenshot.addEventListener("error", function(event) {
   110     this.setAttribute("loading", "error");
   111   });
   113   let addonPage = document.getElementById("addons-page");
   114   addonPage.addEventListener("dragenter", function(event) {
   115     gDragDrop.onDragOver(event);
   116   });
   117   addonPage.addEventListener("dragover", function(event) {
   118     gDragDrop.onDragOver(event);
   119   });
   120   addonPage.addEventListener("drop", function(event) {
   121     gDragDrop.onDrop(event);
   122   });
   123   addonPage.addEventListener("keypress", function(event) {
   124     gHeader.onKeyPress(event);
   125   });
   127   gViewController.initialize();
   128   gCategories.initialize();
   129   gHeader.initialize();
   130   gEventManager.initialize();
   131   Services.obs.addObserver(sendEMPong, "EM-ping", false);
   132   Services.obs.notifyObservers(window, "EM-loaded", "");
   134   // If the initial view has already been selected (by a call to loadView from
   135   // the above notifications) then bail out now
   136   if (gViewController.initialViewSelected)
   137     return;
   139   // If there is a history state to restore then use that
   140   if (window.history.state) {
   141     gViewController.updateState(window.history.state);
   142     return;
   143   }
   145   // Default to the last selected category
   146   var view = gCategories.node.value;
   148   // Allow passing in a view through the window arguments
   149   if ("arguments" in window && window.arguments.length > 0 &&
   150       window.arguments[0] !== null && "view" in window.arguments[0]) {
   151     view = window.arguments[0].view;
   152   }
   154   gViewController.loadInitialView(view);
   156   Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false);
   157   Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false);
   158 }
   160 function notifyInitialized() {
   161   if (!gIsInitializing)
   162     return;
   164   gPendingInitializations--;
   165   if (!gIsInitializing) {
   166     var event = document.createEvent("Events");
   167     event.initEvent("Initialized", true, true);
   168     document.dispatchEvent(event);
   169   }
   170 }
   172 function shutdown() {
   173   gCategories.shutdown();
   174   gSearchView.shutdown();
   175   gEventManager.shutdown();
   176   gViewController.shutdown();
   177   Services.obs.removeObserver(sendEMPong, "EM-ping");
   178   Services.prefs.removeObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged);
   179   Services.prefs.removeObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged);
   180 }
   182 function sendEMPong(aSubject, aTopic, aData) {
   183   Services.obs.notifyObservers(window, "EM-pong", "");
   184 }
   186 // Used by external callers to load a specific view into the manager
   187 function loadView(aViewId) {
   188   if (!gViewController.initialViewSelected) {
   189     // The caller opened the window and immediately loaded the view so it
   190     // should be the initial history entry
   192     gViewController.loadInitialView(aViewId);
   193   } else {
   194     gViewController.loadView(aViewId);
   195   }
   196 }
   198 function isDiscoverEnabled() {
   199   if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID)
   200     return false;
   202   try {
   203     if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED))
   204       return false;
   205   } catch (e) {}
   207   try {
   208     if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED))
   209       return false;
   210   } catch (e) {}
   212   return true;
   213 }
   215 function getExperimentEndDate(aAddon) {
   216   if (!("@mozilla.org/browser/experiments-service;1" in Cc)) {
   217     return 0;
   218   }
   220   if (!aAddon.isActive) {
   221     return aAddon.endDate;
   222   }
   224   let experiment = Experiments.instance().getActiveExperiment();
   225   if (!experiment) {
   226     return 0;
   227   }
   229   return experiment.endDate;
   230 }
   232 /**
   233  * Obtain the main DOMWindow for the current context.
   234  */
   235 function getMainWindow() {
   236   return window.QueryInterface(Ci.nsIInterfaceRequestor)
   237                .getInterface(Ci.nsIWebNavigation)
   238                .QueryInterface(Ci.nsIDocShellTreeItem)
   239                .rootTreeItem
   240                .QueryInterface(Ci.nsIInterfaceRequestor)
   241                .getInterface(Ci.nsIDOMWindow);
   242 }
   244 /**
   245  * Obtain the DOMWindow that can open a preferences pane.
   246  *
   247  * This is essentially "get the browser chrome window" with the added check
   248  * that the supposed browser chrome window is capable of opening a preferences
   249  * pane.
   250  *
   251  * This may return null if we can't find the browser chrome window.
   252  */
   253 function getMainWindowWithPreferencesPane() {
   254   let mainWindow = getMainWindow();
   255   if (mainWindow && "openAdvancedPreferences" in mainWindow) {
   256     return mainWindow;
   257   } else {
   258     return null;
   259   }
   260 }
   262 /**
   263  * A wrapper around the HTML5 session history service that allows the browser
   264  * back/forward controls to work within the manager
   265  */
   266 var HTML5History = {
   267   get index() {
   268     return window.QueryInterface(Ci.nsIInterfaceRequestor)
   269                  .getInterface(Ci.nsIWebNavigation)
   270                  .sessionHistory.index;
   271   },
   273   get canGoBack() {
   274     return window.QueryInterface(Ci.nsIInterfaceRequestor)
   275                  .getInterface(Ci.nsIWebNavigation)
   276                  .canGoBack;
   277   },
   279   get canGoForward() {
   280     return window.QueryInterface(Ci.nsIInterfaceRequestor)
   281                  .getInterface(Ci.nsIWebNavigation)
   282                  .canGoForward;
   283   },
   285   back: function HTML5History_back() {
   286     window.history.back();
   287     gViewController.updateCommand("cmd_back");
   288     gViewController.updateCommand("cmd_forward");
   289   },
   291   forward: function HTML5History_forward() {
   292     window.history.forward();
   293     gViewController.updateCommand("cmd_back");
   294     gViewController.updateCommand("cmd_forward");
   295   },
   297   pushState: function HTML5History_pushState(aState) {
   298     window.history.pushState(aState, document.title);
   299   },
   301   replaceState: function HTML5History_replaceState(aState) {
   302     window.history.replaceState(aState, document.title);
   303   },
   305   popState: function HTML5History_popState() {
   306     function onStatePopped(aEvent) {
   307       window.removeEventListener("popstate", onStatePopped, true);
   308       // TODO To ensure we can't go forward again we put an additional entry
   309       // for the current state into the history. Ideally we would just strip
   310       // the history but there doesn't seem to be a way to do that. Bug 590661
   311       window.history.pushState(aEvent.state, document.title);
   312     }
   313     window.addEventListener("popstate", onStatePopped, true);
   314     window.history.back();
   315     gViewController.updateCommand("cmd_back");
   316     gViewController.updateCommand("cmd_forward");
   317   }
   318 };
   320 /**
   321  * A wrapper around a fake history service
   322  */
   323 var FakeHistory = {
   324   pos: 0,
   325   states: [null],
   327   get index() {
   328     return this.pos;
   329   },
   331   get canGoBack() {
   332     return this.pos > 0;
   333   },
   335   get canGoForward() {
   336     return (this.pos + 1) < this.states.length;
   337   },
   339   back: function FakeHistory_back() {
   340     if (this.pos == 0)
   341       throw Components.Exception("Cannot go back from this point");
   343     this.pos--;
   344     gViewController.updateState(this.states[this.pos]);
   345     gViewController.updateCommand("cmd_back");
   346     gViewController.updateCommand("cmd_forward");
   347   },
   349   forward: function FakeHistory_forward() {
   350     if ((this.pos + 1) >= this.states.length)
   351       throw Components.Exception("Cannot go forward from this point");
   353     this.pos++;
   354     gViewController.updateState(this.states[this.pos]);
   355     gViewController.updateCommand("cmd_back");
   356     gViewController.updateCommand("cmd_forward");
   357   },
   359   pushState: function FakeHistory_pushState(aState) {
   360     this.pos++;
   361     this.states.splice(this.pos, this.states.length);
   362     this.states.push(aState);
   363   },
   365   replaceState: function FakeHistory_replaceState(aState) {
   366     this.states[this.pos] = aState;
   367   },
   369   popState: function FakeHistory_popState() {
   370     if (this.pos == 0)
   371       throw Components.Exception("Cannot popState from this view");
   373     this.states.splice(this.pos, this.states.length);
   374     this.pos--;
   376     gViewController.updateState(this.states[this.pos]);
   377     gViewController.updateCommand("cmd_back");
   378     gViewController.updateCommand("cmd_forward");
   379   }
   380 };
   382 // If the window has a session history then use the HTML5 History wrapper
   383 // otherwise use our fake history implementation
   384 if (window.QueryInterface(Ci.nsIInterfaceRequestor)
   385           .getInterface(Ci.nsIWebNavigation)
   386           .sessionHistory) {
   387   var gHistory = HTML5History;
   388 }
   389 else {
   390   gHistory = FakeHistory;
   391 }
   393 var gEventManager = {
   394   _listeners: {},
   395   _installListeners: [],
   397   initialize: function gEM_initialize() {
   398     var self = this;
   399     const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling",
   400                           "onDisabled", "onUninstalling", "onUninstalled",
   401                           "onInstalled", "onOperationCancelled",
   402                           "onUpdateAvailable", "onUpdateFinished",
   403                           "onCompatibilityUpdateAvailable",
   404                           "onPropertyChanged"];
   405     for (let evt of ADDON_EVENTS) {
   406       let event = evt;
   407       self[event] = function initialize_delegateAddonEvent(...aArgs) {
   408         self.delegateAddonEvent(event, aArgs);
   409       };
   410     }
   412     const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted",
   413                             "onDownloadEnded", "onDownloadFailed",
   414                             "onDownloadProgress", "onDownloadCancelled",
   415                             "onInstallStarted", "onInstallEnded",
   416                             "onInstallFailed", "onInstallCancelled",
   417                             "onExternalInstall"];
   418     for (let evt of INSTALL_EVENTS) {
   419       let event = evt;
   420       self[event] = function initialize_delegateInstallEvent(...aArgs) {
   421         self.delegateInstallEvent(event, aArgs);
   422       };
   423     }
   425     AddonManager.addManagerListener(this);
   426     AddonManager.addInstallListener(this);
   427     AddonManager.addAddonListener(this);
   429     this.refreshGlobalWarning();
   430     this.refreshAutoUpdateDefault();
   432     var contextMenu = document.getElementById("addonitem-popup");
   433     contextMenu.addEventListener("popupshowing", function contextMenu_onPopupshowing() {
   434       var addon = gViewController.currentViewObj.getSelectedAddon();
   435       contextMenu.setAttribute("addontype", addon.type);
   437       var menuSep = document.getElementById("addonitem-menuseparator");
   438       var countEnabledMenuCmds = 0;
   439       for (let child of contextMenu.children) {
   440         if (child.nodeName == "menuitem" &&
   441           gViewController.isCommandEnabled(child.command)) {
   442             countEnabledMenuCmds++;
   443         }
   444       }
   446       // with only one menu item, we hide the menu separator
   447       menuSep.hidden = (countEnabledMenuCmds <= 1);
   449     }, false);
   450   },
   452   shutdown: function gEM_shutdown() {
   453     AddonManager.removeManagerListener(this);
   454     AddonManager.removeInstallListener(this);
   455     AddonManager.removeAddonListener(this);
   456   },
   458   registerAddonListener: function gEM_registerAddonListener(aListener, aAddonId) {
   459     if (!(aAddonId in this._listeners))
   460       this._listeners[aAddonId] = [];
   461     else if (this._listeners[aAddonId].indexOf(aListener) != -1)
   462       return;
   463     this._listeners[aAddonId].push(aListener);
   464   },
   466   unregisterAddonListener: function gEM_unregisterAddonListener(aListener, aAddonId) {
   467     if (!(aAddonId in this._listeners))
   468       return;
   469     var index = this._listeners[aAddonId].indexOf(aListener);
   470     if (index == -1)
   471       return;
   472     this._listeners[aAddonId].splice(index, 1);
   473   },
   475   registerInstallListener: function gEM_registerInstallListener(aListener) {
   476     if (this._installListeners.indexOf(aListener) != -1)
   477       return;
   478     this._installListeners.push(aListener);
   479   },
   481   unregisterInstallListener: function gEM_unregisterInstallListener(aListener) {
   482     var i = this._installListeners.indexOf(aListener);
   483     if (i == -1)
   484       return;
   485     this._installListeners.splice(i, 1);
   486   },
   488   delegateAddonEvent: function gEM_delegateAddonEvent(aEvent, aParams) {
   489     var addon = aParams.shift();
   490     if (!(addon.id in this._listeners))
   491       return;
   493     var listeners = this._listeners[addon.id];
   494     for (let listener of listeners) {
   495       if (!(aEvent in listener))
   496         continue;
   497       try {
   498         listener[aEvent].apply(listener, aParams);
   499       } catch(e) {
   500         // this shouldn't be fatal
   501         Cu.reportError(e);
   502       }
   503     }
   504   },
   506   delegateInstallEvent: function gEM_delegateInstallEvent(aEvent, aParams) {
   507     var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon;
   508     // If the install is an update then send the event to all listeners
   509     // registered for the existing add-on
   510     if (existingAddon)
   511       this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams));
   513     for (let listener of this._installListeners) {
   514       if (!(aEvent in listener))
   515         continue;
   516       try {
   517         listener[aEvent].apply(listener, aParams);
   518       } catch(e) {
   519         // this shouldn't be fatal
   520         Cu.reportError(e);
   521       }
   522     }
   523   },
   525   refreshGlobalWarning: function gEM_refreshGlobalWarning() {
   526     var page = document.getElementById("addons-page");
   528     if (Services.appinfo.inSafeMode) {
   529       page.setAttribute("warning", "safemode");
   530       return;
   531     }
   533     if (AddonManager.checkUpdateSecurityDefault &&
   534         !AddonManager.checkUpdateSecurity) {
   535       page.setAttribute("warning", "updatesecurity");
   536       return;
   537     }
   539     if (!AddonManager.checkCompatibility) {
   540       page.setAttribute("warning", "checkcompatibility");
   541       return;
   542     }
   544     page.removeAttribute("warning");
   545   },
   547   refreshAutoUpdateDefault: function gEM_refreshAutoUpdateDefault() {
   548     var updateEnabled = AddonManager.updateEnabled;
   549     var autoUpdateDefault = AddonManager.autoUpdateDefault;
   551     // The checkbox needs to reflect that both prefs need to be true
   552     // for updates to be checked for and applied automatically
   553     document.getElementById("utils-autoUpdateDefault")
   554             .setAttribute("checked", updateEnabled && autoUpdateDefault);
   556     document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault;
   557     document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault;
   558   },
   560   onCompatibilityModeChanged: function gEM_onCompatibilityModeChanged() {
   561     this.refreshGlobalWarning();
   562   },
   564   onCheckUpdateSecurityChanged: function gEM_onCheckUpdateSecurityChanged() {
   565     this.refreshGlobalWarning();
   566   },
   568   onUpdateModeChanged: function gEM_onUpdateModeChanged() {
   569     this.refreshAutoUpdateDefault();
   570   }
   571 };
   574 var gViewController = {
   575   viewPort: null,
   576   currentViewId: "",
   577   currentViewObj: null,
   578   currentViewRequest: 0,
   579   viewObjects: {},
   580   viewChangeCallback: null,
   581   initialViewSelected: false,
   582   lastHistoryIndex: -1,
   584   initialize: function gVC_initialize() {
   585     this.viewPort = document.getElementById("view-port");
   587     this.viewObjects["search"] = gSearchView;
   588     this.viewObjects["discover"] = gDiscoverView;
   589     this.viewObjects["list"] = gListView;
   590     this.viewObjects["detail"] = gDetailView;
   591     this.viewObjects["updates"] = gUpdatesView;
   593     for each (let view in this.viewObjects)
   594       view.initialize();
   596     window.controllers.appendController(this);
   598     window.addEventListener("popstate",
   599                             function window_onStatePopped(e) {
   600                               gViewController.updateState(e.state);
   601                             },
   602                             false);
   603   },
   605   shutdown: function gVC_shutdown() {
   606     if (this.currentViewObj)
   607       this.currentViewObj.hide();
   608     this.currentViewRequest = 0;
   610     for each(let view in this.viewObjects) {
   611       if ("shutdown" in view) {
   612         try {
   613           view.shutdown();
   614         } catch(e) {
   615           // this shouldn't be fatal
   616           Cu.reportError(e);
   617         }
   618       }
   619     }
   621     window.controllers.removeController(this);
   622   },
   624   updateState: function gVC_updateState(state) {
   625     try {
   626       this.loadViewInternal(state.view, state.previousView, state);
   627       this.lastHistoryIndex = gHistory.index;
   628     }
   629     catch (e) {
   630       // The attempt to load the view failed, try moving further along history
   631       if (this.lastHistoryIndex > gHistory.index) {
   632         if (gHistory.canGoBack)
   633           gHistory.back();
   634         else
   635           gViewController.replaceView(VIEW_DEFAULT);
   636       } else {
   637         if (gHistory.canGoForward)
   638           gHistory.forward();
   639         else
   640           gViewController.replaceView(VIEW_DEFAULT);
   641       }
   642     }
   643   },
   645   parseViewId: function gVC_parseViewId(aViewId) {
   646     var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/;
   647     var [,viewType, viewParam] = aViewId.match(matchRegex) || [];
   648     return {type: viewType, param: decodeURIComponent(viewParam)};
   649   },
   651   get isLoading() {
   652     return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading");
   653   },
   655   loadView: function gVC_loadView(aViewId) {
   656     var isRefresh = false;
   657     if (aViewId == this.currentViewId) {
   658       if (this.isLoading)
   659         return;
   660       if (!("refresh" in this.currentViewObj))
   661         return;
   662       if (!this.currentViewObj.canRefresh())
   663         return;
   664       isRefresh = true;
   665     }
   667     var state = {
   668       view: aViewId,
   669       previousView: this.currentViewId
   670     };
   671     if (!isRefresh) {
   672       gHistory.pushState(state);
   673       this.lastHistoryIndex = gHistory.index;
   674     }
   675     this.loadViewInternal(aViewId, this.currentViewId, state);
   676   },
   678   // Replaces the existing view with a new one, rewriting the current history
   679   // entry to match.
   680   replaceView: function gVC_replaceView(aViewId) {
   681     if (aViewId == this.currentViewId)
   682       return;
   684     var state = {
   685       view: aViewId,
   686       previousView: null
   687     };
   688     gHistory.replaceState(state);
   689     this.loadViewInternal(aViewId, null, state);
   690   },
   692   loadInitialView: function gVC_loadInitialView(aViewId) {
   693     var state = {
   694       view: aViewId,
   695       previousView: null
   696     };
   697     gHistory.replaceState(state);
   699     this.loadViewInternal(aViewId, null, state);
   700     this.initialViewSelected = true;
   701     notifyInitialized();
   702   },
   704   loadViewInternal: function gVC_loadViewInternal(aViewId, aPreviousView, aState) {
   705     var view = this.parseViewId(aViewId);
   707     if (!view.type || !(view.type in this.viewObjects))
   708       throw Components.Exception("Invalid view: " + view.type);
   710     var viewObj = this.viewObjects[view.type];
   711     if (!viewObj.node)
   712       throw Components.Exception("Root node doesn't exist for '" + view.type + "' view");
   714     if (this.currentViewObj && aViewId != aPreviousView) {
   715       try {
   716         let canHide = this.currentViewObj.hide();
   717         if (canHide === false)
   718           return;
   719         this.viewPort.selectedPanel.removeAttribute("loading");
   720       } catch (e) {
   721         // this shouldn't be fatal
   722         Cu.reportError(e);
   723       }
   724     }
   726     gCategories.select(aViewId, aPreviousView);
   728     this.currentViewId = aViewId;
   729     this.currentViewObj = viewObj;
   731     this.viewPort.selectedPanel = this.currentViewObj.node;
   732     this.viewPort.selectedPanel.setAttribute("loading", "true");
   733     this.currentViewObj.node.focus();
   735     if (aViewId == aPreviousView)
   736       this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState);
   737     else
   738       this.currentViewObj.show(view.param, ++this.currentViewRequest, aState);
   739   },
   741   // Moves back in the document history and removes the current history entry
   742   popState: function gVC_popState(aCallback) {
   743     this.viewChangeCallback = aCallback;
   744     gHistory.popState();
   745   },
   747   notifyViewChanged: function gVC_notifyViewChanged() {
   748     this.viewPort.selectedPanel.removeAttribute("loading");
   750     if (this.viewChangeCallback) {
   751       this.viewChangeCallback();
   752       this.viewChangeCallback = null;
   753     }
   755     var event = document.createEvent("Events");
   756     event.initEvent("ViewChanged", true, true);
   757     this.currentViewObj.node.dispatchEvent(event);
   758   },
   760   commands: {
   761     cmd_back: {
   762       isEnabled: function cmd_back_isEnabled() {
   763         return gHistory.canGoBack;
   764       },
   765       doCommand: function cmd_back_doCommand() {
   766         gHistory.back();
   767       }
   768     },
   770     cmd_forward: {
   771       isEnabled: function cmd_forward_isEnabled() {
   772         return gHistory.canGoForward;
   773       },
   774       doCommand: function cmd_forward_doCommand() {
   775         gHistory.forward();
   776       }
   777     },
   779     cmd_focusSearch: {
   780       isEnabled: () => true,
   781       doCommand: function cmd_focusSearch_doCommand() {
   782         gHeader.focusSearchBox();
   783       }
   784     },
   786     cmd_restartApp: {
   787       isEnabled: function cmd_restartApp_isEnabled() true,
   788       doCommand: function cmd_restartApp_doCommand() {
   789         let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
   790                          createInstance(Ci.nsISupportsPRBool);
   791         Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
   792                                      "restart");
   793         if (cancelQuit.data)
   794           return; // somebody canceled our quit request
   796         let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
   797                          getService(Ci.nsIAppStartup);
   798         appStartup.quit(Ci.nsIAppStartup.eAttemptQuit |  Ci.nsIAppStartup.eRestart);
   799       }
   800     },
   802     cmd_enableCheckCompatibility: {
   803       isEnabled: function cmd_enableCheckCompatibility_isEnabled() true,
   804       doCommand: function cmd_enableCheckCompatibility_doCommand() {
   805         AddonManager.checkCompatibility = true;
   806       }
   807     },
   809     cmd_enableUpdateSecurity: {
   810       isEnabled: function cmd_enableUpdateSecurity_isEnabled() true,
   811       doCommand: function cmd_enableUpdateSecurity_doCommand() {
   812         AddonManager.checkUpdateSecurity = true;
   813       }
   814     },
   816     cmd_pluginCheck: {
   817       isEnabled: function cmd_pluginCheck_isEnabled() true,
   818       doCommand: function cmd_pluginCheck_doCommand() {
   819         openURL(Services.urlFormatter.formatURLPref("plugins.update.url"));
   820       }
   821     },
   823     cmd_toggleAutoUpdateDefault: {
   824       isEnabled: function cmd_toggleAutoUpdateDefault_isEnabled() true,
   825       doCommand: function cmd_toggleAutoUpdateDefault_doCommand() {
   826         if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) {
   827           // One or both of the prefs is false, i.e. the checkbox is not checked.
   828           // Now toggle both to true. If the user wants us to auto-update
   829           // add-ons, we also need to auto-check for updates.
   830           AddonManager.updateEnabled = true;
   831           AddonManager.autoUpdateDefault = true;
   832         } else {
   833           // Both prefs are true, i.e. the checkbox is checked.
   834           // Toggle the auto pref to false, but don't touch the enabled check.
   835           AddonManager.autoUpdateDefault = false;
   836         }
   837       }
   838     },
   840     cmd_resetAddonAutoUpdate: {
   841       isEnabled: function cmd_resetAddonAutoUpdate_isEnabled() true,
   842       doCommand: function cmd_resetAddonAutoUpdate_doCommand() {
   843         AddonManager.getAllAddons(function cmd_resetAddonAutoUpdate_getAllAddons(aAddonList) {
   844           for (let addon of aAddonList) {
   845             if ("applyBackgroundUpdates" in addon)
   846               addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
   847           }
   848         });
   849       }
   850     },
   852     cmd_goToDiscoverPane: {
   853       isEnabled: function cmd_goToDiscoverPane_isEnabled() {
   854         return gDiscoverView.enabled;
   855       },
   856       doCommand: function cmd_goToDiscoverPane_doCommand() {
   857         gViewController.loadView("addons://discover/");
   858       }
   859     },
   861     cmd_goToRecentUpdates: {
   862       isEnabled: function cmd_goToRecentUpdates_isEnabled() true,
   863       doCommand: function cmd_goToRecentUpdates_doCommand() {
   864         gViewController.loadView("addons://updates/recent");
   865       }
   866     },
   868     cmd_goToAvailableUpdates: {
   869       isEnabled: function cmd_goToAvailableUpdates_isEnabled() true,
   870       doCommand: function cmd_goToAvailableUpdates_doCommand() {
   871         gViewController.loadView("addons://updates/available");
   872       }
   873     },
   875     cmd_showItemDetails: {
   876       isEnabled: function cmd_showItemDetails_isEnabled(aAddon) {
   877         return !!aAddon && (gViewController.currentViewObj != gDetailView);
   878       },
   879       doCommand: function cmd_showItemDetails_doCommand(aAddon, aScrollToPreferences) {
   880         gViewController.loadView("addons://detail/" +
   881                                  encodeURIComponent(aAddon.id) +
   882                                  (aScrollToPreferences ? "/preferences" : ""));
   883       }
   884     },
   886     cmd_findAllUpdates: {
   887       inProgress: false,
   888       isEnabled: function cmd_findAllUpdates_isEnabled() !this.inProgress,
   889       doCommand: function cmd_findAllUpdates_doCommand() {
   890         this.inProgress = true;
   891         gViewController.updateCommand("cmd_findAllUpdates");
   892         document.getElementById("updates-noneFound").hidden = true;
   893         document.getElementById("updates-progress").hidden = false;
   894         document.getElementById("updates-manualUpdatesFound-btn").hidden = true;
   896         var pendingChecks = 0;
   897         var numUpdated = 0;
   898         var numManualUpdates = 0;
   899         var restartNeeded = false;
   900         var self = this;
   902         function updateStatus() {
   903           if (pendingChecks > 0)
   904             return;
   906           self.inProgress = false;
   907           gViewController.updateCommand("cmd_findAllUpdates");
   908           document.getElementById("updates-progress").hidden = true;
   909           gUpdatesView.maybeRefresh();
   911           if (numManualUpdates > 0 && numUpdated == 0) {
   912             document.getElementById("updates-manualUpdatesFound-btn").hidden = false;
   913             return;
   914           }
   916           if (numUpdated == 0) {
   917             document.getElementById("updates-noneFound").hidden = false;
   918             return;
   919           }
   921           if (restartNeeded) {
   922             document.getElementById("updates-downloaded").hidden = false;
   923             document.getElementById("updates-restart-btn").hidden = false;
   924           } else {
   925             document.getElementById("updates-installed").hidden = false;
   926           }
   927         }
   929         var updateInstallListener = {
   930           onDownloadFailed: function cmd_findAllUpdates_downloadFailed() {
   931             pendingChecks--;
   932             updateStatus();
   933           },
   934           onInstallFailed: function cmd_findAllUpdates_installFailed() {
   935             pendingChecks--;
   936             updateStatus();
   937           },
   938           onInstallEnded: function cmd_findAllUpdates_installEnded(aInstall, aAddon) {
   939             pendingChecks--;
   940             numUpdated++;
   941             if (isPending(aInstall.existingAddon, "upgrade"))
   942               restartNeeded = true;
   943             updateStatus();
   944           }
   945         };
   947         var updateCheckListener = {
   948           onUpdateAvailable: function cmd_findAllUpdates_updateAvailable(aAddon, aInstall) {
   949             gEventManager.delegateAddonEvent("onUpdateAvailable",
   950                                              [aAddon, aInstall]);
   951             if (AddonManager.shouldAutoUpdate(aAddon)) {
   952               aInstall.addListener(updateInstallListener);
   953               aInstall.install();
   954             } else {
   955               pendingChecks--;
   956               numManualUpdates++;
   957               updateStatus();
   958             }
   959           },
   960           onNoUpdateAvailable: function cmd_findAllUpdates_noUpdateAvailable(aAddon) {
   961             pendingChecks--;
   962             updateStatus();
   963           },
   964           onUpdateFinished: function cmd_findAllUpdates_updateFinished(aAddon, aError) {
   965             gEventManager.delegateAddonEvent("onUpdateFinished",
   966                                              [aAddon, aError]);
   967           }
   968         };
   970         AddonManager.getAddonsByTypes(null, function cmd_findAllUpdates_getAddonsByTypes(aAddonList) {
   971           for (let addon of aAddonList) {
   972             if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) {
   973               pendingChecks++;
   974               addon.findUpdates(updateCheckListener,
   975                                 AddonManager.UPDATE_WHEN_USER_REQUESTED);
   976             }
   977           }
   979           if (pendingChecks == 0)
   980             updateStatus();
   981         });
   982       }
   983     },
   985     cmd_findItemUpdates: {
   986       isEnabled: function cmd_findItemUpdates_isEnabled(aAddon) {
   987         if (!aAddon)
   988           return false;
   989         return hasPermission(aAddon, "upgrade");
   990       },
   991       doCommand: function cmd_findItemUpdates_doCommand(aAddon) {
   992         var listener = {
   993           onUpdateAvailable: function cmd_findItemUpdates_updateAvailable(aAddon, aInstall) {
   994             gEventManager.delegateAddonEvent("onUpdateAvailable",
   995                                              [aAddon, aInstall]);
   996             if (AddonManager.shouldAutoUpdate(aAddon))
   997               aInstall.install();
   998           },
   999           onNoUpdateAvailable: function cmd_findItemUpdates_noUpdateAvailable(aAddon) {
  1000             gEventManager.delegateAddonEvent("onNoUpdateAvailable",
  1001                                              [aAddon]);
  1003         };
  1004         gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]);
  1005         aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
  1007     },
  1009     cmd_debugItem: {
  1010       doCommand: function cmd_debugItem_doCommand(aAddon) {
  1011         BrowserToolboxProcess.init({ addonID: aAddon.id });
  1012       },
  1014       isEnabled: function cmd_debugItem_isEnabled(aAddon) {
  1015         let debuggerEnabled = Services.prefs.
  1016                               getBoolPref(PREF_ADDON_DEBUGGING_ENABLED);
  1017         let remoteEnabled = Services.prefs.
  1018                             getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED);
  1019         return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled;
  1021     },
  1023     cmd_showItemPreferences: {
  1024       isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) {
  1025         if (!aAddon || !aAddon.isActive || !aAddon.optionsURL)
  1026           return false;
  1027         if (gViewController.currentViewObj == gDetailView &&
  1028             aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
  1029           return false;
  1031         if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO)
  1032           return false;
  1033         return true;
  1034       },
  1035       doCommand: function cmd_showItemPreferences_doCommand(aAddon) {
  1036         if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) {
  1037           gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);
  1038           return;
  1040         var optionsURL = aAddon.optionsURL;
  1041         if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB &&
  1042             openOptionsInTab(optionsURL)) {
  1043           return;
  1045         var windows = Services.wm.getEnumerator(null);
  1046         while (windows.hasMoreElements()) {
  1047           var win = windows.getNext();
  1048           if (win.closed) {
  1049             continue;
  1051           if (win.document.documentURI == optionsURL) {
  1052             win.focus();
  1053             return;
  1056         var features = "chrome,titlebar,toolbar,centerscreen";
  1057         try {
  1058           var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply");
  1059           features += instantApply ? ",dialog=no" : ",modal";
  1060         } catch (e) {
  1061           features += ",modal";
  1063         openDialog(optionsURL, "", features);
  1065     },
  1067     cmd_showItemAbout: {
  1068       isEnabled: function cmd_showItemAbout_isEnabled(aAddon) {
  1069         // XXXunf This may be applicable to install items too. See bug 561260
  1070         return !!aAddon;
  1071       },
  1072       doCommand: function cmd_showItemAbout_doCommand(aAddon) {
  1073         var aboutURL = aAddon.aboutURL;
  1074         if (aboutURL)
  1075           openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon);
  1076         else
  1077           openDialog("chrome://mozapps/content/extensions/about.xul",
  1078                      "", "chrome,centerscreen,modal", aAddon);
  1080     },
  1082     cmd_enableItem: {
  1083       isEnabled: function cmd_enableItem_isEnabled(aAddon) {
  1084         if (!aAddon)
  1085           return false;
  1086         let addonType = AddonManager.addonTypes[aAddon.type];
  1087         return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
  1088                 hasPermission(aAddon, "enable"));
  1089       },
  1090       doCommand: function cmd_enableItem_doCommand(aAddon) {
  1091         aAddon.userDisabled = false;
  1092       },
  1093       getTooltip: function cmd_enableItem_getTooltip(aAddon) {
  1094         if (!aAddon)
  1095           return "";
  1096         if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE)
  1097           return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip");
  1098         return gStrings.ext.GetStringFromName("enableAddonTooltip");
  1100     },
  1102     cmd_disableItem: {
  1103       isEnabled: function cmd_disableItem_isEnabled(aAddon) {
  1104         if (!aAddon)
  1105           return false;
  1106         let addonType = AddonManager.addonTypes[aAddon.type];
  1107         return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
  1108                 hasPermission(aAddon, "disable"));
  1109       },
  1110       doCommand: function cmd_disableItem_doCommand(aAddon) {
  1111         aAddon.userDisabled = true;
  1112       },
  1113       getTooltip: function cmd_disableItem_getTooltip(aAddon) {
  1114         if (!aAddon)
  1115           return "";
  1116         if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE)
  1117           return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip");
  1118         return gStrings.ext.GetStringFromName("disableAddonTooltip");
  1120     },
  1122     cmd_installItem: {
  1123       isEnabled: function cmd_installItem_isEnabled(aAddon) {
  1124         if (!aAddon)
  1125           return false;
  1126         return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE;
  1127       },
  1128       doCommand: function cmd_installItem_doCommand(aAddon) {
  1129         function doInstall() {
  1130           gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote();
  1133         if (gViewController.currentViewObj == gDetailView)
  1134           gViewController.popState(doInstall);
  1135         else
  1136           doInstall();
  1138     },
  1140     cmd_purchaseItem: {
  1141       isEnabled: function cmd_purchaseItem_isEnabled(aAddon) {
  1142         if (!aAddon)
  1143           return false;
  1144         return !!aAddon.purchaseURL;
  1145       },
  1146       doCommand: function cmd_purchaseItem_doCommand(aAddon) {
  1147         openURL(aAddon.purchaseURL);
  1149     },
  1151     cmd_uninstallItem: {
  1152       isEnabled: function cmd_uninstallItem_isEnabled(aAddon) {
  1153         if (!aAddon)
  1154           return false;
  1155         return hasPermission(aAddon, "uninstall");
  1156       },
  1157       doCommand: function cmd_uninstallItem_doCommand(aAddon) {
  1158         if (gViewController.currentViewObj != gDetailView) {
  1159           aAddon.uninstall();
  1160           return;
  1163         gViewController.popState(function cmd_uninstallItem_popState() {
  1164           gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall();
  1165         });
  1166       },
  1167       getTooltip: function cmd_uninstallItem_getTooltip(aAddon) {
  1168         if (!aAddon)
  1169           return "";
  1170         if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL)
  1171           return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip");
  1172         return gStrings.ext.GetStringFromName("uninstallAddonTooltip");
  1174     },
  1176     cmd_cancelUninstallItem: {
  1177       isEnabled: function cmd_cancelUninstallItem_isEnabled(aAddon) {
  1178         if (!aAddon)
  1179           return false;
  1180         return isPending(aAddon, "uninstall");
  1181       },
  1182       doCommand: function cmd_cancelUninstallItem_doCommand(aAddon) {
  1183         aAddon.cancelUninstall();
  1185     },
  1187     cmd_installFromFile: {
  1188       isEnabled: function cmd_installFromFile_isEnabled() true,
  1189       doCommand: function cmd_installFromFile_doCommand() {
  1190         const nsIFilePicker = Ci.nsIFilePicker;
  1191         var fp = Cc["@mozilla.org/filepicker;1"]
  1192                    .createInstance(nsIFilePicker);
  1193         fp.init(window,
  1194                 gStrings.ext.GetStringFromName("installFromFile.dialogTitle"),
  1195                 nsIFilePicker.modeOpenMultiple);
  1196         try {
  1197           fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"),
  1198                           "*.xpi;*.jar");
  1199           fp.appendFilters(nsIFilePicker.filterAll);
  1200         } catch (e) { }
  1202         if (fp.show() != nsIFilePicker.returnOK)
  1203           return;
  1205         var files = fp.files;
  1206         var installs = [];
  1208         function buildNextInstall() {
  1209           if (!files.hasMoreElements()) {
  1210             if (installs.length > 0) {
  1211               // Display the normal install confirmation for the installs
  1212               AddonManager.installAddonsFromWebpage("application/x-xpinstall",
  1213                                                     window, null, installs);
  1215             return;
  1218           var file = files.getNext();
  1219           AddonManager.getInstallForFile(file, function cmd_installFromFile_getInstallForFile(aInstall) {
  1220             installs.push(aInstall);
  1221             buildNextInstall();
  1222           });
  1225         buildNextInstall();
  1227     },
  1229     cmd_cancelOperation: {
  1230       isEnabled: function cmd_cancelOperation_isEnabled(aAddon) {
  1231         if (!aAddon)
  1232           return false;
  1233         return aAddon.pendingOperations != AddonManager.PENDING_NONE;
  1234       },
  1235       doCommand: function cmd_cancelOperation_doCommand(aAddon) {
  1236         if (isPending(aAddon, "install")) {
  1237           aAddon.install.cancel();
  1238         } else if (isPending(aAddon, "upgrade")) {
  1239           aAddon.pendingUpgrade.install.cancel();
  1240         } else if (isPending(aAddon, "uninstall")) {
  1241           aAddon.cancelUninstall();
  1242         } else if (isPending(aAddon, "enable")) {
  1243           aAddon.userDisabled = true;
  1244         } else if (isPending(aAddon, "disable")) {
  1245           aAddon.userDisabled = false;
  1248     },
  1250     cmd_contribute: {
  1251       isEnabled: function cmd_contribute_isEnabled(aAddon) {
  1252         if (!aAddon)
  1253           return false;
  1254         return ("contributionURL" in aAddon && aAddon.contributionURL);
  1255       },
  1256       doCommand: function cmd_contribute_doCommand(aAddon) {
  1257         openURL(aAddon.contributionURL);
  1259     },
  1261     cmd_askToActivateItem: {
  1262       isEnabled: function cmd_askToActivateItem_isEnabled(aAddon) {
  1263         if (!aAddon)
  1264           return false;
  1265         let addonType = AddonManager.addonTypes[aAddon.type];
  1266         return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
  1267                 hasPermission(aAddon, "ask_to_activate"));
  1268       },
  1269       doCommand: function cmd_askToActivateItem_doCommand(aAddon) {
  1270         aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
  1272     },
  1274     cmd_alwaysActivateItem: {
  1275       isEnabled: function cmd_alwaysActivateItem_isEnabled(aAddon) {
  1276         if (!aAddon)
  1277           return false;
  1278         let addonType = AddonManager.addonTypes[aAddon.type];
  1279         return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
  1280                 hasPermission(aAddon, "enable"));
  1281       },
  1282       doCommand: function cmd_alwaysActivateItem_doCommand(aAddon) {
  1283         aAddon.userDisabled = false;
  1285     },
  1287     cmd_neverActivateItem: {
  1288       isEnabled: function cmd_neverActivateItem_isEnabled(aAddon) {
  1289         if (!aAddon)
  1290           return false;
  1291         let addonType = AddonManager.addonTypes[aAddon.type];
  1292         return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) &&
  1293                 hasPermission(aAddon, "disable"));
  1294       },
  1295       doCommand: function cmd_neverActivateItem_doCommand(aAddon) {
  1296         aAddon.userDisabled = true;
  1298     },
  1300     cmd_experimentsLearnMore: {
  1301       isEnabled: function cmd_experimentsLearnMore_isEnabled() {
  1302         let mainWindow = getMainWindow();
  1303         return mainWindow && "switchToTabHavingURI" in mainWindow;
  1304       },
  1305       doCommand: function cmd_experimentsLearnMore_doCommand() {
  1306         let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL");
  1307         openOptionsInTab(url);
  1308       },
  1309     },
  1311     cmd_experimentsOpenTelemetryPreferences: {
  1312       isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() {
  1313         return !!getMainWindowWithPreferencesPane();
  1314       },
  1315       doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() {
  1316         let mainWindow = getMainWindowWithPreferencesPane();
  1317         mainWindow.openAdvancedPreferences("dataChoicesTab");
  1318       },
  1319     },
  1320   },
  1322   supportsCommand: function gVC_supportsCommand(aCommand) {
  1323     return (aCommand in this.commands);
  1324   },
  1326   isCommandEnabled: function gVC_isCommandEnabled(aCommand) {
  1327     if (!this.supportsCommand(aCommand))
  1328       return false;
  1329     var addon = this.currentViewObj.getSelectedAddon();
  1330     return this.commands[aCommand].isEnabled(addon);
  1331   },
  1333   updateCommands: function gVC_updateCommands() {
  1334     // wait until the view is initialized
  1335     if (!this.currentViewObj)
  1336       return;
  1337     var addon = this.currentViewObj.getSelectedAddon();
  1338     for (let commandId in this.commands)
  1339       this.updateCommand(commandId, addon);
  1340   },
  1342   updateCommand: function gVC_updateCommand(aCommandId, aAddon) {
  1343     if (typeof aAddon == "undefined")
  1344       aAddon = this.currentViewObj.getSelectedAddon();
  1345     var cmd = this.commands[aCommandId];
  1346     var cmdElt = document.getElementById(aCommandId);
  1347     cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon));
  1348     if ("getTooltip" in cmd) {
  1349       let tooltip = cmd.getTooltip(aAddon);
  1350       if (tooltip)
  1351         cmdElt.setAttribute("tooltiptext", tooltip);
  1352       else
  1353         cmdElt.removeAttribute("tooltiptext");
  1355   },
  1357   doCommand: function gVC_doCommand(aCommand, aAddon) {
  1358     if (!this.supportsCommand(aCommand))
  1359       return;
  1360     var cmd = this.commands[aCommand];
  1361     if (!aAddon)
  1362       aAddon = this.currentViewObj.getSelectedAddon();
  1363     if (!cmd.isEnabled(aAddon))
  1364       return;
  1365     cmd.doCommand(aAddon);
  1366   },
  1368   onEvent: function gVC_onEvent() {}
  1369 };
  1371 function hasInlineOptions(aAddon) {
  1372   return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE ||
  1373           aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO);
  1376 function openOptionsInTab(optionsURL) {
  1377   let mainWindow = getMainWindow();
  1378   if ("switchToTabHavingURI" in mainWindow) {
  1379     mainWindow.switchToTabHavingURI(optionsURL, true);
  1380     return true;
  1382   return false;
  1385 function formatDate(aDate) {
  1386   return Cc["@mozilla.org/intl/scriptabledateformat;1"]
  1387            .getService(Ci.nsIScriptableDateFormat)
  1388            .FormatDate("",
  1389                        Ci.nsIScriptableDateFormat.dateFormatLong,
  1390                        aDate.getFullYear(),
  1391                        aDate.getMonth() + 1,
  1392                        aDate.getDate()
  1393                        );
  1397 function hasPermission(aAddon, aPerm) {
  1398   var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()];
  1399   return !!(aAddon.permissions & perm);
  1403 function isPending(aAddon, aAction) {
  1404   var action = AddonManager["PENDING_" + aAction.toUpperCase()];
  1405   return !!(aAddon.pendingOperations & action);
  1408 function isInState(aInstall, aState) {
  1409   var state = AddonManager["STATE_" + aState.toUpperCase()];
  1410   return aInstall.state == state;
  1413 function shouldShowVersionNumber(aAddon) {
  1414   if (!aAddon.version)
  1415     return false;
  1417   // The version number is hidden for lightweight themes.
  1418   if (aAddon.type == "theme")
  1419     return !/@personas\.mozilla\.org$/.test(aAddon.id);
  1421   return true;
  1424 function createItem(aObj, aIsInstall, aIsRemote) {
  1425   let item = document.createElement("richlistitem");
  1427   item.setAttribute("class", "addon addon-view");
  1428   item.setAttribute("name", aObj.name);
  1429   item.setAttribute("type", aObj.type);
  1430   item.setAttribute("remote", !!aIsRemote);
  1432   if (aIsInstall) {
  1433     item.mInstall = aObj;
  1435     if (aObj.state != AddonManager.STATE_INSTALLED) {
  1436       item.setAttribute("status", "installing");
  1437       return item;
  1439     aObj = aObj.addon;
  1442   item.mAddon = aObj;
  1444   item.setAttribute("status", "installed");
  1446   // set only attributes needed for sorting and XBL binding,
  1447   // the binding handles the rest
  1448   item.setAttribute("value", aObj.id);
  1450   if (aObj.type == "experiment") {
  1451     item.endDate = getExperimentEndDate(aObj);
  1454   return item;
  1457 function sortElements(aElements, aSortBy, aAscending) {
  1458   // aSortBy is an Array of attributes to sort by, in decending
  1459   // order of priority.
  1461   const DATE_FIELDS = ["updateDate"];
  1462   const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"];
  1464   // We're going to group add-ons into the following buckets:
  1465   //
  1466   //  enabledInstalled
  1467   //    * Enabled
  1468   //    * Incompatible but enabled because compatibility checking is off
  1469   //    * Waiting to be installed
  1470   //    * Waiting to be enabled
  1471   //
  1472   //  pendingDisable
  1473   //    * Waiting to be disabled
  1474   //
  1475   //  pendingUninstall
  1476   //    * Waiting to be removed
  1477   //
  1478   //  disabledIncompatibleBlocked
  1479   //    * Disabled
  1480   //    * Incompatible
  1481   //    * Blocklisted
  1483   const UISTATE_ORDER = ["enabled", "pendingDisable", "pendingUninstall",
  1484                          "disabled"];
  1486   function dateCompare(a, b) {
  1487     var aTime = a.getTime();
  1488     var bTime = b.getTime();
  1489     if (aTime < bTime)
  1490       return -1;
  1491     if (aTime > bTime)
  1492       return 1;
  1493     return 0;
  1496   function numberCompare(a, b) {
  1497     return a - b;
  1500   function stringCompare(a, b) {
  1501     return a.localeCompare(b);
  1504   function uiStateCompare(a, b) {
  1505     // If we're in descending order, swap a and b, because
  1506     // we don't ever want to have descending uiStates
  1507     if (!aAscending)
  1508       [a, b] = [b, a];
  1510     return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b));
  1513   function getValue(aObj, aKey) {
  1514     if (!aObj)
  1515       return null;
  1517     if (aObj.hasAttribute(aKey))
  1518       return aObj.getAttribute(aKey);
  1520     var addon = aObj.mAddon || aObj.mInstall;
  1521     if (!addon)
  1522       return null;
  1524     if (aKey == "uiState") {
  1525       if (addon.pendingOperations == AddonManager.PENDING_DISABLE)
  1526         return "pendingDisable";
  1527       if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL)
  1528         return "pendingUninstall";
  1529       if (!addon.isActive &&
  1530           (addon.pendingOperations != AddonManager.PENDING_ENABLE &&
  1531            addon.pendingOperations != AddonManager.PENDING_INSTALL))
  1532         return "disabled";
  1533       else
  1534         return "enabled";
  1537     return addon[aKey];
  1540   // aSortFuncs will hold the sorting functions that we'll
  1541   // use per element, in the correct order.
  1542   var aSortFuncs = [];
  1544   for (let i = 0; i < aSortBy.length; i++) {
  1545     var sortBy = aSortBy[i];
  1547     aSortFuncs[i] = stringCompare;
  1549     if (sortBy == "uiState")
  1550       aSortFuncs[i] = uiStateCompare;
  1551     else if (DATE_FIELDS.indexOf(sortBy) != -1)
  1552       aSortFuncs[i] = dateCompare;
  1553     else if (NUMERIC_FIELDS.indexOf(sortBy) != -1)
  1554       aSortFuncs[i] = numberCompare;
  1558   aElements.sort(function elementsSort(a, b) {
  1559     if (!aAscending)
  1560       [a, b] = [b, a];
  1562     for (let i = 0; i < aSortFuncs.length; i++) {
  1563       var sortBy = aSortBy[i];
  1564       var aValue = getValue(a, sortBy);
  1565       var bValue = getValue(b, sortBy);
  1567       if (!aValue && !bValue)
  1568         return 0;
  1569       if (!aValue)
  1570         return -1;
  1571       if (!bValue)
  1572         return 1;
  1573       if (aValue != bValue) {
  1574         var result = aSortFuncs[i](aValue, bValue);
  1576         if (result != 0)
  1577           return result;
  1581     // If we got here, then all values of a and b
  1582     // must have been equal.
  1583     return 0;
  1585   });
  1588 function sortList(aList, aSortBy, aAscending) {
  1589   var elements = Array.slice(aList.childNodes, 0);
  1590   sortElements(elements, [aSortBy], aAscending);
  1592   while (aList.listChild)
  1593     aList.removeChild(aList.lastChild);
  1595   for (let element of elements)
  1596     aList.appendChild(element);
  1599 function getAddonsAndInstalls(aType, aCallback) {
  1600   let addons = null, installs = null;
  1601   let types = (aType != null) ? [aType] : null;
  1603   AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) {
  1604     addons = aAddonsList;
  1605     if (installs != null)
  1606       aCallback(addons, installs);
  1607   });
  1609   AddonManager.getInstallsByTypes(types, function getAddonsAndInstalls_getInstallsByTypes(aInstallsList) {
  1610     // skip over upgrade installs and non-active installs
  1611     installs = aInstallsList.filter(function installsFilter(aInstall) {
  1612       return !(aInstall.existingAddon ||
  1613                aInstall.state == AddonManager.STATE_AVAILABLE);
  1614     });
  1616     if (addons != null)
  1617       aCallback(addons, installs)
  1618   });
  1621 function doPendingUninstalls(aListBox) {
  1622   // Uninstalling add-ons can mutate the list so find the add-ons first then
  1623   // uninstall them
  1624   var items = [];
  1625   var listitem = aListBox.firstChild;
  1626   while (listitem) {
  1627     if (listitem.getAttribute("pending") == "uninstall" &&
  1628         !listitem.isPending("uninstall"))
  1629       items.push(listitem.mAddon);
  1630     listitem = listitem.nextSibling;
  1633   for (let addon of items)
  1634     addon.uninstall();
  1637 var gCategories = {
  1638   node: null,
  1639   _search: null,
  1641   initialize: function gCategories_initialize() {
  1642     this.node = document.getElementById("categories");
  1643     this._search = this.get("addons://search/");
  1645     var types = AddonManager.addonTypes;
  1646     for (var type in types)
  1647       this.onTypeAdded(types[type]);
  1649     AddonManager.addTypeListener(this);
  1651     try {
  1652       this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY);
  1653     } catch (e) { }
  1655     // If there was no last view or no existing category matched the last view
  1656     // then the list will default to selecting the search category and we never
  1657     // want to show that as the first view so switch to the default category
  1658     if (!this.node.selectedItem || this.node.selectedItem == this._search)
  1659       this.node.value = VIEW_DEFAULT;
  1661     var self = this;
  1662     this.node.addEventListener("select", function node_onSelected() {
  1663       self.maybeHideSearch();
  1664       gViewController.loadView(self.node.selectedItem.value);
  1665     }, false);
  1667     this.node.addEventListener("click", function node_onClicked(aEvent) {
  1668       var selectedItem = self.node.selectedItem;
  1669       if (aEvent.target.localName == "richlistitem" &&
  1670           aEvent.target == selectedItem) {
  1671         var viewId = selectedItem.value;
  1673         if (gViewController.parseViewId(viewId).type == "search") {
  1674           viewId += encodeURIComponent(gHeader.searchQuery);
  1677         gViewController.loadView(viewId);
  1679     }, false);
  1680   },
  1682   shutdown: function gCategories_shutdown() {
  1683     AddonManager.removeTypeListener(this);
  1684   },
  1686   _insertCategory: function gCategories_insertCategory(aId, aName, aView, aPriority, aStartHidden) {
  1687     // If this category already exists then don't re-add it
  1688     if (document.getElementById("category-" + aId))
  1689       return;
  1691     var category = document.createElement("richlistitem");
  1692     category.setAttribute("id", "category-" + aId);
  1693     category.setAttribute("value", aView);
  1694     category.setAttribute("class", "category");
  1695     category.setAttribute("name", aName);
  1696     category.setAttribute("tooltiptext", aName);
  1697     category.setAttribute("priority", aPriority);
  1698     category.setAttribute("hidden", aStartHidden);
  1700     var node;
  1701     for (node of this.node.children) {
  1702       var nodePriority = parseInt(node.getAttribute("priority"));
  1703       // If the new type's priority is higher than this one then this is the
  1704       // insertion point
  1705       if (aPriority < nodePriority)
  1706         break;
  1707       // If the new type's priority is lower than this one then this is isn't
  1708       // the insertion point
  1709       if (aPriority > nodePriority)
  1710         continue;
  1711       // If the priorities are equal and the new type's name is earlier
  1712       // alphabetically then this is the insertion point
  1713       if (String.localeCompare(aName, node.getAttribute("name")) < 0)
  1714         break;
  1717     this.node.insertBefore(category, node);
  1718   },
  1720   _removeCategory: function gCategories_removeCategory(aId) {
  1721     var category = document.getElementById("category-" + aId);
  1722     if (!category)
  1723       return;
  1725     // If this category is currently selected then switch to the default view
  1726     if (this.node.selectedItem == category)
  1727       gViewController.replaceView(VIEW_DEFAULT);
  1729     this.node.removeChild(category);
  1730   },
  1732   onTypeAdded: function gCategories_onTypeAdded(aType) {
  1733     // Ignore types that we don't have a view object for
  1734     if (!(aType.viewType in gViewController.viewObjects))
  1735       return;
  1737     var aViewId = "addons://" + aType.viewType + "/" + aType.id;
  1739     var startHidden = false;
  1740     if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) {
  1741       var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id);
  1742       try {
  1743         startHidden = Services.prefs.getBoolPref(prefName);
  1745       catch (e) {
  1746         // Default to hidden
  1747         startHidden = true;
  1750       var self = this;
  1751       gPendingInitializations++;
  1752       getAddonsAndInstalls(aType.id, function onTypeAdded_getAddonsAndInstalls(aAddonsList, aInstallsList) {
  1753         var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0);
  1754         var item = self.get(aViewId);
  1756         // Don't load view that is becoming hidden
  1757         if (hidden && aViewId == gViewController.currentViewId)
  1758           gViewController.loadView(VIEW_DEFAULT);
  1760         item.hidden = hidden;
  1761         Services.prefs.setBoolPref(prefName, hidden);
  1763         if (aAddonsList.length > 0 || aInstallsList.length > 0) {
  1764           notifyInitialized();
  1765           return;
  1768         gEventManager.registerInstallListener({
  1769           onDownloadStarted: function gCategories_onDownloadStarted(aInstall) {
  1770             this._maybeShowCategory(aInstall);
  1771           },
  1773           onInstallStarted: function gCategories_onInstallStarted(aInstall) {
  1774             this._maybeShowCategory(aInstall);
  1775           },
  1777           onInstallEnded: function gCategories_onInstallEnded(aInstall, aAddon) {
  1778             this._maybeShowCategory(aAddon);
  1779           },
  1781           onExternalInstall: function gCategories_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
  1782             this._maybeShowCategory(aAddon);
  1783           },
  1785           _maybeShowCategory: function gCategories_maybeShowCategory(aAddon) {
  1786             if (aType.id == aAddon.type) {
  1787               self.get(aViewId).hidden = false;
  1788               Services.prefs.setBoolPref(prefName, false);
  1789               gEventManager.unregisterInstallListener(this);
  1792         });
  1794         notifyInitialized();
  1795       });
  1798     this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority,
  1799                          startHidden);
  1800   },
  1802   onTypeRemoved: function gCategories_onTypeRemoved(aType) {
  1803     this._removeCategory(aType.id);
  1804   },
  1806   get selected() {
  1807     return this.node.selectedItem ? this.node.selectedItem.value : null;
  1808   },
  1810   select: function gCategories_select(aId, aPreviousView) {
  1811     var view = gViewController.parseViewId(aId);
  1812     if (view.type == "detail" && aPreviousView) {
  1813       aId = aPreviousView;
  1814       view = gViewController.parseViewId(aPreviousView);
  1817     Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId);
  1819     if (this.node.selectedItem &&
  1820         this.node.selectedItem.value == aId) {
  1821       this.node.selectedItem.hidden = false;
  1822       this.node.selectedItem.disabled = false;
  1823       return;
  1826     if (view.type == "search")
  1827       var item = this._search;
  1828     else
  1829       var item = this.get(aId);
  1831     if (item) {
  1832       item.hidden = false;
  1833       item.disabled = false;
  1834       this.node.suppressOnSelect = true;
  1835       this.node.selectedItem = item;
  1836       this.node.suppressOnSelect = false;
  1837       this.node.ensureElementIsVisible(item);
  1839       this.maybeHideSearch();
  1841   },
  1843   get: function gCategories_get(aId) {
  1844     var items = document.getElementsByAttribute("value", aId);
  1845     if (items.length)
  1846       return items[0];
  1847     return null;
  1848   },
  1850   setBadge: function gCategories_setBadge(aId, aCount) {
  1851     let item = this.get(aId);
  1852     if (item)
  1853       item.badgeCount = aCount;
  1854   },
  1856   maybeHideSearch: function gCategories_maybeHideSearch() {
  1857     var view = gViewController.parseViewId(this.node.selectedItem.value);
  1858     this._search.disabled = view.type != "search";
  1860 };
  1863 var gHeader = {
  1864   _search: null,
  1865   _dest: "",
  1867   initialize: function gHeader_initialize() {
  1868     this._search = document.getElementById("header-search");
  1870     this._search.addEventListener("command", function search_onCommand(aEvent) {
  1871       var query = aEvent.target.value;
  1872       if (query.length == 0)
  1873         return;
  1875       gViewController.loadView("addons://search/" + encodeURIComponent(query));
  1876     }, false);
  1878     function updateNavButtonVisibility() {
  1879       var shouldShow = gHeader.shouldShowNavButtons;
  1880       document.getElementById("back-btn").hidden = !shouldShow;
  1881       document.getElementById("forward-btn").hidden = !shouldShow;
  1884     window.addEventListener("focus", function window_onFocus(aEvent) {
  1885       if (aEvent.target == window)
  1886         updateNavButtonVisibility();
  1887     }, false);
  1889     updateNavButtonVisibility();
  1890   },
  1892   focusSearchBox: function gHeader_focusSearchBox() {
  1893     this._search.focus();
  1894   },
  1896   onKeyPress: function gHeader_onKeyPress(aEvent) {
  1897     if (String.fromCharCode(aEvent.charCode) == "/") {
  1898       this.focusSearchBox();
  1899       return;
  1902     // XXXunf Temporary until bug 371900 is fixed.
  1903     let key = document.getElementById("focusSearch").getAttribute("key");
  1904 #ifdef XP_MACOSX
  1905     let keyModifier = aEvent.metaKey;
  1906 #else
  1907     let keyModifier = aEvent.ctrlKey;
  1908 #endif
  1909     if (String.fromCharCode(aEvent.charCode) == key && keyModifier) {
  1910       this.focusSearchBox();
  1911       return;
  1913   },
  1915   get shouldShowNavButtons() {
  1916     var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor)
  1917                              .getInterface(Ci.nsIWebNavigation)
  1918                              .QueryInterface(Ci.nsIDocShellTreeItem);
  1920     // If there is no outer frame then make the buttons visible
  1921     if (docshellItem.rootTreeItem == docshellItem)
  1922       return true;
  1924     var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
  1925                                             .getInterface(Ci.nsIDOMWindow);
  1926     var outerDoc = outerWin.document;
  1927     var node = outerDoc.getElementById("back-button");
  1928     // If the outer frame has no back-button then make the buttons visible
  1929     if (!node)
  1930       return true;
  1932     // If the back-button or any of its parents are hidden then make the buttons
  1933     // visible
  1934     while (node != outerDoc) {
  1935       var style = outerWin.getComputedStyle(node, "");
  1936       if (style.display == "none")
  1937         return true;
  1938       if (style.visibility != "visible")
  1939         return true;
  1940       node = node.parentNode;
  1943     return false;
  1944   },
  1946   get searchQuery() {
  1947     return this._search.value;
  1948   },
  1950   set searchQuery(aQuery) {
  1951     this._search.value = aQuery;
  1952   },
  1953 };
  1956 var gDiscoverView = {
  1957   node: null,
  1958   enabled: true,
  1959   // Set to true after the view is first shown. If initialization completes
  1960   // after this then it must also load the discover homepage
  1961   loaded: false,
  1962   _browser: null,
  1963   _loading: null,
  1964   _error: null,
  1965   homepageURL: null,
  1966   _loadListeners: [],
  1968   initialize: function gDiscoverView_initialize() {
  1969     this.enabled = isDiscoverEnabled();
  1970     if (!this.enabled) {
  1971       gCategories.get("addons://discover/").hidden = true;
  1972       return;
  1975     this.node = document.getElementById("discover-view");
  1976     this._loading = document.getElementById("discover-loading");
  1977     this._error = document.getElementById("discover-error");
  1978     this._browser = document.getElementById("discover-browser");
  1980     let compatMode = "normal";
  1981     if (!AddonManager.checkCompatibility)
  1982       compatMode = "ignore";
  1983     else if (AddonManager.strictCompatibility)
  1984       compatMode = "strict";
  1986     var url = Services.prefs.getCharPref(PREF_DISCOVERURL);
  1987     url = url.replace("%COMPATIBILITY_MODE%", compatMode);
  1988     url = Services.urlFormatter.formatURL(url);
  1990     var self = this;
  1992     function setURL(aURL) {
  1993       try {
  1994         self.homepageURL = Services.io.newURI(aURL, null, null);
  1995       } catch (e) {
  1996         self.showError();
  1997         notifyInitialized();
  1998         return;
  2001       self._browser.homePage = self.homepageURL.spec;
  2002       self._browser.addProgressListener(self);
  2004       if (self.loaded)
  2005         self._loadURL(self.homepageURL.spec, false, notifyInitialized);
  2006       else
  2007         notifyInitialized();
  2010     if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) {
  2011       setURL(url);
  2012       return;
  2015     gPendingInitializations++;
  2016     AddonManager.getAllAddons(function initialize_getAllAddons(aAddons) {
  2017       var list = {};
  2018       for (let addon of aAddons) {
  2019         var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%",
  2020                                                                addon.id);
  2021         try {
  2022           if (!Services.prefs.getBoolPref(prefName))
  2023             continue;
  2024         } catch (e) { }
  2025         list[addon.id] = {
  2026           name: addon.name,
  2027           version: addon.version,
  2028           type: addon.type,
  2029           userDisabled: addon.userDisabled,
  2030           isCompatible: addon.isCompatible,
  2031           isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED
  2035       setURL(url + "#" + JSON.stringify(list));
  2036     });
  2037   },
  2039   destroy: function gDiscoverView_destroy() {
  2040     try {
  2041       this._browser.removeProgressListener(this);
  2043     catch (e) {
  2044       // Ignore the case when the listener wasn't already registered
  2046   },
  2048   show: function gDiscoverView_show(aParam, aRequest, aState, aIsRefresh) {
  2049     gViewController.updateCommands();
  2051     // If we're being told to load a specific URL then just do that
  2052     if (aState && "url" in aState) {
  2053       this.loaded = true;
  2054       this._loadURL(aState.url);
  2057     // If the view has loaded before and still at the homepage (if refreshing),
  2058     // and the error page is not visible then there is nothing else to do
  2059     if (this.loaded && this.node.selectedPanel != this._error &&
  2060         (!aIsRefresh || (this._browser.currentURI &&
  2061          this._browser.currentURI.spec == this._browser.homePage))) {
  2062       gViewController.notifyViewChanged();
  2063       return;
  2066     this.loaded = true;
  2068     // No homepage means initialization isn't complete, the browser will get
  2069     // loaded once initialization is complete
  2070     if (!this.homepageURL) {
  2071       this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController));
  2072       return;
  2075     this._loadURL(this.homepageURL.spec, aIsRefresh,
  2076                   gViewController.notifyViewChanged.bind(gViewController));
  2077   },
  2079   canRefresh: function gDiscoverView_canRefresh() {
  2080     if (this._browser.currentURI &&
  2081         this._browser.currentURI.spec == this._browser.homePage)
  2082       return false;
  2083     return true;
  2084   },
  2086   refresh: function gDiscoverView_refresh(aParam, aRequest, aState) {
  2087     this.show(aParam, aRequest, aState, true);
  2088   },
  2090   hide: function gDiscoverView_hide() { },
  2092   showError: function gDiscoverView_showError() {
  2093     this.node.selectedPanel = this._error;
  2094   },
  2096   _loadURL: function gDiscoverView_loadURL(aURL, aKeepHistory, aCallback) {
  2097     if (this._browser.currentURI.spec == aURL) {
  2098       if (aCallback)
  2099         aCallback();
  2100       return;
  2103     if (aCallback)
  2104       this._loadListeners.push(aCallback);
  2106     var flags = 0;
  2107     if (!aKeepHistory)
  2108       flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
  2110     this._browser.loadURIWithFlags(aURL, flags);
  2111   },
  2113   onLocationChange: function gDiscoverView_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
  2114     // Ignore the about:blank load
  2115     if (aLocation.spec == "about:blank")
  2116       return;
  2118     // When using the real session history the inner-frame will update the
  2119     // session history automatically, if using the fake history though it must
  2120     // be manually updated
  2121     if (gHistory == FakeHistory) {
  2122       var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell);
  2124       var state = {
  2125         view: "addons://discover/",
  2126         url: aLocation.spec
  2127       };
  2129       var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16;
  2130       if (docshell.loadType & replaceHistory)
  2131         gHistory.replaceState(state);
  2132       else
  2133         gHistory.pushState(state);
  2134       gViewController.lastHistoryIndex = gHistory.index;
  2137     gViewController.updateCommands();
  2139     // If the hostname is the same as the new location's host and either the
  2140     // default scheme is insecure or the new location is secure then continue
  2141     // with the load
  2142     if (aLocation.host == this.homepageURL.host &&
  2143         (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https")))
  2144       return;
  2146     // Canceling the request will send an error to onStateChange which will show
  2147     // the error page
  2148     aRequest.cancel(Components.results.NS_BINDING_ABORTED);
  2149   },
  2151   onSecurityChange: function gDiscoverView_onSecurityChange(aWebProgress, aRequest, aState) {
  2152     // Don't care about security if the page is not https
  2153     if (!this.homepageURL.schemeIs("https"))
  2154       return;
  2156     // If the request was secure then it is ok
  2157     if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE)
  2158       return;
  2160     // Canceling the request will send an error to onStateChange which will show
  2161     // the error page
  2162     aRequest.cancel(Components.results.NS_BINDING_ABORTED);
  2163   },
  2165   onStateChange: function gDiscoverView_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
  2166     let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
  2167                         Ci.nsIWebProgressListener.STATE_IS_REQUEST |
  2168                         Ci.nsIWebProgressListener.STATE_TRANSFERRING;
  2169     // Once transferring begins show the content
  2170     if (aStateFlags & transferStart)
  2171       this.node.selectedPanel = this._browser;
  2173     // Only care about the network events
  2174     if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK)))
  2175       return;
  2177     // If this is the start of network activity then show the loading page
  2178     if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START))
  2179       this.node.selectedPanel = this._loading;
  2181     // Ignore anything except stop events
  2182     if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP)))
  2183       return;
  2185     // Consider the successful load of about:blank as still loading
  2186     if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank")
  2187       return;
  2189     // If there was an error loading the page or the new hostname is not the
  2190     // same as the default hostname or the default scheme is secure and the new
  2191     // scheme is insecure then show the error page
  2192     const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021;
  2193     if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) ||
  2194         (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) {
  2195       this.showError();
  2196     } else {
  2197       // Got a successful load, make sure the browser is visible
  2198       this.node.selectedPanel = this._browser;
  2199       gViewController.updateCommands();
  2202     var listeners = this._loadListeners;
  2203     this._loadListeners = [];
  2205     for (let listener of listeners)
  2206       listener();
  2207   },
  2209   onProgressChange: function gDiscoverView_onProgressChange() { },
  2210   onStatusChange: function gDiscoverView_onStatusChange() { },
  2212   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
  2213                                          Ci.nsISupportsWeakReference]),
  2215   getSelectedAddon: function gDiscoverView_getSelectedAddon() null
  2216 };
  2219 var gCachedAddons = {};
  2221 var gSearchView = {
  2222   node: null,
  2223   _filter: null,
  2224   _sorters: null,
  2225   _loading: null,
  2226   _listBox: null,
  2227   _emptyNotice: null,
  2228   _allResultsLink: null,
  2229   _lastQuery: null,
  2230   _lastRemoteTotal: 0,
  2231   _pendingSearches: 0,
  2233   initialize: function gSearchView_initialize() {
  2234     this.node = document.getElementById("search-view");
  2235     this._filter = document.getElementById("search-filter-radiogroup");
  2236     this._sorters = document.getElementById("search-sorters");
  2237     this._sorters.handler = this;
  2238     this._loading = document.getElementById("search-loading");
  2239     this._listBox = document.getElementById("search-list");
  2240     this._emptyNotice = document.getElementById("search-list-empty");
  2241     this._allResultsLink = document.getElementById("search-allresults-link");
  2243     if (!AddonManager.isInstallEnabled("application/x-xpinstall"))
  2244       this._filter.hidden = true;
  2246     var self = this;
  2247     this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
  2248       if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
  2249         var item = self._listBox.selectedItem;
  2250         if (item)
  2251           item.showInDetailView();
  2253     }, false);
  2255     this._filter.addEventListener("command", function filter_onCommand() self.updateView(), false);
  2256   },
  2258   shutdown: function gSearchView_shutdown() {
  2259     if (AddonRepository.isSearching)
  2260       AddonRepository.cancelSearch();
  2261   },
  2263   get isSearching() {
  2264     return this._pendingSearches > 0;
  2265   },
  2267   show: function gSearchView_show(aQuery, aRequest) {
  2268     gEventManager.registerInstallListener(this);
  2270     this.showEmptyNotice(false);
  2271     this.showAllResultsLink(0);
  2272     this.showLoading(true);
  2273     this._sorters.showprice = false;
  2275     gHeader.searchQuery = aQuery;
  2276     aQuery = aQuery.trim().toLocaleLowerCase();
  2277     if (this._lastQuery == aQuery) {
  2278       this.updateView();
  2279       gViewController.notifyViewChanged();
  2280       return;
  2282     this._lastQuery = aQuery;
  2284     if (AddonRepository.isSearching)
  2285       AddonRepository.cancelSearch();
  2287     while (this._listBox.firstChild.localName == "richlistitem")
  2288       this._listBox.removeChild(this._listBox.firstChild);
  2290     var self = this;
  2291     gCachedAddons = {};
  2292     this._pendingSearches = 2;
  2293     this._sorters.setSort("relevancescore", false);
  2295     var elements = [];
  2297     function createSearchResults(aObjsList, aIsInstall, aIsRemote) {
  2298       for (let index in aObjsList) {
  2299         let obj = aObjsList[index];
  2300         let score = aObjsList.length - index;
  2301         if (!aIsRemote && aQuery.length > 0) {
  2302           score = self.getMatchScore(obj, aQuery);
  2303           if (score == 0)
  2304             continue;
  2307         let item = createItem(obj, aIsInstall, aIsRemote);
  2308         item.setAttribute("relevancescore", score);
  2309         if (aIsRemote) {
  2310           gCachedAddons[obj.id] = obj;
  2311           if (obj.purchaseURL)
  2312             self._sorters.showprice = true;
  2315         elements.push(item);
  2319     function finishSearch(createdCount) {
  2320       if (elements.length > 0) {
  2321         sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
  2322         for (let element of elements)
  2323           self._listBox.insertBefore(element, self._listBox.lastChild);
  2324         self.updateListAttributes();
  2327       self._pendingSearches--;
  2328       self.updateView();
  2330       if (!self.isSearching)
  2331         gViewController.notifyViewChanged();
  2334     getAddonsAndInstalls(null, function show_getAddonsAndInstalls(aAddons, aInstalls) {
  2335       if (gViewController && aRequest != gViewController.currentViewRequest)
  2336         return;
  2338       createSearchResults(aAddons, false, false);
  2339       createSearchResults(aInstalls, true, false);
  2340       finishSearch();
  2341     });
  2343     var maxRemoteResults = 0;
  2344     try {
  2345       maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS);
  2346     } catch(e) {}
  2348     if (maxRemoteResults <= 0) {
  2349       finishSearch(0);
  2350       return;
  2353     AddonRepository.searchAddons(aQuery, maxRemoteResults, {
  2354       searchFailed: function show_SearchFailed() {
  2355         if (gViewController && aRequest != gViewController.currentViewRequest)
  2356           return;
  2358         self._lastRemoteTotal = 0;
  2360         // XXXunf Better handling of AMO search failure. See bug 579502
  2361         finishSearch(0); // Silently fail
  2362       },
  2364       searchSucceeded: function show_SearchSucceeded(aAddonsList, aAddonCount, aTotalResults) {
  2365         if (gViewController && aRequest != gViewController.currentViewRequest)
  2366           return;
  2368         if (aTotalResults > maxRemoteResults)
  2369           self._lastRemoteTotal = aTotalResults;
  2370         else
  2371           self._lastRemoteTotal = 0;
  2373         var createdCount = createSearchResults(aAddonsList, false, true);
  2374         finishSearch(createdCount);
  2376     });
  2377   },
  2379   showLoading: function gSearchView_showLoading(aLoading) {
  2380     this._loading.hidden = !aLoading;
  2381     this._listBox.hidden = aLoading;
  2382   },
  2384   updateView: function gSearchView_updateView() {
  2385     var showLocal = this._filter.value == "local";
  2387     if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall"))
  2388       showLocal = true;
  2390     this._listBox.setAttribute("local", showLocal);
  2391     this._listBox.setAttribute("remote", !showLocal);
  2393     this.showLoading(this.isSearching && !showLocal);
  2394     if (!this.isSearching) {
  2395       var isEmpty = true;
  2396       var results = this._listBox.getElementsByTagName("richlistitem");
  2397       for (let result of results) {
  2398         var isRemote = (result.getAttribute("remote") == "true");
  2399         if ((isRemote && !showLocal) || (!isRemote && showLocal)) {
  2400           isEmpty = false;
  2401           break;
  2405       this.showEmptyNotice(isEmpty);
  2406       this.showAllResultsLink(this._lastRemoteTotal);
  2409     gViewController.updateCommands();
  2410   },
  2412   hide: function gSearchView_hide() {
  2413     gEventManager.unregisterInstallListener(this);
  2414     doPendingUninstalls(this._listBox);
  2415   },
  2417   getMatchScore: function gSearchView_getMatchScore(aObj, aQuery) {
  2418     var score = 0;
  2419     score += this.calculateMatchScore(aObj.name, aQuery,
  2420                                       SEARCH_SCORE_MULTIPLIER_NAME);
  2421     score += this.calculateMatchScore(aObj.description, aQuery,
  2422                                       SEARCH_SCORE_MULTIPLIER_DESCRIPTION);
  2423     return score;
  2424   },
  2426   calculateMatchScore: function gSearchView_calculateMatchScore(aStr, aQuery, aMultiplier) {
  2427     var score = 0;
  2428     if (!aStr || aQuery.length == 0)
  2429       return score;
  2431     aStr = aStr.trim().toLocaleLowerCase();
  2432     var haystack = aStr.split(/\s+/);
  2433     var needles = aQuery.split(/\s+/);
  2435     for (let needle of needles) {
  2436       for (let hay of haystack) {
  2437         if (hay == needle) {
  2438           // matching whole words is best
  2439           score += SEARCH_SCORE_MATCH_WHOLEWORD;
  2440         } else {
  2441           let i = hay.indexOf(needle);
  2442           if (i == 0) // matching on word boundries is also good
  2443             score += SEARCH_SCORE_MATCH_WORDBOUNDRY;
  2444           else if (i > 0) // substring matches not so good
  2445             score += SEARCH_SCORE_MATCH_SUBSTRING;
  2450     // give progressively higher score for longer queries, since longer queries
  2451     // are more likely to be unique and therefore more relevant.
  2452     if (needles.length > 1 && aStr.indexOf(aQuery) != -1)
  2453       score += needles.length;
  2455     return score * aMultiplier;
  2456   },
  2458   showEmptyNotice: function gSearchView_showEmptyNotice(aShow) {
  2459     this._emptyNotice.hidden = !aShow;
  2460     this._listBox.hidden = aShow;
  2461   },
  2463   showAllResultsLink: function gSearchView_showAllResultsLink(aTotalResults) {
  2464     if (aTotalResults == 0) {
  2465       this._allResultsLink.hidden = true;
  2466       return;
  2469     var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults");
  2470     linkStr = PluralForm.get(aTotalResults, linkStr);
  2471     linkStr = linkStr.replace("#1", aTotalResults);
  2472     this._allResultsLink.setAttribute("value", linkStr);
  2474     this._allResultsLink.setAttribute("href",
  2475                                       AddonRepository.getSearchURL(this._lastQuery));
  2476     this._allResultsLink.hidden = false;
  2477  },
  2479   updateListAttributes: function gSearchView_updateListAttributes() {
  2480     var item = this._listBox.querySelector("richlistitem[remote='true'][first]");
  2481     if (item)
  2482       item.removeAttribute("first");
  2483     item = this._listBox.querySelector("richlistitem[remote='true'][last]");
  2484     if (item)
  2485       item.removeAttribute("last");
  2486     var items = this._listBox.querySelectorAll("richlistitem[remote='true']");
  2487     if (items.length > 0) {
  2488       items[0].setAttribute("first", true);
  2489       items[items.length - 1].setAttribute("last", true);
  2492     item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]");
  2493     if (item)
  2494       item.removeAttribute("first");
  2495     item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]");
  2496     if (item)
  2497       item.removeAttribute("last");
  2498     items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])");
  2499     if (items.length > 0) {
  2500       items[0].setAttribute("first", true);
  2501       items[items.length - 1].setAttribute("last", true);
  2504   },
  2506   onSortChanged: function gSearchView_onSortChanged(aSortBy, aAscending) {
  2507     var footer = this._listBox.lastChild;
  2508     this._listBox.removeChild(footer);
  2510     sortList(this._listBox, aSortBy, aAscending);
  2511     this.updateListAttributes();
  2513     this._listBox.appendChild(footer);
  2514   },
  2516   onDownloadCancelled: function gSearchView_onDownloadCancelled(aInstall) {
  2517     this.removeInstall(aInstall);
  2518   },
  2520   onInstallCancelled: function gSearchView_onInstallCancelled(aInstall) {
  2521     this.removeInstall(aInstall);
  2522   },
  2524   removeInstall: function gSearchView_removeInstall(aInstall) {
  2525     for (let item of this._listBox.childNodes) {
  2526       if (item.mInstall == aInstall) {
  2527         this._listBox.removeChild(item);
  2528         return;
  2531   },
  2533   getSelectedAddon: function gSearchView_getSelectedAddon() {
  2534     var item = this._listBox.selectedItem;
  2535     if (item)
  2536       return item.mAddon;
  2537     return null;
  2538   },
  2540   getListItemForID: function gSearchView_getListItemForID(aId) {
  2541     var listitem = this._listBox.firstChild;
  2542     while (listitem) {
  2543       if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
  2544         return listitem;
  2545       listitem = listitem.nextSibling;
  2547     return null;
  2549 };
  2552 var gListView = {
  2553   node: null,
  2554   _listBox: null,
  2555   _emptyNotice: null,
  2556   _type: null,
  2558   initialize: function gListView_initialize() {
  2559     this.node = document.getElementById("list-view");
  2560     this._listBox = document.getElementById("addon-list");
  2561     this._emptyNotice = document.getElementById("addon-list-empty");
  2563     var self = this;
  2564     this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) {
  2565       if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
  2566         var item = self._listBox.selectedItem;
  2567         if (item)
  2568           item.showInDetailView();
  2570     }, false);
  2571   },
  2573   show: function gListView_show(aType, aRequest) {
  2574     if (!(aType in AddonManager.addonTypes))
  2575       throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG);
  2577     this._type = aType;
  2578     this.node.setAttribute("type", aType);
  2579     this.showEmptyNotice(false);
  2581     while (this._listBox.itemCount > 0)
  2582       this._listBox.removeItemAt(0);
  2584     var self = this;
  2585     getAddonsAndInstalls(aType, function show_getAddonsAndInstalls(aAddonsList, aInstallsList) {
  2586       if (gViewController && aRequest != gViewController.currentViewRequest)
  2587         return;
  2589       var elements = [];
  2591       for (let addonItem of aAddonsList)
  2592         elements.push(createItem(addonItem));
  2594       for (let installItem of aInstallsList)
  2595         elements.push(createItem(installItem, true));
  2597       self.showEmptyNotice(elements.length == 0);
  2598       if (elements.length > 0) {
  2599         sortElements(elements, ["uiState", "name"], true);
  2600         for (let element of elements)
  2601           self._listBox.appendChild(element);
  2604       gEventManager.registerInstallListener(self);
  2605       gViewController.updateCommands();
  2606       gViewController.notifyViewChanged();
  2607     });
  2608   },
  2610   hide: function gListView_hide() {
  2611     gEventManager.unregisterInstallListener(this);
  2612     doPendingUninstalls(this._listBox);
  2613   },
  2615   showEmptyNotice: function gListView_showEmptyNotice(aShow) {
  2616     this._emptyNotice.hidden = !aShow;
  2617   },
  2619   onSortChanged: function gListView_onSortChanged(aSortBy, aAscending) {
  2620     sortList(this._listBox, aSortBy, aAscending);
  2621   },
  2623   onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) {
  2624     // The existing list item will take care of upgrade installs
  2625     if (aExistingAddon)
  2626       return;
  2628     this.addItem(aAddon);
  2629   },
  2631   onDownloadStarted: function gListView_onDownloadStarted(aInstall) {
  2632     this.addItem(aInstall, true);
  2633   },
  2635   onInstallStarted: function gListView_onInstallStarted(aInstall) {
  2636     this.addItem(aInstall, true);
  2637   },
  2639   onDownloadCancelled: function gListView_onDownloadCancelled(aInstall) {
  2640     this.removeItem(aInstall, true);
  2641   },
  2643   onInstallCancelled: function gListView_onInstallCancelled(aInstall) {
  2644     this.removeItem(aInstall, true);
  2645   },
  2647   onInstallEnded: function gListView_onInstallEnded(aInstall) {
  2648     // Remove any install entries for upgrades, their status will appear against
  2649     // the existing item
  2650     if (aInstall.existingAddon)
  2651       this.removeItem(aInstall, true);
  2653     if (aInstall.addon.type == "experiment") {
  2654       let item = this.getListItemForID(aInstall.addon.id);
  2655       if (item) {
  2656         item.endDate = getExperimentEndDate(aInstall.addon);
  2659   },
  2661   addItem: function gListView_addItem(aObj, aIsInstall) {
  2662     if (aObj.type != this._type)
  2663       return;
  2665     if (aIsInstall && aObj.existingAddon)
  2666       return;
  2668     let prop = aIsInstall ? "mInstall" : "mAddon";
  2669     for (let item of this._listBox.childNodes) {
  2670       if (item[prop] == aObj)
  2671         return;
  2674     let item = createItem(aObj, aIsInstall);
  2675     this._listBox.insertBefore(item, this._listBox.firstChild);
  2676     this.showEmptyNotice(false);
  2677   },
  2679   removeItem: function gListView_removeItem(aObj, aIsInstall) {
  2680     let prop = aIsInstall ? "mInstall" : "mAddon";
  2682     for (let item of this._listBox.childNodes) {
  2683       if (item[prop] == aObj) {
  2684         this._listBox.removeChild(item);
  2685         this.showEmptyNotice(this._listBox.itemCount == 0);
  2686         return;
  2689   },
  2691   getSelectedAddon: function gListView_getSelectedAddon() {
  2692     var item = this._listBox.selectedItem;
  2693     if (item)
  2694       return item.mAddon;
  2695     return null;
  2696   },
  2698   getListItemForID: function gListView_getListItemForID(aId) {
  2699     var listitem = this._listBox.firstChild;
  2700     while (listitem) {
  2701       if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId)
  2702         return listitem;
  2703       listitem = listitem.nextSibling;
  2705     return null;
  2707 };
  2710 var gDetailView = {
  2711   node: null,
  2712   _addon: null,
  2713   _loadingTimer: null,
  2714   _autoUpdate: null,
  2716   initialize: function gDetailView_initialize() {
  2717     this.node = document.getElementById("detail-view");
  2719     this._autoUpdate = document.getElementById("detail-autoUpdate");
  2721     var self = this;
  2722     this._autoUpdate.addEventListener("command", function autoUpdate_onCommand() {
  2723       self._addon.applyBackgroundUpdates = self._autoUpdate.value;
  2724     }, true);
  2725   },
  2727   shutdown: function gDetailView_shutdown() {
  2728     AddonManager.removeManagerListener(this);
  2729   },
  2731   onUpdateModeChanged: function gDetailView_onUpdateModeChanged() {
  2732     this.onPropertyChanged(["applyBackgroundUpdates"]);
  2733   },
  2735   _updateView: function gDetailView_updateView(aAddon, aIsRemote, aScrollToPreferences) {
  2736     AddonManager.addManagerListener(this);
  2737     this.clearLoading();
  2739     this._addon = aAddon;
  2740     gEventManager.registerAddonListener(this, aAddon.id);
  2741     gEventManager.registerInstallListener(this);
  2743     this.node.setAttribute("type", aAddon.type);
  2745     // If the search category isn't selected then make sure to select the
  2746     // correct category
  2747     if (gCategories.selected != "addons://search/")
  2748       gCategories.select("addons://list/" + aAddon.type);
  2750     document.getElementById("detail-name").textContent = aAddon.name;
  2751     var icon = aAddon.icon64URL ? aAddon.icon64URL : aAddon.iconURL;
  2752     document.getElementById("detail-icon").src = icon ? icon : "";
  2753     document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL);
  2755     var version = document.getElementById("detail-version");
  2756     if (shouldShowVersionNumber(aAddon)) {
  2757       version.hidden = false;
  2758       version.value = aAddon.version;
  2759     } else {
  2760       version.hidden = true;
  2763     var screenshot = document.getElementById("detail-screenshot");
  2764     if (aAddon.screenshots && aAddon.screenshots.length > 0) {
  2765       if (aAddon.screenshots[0].thumbnailURL) {
  2766         screenshot.src = aAddon.screenshots[0].thumbnailURL;
  2767         screenshot.width = aAddon.screenshots[0].thumbnailWidth;
  2768         screenshot.height = aAddon.screenshots[0].thumbnailHeight;
  2769       } else {
  2770         screenshot.src = aAddon.screenshots[0].url;
  2771         screenshot.width = aAddon.screenshots[0].width;
  2772         screenshot.height = aAddon.screenshots[0].height;
  2774       screenshot.setAttribute("loading", "true");
  2775       screenshot.hidden = false;
  2776     } else {
  2777       screenshot.hidden = true;
  2780     var desc = document.getElementById("detail-desc");
  2781     desc.textContent = aAddon.description;
  2783     var fullDesc = document.getElementById("detail-fulldesc");
  2784     if (aAddon.fullDescription) {
  2785       fullDesc.textContent = aAddon.fullDescription;
  2786       fullDesc.hidden = false;
  2787     } else {
  2788       fullDesc.hidden = true;
  2791     var contributions = document.getElementById("detail-contributions");
  2792     if ("contributionURL" in aAddon && aAddon.contributionURL) {
  2793       contributions.hidden = false;
  2794       var amount = document.getElementById("detail-contrib-suggested");
  2795       if (aAddon.contributionAmount) {
  2796         amount.value = gStrings.ext.formatStringFromName("contributionAmount2",
  2797                                                          [aAddon.contributionAmount],
  2798                                                          1);
  2799         amount.hidden = false;
  2800       } else {
  2801         amount.hidden = true;
  2803     } else {
  2804       contributions.hidden = true;
  2807     if ("purchaseURL" in aAddon && aAddon.purchaseURL) {
  2808       var purchase = document.getElementById("detail-purchase-btn");
  2809       purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label",
  2810                                                          [aAddon.purchaseDisplayAmount],
  2811                                                          1);
  2812       purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey");
  2815     var updateDateRow = document.getElementById("detail-dateUpdated");
  2816     if (aAddon.updateDate) {
  2817       var date = formatDate(aAddon.updateDate);
  2818       updateDateRow.value = date;
  2819     } else {
  2820       updateDateRow.value = null;
  2823     // TODO if the add-on was downloaded from releases.mozilla.org link to the
  2824     // AMO profile (bug 590344)
  2825     if (false) {
  2826       document.getElementById("detail-repository-row").hidden = false;
  2827       document.getElementById("detail-homepage-row").hidden = true;
  2828       var repository = document.getElementById("detail-repository");
  2829       repository.value = aAddon.homepageURL;
  2830       repository.href = aAddon.homepageURL;
  2831     } else if (aAddon.homepageURL) {
  2832       document.getElementById("detail-repository-row").hidden = true;
  2833       document.getElementById("detail-homepage-row").hidden = false;
  2834       var homepage = document.getElementById("detail-homepage");
  2835       homepage.value = aAddon.homepageURL;
  2836       homepage.href = aAddon.homepageURL;
  2837     } else {
  2838       document.getElementById("detail-repository-row").hidden = true;
  2839       document.getElementById("detail-homepage-row").hidden = true;
  2842     var rating = document.getElementById("detail-rating");
  2843     if (aAddon.averageRating) {
  2844       rating.averageRating = aAddon.averageRating;
  2845       rating.hidden = false;
  2846     } else {
  2847       rating.hidden = true;
  2850     var reviews = document.getElementById("detail-reviews");
  2851     if (aAddon.reviewURL) {
  2852       var text = gStrings.ext.GetStringFromName("numReviews");
  2853       text = PluralForm.get(aAddon.reviewCount, text)
  2854       text = text.replace("#1", aAddon.reviewCount);
  2855       reviews.value = text;
  2856       reviews.hidden = false;
  2857       reviews.href = aAddon.reviewURL;
  2858     } else {
  2859       reviews.hidden = true;
  2862     document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL;
  2864     var sizeRow = document.getElementById("detail-size");
  2865     if (aAddon.size && aIsRemote) {
  2866       let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size));
  2867       let formatted = gStrings.dl.GetStringFromName("doneSize");
  2868       formatted = formatted.replace("#1", size).replace("#2", unit);
  2869       sizeRow.value = formatted;
  2870     } else {
  2871       sizeRow.value = null;
  2874     var downloadsRow = document.getElementById("detail-downloads");
  2875     if (aAddon.totalDownloads && aIsRemote) {
  2876       var downloads = aAddon.totalDownloads;
  2877       downloadsRow.value = downloads;
  2878     } else {
  2879       downloadsRow.value = null;
  2882     var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade") && aAddon.id != AddonManager.hotfixID;
  2883     document.getElementById("detail-updates-row").hidden = !canUpdate;
  2885     if ("applyBackgroundUpdates" in aAddon) {
  2886       this._autoUpdate.hidden = false;
  2887       this._autoUpdate.value = aAddon.applyBackgroundUpdates;
  2888       let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
  2889       document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
  2890     } else {
  2891       this._autoUpdate.hidden = true;
  2892       document.getElementById("detail-findUpdates-btn").hidden = false;
  2895     document.getElementById("detail-prefs-btn").hidden = !aIsRemote &&
  2896       !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon);
  2898     var gridRows = document.querySelectorAll("#detail-grid rows row");
  2899     let first = true;
  2900     for (let gridRow of gridRows) {
  2901       if (first && window.getComputedStyle(gridRow, null).getPropertyValue("display") != "none") {
  2902         gridRow.setAttribute("first-row", true);
  2903         first = false;
  2904       } else {
  2905         gridRow.removeAttribute("first-row");
  2909     if (this._addon.type == "experiment") {
  2910       let prefix = "details.experiment.";
  2911       let active = this._addon.isActive;
  2913       let stateKey = prefix + "state." + (active ? "active" : "complete");
  2914       let node = document.getElementById("detail-experiment-state");
  2915       node.value = gStrings.ext.GetStringFromName(stateKey);
  2917       let now = Date.now();
  2918       let end = getExperimentEndDate(this._addon);
  2919       let days = Math.abs(end - now) / (24 * 60 * 60 * 1000);
  2921       let timeKey = prefix + "time.";
  2922       let timeMessage;
  2923       if (days < 1) {
  2924         timeKey += (active ? "endsToday" : "endedToday");
  2925         timeMessage = gStrings.ext.GetStringFromName(timeKey);
  2926       } else {
  2927         timeKey += (active ? "daysRemaining" : "daysPassed");
  2928         days = Math.round(days);
  2929         let timeString = gStrings.ext.GetStringFromName(timeKey);
  2930         timeMessage = PluralForm.get(days, timeString)
  2931                                 .replace("#1", days);
  2934       document.getElementById("detail-experiment-time").value = timeMessage;
  2937     this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() {
  2938       this.updateState();
  2939       gViewController.notifyViewChanged();
  2940     }).bind(this));
  2941   },
  2943   show: function gDetailView_show(aAddonId, aRequest) {
  2944     let index = aAddonId.indexOf("/preferences");
  2945     let scrollToPreferences = false;
  2946     if (index >= 0) {
  2947       aAddonId = aAddonId.substring(0, index);
  2948       scrollToPreferences = true;
  2951     var self = this;
  2952     this._loadingTimer = setTimeout(function loadTimeOutTimer() {
  2953       self.node.setAttribute("loading-extended", true);
  2954     }, LOADING_MSG_DELAY);
  2956     var view = gViewController.currentViewId;
  2958     AddonManager.getAddonByID(aAddonId, function show_getAddonByID(aAddon) {
  2959       if (gViewController && aRequest != gViewController.currentViewRequest)
  2960         return;
  2962       if (aAddon) {
  2963         self._updateView(aAddon, false, scrollToPreferences);
  2964         return;
  2967       // Look for an add-on pending install
  2968       AddonManager.getAllInstalls(function show_getAllInstalls(aInstalls) {
  2969         for (let install of aInstalls) {
  2970           if (install.state == AddonManager.STATE_INSTALLED &&
  2971               install.addon.id == aAddonId) {
  2972             self._updateView(install.addon, false);
  2973             return;
  2977         if (aAddonId in gCachedAddons) {
  2978           self._updateView(gCachedAddons[aAddonId], true);
  2979           return;
  2982         // This might happen due to session restore restoring us back to an
  2983         // add-on that doesn't exist but otherwise shouldn't normally happen.
  2984         // Either way just revert to the default view.
  2985         gViewController.replaceView(VIEW_DEFAULT);
  2986       });
  2987     });
  2988   },
  2990   hide: function gDetailView_hide() {
  2991     AddonManager.removeManagerListener(this);
  2992     this.clearLoading();
  2993     if (this._addon) {
  2994       if (hasInlineOptions(this._addon)) {
  2995         Services.obs.notifyObservers(document,
  2996                                      AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
  2997                                      this._addon.id);
  3000       gEventManager.unregisterAddonListener(this, this._addon.id);
  3001       gEventManager.unregisterInstallListener(this);
  3002       this._addon = null;
  3004       // Flush the preferences to disk so they survive any crash
  3005       if (this.node.getElementsByTagName("setting").length)
  3006         Services.prefs.savePrefFile(null);
  3008   },
  3010   updateState: function gDetailView_updateState() {
  3011     gViewController.updateCommands();
  3013     var pending = this._addon.pendingOperations;
  3014     if (pending != AddonManager.PENDING_NONE) {
  3015       this.node.removeAttribute("notification");
  3017       var pending = null;
  3018       const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall",
  3019                                   "upgrade"];
  3020       for (let op of PENDING_OPERATIONS) {
  3021         if (isPending(this._addon, op))
  3022           pending = op;
  3025       this.node.setAttribute("pending", pending);
  3026       document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName(
  3027         "details.notification." + pending,
  3028         [this._addon.name, gStrings.brandShortName], 2
  3029       );
  3030     } else {
  3031       this.node.removeAttribute("pending");
  3033       if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
  3034         this.node.setAttribute("notification", "error");
  3035         document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
  3036           "details.notification.blocked",
  3037           [this._addon.name], 1
  3038         );
  3039         var errorLink = document.getElementById("detail-error-link");
  3040         errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link");
  3041         errorLink.href = this._addon.blocklistURL;
  3042         errorLink.hidden = false;
  3043       } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility ||
  3044         (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) {
  3045         this.node.setAttribute("notification", "warning");
  3046         document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
  3047           "details.notification.incompatible",
  3048           [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3
  3049         );
  3050         document.getElementById("detail-warning-link").hidden = true;
  3051       } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
  3052         this.node.setAttribute("notification", "warning");
  3053         document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
  3054           "details.notification.softblocked",
  3055           [this._addon.name], 1
  3056         );
  3057         var warningLink = document.getElementById("detail-warning-link");
  3058         warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link");
  3059         warningLink.href = this._addon.blocklistURL;
  3060         warningLink.hidden = false;
  3061       } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
  3062         this.node.setAttribute("notification", "warning");
  3063         document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
  3064           "details.notification.outdated",
  3065           [this._addon.name], 1
  3066         );
  3067         var warningLink = document.getElementById("detail-warning-link");
  3068         warningLink.value = gStrings.ext.GetStringFromName("details.notification.outdated.link");
  3069         warningLink.href = Services.urlFormatter.formatURLPref("plugins.update.url");
  3070         warningLink.hidden = false;
  3071       } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
  3072         this.node.setAttribute("notification", "error");
  3073         document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
  3074           "details.notification.vulnerableUpdatable",
  3075           [this._addon.name], 1
  3076         );
  3077         var errorLink = document.getElementById("detail-error-link");
  3078         errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link");
  3079         errorLink.href = this._addon.blocklistURL;
  3080         errorLink.hidden = false;
  3081       } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
  3082         this.node.setAttribute("notification", "error");
  3083         document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
  3084           "details.notification.vulnerableNoUpdate",
  3085           [this._addon.name], 1
  3086         );
  3087         var errorLink = document.getElementById("detail-error-link");
  3088         errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link");
  3089         errorLink.href = this._addon.blocklistURL;
  3090         errorLink.hidden = false;
  3091       } else {
  3092         this.node.removeAttribute("notification");
  3096     let menulist = document.getElementById("detail-state-menulist");
  3097     let addonType = AddonManager.addonTypes[this._addon.type];
  3098     if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE &&
  3099         (hasPermission(this._addon, "ask_to_activate") ||
  3100          hasPermission(this._addon, "enable") ||
  3101          hasPermission(this._addon, "disable"))) {
  3102       let askItem = document.getElementById("detail-ask-to-activate-menuitem");
  3103       let alwaysItem = document.getElementById("detail-always-activate-menuitem");
  3104       let neverItem = document.getElementById("detail-never-activate-menuitem");
  3105       if (this._addon.userDisabled === true) {
  3106         menulist.selectedItem = neverItem;
  3107       } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) {
  3108         menulist.selectedItem = askItem;
  3109       } else {
  3110         menulist.selectedItem = alwaysItem;
  3112       menulist.hidden = false;
  3113     } else {
  3114       menulist.hidden = true;
  3117     this.node.setAttribute("active", this._addon.isActive);
  3118   },
  3120   clearLoading: function gDetailView_clearLoading() {
  3121     if (this._loadingTimer) {
  3122       clearTimeout(this._loadingTimer);
  3123       this._loadingTimer = null;
  3126     this.node.removeAttribute("loading-extended");
  3127   },
  3129   emptySettingsRows: function gDetailView_emptySettingsRows() {
  3130     var lastRow = document.getElementById("detail-downloads");
  3131     var rows = lastRow.parentNode;
  3132     while (lastRow.nextSibling)
  3133       rows.removeChild(rows.lastChild);
  3134   },
  3136   fillSettingsRows: function gDetailView_fillSettingsRows(aScrollToPreferences, aCallback) {
  3137     this.emptySettingsRows();
  3138     if (!hasInlineOptions(this._addon)) {
  3139       if (aCallback)
  3140         aCallback();
  3141       return;
  3144     // This function removes and returns the text content of aNode without
  3145     // removing any child elements. Removing the text nodes ensures any XBL
  3146     // bindings apply properly.
  3147     function stripTextNodes(aNode) {
  3148       var text = '';
  3149       for (var i = 0; i < aNode.childNodes.length; i++) {
  3150         if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) {
  3151           text += aNode.childNodes[i].textContent;
  3152           aNode.removeChild(aNode.childNodes[i--]);
  3153         } else {
  3154           text += stripTextNodes(aNode.childNodes[i]);
  3157       return text;
  3160     var rows = document.getElementById("detail-downloads").parentNode;
  3162     try {
  3163       var xhr = new XMLHttpRequest();
  3164       xhr.open("GET", this._addon.optionsURL, true);
  3165       xhr.responseType = "xml";
  3166       xhr.onload = (function fillSettingsRows_onload() {
  3167         var xml = xhr.responseXML;
  3168         var settings = xml.querySelectorAll(":root > setting");
  3170         var firstSetting = null;
  3171         for (var setting of settings) {
  3173           var desc = stripTextNodes(setting).trim();
  3174           if (!setting.hasAttribute("desc"))
  3175             setting.setAttribute("desc", desc);
  3177           var type = setting.getAttribute("type");
  3178           if (type == "file" || type == "directory")
  3179             setting.setAttribute("fullpath", "true");
  3181           setting = document.importNode(setting, true);
  3182           var style = setting.getAttribute("style");
  3183           if (style) {
  3184             setting.removeAttribute("style");
  3185             setting.setAttribute("style", style);
  3188           rows.appendChild(setting);
  3189           var visible = window.getComputedStyle(setting, null).getPropertyValue("display") != "none";
  3190           if (!firstSetting && visible) {
  3191             setting.setAttribute("first-row", true);
  3192             firstSetting = setting;
  3196         // Ensure the page has loaded and force the XBL bindings to be synchronously applied,
  3197         // then notify observers.
  3198         if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) {
  3199           gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() {
  3200             gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false);
  3201             if (firstSetting)
  3202               firstSetting.clientTop;
  3203             Services.obs.notifyObservers(document,
  3204                                          AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
  3205                                          gDetailView._addon.id);
  3206             if (aScrollToPreferences)
  3207               gDetailView.scrollToPreferencesRows();
  3208           }, false);
  3209         } else {
  3210           if (firstSetting)
  3211             firstSetting.clientTop;
  3212           Services.obs.notifyObservers(document,
  3213                                        AddonManager.OPTIONS_NOTIFICATION_DISPLAYED,
  3214                                        this._addon.id);
  3215           if (aScrollToPreferences)
  3216             gDetailView.scrollToPreferencesRows();
  3218         if (aCallback)
  3219           aCallback();
  3220       }).bind(this);
  3221       xhr.onerror = function fillSettingsRows_onerror(aEvent) {
  3222         Cu.reportError("Error " + aEvent.target.status +
  3223                        " occurred while receiving " + this._addon.optionsURL);
  3224         if (aCallback)
  3225           aCallback();
  3226       };
  3227       xhr.send();
  3228     } catch(e) {
  3229       Cu.reportError(e);
  3230       if (aCallback)
  3231         aCallback();
  3233   },
  3235   scrollToPreferencesRows: function gDetailView_scrollToPreferencesRows() {
  3236     // We find this row, rather than remembering it from above,
  3237     // in case it has been changed by the observers.
  3238     let firstRow = gDetailView.node.querySelector('setting[first-row="true"]');
  3239     if (firstRow) {
  3240       let top = firstRow.boxObject.y;
  3241       top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top"));
  3243       let detailViewBoxObject = gDetailView.node.boxObject;
  3244       top -= detailViewBoxObject.y;
  3246       detailViewBoxObject.QueryInterface(Ci.nsIScrollBoxObject);
  3247       detailViewBoxObject.scrollTo(0, top);
  3249   },
  3251   getSelectedAddon: function gDetailView_getSelectedAddon() {
  3252     return this._addon;
  3253   },
  3255   onEnabling: function gDetailView_onEnabling() {
  3256     this.updateState();
  3257   },
  3259   onEnabled: function gDetailView_onEnabled() {
  3260     this.updateState();
  3261     this.fillSettingsRows();
  3262   },
  3264   onDisabling: function gDetailView_onDisabling(aNeedsRestart) {
  3265     this.updateState();
  3266     if (!aNeedsRestart && hasInlineOptions(this._addon)) {
  3267       Services.obs.notifyObservers(document,
  3268                                    AddonManager.OPTIONS_NOTIFICATION_HIDDEN,
  3269                                    this._addon.id);
  3271   },
  3273   onDisabled: function gDetailView_onDisabled() {
  3274     this.updateState();
  3275     this.emptySettingsRows();
  3276   },
  3278   onUninstalling: function gDetailView_onUninstalling() {
  3279     this.updateState();
  3280   },
  3282   onUninstalled: function gDetailView_onUninstalled() {
  3283     gViewController.popState();
  3284   },
  3286   onOperationCancelled: function gDetailView_onOperationCancelled() {
  3287     this.updateState();
  3288   },
  3290   onPropertyChanged: function gDetailView_onPropertyChanged(aProperties) {
  3291     if (aProperties.indexOf("applyBackgroundUpdates") != -1) {
  3292       this._autoUpdate.value = this._addon.applyBackgroundUpdates;
  3293       let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon);
  3294       document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates;
  3297     if (aProperties.indexOf("appDisabled") != -1 ||
  3298         aProperties.indexOf("userDisabled") != -1)
  3299       this.updateState();
  3300   },
  3302   onExternalInstall: function gDetailView_onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) {
  3303     // Only care about upgrades for the currently displayed add-on
  3304     if (!aExistingAddon || aExistingAddon.id != this._addon.id)
  3305       return;
  3307     if (!aNeedsRestart)
  3308       this._updateView(aAddon, false);
  3309     else
  3310       this.updateState();
  3311   },
  3313   onInstallCancelled: function gDetailView_onInstallCancelled(aInstall) {
  3314     if (aInstall.addon.id == this._addon.id)
  3315       gViewController.popState();
  3317 };
  3320 var gUpdatesView = {
  3321   node: null,
  3322   _listBox: null,
  3323   _emptyNotice: null,
  3324   _sorters: null,
  3325   _updateSelected: null,
  3326   _categoryItem: null,
  3328   initialize: function gUpdatesView_initialize() {
  3329     this.node = document.getElementById("updates-view");
  3330     this._listBox = document.getElementById("updates-list");
  3331     this._emptyNotice = document.getElementById("updates-list-empty");
  3332     this._sorters = document.getElementById("updates-sorters");
  3333     this._sorters.handler = this;
  3335     this._categoryItem = gCategories.get("addons://updates/available");
  3337     this._updateSelected = document.getElementById("update-selected-btn");
  3338     this._updateSelected.addEventListener("command", function updateSelected_onCommand() {
  3339       gUpdatesView.installSelected();
  3340     }, false);
  3342     this.updateAvailableCount(true);
  3344     AddonManager.addAddonListener(this);
  3345     AddonManager.addInstallListener(this);
  3346   },
  3348   shutdown: function gUpdatesView_shutdown() {
  3349     AddonManager.removeAddonListener(this);
  3350     AddonManager.removeInstallListener(this);
  3351   },
  3353   show: function gUpdatesView_show(aType, aRequest) {
  3354     document.getElementById("empty-availableUpdates-msg").hidden = aType != "available";
  3355     document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent";
  3356     this.showEmptyNotice(false);
  3358     while (this._listBox.itemCount > 0)
  3359       this._listBox.removeItemAt(0);
  3361     this.node.setAttribute("updatetype", aType);
  3362     if (aType == "recent")
  3363       this._showRecentUpdates(aRequest);
  3364     else
  3365       this._showAvailableUpdates(false, aRequest);
  3366   },
  3368   hide: function gUpdatesView_hide() {
  3369     this._updateSelected.hidden = true;
  3370     this._categoryItem.disabled = this._categoryItem.badgeCount == 0;
  3371     doPendingUninstalls(this._listBox);
  3372   },
  3374   _showRecentUpdates: function gUpdatesView_showRecentUpdates(aRequest) {
  3375     var self = this;
  3376     AddonManager.getAllAddons(function showRecentUpdates_getAllAddons(aAddonsList) {
  3377       if (gViewController && aRequest != gViewController.currentViewRequest)
  3378         return;
  3380       var elements = [];
  3381       let threshold = Date.now() - UPDATES_RECENT_TIMESPAN;
  3382       for (let addon of aAddonsList) {
  3383         if (!addon.updateDate || addon.updateDate.getTime() < threshold)
  3384           continue;
  3386         elements.push(createItem(addon));
  3389       self.showEmptyNotice(elements.length == 0);
  3390       if (elements.length > 0) {
  3391         sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
  3392         for (let element of elements)
  3393           self._listBox.appendChild(element);
  3396       gViewController.notifyViewChanged();
  3397     });
  3398   },
  3400   _showAvailableUpdates: function gUpdatesView_showAvailableUpdates(aIsRefresh, aRequest) {
  3401     /* Disable the Update Selected button so it can't get clicked
  3402        before everything is initialized asynchronously.
  3403        It will get re-enabled by maybeDisableUpdateSelected(). */
  3404     this._updateSelected.disabled = true;
  3406     var self = this;
  3407     AddonManager.getAllInstalls(function showAvailableUpdates_getAllInstalls(aInstallsList) {
  3408       if (!aIsRefresh && gViewController && aRequest &&
  3409           aRequest != gViewController.currentViewRequest)
  3410         return;
  3412       if (aIsRefresh) {
  3413         self.showEmptyNotice(false);
  3414         self._updateSelected.hidden = true;
  3416         while (self._listBox.itemCount > 0)
  3417           self._listBox.removeItemAt(0);
  3420       var elements = [];
  3422       for (let install of aInstallsList) {
  3423         if (!self.isManualUpdate(install))
  3424           continue;
  3426         let item = createItem(install.existingAddon);
  3427         item.setAttribute("upgrade", true);
  3428         item.addEventListener("IncludeUpdateChanged", function item_onIncludeUpdateChanged() {
  3429           self.maybeDisableUpdateSelected();
  3430         }, false);
  3431         elements.push(item);
  3434       self.showEmptyNotice(elements.length == 0);
  3435       if (elements.length > 0) {
  3436         self._updateSelected.hidden = false;
  3437         sortElements(elements, [self._sorters.sortBy], self._sorters.ascending);
  3438         for (let element of elements)
  3439           self._listBox.appendChild(element);
  3442       // ensure badge count is in sync
  3443       self._categoryItem.badgeCount = self._listBox.itemCount;
  3445       gViewController.notifyViewChanged();
  3446     });
  3447   },
  3449   showEmptyNotice: function gUpdatesView_showEmptyNotice(aShow) {
  3450     this._emptyNotice.hidden = !aShow;
  3451   },
  3453   isManualUpdate: function gUpdatesView_isManualUpdate(aInstall, aOnlyAvailable) {
  3454     var isManual = aInstall.existingAddon &&
  3455                    !AddonManager.shouldAutoUpdate(aInstall.existingAddon);
  3456     if (isManual && aOnlyAvailable)
  3457       return isInState(aInstall, "available");
  3458     return isManual;
  3459   },
  3461   maybeRefresh: function gUpdatesView_maybeRefresh() {
  3462     if (gViewController.currentViewId == "addons://updates/available")
  3463       this._showAvailableUpdates(true);
  3464     this.updateAvailableCount();
  3465   },
  3467   updateAvailableCount: function gUpdatesView_updateAvailableCount(aInitializing) {
  3468     if (aInitializing)
  3469       gPendingInitializations++;
  3470     var self = this;
  3471     AddonManager.getAllInstalls(function updateAvailableCount_getAllInstalls(aInstallsList) {
  3472       var count = aInstallsList.filter(function installListFilter(aInstall) {
  3473         return self.isManualUpdate(aInstall, true);
  3474       }).length;
  3475       self._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" &&
  3476                                     count == 0;
  3477       self._categoryItem.badgeCount = count;
  3478       if (aInitializing)
  3479         notifyInitialized();
  3480     });
  3481   },
  3483   maybeDisableUpdateSelected: function gUpdatesView_maybeDisableUpdateSelected() {
  3484     for (let item of this._listBox.childNodes) {
  3485       if (item.includeUpdate) {
  3486         this._updateSelected.disabled = false;
  3487         return;
  3490     this._updateSelected.disabled = true;
  3491   },
  3493   installSelected: function gUpdatesView_installSelected() {
  3494     for (let item of this._listBox.childNodes) {
  3495       if (item.includeUpdate)
  3496         item.upgrade();
  3499     this._updateSelected.disabled = true;
  3500   },
  3502   getSelectedAddon: function gUpdatesView_getSelectedAddon() {
  3503     var item = this._listBox.selectedItem;
  3504     if (item)
  3505       return item.mAddon;
  3506     return null;
  3507   },
  3509   getListItemForID: function gUpdatesView_getListItemForID(aId) {
  3510     var listitem = this._listBox.firstChild;
  3511     while (listitem) {
  3512       if (listitem.mAddon.id == aId)
  3513         return listitem;
  3514       listitem = listitem.nextSibling;
  3516     return null;
  3517   },
  3519   onSortChanged: function gUpdatesView_onSortChanged(aSortBy, aAscending) {
  3520     sortList(this._listBox, aSortBy, aAscending);
  3521   },
  3523   onNewInstall: function gUpdatesView_onNewInstall(aInstall) {
  3524     if (!this.isManualUpdate(aInstall))
  3525       return;
  3526     this.maybeRefresh();
  3527   },
  3529   onInstallStarted: function gUpdatesView_onInstallStarted(aInstall) {
  3530     this.updateAvailableCount();
  3531   },
  3533   onInstallCancelled: function gUpdatesView_onInstallCancelled(aInstall) {
  3534     if (!this.isManualUpdate(aInstall))
  3535       return;
  3536     this.maybeRefresh();
  3537   },
  3539   onPropertyChanged: function gUpdatesView_onPropertyChanged(aAddon, aProperties) {
  3540     if (aProperties.indexOf("applyBackgroundUpdates") != -1)
  3541       this.updateAvailableCount();
  3543 };
  3545 function debuggingPrefChanged() {
  3546   gViewController.updateState();
  3547   gViewController.updateCommands();
  3548   gViewController.notifyViewChanged();
  3551 var gDragDrop = {
  3552   onDragOver: function gDragDrop_onDragOver(aEvent) {
  3553     var types = aEvent.dataTransfer.types;
  3554     if (types.contains("text/uri-list") ||
  3555         types.contains("text/x-moz-url") ||
  3556         types.contains("application/x-moz-file"))
  3557       aEvent.preventDefault();
  3558   },
  3560   onDrop: function gDragDrop_onDrop(aEvent) {
  3561     var dataTransfer = aEvent.dataTransfer;
  3562     var urls = [];
  3564     // Convert every dropped item into a url
  3565     for (var i = 0; i < dataTransfer.mozItemCount; i++) {
  3566       var url = dataTransfer.mozGetDataAt("text/uri-list", i);
  3567       if (url) {
  3568         urls.push(url);
  3569         continue;
  3572       url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
  3573       if (url) {
  3574         urls.push(url.split("\n")[0]);
  3575         continue;
  3578       var file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
  3579       if (file) {
  3580         urls.push(Services.io.newFileURI(file).spec);
  3581         continue;
  3585     var pos = 0;
  3586     var installs = [];
  3588     function buildNextInstall() {
  3589       if (pos == urls.length) {
  3590         if (installs.length > 0) {
  3591           // Display the normal install confirmation for the installs
  3592           AddonManager.installAddonsFromWebpage("application/x-xpinstall",
  3593                                                 window, null, installs);
  3595         return;
  3598       AddonManager.getInstallForURL(urls[pos++], function onDrop_getInstallForURL(aInstall) {
  3599         installs.push(aInstall);
  3600         buildNextInstall();
  3601       }, "application/x-xpinstall");
  3604     buildNextInstall();
  3606     aEvent.preventDefault();
  3608 };

mercurial