1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/mozapps/extensions/content/extensions.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,3608 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 +const Cr = Components.results; 1.14 + 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource://gre/modules/DownloadUtils.jsm"); 1.18 +Cu.import("resource://gre/modules/AddonManager.jsm"); 1.19 +Cu.import("resource://gre/modules/addons/AddonRepository.jsm"); 1.20 + 1.21 +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", 1.22 + "resource://gre/modules/PluralForm.jsm"); 1.23 + 1.24 +XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () { 1.25 + return Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {}). 1.26 + BrowserToolboxProcess; 1.27 +}); 1.28 +XPCOMUtils.defineLazyModuleGetter(this, "Experiments", 1.29 + "resource:///modules/experiments/Experiments.jsm"); 1.30 + 1.31 +const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; 1.32 +const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; 1.33 +const PREF_XPI_ENABLED = "xpinstall.enabled"; 1.34 +const PREF_MAXRESULTS = "extensions.getAddons.maxResults"; 1.35 +const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; 1.36 +const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled"; 1.37 +const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden"; 1.38 +const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory"; 1.39 +const PREF_ADDON_DEBUGGING_ENABLED = "devtools.chrome.enabled"; 1.40 +const PREF_REMOTE_DEBUGGING_ENABLED = "devtools.debugger.remote-enabled"; 1.41 + 1.42 +const LOADING_MSG_DELAY = 100; 1.43 + 1.44 +const SEARCH_SCORE_MULTIPLIER_NAME = 2; 1.45 +const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2; 1.46 + 1.47 +// Use integers so search scores are sortable by nsIXULSortService 1.48 +const SEARCH_SCORE_MATCH_WHOLEWORD = 10; 1.49 +const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6; 1.50 +const SEARCH_SCORE_MATCH_SUBSTRING = 3; 1.51 + 1.52 +const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds) 1.53 +const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl"; 1.54 + 1.55 +const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" 1.56 + 1.57 +const VIEW_DEFAULT = "addons://discover/"; 1.58 + 1.59 +var gStrings = {}; 1.60 +XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc", 1.61 + "@mozilla.org/intl/stringbundle;1", 1.62 + "nsIStringBundleService"); 1.63 + 1.64 +XPCOMUtils.defineLazyGetter(gStrings, "brand", function brandLazyGetter() { 1.65 + return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties"); 1.66 +}); 1.67 +XPCOMUtils.defineLazyGetter(gStrings, "ext", function extLazyGetter() { 1.68 + return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties"); 1.69 +}); 1.70 +XPCOMUtils.defineLazyGetter(gStrings, "dl", function dlLazyGetter() { 1.71 + return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties"); 1.72 +}); 1.73 + 1.74 +XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function brandShortNameLazyGetter() { 1.75 + return this.brand.GetStringFromName("brandShortName"); 1.76 +}); 1.77 +XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function appVersionLazyGetter() { 1.78 + return Services.appinfo.version; 1.79 +}); 1.80 + 1.81 +document.addEventListener("load", initialize, true); 1.82 +window.addEventListener("unload", shutdown, false); 1.83 + 1.84 +var gPendingInitializations = 1; 1.85 +this.__defineGetter__("gIsInitializing", function gIsInitializingGetter() gPendingInitializations > 0); 1.86 + 1.87 +function initialize(event) { 1.88 + // XXXbz this listener gets _all_ load events for all nodes in the 1.89 + // document... but relies on not being called "too early". 1.90 + if (event.target instanceof XMLStylesheetProcessingInstruction) { 1.91 + return; 1.92 + } 1.93 + document.removeEventListener("load", initialize, true); 1.94 + 1.95 + let globalCommandSet = document.getElementById("globalCommandSet"); 1.96 + globalCommandSet.addEventListener("command", function(event) { 1.97 + gViewController.doCommand(event.target.id); 1.98 + }); 1.99 + 1.100 + let viewCommandSet = document.getElementById("viewCommandSet"); 1.101 + viewCommandSet.addEventListener("commandupdate", function(event) { 1.102 + gViewController.updateCommands(); 1.103 + }); 1.104 + viewCommandSet.addEventListener("command", function(event) { 1.105 + gViewController.doCommand(event.target.id); 1.106 + }); 1.107 + 1.108 + let detailScreenshot = document.getElementById("detail-screenshot"); 1.109 + detailScreenshot.addEventListener("load", function(event) { 1.110 + this.removeAttribute("loading"); 1.111 + }); 1.112 + detailScreenshot.addEventListener("error", function(event) { 1.113 + this.setAttribute("loading", "error"); 1.114 + }); 1.115 + 1.116 + let addonPage = document.getElementById("addons-page"); 1.117 + addonPage.addEventListener("dragenter", function(event) { 1.118 + gDragDrop.onDragOver(event); 1.119 + }); 1.120 + addonPage.addEventListener("dragover", function(event) { 1.121 + gDragDrop.onDragOver(event); 1.122 + }); 1.123 + addonPage.addEventListener("drop", function(event) { 1.124 + gDragDrop.onDrop(event); 1.125 + }); 1.126 + addonPage.addEventListener("keypress", function(event) { 1.127 + gHeader.onKeyPress(event); 1.128 + }); 1.129 + 1.130 + gViewController.initialize(); 1.131 + gCategories.initialize(); 1.132 + gHeader.initialize(); 1.133 + gEventManager.initialize(); 1.134 + Services.obs.addObserver(sendEMPong, "EM-ping", false); 1.135 + Services.obs.notifyObservers(window, "EM-loaded", ""); 1.136 + 1.137 + // If the initial view has already been selected (by a call to loadView from 1.138 + // the above notifications) then bail out now 1.139 + if (gViewController.initialViewSelected) 1.140 + return; 1.141 + 1.142 + // If there is a history state to restore then use that 1.143 + if (window.history.state) { 1.144 + gViewController.updateState(window.history.state); 1.145 + return; 1.146 + } 1.147 + 1.148 + // Default to the last selected category 1.149 + var view = gCategories.node.value; 1.150 + 1.151 + // Allow passing in a view through the window arguments 1.152 + if ("arguments" in window && window.arguments.length > 0 && 1.153 + window.arguments[0] !== null && "view" in window.arguments[0]) { 1.154 + view = window.arguments[0].view; 1.155 + } 1.156 + 1.157 + gViewController.loadInitialView(view); 1.158 + 1.159 + Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false); 1.160 + Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false); 1.161 +} 1.162 + 1.163 +function notifyInitialized() { 1.164 + if (!gIsInitializing) 1.165 + return; 1.166 + 1.167 + gPendingInitializations--; 1.168 + if (!gIsInitializing) { 1.169 + var event = document.createEvent("Events"); 1.170 + event.initEvent("Initialized", true, true); 1.171 + document.dispatchEvent(event); 1.172 + } 1.173 +} 1.174 + 1.175 +function shutdown() { 1.176 + gCategories.shutdown(); 1.177 + gSearchView.shutdown(); 1.178 + gEventManager.shutdown(); 1.179 + gViewController.shutdown(); 1.180 + Services.obs.removeObserver(sendEMPong, "EM-ping"); 1.181 + Services.prefs.removeObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged); 1.182 + Services.prefs.removeObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged); 1.183 +} 1.184 + 1.185 +function sendEMPong(aSubject, aTopic, aData) { 1.186 + Services.obs.notifyObservers(window, "EM-pong", ""); 1.187 +} 1.188 + 1.189 +// Used by external callers to load a specific view into the manager 1.190 +function loadView(aViewId) { 1.191 + if (!gViewController.initialViewSelected) { 1.192 + // The caller opened the window and immediately loaded the view so it 1.193 + // should be the initial history entry 1.194 + 1.195 + gViewController.loadInitialView(aViewId); 1.196 + } else { 1.197 + gViewController.loadView(aViewId); 1.198 + } 1.199 +} 1.200 + 1.201 +function isDiscoverEnabled() { 1.202 + if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID) 1.203 + return false; 1.204 + 1.205 + try { 1.206 + if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED)) 1.207 + return false; 1.208 + } catch (e) {} 1.209 + 1.210 + try { 1.211 + if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED)) 1.212 + return false; 1.213 + } catch (e) {} 1.214 + 1.215 + return true; 1.216 +} 1.217 + 1.218 +function getExperimentEndDate(aAddon) { 1.219 + if (!("@mozilla.org/browser/experiments-service;1" in Cc)) { 1.220 + return 0; 1.221 + } 1.222 + 1.223 + if (!aAddon.isActive) { 1.224 + return aAddon.endDate; 1.225 + } 1.226 + 1.227 + let experiment = Experiments.instance().getActiveExperiment(); 1.228 + if (!experiment) { 1.229 + return 0; 1.230 + } 1.231 + 1.232 + return experiment.endDate; 1.233 +} 1.234 + 1.235 +/** 1.236 + * Obtain the main DOMWindow for the current context. 1.237 + */ 1.238 +function getMainWindow() { 1.239 + return window.QueryInterface(Ci.nsIInterfaceRequestor) 1.240 + .getInterface(Ci.nsIWebNavigation) 1.241 + .QueryInterface(Ci.nsIDocShellTreeItem) 1.242 + .rootTreeItem 1.243 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.244 + .getInterface(Ci.nsIDOMWindow); 1.245 +} 1.246 + 1.247 +/** 1.248 + * Obtain the DOMWindow that can open a preferences pane. 1.249 + * 1.250 + * This is essentially "get the browser chrome window" with the added check 1.251 + * that the supposed browser chrome window is capable of opening a preferences 1.252 + * pane. 1.253 + * 1.254 + * This may return null if we can't find the browser chrome window. 1.255 + */ 1.256 +function getMainWindowWithPreferencesPane() { 1.257 + let mainWindow = getMainWindow(); 1.258 + if (mainWindow && "openAdvancedPreferences" in mainWindow) { 1.259 + return mainWindow; 1.260 + } else { 1.261 + return null; 1.262 + } 1.263 +} 1.264 + 1.265 +/** 1.266 + * A wrapper around the HTML5 session history service that allows the browser 1.267 + * back/forward controls to work within the manager 1.268 + */ 1.269 +var HTML5History = { 1.270 + get index() { 1.271 + return window.QueryInterface(Ci.nsIInterfaceRequestor) 1.272 + .getInterface(Ci.nsIWebNavigation) 1.273 + .sessionHistory.index; 1.274 + }, 1.275 + 1.276 + get canGoBack() { 1.277 + return window.QueryInterface(Ci.nsIInterfaceRequestor) 1.278 + .getInterface(Ci.nsIWebNavigation) 1.279 + .canGoBack; 1.280 + }, 1.281 + 1.282 + get canGoForward() { 1.283 + return window.QueryInterface(Ci.nsIInterfaceRequestor) 1.284 + .getInterface(Ci.nsIWebNavigation) 1.285 + .canGoForward; 1.286 + }, 1.287 + 1.288 + back: function HTML5History_back() { 1.289 + window.history.back(); 1.290 + gViewController.updateCommand("cmd_back"); 1.291 + gViewController.updateCommand("cmd_forward"); 1.292 + }, 1.293 + 1.294 + forward: function HTML5History_forward() { 1.295 + window.history.forward(); 1.296 + gViewController.updateCommand("cmd_back"); 1.297 + gViewController.updateCommand("cmd_forward"); 1.298 + }, 1.299 + 1.300 + pushState: function HTML5History_pushState(aState) { 1.301 + window.history.pushState(aState, document.title); 1.302 + }, 1.303 + 1.304 + replaceState: function HTML5History_replaceState(aState) { 1.305 + window.history.replaceState(aState, document.title); 1.306 + }, 1.307 + 1.308 + popState: function HTML5History_popState() { 1.309 + function onStatePopped(aEvent) { 1.310 + window.removeEventListener("popstate", onStatePopped, true); 1.311 + // TODO To ensure we can't go forward again we put an additional entry 1.312 + // for the current state into the history. Ideally we would just strip 1.313 + // the history but there doesn't seem to be a way to do that. Bug 590661 1.314 + window.history.pushState(aEvent.state, document.title); 1.315 + } 1.316 + window.addEventListener("popstate", onStatePopped, true); 1.317 + window.history.back(); 1.318 + gViewController.updateCommand("cmd_back"); 1.319 + gViewController.updateCommand("cmd_forward"); 1.320 + } 1.321 +}; 1.322 + 1.323 +/** 1.324 + * A wrapper around a fake history service 1.325 + */ 1.326 +var FakeHistory = { 1.327 + pos: 0, 1.328 + states: [null], 1.329 + 1.330 + get index() { 1.331 + return this.pos; 1.332 + }, 1.333 + 1.334 + get canGoBack() { 1.335 + return this.pos > 0; 1.336 + }, 1.337 + 1.338 + get canGoForward() { 1.339 + return (this.pos + 1) < this.states.length; 1.340 + }, 1.341 + 1.342 + back: function FakeHistory_back() { 1.343 + if (this.pos == 0) 1.344 + throw Components.Exception("Cannot go back from this point"); 1.345 + 1.346 + this.pos--; 1.347 + gViewController.updateState(this.states[this.pos]); 1.348 + gViewController.updateCommand("cmd_back"); 1.349 + gViewController.updateCommand("cmd_forward"); 1.350 + }, 1.351 + 1.352 + forward: function FakeHistory_forward() { 1.353 + if ((this.pos + 1) >= this.states.length) 1.354 + throw Components.Exception("Cannot go forward from this point"); 1.355 + 1.356 + this.pos++; 1.357 + gViewController.updateState(this.states[this.pos]); 1.358 + gViewController.updateCommand("cmd_back"); 1.359 + gViewController.updateCommand("cmd_forward"); 1.360 + }, 1.361 + 1.362 + pushState: function FakeHistory_pushState(aState) { 1.363 + this.pos++; 1.364 + this.states.splice(this.pos, this.states.length); 1.365 + this.states.push(aState); 1.366 + }, 1.367 + 1.368 + replaceState: function FakeHistory_replaceState(aState) { 1.369 + this.states[this.pos] = aState; 1.370 + }, 1.371 + 1.372 + popState: function FakeHistory_popState() { 1.373 + if (this.pos == 0) 1.374 + throw Components.Exception("Cannot popState from this view"); 1.375 + 1.376 + this.states.splice(this.pos, this.states.length); 1.377 + this.pos--; 1.378 + 1.379 + gViewController.updateState(this.states[this.pos]); 1.380 + gViewController.updateCommand("cmd_back"); 1.381 + gViewController.updateCommand("cmd_forward"); 1.382 + } 1.383 +}; 1.384 + 1.385 +// If the window has a session history then use the HTML5 History wrapper 1.386 +// otherwise use our fake history implementation 1.387 +if (window.QueryInterface(Ci.nsIInterfaceRequestor) 1.388 + .getInterface(Ci.nsIWebNavigation) 1.389 + .sessionHistory) { 1.390 + var gHistory = HTML5History; 1.391 +} 1.392 +else { 1.393 + gHistory = FakeHistory; 1.394 +} 1.395 + 1.396 +var gEventManager = { 1.397 + _listeners: {}, 1.398 + _installListeners: [], 1.399 + 1.400 + initialize: function gEM_initialize() { 1.401 + var self = this; 1.402 + const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling", 1.403 + "onDisabled", "onUninstalling", "onUninstalled", 1.404 + "onInstalled", "onOperationCancelled", 1.405 + "onUpdateAvailable", "onUpdateFinished", 1.406 + "onCompatibilityUpdateAvailable", 1.407 + "onPropertyChanged"]; 1.408 + for (let evt of ADDON_EVENTS) { 1.409 + let event = evt; 1.410 + self[event] = function initialize_delegateAddonEvent(...aArgs) { 1.411 + self.delegateAddonEvent(event, aArgs); 1.412 + }; 1.413 + } 1.414 + 1.415 + const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted", 1.416 + "onDownloadEnded", "onDownloadFailed", 1.417 + "onDownloadProgress", "onDownloadCancelled", 1.418 + "onInstallStarted", "onInstallEnded", 1.419 + "onInstallFailed", "onInstallCancelled", 1.420 + "onExternalInstall"]; 1.421 + for (let evt of INSTALL_EVENTS) { 1.422 + let event = evt; 1.423 + self[event] = function initialize_delegateInstallEvent(...aArgs) { 1.424 + self.delegateInstallEvent(event, aArgs); 1.425 + }; 1.426 + } 1.427 + 1.428 + AddonManager.addManagerListener(this); 1.429 + AddonManager.addInstallListener(this); 1.430 + AddonManager.addAddonListener(this); 1.431 + 1.432 + this.refreshGlobalWarning(); 1.433 + this.refreshAutoUpdateDefault(); 1.434 + 1.435 + var contextMenu = document.getElementById("addonitem-popup"); 1.436 + contextMenu.addEventListener("popupshowing", function contextMenu_onPopupshowing() { 1.437 + var addon = gViewController.currentViewObj.getSelectedAddon(); 1.438 + contextMenu.setAttribute("addontype", addon.type); 1.439 + 1.440 + var menuSep = document.getElementById("addonitem-menuseparator"); 1.441 + var countEnabledMenuCmds = 0; 1.442 + for (let child of contextMenu.children) { 1.443 + if (child.nodeName == "menuitem" && 1.444 + gViewController.isCommandEnabled(child.command)) { 1.445 + countEnabledMenuCmds++; 1.446 + } 1.447 + } 1.448 + 1.449 + // with only one menu item, we hide the menu separator 1.450 + menuSep.hidden = (countEnabledMenuCmds <= 1); 1.451 + 1.452 + }, false); 1.453 + }, 1.454 + 1.455 + shutdown: function gEM_shutdown() { 1.456 + AddonManager.removeManagerListener(this); 1.457 + AddonManager.removeInstallListener(this); 1.458 + AddonManager.removeAddonListener(this); 1.459 + }, 1.460 + 1.461 + registerAddonListener: function gEM_registerAddonListener(aListener, aAddonId) { 1.462 + if (!(aAddonId in this._listeners)) 1.463 + this._listeners[aAddonId] = []; 1.464 + else if (this._listeners[aAddonId].indexOf(aListener) != -1) 1.465 + return; 1.466 + this._listeners[aAddonId].push(aListener); 1.467 + }, 1.468 + 1.469 + unregisterAddonListener: function gEM_unregisterAddonListener(aListener, aAddonId) { 1.470 + if (!(aAddonId in this._listeners)) 1.471 + return; 1.472 + var index = this._listeners[aAddonId].indexOf(aListener); 1.473 + if (index == -1) 1.474 + return; 1.475 + this._listeners[aAddonId].splice(index, 1); 1.476 + }, 1.477 + 1.478 + registerInstallListener: function gEM_registerInstallListener(aListener) { 1.479 + if (this._installListeners.indexOf(aListener) != -1) 1.480 + return; 1.481 + this._installListeners.push(aListener); 1.482 + }, 1.483 + 1.484 + unregisterInstallListener: function gEM_unregisterInstallListener(aListener) { 1.485 + var i = this._installListeners.indexOf(aListener); 1.486 + if (i == -1) 1.487 + return; 1.488 + this._installListeners.splice(i, 1); 1.489 + }, 1.490 + 1.491 + delegateAddonEvent: function gEM_delegateAddonEvent(aEvent, aParams) { 1.492 + var addon = aParams.shift(); 1.493 + if (!(addon.id in this._listeners)) 1.494 + return; 1.495 + 1.496 + var listeners = this._listeners[addon.id]; 1.497 + for (let listener of listeners) { 1.498 + if (!(aEvent in listener)) 1.499 + continue; 1.500 + try { 1.501 + listener[aEvent].apply(listener, aParams); 1.502 + } catch(e) { 1.503 + // this shouldn't be fatal 1.504 + Cu.reportError(e); 1.505 + } 1.506 + } 1.507 + }, 1.508 + 1.509 + delegateInstallEvent: function gEM_delegateInstallEvent(aEvent, aParams) { 1.510 + var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon; 1.511 + // If the install is an update then send the event to all listeners 1.512 + // registered for the existing add-on 1.513 + if (existingAddon) 1.514 + this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams)); 1.515 + 1.516 + for (let listener of this._installListeners) { 1.517 + if (!(aEvent in listener)) 1.518 + continue; 1.519 + try { 1.520 + listener[aEvent].apply(listener, aParams); 1.521 + } catch(e) { 1.522 + // this shouldn't be fatal 1.523 + Cu.reportError(e); 1.524 + } 1.525 + } 1.526 + }, 1.527 + 1.528 + refreshGlobalWarning: function gEM_refreshGlobalWarning() { 1.529 + var page = document.getElementById("addons-page"); 1.530 + 1.531 + if (Services.appinfo.inSafeMode) { 1.532 + page.setAttribute("warning", "safemode"); 1.533 + return; 1.534 + } 1.535 + 1.536 + if (AddonManager.checkUpdateSecurityDefault && 1.537 + !AddonManager.checkUpdateSecurity) { 1.538 + page.setAttribute("warning", "updatesecurity"); 1.539 + return; 1.540 + } 1.541 + 1.542 + if (!AddonManager.checkCompatibility) { 1.543 + page.setAttribute("warning", "checkcompatibility"); 1.544 + return; 1.545 + } 1.546 + 1.547 + page.removeAttribute("warning"); 1.548 + }, 1.549 + 1.550 + refreshAutoUpdateDefault: function gEM_refreshAutoUpdateDefault() { 1.551 + var updateEnabled = AddonManager.updateEnabled; 1.552 + var autoUpdateDefault = AddonManager.autoUpdateDefault; 1.553 + 1.554 + // The checkbox needs to reflect that both prefs need to be true 1.555 + // for updates to be checked for and applied automatically 1.556 + document.getElementById("utils-autoUpdateDefault") 1.557 + .setAttribute("checked", updateEnabled && autoUpdateDefault); 1.558 + 1.559 + document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault; 1.560 + document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault; 1.561 + }, 1.562 + 1.563 + onCompatibilityModeChanged: function gEM_onCompatibilityModeChanged() { 1.564 + this.refreshGlobalWarning(); 1.565 + }, 1.566 + 1.567 + onCheckUpdateSecurityChanged: function gEM_onCheckUpdateSecurityChanged() { 1.568 + this.refreshGlobalWarning(); 1.569 + }, 1.570 + 1.571 + onUpdateModeChanged: function gEM_onUpdateModeChanged() { 1.572 + this.refreshAutoUpdateDefault(); 1.573 + } 1.574 +}; 1.575 + 1.576 + 1.577 +var gViewController = { 1.578 + viewPort: null, 1.579 + currentViewId: "", 1.580 + currentViewObj: null, 1.581 + currentViewRequest: 0, 1.582 + viewObjects: {}, 1.583 + viewChangeCallback: null, 1.584 + initialViewSelected: false, 1.585 + lastHistoryIndex: -1, 1.586 + 1.587 + initialize: function gVC_initialize() { 1.588 + this.viewPort = document.getElementById("view-port"); 1.589 + 1.590 + this.viewObjects["search"] = gSearchView; 1.591 + this.viewObjects["discover"] = gDiscoverView; 1.592 + this.viewObjects["list"] = gListView; 1.593 + this.viewObjects["detail"] = gDetailView; 1.594 + this.viewObjects["updates"] = gUpdatesView; 1.595 + 1.596 + for each (let view in this.viewObjects) 1.597 + view.initialize(); 1.598 + 1.599 + window.controllers.appendController(this); 1.600 + 1.601 + window.addEventListener("popstate", 1.602 + function window_onStatePopped(e) { 1.603 + gViewController.updateState(e.state); 1.604 + }, 1.605 + false); 1.606 + }, 1.607 + 1.608 + shutdown: function gVC_shutdown() { 1.609 + if (this.currentViewObj) 1.610 + this.currentViewObj.hide(); 1.611 + this.currentViewRequest = 0; 1.612 + 1.613 + for each(let view in this.viewObjects) { 1.614 + if ("shutdown" in view) { 1.615 + try { 1.616 + view.shutdown(); 1.617 + } catch(e) { 1.618 + // this shouldn't be fatal 1.619 + Cu.reportError(e); 1.620 + } 1.621 + } 1.622 + } 1.623 + 1.624 + window.controllers.removeController(this); 1.625 + }, 1.626 + 1.627 + updateState: function gVC_updateState(state) { 1.628 + try { 1.629 + this.loadViewInternal(state.view, state.previousView, state); 1.630 + this.lastHistoryIndex = gHistory.index; 1.631 + } 1.632 + catch (e) { 1.633 + // The attempt to load the view failed, try moving further along history 1.634 + if (this.lastHistoryIndex > gHistory.index) { 1.635 + if (gHistory.canGoBack) 1.636 + gHistory.back(); 1.637 + else 1.638 + gViewController.replaceView(VIEW_DEFAULT); 1.639 + } else { 1.640 + if (gHistory.canGoForward) 1.641 + gHistory.forward(); 1.642 + else 1.643 + gViewController.replaceView(VIEW_DEFAULT); 1.644 + } 1.645 + } 1.646 + }, 1.647 + 1.648 + parseViewId: function gVC_parseViewId(aViewId) { 1.649 + var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/; 1.650 + var [,viewType, viewParam] = aViewId.match(matchRegex) || []; 1.651 + return {type: viewType, param: decodeURIComponent(viewParam)}; 1.652 + }, 1.653 + 1.654 + get isLoading() { 1.655 + return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading"); 1.656 + }, 1.657 + 1.658 + loadView: function gVC_loadView(aViewId) { 1.659 + var isRefresh = false; 1.660 + if (aViewId == this.currentViewId) { 1.661 + if (this.isLoading) 1.662 + return; 1.663 + if (!("refresh" in this.currentViewObj)) 1.664 + return; 1.665 + if (!this.currentViewObj.canRefresh()) 1.666 + return; 1.667 + isRefresh = true; 1.668 + } 1.669 + 1.670 + var state = { 1.671 + view: aViewId, 1.672 + previousView: this.currentViewId 1.673 + }; 1.674 + if (!isRefresh) { 1.675 + gHistory.pushState(state); 1.676 + this.lastHistoryIndex = gHistory.index; 1.677 + } 1.678 + this.loadViewInternal(aViewId, this.currentViewId, state); 1.679 + }, 1.680 + 1.681 + // Replaces the existing view with a new one, rewriting the current history 1.682 + // entry to match. 1.683 + replaceView: function gVC_replaceView(aViewId) { 1.684 + if (aViewId == this.currentViewId) 1.685 + return; 1.686 + 1.687 + var state = { 1.688 + view: aViewId, 1.689 + previousView: null 1.690 + }; 1.691 + gHistory.replaceState(state); 1.692 + this.loadViewInternal(aViewId, null, state); 1.693 + }, 1.694 + 1.695 + loadInitialView: function gVC_loadInitialView(aViewId) { 1.696 + var state = { 1.697 + view: aViewId, 1.698 + previousView: null 1.699 + }; 1.700 + gHistory.replaceState(state); 1.701 + 1.702 + this.loadViewInternal(aViewId, null, state); 1.703 + this.initialViewSelected = true; 1.704 + notifyInitialized(); 1.705 + }, 1.706 + 1.707 + loadViewInternal: function gVC_loadViewInternal(aViewId, aPreviousView, aState) { 1.708 + var view = this.parseViewId(aViewId); 1.709 + 1.710 + if (!view.type || !(view.type in this.viewObjects)) 1.711 + throw Components.Exception("Invalid view: " + view.type); 1.712 + 1.713 + var viewObj = this.viewObjects[view.type]; 1.714 + if (!viewObj.node) 1.715 + throw Components.Exception("Root node doesn't exist for '" + view.type + "' view"); 1.716 + 1.717 + if (this.currentViewObj && aViewId != aPreviousView) { 1.718 + try { 1.719 + let canHide = this.currentViewObj.hide(); 1.720 + if (canHide === false) 1.721 + return; 1.722 + this.viewPort.selectedPanel.removeAttribute("loading"); 1.723 + } catch (e) { 1.724 + // this shouldn't be fatal 1.725 + Cu.reportError(e); 1.726 + } 1.727 + } 1.728 + 1.729 + gCategories.select(aViewId, aPreviousView); 1.730 + 1.731 + this.currentViewId = aViewId; 1.732 + this.currentViewObj = viewObj; 1.733 + 1.734 + this.viewPort.selectedPanel = this.currentViewObj.node; 1.735 + this.viewPort.selectedPanel.setAttribute("loading", "true"); 1.736 + this.currentViewObj.node.focus(); 1.737 + 1.738 + if (aViewId == aPreviousView) 1.739 + this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState); 1.740 + else 1.741 + this.currentViewObj.show(view.param, ++this.currentViewRequest, aState); 1.742 + }, 1.743 + 1.744 + // Moves back in the document history and removes the current history entry 1.745 + popState: function gVC_popState(aCallback) { 1.746 + this.viewChangeCallback = aCallback; 1.747 + gHistory.popState(); 1.748 + }, 1.749 + 1.750 + notifyViewChanged: function gVC_notifyViewChanged() { 1.751 + this.viewPort.selectedPanel.removeAttribute("loading"); 1.752 + 1.753 + if (this.viewChangeCallback) { 1.754 + this.viewChangeCallback(); 1.755 + this.viewChangeCallback = null; 1.756 + } 1.757 + 1.758 + var event = document.createEvent("Events"); 1.759 + event.initEvent("ViewChanged", true, true); 1.760 + this.currentViewObj.node.dispatchEvent(event); 1.761 + }, 1.762 + 1.763 + commands: { 1.764 + cmd_back: { 1.765 + isEnabled: function cmd_back_isEnabled() { 1.766 + return gHistory.canGoBack; 1.767 + }, 1.768 + doCommand: function cmd_back_doCommand() { 1.769 + gHistory.back(); 1.770 + } 1.771 + }, 1.772 + 1.773 + cmd_forward: { 1.774 + isEnabled: function cmd_forward_isEnabled() { 1.775 + return gHistory.canGoForward; 1.776 + }, 1.777 + doCommand: function cmd_forward_doCommand() { 1.778 + gHistory.forward(); 1.779 + } 1.780 + }, 1.781 + 1.782 + cmd_focusSearch: { 1.783 + isEnabled: () => true, 1.784 + doCommand: function cmd_focusSearch_doCommand() { 1.785 + gHeader.focusSearchBox(); 1.786 + } 1.787 + }, 1.788 + 1.789 + cmd_restartApp: { 1.790 + isEnabled: function cmd_restartApp_isEnabled() true, 1.791 + doCommand: function cmd_restartApp_doCommand() { 1.792 + let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. 1.793 + createInstance(Ci.nsISupportsPRBool); 1.794 + Services.obs.notifyObservers(cancelQuit, "quit-application-requested", 1.795 + "restart"); 1.796 + if (cancelQuit.data) 1.797 + return; // somebody canceled our quit request 1.798 + 1.799 + let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]. 1.800 + getService(Ci.nsIAppStartup); 1.801 + appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart); 1.802 + } 1.803 + }, 1.804 + 1.805 + cmd_enableCheckCompatibility: { 1.806 + isEnabled: function cmd_enableCheckCompatibility_isEnabled() true, 1.807 + doCommand: function cmd_enableCheckCompatibility_doCommand() { 1.808 + AddonManager.checkCompatibility = true; 1.809 + } 1.810 + }, 1.811 + 1.812 + cmd_enableUpdateSecurity: { 1.813 + isEnabled: function cmd_enableUpdateSecurity_isEnabled() true, 1.814 + doCommand: function cmd_enableUpdateSecurity_doCommand() { 1.815 + AddonManager.checkUpdateSecurity = true; 1.816 + } 1.817 + }, 1.818 + 1.819 + cmd_pluginCheck: { 1.820 + isEnabled: function cmd_pluginCheck_isEnabled() true, 1.821 + doCommand: function cmd_pluginCheck_doCommand() { 1.822 + openURL(Services.urlFormatter.formatURLPref("plugins.update.url")); 1.823 + } 1.824 + }, 1.825 + 1.826 + cmd_toggleAutoUpdateDefault: { 1.827 + isEnabled: function cmd_toggleAutoUpdateDefault_isEnabled() true, 1.828 + doCommand: function cmd_toggleAutoUpdateDefault_doCommand() { 1.829 + if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) { 1.830 + // One or both of the prefs is false, i.e. the checkbox is not checked. 1.831 + // Now toggle both to true. If the user wants us to auto-update 1.832 + // add-ons, we also need to auto-check for updates. 1.833 + AddonManager.updateEnabled = true; 1.834 + AddonManager.autoUpdateDefault = true; 1.835 + } else { 1.836 + // Both prefs are true, i.e. the checkbox is checked. 1.837 + // Toggle the auto pref to false, but don't touch the enabled check. 1.838 + AddonManager.autoUpdateDefault = false; 1.839 + } 1.840 + } 1.841 + }, 1.842 + 1.843 + cmd_resetAddonAutoUpdate: { 1.844 + isEnabled: function cmd_resetAddonAutoUpdate_isEnabled() true, 1.845 + doCommand: function cmd_resetAddonAutoUpdate_doCommand() { 1.846 + AddonManager.getAllAddons(function cmd_resetAddonAutoUpdate_getAllAddons(aAddonList) { 1.847 + for (let addon of aAddonList) { 1.848 + if ("applyBackgroundUpdates" in addon) 1.849 + addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; 1.850 + } 1.851 + }); 1.852 + } 1.853 + }, 1.854 + 1.855 + cmd_goToDiscoverPane: { 1.856 + isEnabled: function cmd_goToDiscoverPane_isEnabled() { 1.857 + return gDiscoverView.enabled; 1.858 + }, 1.859 + doCommand: function cmd_goToDiscoverPane_doCommand() { 1.860 + gViewController.loadView("addons://discover/"); 1.861 + } 1.862 + }, 1.863 + 1.864 + cmd_goToRecentUpdates: { 1.865 + isEnabled: function cmd_goToRecentUpdates_isEnabled() true, 1.866 + doCommand: function cmd_goToRecentUpdates_doCommand() { 1.867 + gViewController.loadView("addons://updates/recent"); 1.868 + } 1.869 + }, 1.870 + 1.871 + cmd_goToAvailableUpdates: { 1.872 + isEnabled: function cmd_goToAvailableUpdates_isEnabled() true, 1.873 + doCommand: function cmd_goToAvailableUpdates_doCommand() { 1.874 + gViewController.loadView("addons://updates/available"); 1.875 + } 1.876 + }, 1.877 + 1.878 + cmd_showItemDetails: { 1.879 + isEnabled: function cmd_showItemDetails_isEnabled(aAddon) { 1.880 + return !!aAddon && (gViewController.currentViewObj != gDetailView); 1.881 + }, 1.882 + doCommand: function cmd_showItemDetails_doCommand(aAddon, aScrollToPreferences) { 1.883 + gViewController.loadView("addons://detail/" + 1.884 + encodeURIComponent(aAddon.id) + 1.885 + (aScrollToPreferences ? "/preferences" : "")); 1.886 + } 1.887 + }, 1.888 + 1.889 + cmd_findAllUpdates: { 1.890 + inProgress: false, 1.891 + isEnabled: function cmd_findAllUpdates_isEnabled() !this.inProgress, 1.892 + doCommand: function cmd_findAllUpdates_doCommand() { 1.893 + this.inProgress = true; 1.894 + gViewController.updateCommand("cmd_findAllUpdates"); 1.895 + document.getElementById("updates-noneFound").hidden = true; 1.896 + document.getElementById("updates-progress").hidden = false; 1.897 + document.getElementById("updates-manualUpdatesFound-btn").hidden = true; 1.898 + 1.899 + var pendingChecks = 0; 1.900 + var numUpdated = 0; 1.901 + var numManualUpdates = 0; 1.902 + var restartNeeded = false; 1.903 + var self = this; 1.904 + 1.905 + function updateStatus() { 1.906 + if (pendingChecks > 0) 1.907 + return; 1.908 + 1.909 + self.inProgress = false; 1.910 + gViewController.updateCommand("cmd_findAllUpdates"); 1.911 + document.getElementById("updates-progress").hidden = true; 1.912 + gUpdatesView.maybeRefresh(); 1.913 + 1.914 + if (numManualUpdates > 0 && numUpdated == 0) { 1.915 + document.getElementById("updates-manualUpdatesFound-btn").hidden = false; 1.916 + return; 1.917 + } 1.918 + 1.919 + if (numUpdated == 0) { 1.920 + document.getElementById("updates-noneFound").hidden = false; 1.921 + return; 1.922 + } 1.923 + 1.924 + if (restartNeeded) { 1.925 + document.getElementById("updates-downloaded").hidden = false; 1.926 + document.getElementById("updates-restart-btn").hidden = false; 1.927 + } else { 1.928 + document.getElementById("updates-installed").hidden = false; 1.929 + } 1.930 + } 1.931 + 1.932 + var updateInstallListener = { 1.933 + onDownloadFailed: function cmd_findAllUpdates_downloadFailed() { 1.934 + pendingChecks--; 1.935 + updateStatus(); 1.936 + }, 1.937 + onInstallFailed: function cmd_findAllUpdates_installFailed() { 1.938 + pendingChecks--; 1.939 + updateStatus(); 1.940 + }, 1.941 + onInstallEnded: function cmd_findAllUpdates_installEnded(aInstall, aAddon) { 1.942 + pendingChecks--; 1.943 + numUpdated++; 1.944 + if (isPending(aInstall.existingAddon, "upgrade")) 1.945 + restartNeeded = true; 1.946 + updateStatus(); 1.947 + } 1.948 + }; 1.949 + 1.950 + var updateCheckListener = { 1.951 + onUpdateAvailable: function cmd_findAllUpdates_updateAvailable(aAddon, aInstall) { 1.952 + gEventManager.delegateAddonEvent("onUpdateAvailable", 1.953 + [aAddon, aInstall]); 1.954 + if (AddonManager.shouldAutoUpdate(aAddon)) { 1.955 + aInstall.addListener(updateInstallListener); 1.956 + aInstall.install(); 1.957 + } else { 1.958 + pendingChecks--; 1.959 + numManualUpdates++; 1.960 + updateStatus(); 1.961 + } 1.962 + }, 1.963 + onNoUpdateAvailable: function cmd_findAllUpdates_noUpdateAvailable(aAddon) { 1.964 + pendingChecks--; 1.965 + updateStatus(); 1.966 + }, 1.967 + onUpdateFinished: function cmd_findAllUpdates_updateFinished(aAddon, aError) { 1.968 + gEventManager.delegateAddonEvent("onUpdateFinished", 1.969 + [aAddon, aError]); 1.970 + } 1.971 + }; 1.972 + 1.973 + AddonManager.getAddonsByTypes(null, function cmd_findAllUpdates_getAddonsByTypes(aAddonList) { 1.974 + for (let addon of aAddonList) { 1.975 + if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) { 1.976 + pendingChecks++; 1.977 + addon.findUpdates(updateCheckListener, 1.978 + AddonManager.UPDATE_WHEN_USER_REQUESTED); 1.979 + } 1.980 + } 1.981 + 1.982 + if (pendingChecks == 0) 1.983 + updateStatus(); 1.984 + }); 1.985 + } 1.986 + }, 1.987 + 1.988 + cmd_findItemUpdates: { 1.989 + isEnabled: function cmd_findItemUpdates_isEnabled(aAddon) { 1.990 + if (!aAddon) 1.991 + return false; 1.992 + return hasPermission(aAddon, "upgrade"); 1.993 + }, 1.994 + doCommand: function cmd_findItemUpdates_doCommand(aAddon) { 1.995 + var listener = { 1.996 + onUpdateAvailable: function cmd_findItemUpdates_updateAvailable(aAddon, aInstall) { 1.997 + gEventManager.delegateAddonEvent("onUpdateAvailable", 1.998 + [aAddon, aInstall]); 1.999 + if (AddonManager.shouldAutoUpdate(aAddon)) 1.1000 + aInstall.install(); 1.1001 + }, 1.1002 + onNoUpdateAvailable: function cmd_findItemUpdates_noUpdateAvailable(aAddon) { 1.1003 + gEventManager.delegateAddonEvent("onNoUpdateAvailable", 1.1004 + [aAddon]); 1.1005 + } 1.1006 + }; 1.1007 + gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]); 1.1008 + aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED); 1.1009 + } 1.1010 + }, 1.1011 + 1.1012 + cmd_debugItem: { 1.1013 + doCommand: function cmd_debugItem_doCommand(aAddon) { 1.1014 + BrowserToolboxProcess.init({ addonID: aAddon.id }); 1.1015 + }, 1.1016 + 1.1017 + isEnabled: function cmd_debugItem_isEnabled(aAddon) { 1.1018 + let debuggerEnabled = Services.prefs. 1.1019 + getBoolPref(PREF_ADDON_DEBUGGING_ENABLED); 1.1020 + let remoteEnabled = Services.prefs. 1.1021 + getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED); 1.1022 + return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled; 1.1023 + } 1.1024 + }, 1.1025 + 1.1026 + cmd_showItemPreferences: { 1.1027 + isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) { 1.1028 + if (!aAddon || !aAddon.isActive || !aAddon.optionsURL) 1.1029 + return false; 1.1030 + if (gViewController.currentViewObj == gDetailView && 1.1031 + aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) { 1.1032 + return false; 1.1033 + } 1.1034 + if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO) 1.1035 + return false; 1.1036 + return true; 1.1037 + }, 1.1038 + doCommand: function cmd_showItemPreferences_doCommand(aAddon) { 1.1039 + if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) { 1.1040 + gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true); 1.1041 + return; 1.1042 + } 1.1043 + var optionsURL = aAddon.optionsURL; 1.1044 + if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB && 1.1045 + openOptionsInTab(optionsURL)) { 1.1046 + return; 1.1047 + } 1.1048 + var windows = Services.wm.getEnumerator(null); 1.1049 + while (windows.hasMoreElements()) { 1.1050 + var win = windows.getNext(); 1.1051 + if (win.closed) { 1.1052 + continue; 1.1053 + } 1.1054 + if (win.document.documentURI == optionsURL) { 1.1055 + win.focus(); 1.1056 + return; 1.1057 + } 1.1058 + } 1.1059 + var features = "chrome,titlebar,toolbar,centerscreen"; 1.1060 + try { 1.1061 + var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply"); 1.1062 + features += instantApply ? ",dialog=no" : ",modal"; 1.1063 + } catch (e) { 1.1064 + features += ",modal"; 1.1065 + } 1.1066 + openDialog(optionsURL, "", features); 1.1067 + } 1.1068 + }, 1.1069 + 1.1070 + cmd_showItemAbout: { 1.1071 + isEnabled: function cmd_showItemAbout_isEnabled(aAddon) { 1.1072 + // XXXunf This may be applicable to install items too. See bug 561260 1.1073 + return !!aAddon; 1.1074 + }, 1.1075 + doCommand: function cmd_showItemAbout_doCommand(aAddon) { 1.1076 + var aboutURL = aAddon.aboutURL; 1.1077 + if (aboutURL) 1.1078 + openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon); 1.1079 + else 1.1080 + openDialog("chrome://mozapps/content/extensions/about.xul", 1.1081 + "", "chrome,centerscreen,modal", aAddon); 1.1082 + } 1.1083 + }, 1.1084 + 1.1085 + cmd_enableItem: { 1.1086 + isEnabled: function cmd_enableItem_isEnabled(aAddon) { 1.1087 + if (!aAddon) 1.1088 + return false; 1.1089 + let addonType = AddonManager.addonTypes[aAddon.type]; 1.1090 + return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && 1.1091 + hasPermission(aAddon, "enable")); 1.1092 + }, 1.1093 + doCommand: function cmd_enableItem_doCommand(aAddon) { 1.1094 + aAddon.userDisabled = false; 1.1095 + }, 1.1096 + getTooltip: function cmd_enableItem_getTooltip(aAddon) { 1.1097 + if (!aAddon) 1.1098 + return ""; 1.1099 + if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE) 1.1100 + return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip"); 1.1101 + return gStrings.ext.GetStringFromName("enableAddonTooltip"); 1.1102 + } 1.1103 + }, 1.1104 + 1.1105 + cmd_disableItem: { 1.1106 + isEnabled: function cmd_disableItem_isEnabled(aAddon) { 1.1107 + if (!aAddon) 1.1108 + return false; 1.1109 + let addonType = AddonManager.addonTypes[aAddon.type]; 1.1110 + return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && 1.1111 + hasPermission(aAddon, "disable")); 1.1112 + }, 1.1113 + doCommand: function cmd_disableItem_doCommand(aAddon) { 1.1114 + aAddon.userDisabled = true; 1.1115 + }, 1.1116 + getTooltip: function cmd_disableItem_getTooltip(aAddon) { 1.1117 + if (!aAddon) 1.1118 + return ""; 1.1119 + if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE) 1.1120 + return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip"); 1.1121 + return gStrings.ext.GetStringFromName("disableAddonTooltip"); 1.1122 + } 1.1123 + }, 1.1124 + 1.1125 + cmd_installItem: { 1.1126 + isEnabled: function cmd_installItem_isEnabled(aAddon) { 1.1127 + if (!aAddon) 1.1128 + return false; 1.1129 + return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE; 1.1130 + }, 1.1131 + doCommand: function cmd_installItem_doCommand(aAddon) { 1.1132 + function doInstall() { 1.1133 + gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote(); 1.1134 + } 1.1135 + 1.1136 + if (gViewController.currentViewObj == gDetailView) 1.1137 + gViewController.popState(doInstall); 1.1138 + else 1.1139 + doInstall(); 1.1140 + } 1.1141 + }, 1.1142 + 1.1143 + cmd_purchaseItem: { 1.1144 + isEnabled: function cmd_purchaseItem_isEnabled(aAddon) { 1.1145 + if (!aAddon) 1.1146 + return false; 1.1147 + return !!aAddon.purchaseURL; 1.1148 + }, 1.1149 + doCommand: function cmd_purchaseItem_doCommand(aAddon) { 1.1150 + openURL(aAddon.purchaseURL); 1.1151 + } 1.1152 + }, 1.1153 + 1.1154 + cmd_uninstallItem: { 1.1155 + isEnabled: function cmd_uninstallItem_isEnabled(aAddon) { 1.1156 + if (!aAddon) 1.1157 + return false; 1.1158 + return hasPermission(aAddon, "uninstall"); 1.1159 + }, 1.1160 + doCommand: function cmd_uninstallItem_doCommand(aAddon) { 1.1161 + if (gViewController.currentViewObj != gDetailView) { 1.1162 + aAddon.uninstall(); 1.1163 + return; 1.1164 + } 1.1165 + 1.1166 + gViewController.popState(function cmd_uninstallItem_popState() { 1.1167 + gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall(); 1.1168 + }); 1.1169 + }, 1.1170 + getTooltip: function cmd_uninstallItem_getTooltip(aAddon) { 1.1171 + if (!aAddon) 1.1172 + return ""; 1.1173 + if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL) 1.1174 + return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip"); 1.1175 + return gStrings.ext.GetStringFromName("uninstallAddonTooltip"); 1.1176 + } 1.1177 + }, 1.1178 + 1.1179 + cmd_cancelUninstallItem: { 1.1180 + isEnabled: function cmd_cancelUninstallItem_isEnabled(aAddon) { 1.1181 + if (!aAddon) 1.1182 + return false; 1.1183 + return isPending(aAddon, "uninstall"); 1.1184 + }, 1.1185 + doCommand: function cmd_cancelUninstallItem_doCommand(aAddon) { 1.1186 + aAddon.cancelUninstall(); 1.1187 + } 1.1188 + }, 1.1189 + 1.1190 + cmd_installFromFile: { 1.1191 + isEnabled: function cmd_installFromFile_isEnabled() true, 1.1192 + doCommand: function cmd_installFromFile_doCommand() { 1.1193 + const nsIFilePicker = Ci.nsIFilePicker; 1.1194 + var fp = Cc["@mozilla.org/filepicker;1"] 1.1195 + .createInstance(nsIFilePicker); 1.1196 + fp.init(window, 1.1197 + gStrings.ext.GetStringFromName("installFromFile.dialogTitle"), 1.1198 + nsIFilePicker.modeOpenMultiple); 1.1199 + try { 1.1200 + fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"), 1.1201 + "*.xpi;*.jar"); 1.1202 + fp.appendFilters(nsIFilePicker.filterAll); 1.1203 + } catch (e) { } 1.1204 + 1.1205 + if (fp.show() != nsIFilePicker.returnOK) 1.1206 + return; 1.1207 + 1.1208 + var files = fp.files; 1.1209 + var installs = []; 1.1210 + 1.1211 + function buildNextInstall() { 1.1212 + if (!files.hasMoreElements()) { 1.1213 + if (installs.length > 0) { 1.1214 + // Display the normal install confirmation for the installs 1.1215 + AddonManager.installAddonsFromWebpage("application/x-xpinstall", 1.1216 + window, null, installs); 1.1217 + } 1.1218 + return; 1.1219 + } 1.1220 + 1.1221 + var file = files.getNext(); 1.1222 + AddonManager.getInstallForFile(file, function cmd_installFromFile_getInstallForFile(aInstall) { 1.1223 + installs.push(aInstall); 1.1224 + buildNextInstall(); 1.1225 + }); 1.1226 + } 1.1227 + 1.1228 + buildNextInstall(); 1.1229 + } 1.1230 + }, 1.1231 + 1.1232 + cmd_cancelOperation: { 1.1233 + isEnabled: function cmd_cancelOperation_isEnabled(aAddon) { 1.1234 + if (!aAddon) 1.1235 + return false; 1.1236 + return aAddon.pendingOperations != AddonManager.PENDING_NONE; 1.1237 + }, 1.1238 + doCommand: function cmd_cancelOperation_doCommand(aAddon) { 1.1239 + if (isPending(aAddon, "install")) { 1.1240 + aAddon.install.cancel(); 1.1241 + } else if (isPending(aAddon, "upgrade")) { 1.1242 + aAddon.pendingUpgrade.install.cancel(); 1.1243 + } else if (isPending(aAddon, "uninstall")) { 1.1244 + aAddon.cancelUninstall(); 1.1245 + } else if (isPending(aAddon, "enable")) { 1.1246 + aAddon.userDisabled = true; 1.1247 + } else if (isPending(aAddon, "disable")) { 1.1248 + aAddon.userDisabled = false; 1.1249 + } 1.1250 + } 1.1251 + }, 1.1252 + 1.1253 + cmd_contribute: { 1.1254 + isEnabled: function cmd_contribute_isEnabled(aAddon) { 1.1255 + if (!aAddon) 1.1256 + return false; 1.1257 + return ("contributionURL" in aAddon && aAddon.contributionURL); 1.1258 + }, 1.1259 + doCommand: function cmd_contribute_doCommand(aAddon) { 1.1260 + openURL(aAddon.contributionURL); 1.1261 + } 1.1262 + }, 1.1263 + 1.1264 + cmd_askToActivateItem: { 1.1265 + isEnabled: function cmd_askToActivateItem_isEnabled(aAddon) { 1.1266 + if (!aAddon) 1.1267 + return false; 1.1268 + let addonType = AddonManager.addonTypes[aAddon.type]; 1.1269 + return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && 1.1270 + hasPermission(aAddon, "ask_to_activate")); 1.1271 + }, 1.1272 + doCommand: function cmd_askToActivateItem_doCommand(aAddon) { 1.1273 + aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE; 1.1274 + } 1.1275 + }, 1.1276 + 1.1277 + cmd_alwaysActivateItem: { 1.1278 + isEnabled: function cmd_alwaysActivateItem_isEnabled(aAddon) { 1.1279 + if (!aAddon) 1.1280 + return false; 1.1281 + let addonType = AddonManager.addonTypes[aAddon.type]; 1.1282 + return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && 1.1283 + hasPermission(aAddon, "enable")); 1.1284 + }, 1.1285 + doCommand: function cmd_alwaysActivateItem_doCommand(aAddon) { 1.1286 + aAddon.userDisabled = false; 1.1287 + } 1.1288 + }, 1.1289 + 1.1290 + cmd_neverActivateItem: { 1.1291 + isEnabled: function cmd_neverActivateItem_isEnabled(aAddon) { 1.1292 + if (!aAddon) 1.1293 + return false; 1.1294 + let addonType = AddonManager.addonTypes[aAddon.type]; 1.1295 + return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && 1.1296 + hasPermission(aAddon, "disable")); 1.1297 + }, 1.1298 + doCommand: function cmd_neverActivateItem_doCommand(aAddon) { 1.1299 + aAddon.userDisabled = true; 1.1300 + } 1.1301 + }, 1.1302 + 1.1303 + cmd_experimentsLearnMore: { 1.1304 + isEnabled: function cmd_experimentsLearnMore_isEnabled() { 1.1305 + let mainWindow = getMainWindow(); 1.1306 + return mainWindow && "switchToTabHavingURI" in mainWindow; 1.1307 + }, 1.1308 + doCommand: function cmd_experimentsLearnMore_doCommand() { 1.1309 + let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL"); 1.1310 + openOptionsInTab(url); 1.1311 + }, 1.1312 + }, 1.1313 + 1.1314 + cmd_experimentsOpenTelemetryPreferences: { 1.1315 + isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() { 1.1316 + return !!getMainWindowWithPreferencesPane(); 1.1317 + }, 1.1318 + doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() { 1.1319 + let mainWindow = getMainWindowWithPreferencesPane(); 1.1320 + mainWindow.openAdvancedPreferences("dataChoicesTab"); 1.1321 + }, 1.1322 + }, 1.1323 + }, 1.1324 + 1.1325 + supportsCommand: function gVC_supportsCommand(aCommand) { 1.1326 + return (aCommand in this.commands); 1.1327 + }, 1.1328 + 1.1329 + isCommandEnabled: function gVC_isCommandEnabled(aCommand) { 1.1330 + if (!this.supportsCommand(aCommand)) 1.1331 + return false; 1.1332 + var addon = this.currentViewObj.getSelectedAddon(); 1.1333 + return this.commands[aCommand].isEnabled(addon); 1.1334 + }, 1.1335 + 1.1336 + updateCommands: function gVC_updateCommands() { 1.1337 + // wait until the view is initialized 1.1338 + if (!this.currentViewObj) 1.1339 + return; 1.1340 + var addon = this.currentViewObj.getSelectedAddon(); 1.1341 + for (let commandId in this.commands) 1.1342 + this.updateCommand(commandId, addon); 1.1343 + }, 1.1344 + 1.1345 + updateCommand: function gVC_updateCommand(aCommandId, aAddon) { 1.1346 + if (typeof aAddon == "undefined") 1.1347 + aAddon = this.currentViewObj.getSelectedAddon(); 1.1348 + var cmd = this.commands[aCommandId]; 1.1349 + var cmdElt = document.getElementById(aCommandId); 1.1350 + cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon)); 1.1351 + if ("getTooltip" in cmd) { 1.1352 + let tooltip = cmd.getTooltip(aAddon); 1.1353 + if (tooltip) 1.1354 + cmdElt.setAttribute("tooltiptext", tooltip); 1.1355 + else 1.1356 + cmdElt.removeAttribute("tooltiptext"); 1.1357 + } 1.1358 + }, 1.1359 + 1.1360 + doCommand: function gVC_doCommand(aCommand, aAddon) { 1.1361 + if (!this.supportsCommand(aCommand)) 1.1362 + return; 1.1363 + var cmd = this.commands[aCommand]; 1.1364 + if (!aAddon) 1.1365 + aAddon = this.currentViewObj.getSelectedAddon(); 1.1366 + if (!cmd.isEnabled(aAddon)) 1.1367 + return; 1.1368 + cmd.doCommand(aAddon); 1.1369 + }, 1.1370 + 1.1371 + onEvent: function gVC_onEvent() {} 1.1372 +}; 1.1373 + 1.1374 +function hasInlineOptions(aAddon) { 1.1375 + return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE || 1.1376 + aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO); 1.1377 +} 1.1378 + 1.1379 +function openOptionsInTab(optionsURL) { 1.1380 + let mainWindow = getMainWindow(); 1.1381 + if ("switchToTabHavingURI" in mainWindow) { 1.1382 + mainWindow.switchToTabHavingURI(optionsURL, true); 1.1383 + return true; 1.1384 + } 1.1385 + return false; 1.1386 +} 1.1387 + 1.1388 +function formatDate(aDate) { 1.1389 + return Cc["@mozilla.org/intl/scriptabledateformat;1"] 1.1390 + .getService(Ci.nsIScriptableDateFormat) 1.1391 + .FormatDate("", 1.1392 + Ci.nsIScriptableDateFormat.dateFormatLong, 1.1393 + aDate.getFullYear(), 1.1394 + aDate.getMonth() + 1, 1.1395 + aDate.getDate() 1.1396 + ); 1.1397 +} 1.1398 + 1.1399 + 1.1400 +function hasPermission(aAddon, aPerm) { 1.1401 + var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()]; 1.1402 + return !!(aAddon.permissions & perm); 1.1403 +} 1.1404 + 1.1405 + 1.1406 +function isPending(aAddon, aAction) { 1.1407 + var action = AddonManager["PENDING_" + aAction.toUpperCase()]; 1.1408 + return !!(aAddon.pendingOperations & action); 1.1409 +} 1.1410 + 1.1411 +function isInState(aInstall, aState) { 1.1412 + var state = AddonManager["STATE_" + aState.toUpperCase()]; 1.1413 + return aInstall.state == state; 1.1414 +} 1.1415 + 1.1416 +function shouldShowVersionNumber(aAddon) { 1.1417 + if (!aAddon.version) 1.1418 + return false; 1.1419 + 1.1420 + // The version number is hidden for lightweight themes. 1.1421 + if (aAddon.type == "theme") 1.1422 + return !/@personas\.mozilla\.org$/.test(aAddon.id); 1.1423 + 1.1424 + return true; 1.1425 +} 1.1426 + 1.1427 +function createItem(aObj, aIsInstall, aIsRemote) { 1.1428 + let item = document.createElement("richlistitem"); 1.1429 + 1.1430 + item.setAttribute("class", "addon addon-view"); 1.1431 + item.setAttribute("name", aObj.name); 1.1432 + item.setAttribute("type", aObj.type); 1.1433 + item.setAttribute("remote", !!aIsRemote); 1.1434 + 1.1435 + if (aIsInstall) { 1.1436 + item.mInstall = aObj; 1.1437 + 1.1438 + if (aObj.state != AddonManager.STATE_INSTALLED) { 1.1439 + item.setAttribute("status", "installing"); 1.1440 + return item; 1.1441 + } 1.1442 + aObj = aObj.addon; 1.1443 + } 1.1444 + 1.1445 + item.mAddon = aObj; 1.1446 + 1.1447 + item.setAttribute("status", "installed"); 1.1448 + 1.1449 + // set only attributes needed for sorting and XBL binding, 1.1450 + // the binding handles the rest 1.1451 + item.setAttribute("value", aObj.id); 1.1452 + 1.1453 + if (aObj.type == "experiment") { 1.1454 + item.endDate = getExperimentEndDate(aObj); 1.1455 + } 1.1456 + 1.1457 + return item; 1.1458 +} 1.1459 + 1.1460 +function sortElements(aElements, aSortBy, aAscending) { 1.1461 + // aSortBy is an Array of attributes to sort by, in decending 1.1462 + // order of priority. 1.1463 + 1.1464 + const DATE_FIELDS = ["updateDate"]; 1.1465 + const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"]; 1.1466 + 1.1467 + // We're going to group add-ons into the following buckets: 1.1468 + // 1.1469 + // enabledInstalled 1.1470 + // * Enabled 1.1471 + // * Incompatible but enabled because compatibility checking is off 1.1472 + // * Waiting to be installed 1.1473 + // * Waiting to be enabled 1.1474 + // 1.1475 + // pendingDisable 1.1476 + // * Waiting to be disabled 1.1477 + // 1.1478 + // pendingUninstall 1.1479 + // * Waiting to be removed 1.1480 + // 1.1481 + // disabledIncompatibleBlocked 1.1482 + // * Disabled 1.1483 + // * Incompatible 1.1484 + // * Blocklisted 1.1485 + 1.1486 + const UISTATE_ORDER = ["enabled", "pendingDisable", "pendingUninstall", 1.1487 + "disabled"]; 1.1488 + 1.1489 + function dateCompare(a, b) { 1.1490 + var aTime = a.getTime(); 1.1491 + var bTime = b.getTime(); 1.1492 + if (aTime < bTime) 1.1493 + return -1; 1.1494 + if (aTime > bTime) 1.1495 + return 1; 1.1496 + return 0; 1.1497 + } 1.1498 + 1.1499 + function numberCompare(a, b) { 1.1500 + return a - b; 1.1501 + } 1.1502 + 1.1503 + function stringCompare(a, b) { 1.1504 + return a.localeCompare(b); 1.1505 + } 1.1506 + 1.1507 + function uiStateCompare(a, b) { 1.1508 + // If we're in descending order, swap a and b, because 1.1509 + // we don't ever want to have descending uiStates 1.1510 + if (!aAscending) 1.1511 + [a, b] = [b, a]; 1.1512 + 1.1513 + return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b)); 1.1514 + } 1.1515 + 1.1516 + function getValue(aObj, aKey) { 1.1517 + if (!aObj) 1.1518 + return null; 1.1519 + 1.1520 + if (aObj.hasAttribute(aKey)) 1.1521 + return aObj.getAttribute(aKey); 1.1522 + 1.1523 + var addon = aObj.mAddon || aObj.mInstall; 1.1524 + if (!addon) 1.1525 + return null; 1.1526 + 1.1527 + if (aKey == "uiState") { 1.1528 + if (addon.pendingOperations == AddonManager.PENDING_DISABLE) 1.1529 + return "pendingDisable"; 1.1530 + if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL) 1.1531 + return "pendingUninstall"; 1.1532 + if (!addon.isActive && 1.1533 + (addon.pendingOperations != AddonManager.PENDING_ENABLE && 1.1534 + addon.pendingOperations != AddonManager.PENDING_INSTALL)) 1.1535 + return "disabled"; 1.1536 + else 1.1537 + return "enabled"; 1.1538 + } 1.1539 + 1.1540 + return addon[aKey]; 1.1541 + } 1.1542 + 1.1543 + // aSortFuncs will hold the sorting functions that we'll 1.1544 + // use per element, in the correct order. 1.1545 + var aSortFuncs = []; 1.1546 + 1.1547 + for (let i = 0; i < aSortBy.length; i++) { 1.1548 + var sortBy = aSortBy[i]; 1.1549 + 1.1550 + aSortFuncs[i] = stringCompare; 1.1551 + 1.1552 + if (sortBy == "uiState") 1.1553 + aSortFuncs[i] = uiStateCompare; 1.1554 + else if (DATE_FIELDS.indexOf(sortBy) != -1) 1.1555 + aSortFuncs[i] = dateCompare; 1.1556 + else if (NUMERIC_FIELDS.indexOf(sortBy) != -1) 1.1557 + aSortFuncs[i] = numberCompare; 1.1558 + } 1.1559 + 1.1560 + 1.1561 + aElements.sort(function elementsSort(a, b) { 1.1562 + if (!aAscending) 1.1563 + [a, b] = [b, a]; 1.1564 + 1.1565 + for (let i = 0; i < aSortFuncs.length; i++) { 1.1566 + var sortBy = aSortBy[i]; 1.1567 + var aValue = getValue(a, sortBy); 1.1568 + var bValue = getValue(b, sortBy); 1.1569 + 1.1570 + if (!aValue && !bValue) 1.1571 + return 0; 1.1572 + if (!aValue) 1.1573 + return -1; 1.1574 + if (!bValue) 1.1575 + return 1; 1.1576 + if (aValue != bValue) { 1.1577 + var result = aSortFuncs[i](aValue, bValue); 1.1578 + 1.1579 + if (result != 0) 1.1580 + return result; 1.1581 + } 1.1582 + } 1.1583 + 1.1584 + // If we got here, then all values of a and b 1.1585 + // must have been equal. 1.1586 + return 0; 1.1587 + 1.1588 + }); 1.1589 +} 1.1590 + 1.1591 +function sortList(aList, aSortBy, aAscending) { 1.1592 + var elements = Array.slice(aList.childNodes, 0); 1.1593 + sortElements(elements, [aSortBy], aAscending); 1.1594 + 1.1595 + while (aList.listChild) 1.1596 + aList.removeChild(aList.lastChild); 1.1597 + 1.1598 + for (let element of elements) 1.1599 + aList.appendChild(element); 1.1600 +} 1.1601 + 1.1602 +function getAddonsAndInstalls(aType, aCallback) { 1.1603 + let addons = null, installs = null; 1.1604 + let types = (aType != null) ? [aType] : null; 1.1605 + 1.1606 + AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) { 1.1607 + addons = aAddonsList; 1.1608 + if (installs != null) 1.1609 + aCallback(addons, installs); 1.1610 + }); 1.1611 + 1.1612 + AddonManager.getInstallsByTypes(types, function getAddonsAndInstalls_getInstallsByTypes(aInstallsList) { 1.1613 + // skip over upgrade installs and non-active installs 1.1614 + installs = aInstallsList.filter(function installsFilter(aInstall) { 1.1615 + return !(aInstall.existingAddon || 1.1616 + aInstall.state == AddonManager.STATE_AVAILABLE); 1.1617 + }); 1.1618 + 1.1619 + if (addons != null) 1.1620 + aCallback(addons, installs) 1.1621 + }); 1.1622 +} 1.1623 + 1.1624 +function doPendingUninstalls(aListBox) { 1.1625 + // Uninstalling add-ons can mutate the list so find the add-ons first then 1.1626 + // uninstall them 1.1627 + var items = []; 1.1628 + var listitem = aListBox.firstChild; 1.1629 + while (listitem) { 1.1630 + if (listitem.getAttribute("pending") == "uninstall" && 1.1631 + !listitem.isPending("uninstall")) 1.1632 + items.push(listitem.mAddon); 1.1633 + listitem = listitem.nextSibling; 1.1634 + } 1.1635 + 1.1636 + for (let addon of items) 1.1637 + addon.uninstall(); 1.1638 +} 1.1639 + 1.1640 +var gCategories = { 1.1641 + node: null, 1.1642 + _search: null, 1.1643 + 1.1644 + initialize: function gCategories_initialize() { 1.1645 + this.node = document.getElementById("categories"); 1.1646 + this._search = this.get("addons://search/"); 1.1647 + 1.1648 + var types = AddonManager.addonTypes; 1.1649 + for (var type in types) 1.1650 + this.onTypeAdded(types[type]); 1.1651 + 1.1652 + AddonManager.addTypeListener(this); 1.1653 + 1.1654 + try { 1.1655 + this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY); 1.1656 + } catch (e) { } 1.1657 + 1.1658 + // If there was no last view or no existing category matched the last view 1.1659 + // then the list will default to selecting the search category and we never 1.1660 + // want to show that as the first view so switch to the default category 1.1661 + if (!this.node.selectedItem || this.node.selectedItem == this._search) 1.1662 + this.node.value = VIEW_DEFAULT; 1.1663 + 1.1664 + var self = this; 1.1665 + this.node.addEventListener("select", function node_onSelected() { 1.1666 + self.maybeHideSearch(); 1.1667 + gViewController.loadView(self.node.selectedItem.value); 1.1668 + }, false); 1.1669 + 1.1670 + this.node.addEventListener("click", function node_onClicked(aEvent) { 1.1671 + var selectedItem = self.node.selectedItem; 1.1672 + if (aEvent.target.localName == "richlistitem" && 1.1673 + aEvent.target == selectedItem) { 1.1674 + var viewId = selectedItem.value; 1.1675 + 1.1676 + if (gViewController.parseViewId(viewId).type == "search") { 1.1677 + viewId += encodeURIComponent(gHeader.searchQuery); 1.1678 + } 1.1679 + 1.1680 + gViewController.loadView(viewId); 1.1681 + } 1.1682 + }, false); 1.1683 + }, 1.1684 + 1.1685 + shutdown: function gCategories_shutdown() { 1.1686 + AddonManager.removeTypeListener(this); 1.1687 + }, 1.1688 + 1.1689 + _insertCategory: function gCategories_insertCategory(aId, aName, aView, aPriority, aStartHidden) { 1.1690 + // If this category already exists then don't re-add it 1.1691 + if (document.getElementById("category-" + aId)) 1.1692 + return; 1.1693 + 1.1694 + var category = document.createElement("richlistitem"); 1.1695 + category.setAttribute("id", "category-" + aId); 1.1696 + category.setAttribute("value", aView); 1.1697 + category.setAttribute("class", "category"); 1.1698 + category.setAttribute("name", aName); 1.1699 + category.setAttribute("tooltiptext", aName); 1.1700 + category.setAttribute("priority", aPriority); 1.1701 + category.setAttribute("hidden", aStartHidden); 1.1702 + 1.1703 + var node; 1.1704 + for (node of this.node.children) { 1.1705 + var nodePriority = parseInt(node.getAttribute("priority")); 1.1706 + // If the new type's priority is higher than this one then this is the 1.1707 + // insertion point 1.1708 + if (aPriority < nodePriority) 1.1709 + break; 1.1710 + // If the new type's priority is lower than this one then this is isn't 1.1711 + // the insertion point 1.1712 + if (aPriority > nodePriority) 1.1713 + continue; 1.1714 + // If the priorities are equal and the new type's name is earlier 1.1715 + // alphabetically then this is the insertion point 1.1716 + if (String.localeCompare(aName, node.getAttribute("name")) < 0) 1.1717 + break; 1.1718 + } 1.1719 + 1.1720 + this.node.insertBefore(category, node); 1.1721 + }, 1.1722 + 1.1723 + _removeCategory: function gCategories_removeCategory(aId) { 1.1724 + var category = document.getElementById("category-" + aId); 1.1725 + if (!category) 1.1726 + return; 1.1727 + 1.1728 + // If this category is currently selected then switch to the default view 1.1729 + if (this.node.selectedItem == category) 1.1730 + gViewController.replaceView(VIEW_DEFAULT); 1.1731 + 1.1732 + this.node.removeChild(category); 1.1733 + }, 1.1734 + 1.1735 + onTypeAdded: function gCategories_onTypeAdded(aType) { 1.1736 + // Ignore types that we don't have a view object for 1.1737 + if (!(aType.viewType in gViewController.viewObjects)) 1.1738 + return; 1.1739 + 1.1740 + var aViewId = "addons://" + aType.viewType + "/" + aType.id; 1.1741 + 1.1742 + var startHidden = false; 1.1743 + if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) { 1.1744 + var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id); 1.1745 + try { 1.1746 + startHidden = Services.prefs.getBoolPref(prefName); 1.1747 + } 1.1748 + catch (e) { 1.1749 + // Default to hidden 1.1750 + startHidden = true; 1.1751 + } 1.1752 + 1.1753 + var self = this; 1.1754 + gPendingInitializations++; 1.1755 + getAddonsAndInstalls(aType.id, function onTypeAdded_getAddonsAndInstalls(aAddonsList, aInstallsList) { 1.1756 + var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0); 1.1757 + var item = self.get(aViewId); 1.1758 + 1.1759 + // Don't load view that is becoming hidden 1.1760 + if (hidden && aViewId == gViewController.currentViewId) 1.1761 + gViewController.loadView(VIEW_DEFAULT); 1.1762 + 1.1763 + item.hidden = hidden; 1.1764 + Services.prefs.setBoolPref(prefName, hidden); 1.1765 + 1.1766 + if (aAddonsList.length > 0 || aInstallsList.length > 0) { 1.1767 + notifyInitialized(); 1.1768 + return; 1.1769 + } 1.1770 + 1.1771 + gEventManager.registerInstallListener({ 1.1772 + onDownloadStarted: function gCategories_onDownloadStarted(aInstall) { 1.1773 + this._maybeShowCategory(aInstall); 1.1774 + }, 1.1775 + 1.1776 + onInstallStarted: function gCategories_onInstallStarted(aInstall) { 1.1777 + this._maybeShowCategory(aInstall); 1.1778 + }, 1.1779 + 1.1780 + onInstallEnded: function gCategories_onInstallEnded(aInstall, aAddon) { 1.1781 + this._maybeShowCategory(aAddon); 1.1782 + }, 1.1783 + 1.1784 + onExternalInstall: function gCategories_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) { 1.1785 + this._maybeShowCategory(aAddon); 1.1786 + }, 1.1787 + 1.1788 + _maybeShowCategory: function gCategories_maybeShowCategory(aAddon) { 1.1789 + if (aType.id == aAddon.type) { 1.1790 + self.get(aViewId).hidden = false; 1.1791 + Services.prefs.setBoolPref(prefName, false); 1.1792 + gEventManager.unregisterInstallListener(this); 1.1793 + } 1.1794 + } 1.1795 + }); 1.1796 + 1.1797 + notifyInitialized(); 1.1798 + }); 1.1799 + } 1.1800 + 1.1801 + this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority, 1.1802 + startHidden); 1.1803 + }, 1.1804 + 1.1805 + onTypeRemoved: function gCategories_onTypeRemoved(aType) { 1.1806 + this._removeCategory(aType.id); 1.1807 + }, 1.1808 + 1.1809 + get selected() { 1.1810 + return this.node.selectedItem ? this.node.selectedItem.value : null; 1.1811 + }, 1.1812 + 1.1813 + select: function gCategories_select(aId, aPreviousView) { 1.1814 + var view = gViewController.parseViewId(aId); 1.1815 + if (view.type == "detail" && aPreviousView) { 1.1816 + aId = aPreviousView; 1.1817 + view = gViewController.parseViewId(aPreviousView); 1.1818 + } 1.1819 + 1.1820 + Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId); 1.1821 + 1.1822 + if (this.node.selectedItem && 1.1823 + this.node.selectedItem.value == aId) { 1.1824 + this.node.selectedItem.hidden = false; 1.1825 + this.node.selectedItem.disabled = false; 1.1826 + return; 1.1827 + } 1.1828 + 1.1829 + if (view.type == "search") 1.1830 + var item = this._search; 1.1831 + else 1.1832 + var item = this.get(aId); 1.1833 + 1.1834 + if (item) { 1.1835 + item.hidden = false; 1.1836 + item.disabled = false; 1.1837 + this.node.suppressOnSelect = true; 1.1838 + this.node.selectedItem = item; 1.1839 + this.node.suppressOnSelect = false; 1.1840 + this.node.ensureElementIsVisible(item); 1.1841 + 1.1842 + this.maybeHideSearch(); 1.1843 + } 1.1844 + }, 1.1845 + 1.1846 + get: function gCategories_get(aId) { 1.1847 + var items = document.getElementsByAttribute("value", aId); 1.1848 + if (items.length) 1.1849 + return items[0]; 1.1850 + return null; 1.1851 + }, 1.1852 + 1.1853 + setBadge: function gCategories_setBadge(aId, aCount) { 1.1854 + let item = this.get(aId); 1.1855 + if (item) 1.1856 + item.badgeCount = aCount; 1.1857 + }, 1.1858 + 1.1859 + maybeHideSearch: function gCategories_maybeHideSearch() { 1.1860 + var view = gViewController.parseViewId(this.node.selectedItem.value); 1.1861 + this._search.disabled = view.type != "search"; 1.1862 + } 1.1863 +}; 1.1864 + 1.1865 + 1.1866 +var gHeader = { 1.1867 + _search: null, 1.1868 + _dest: "", 1.1869 + 1.1870 + initialize: function gHeader_initialize() { 1.1871 + this._search = document.getElementById("header-search"); 1.1872 + 1.1873 + this._search.addEventListener("command", function search_onCommand(aEvent) { 1.1874 + var query = aEvent.target.value; 1.1875 + if (query.length == 0) 1.1876 + return; 1.1877 + 1.1878 + gViewController.loadView("addons://search/" + encodeURIComponent(query)); 1.1879 + }, false); 1.1880 + 1.1881 + function updateNavButtonVisibility() { 1.1882 + var shouldShow = gHeader.shouldShowNavButtons; 1.1883 + document.getElementById("back-btn").hidden = !shouldShow; 1.1884 + document.getElementById("forward-btn").hidden = !shouldShow; 1.1885 + } 1.1886 + 1.1887 + window.addEventListener("focus", function window_onFocus(aEvent) { 1.1888 + if (aEvent.target == window) 1.1889 + updateNavButtonVisibility(); 1.1890 + }, false); 1.1891 + 1.1892 + updateNavButtonVisibility(); 1.1893 + }, 1.1894 + 1.1895 + focusSearchBox: function gHeader_focusSearchBox() { 1.1896 + this._search.focus(); 1.1897 + }, 1.1898 + 1.1899 + onKeyPress: function gHeader_onKeyPress(aEvent) { 1.1900 + if (String.fromCharCode(aEvent.charCode) == "/") { 1.1901 + this.focusSearchBox(); 1.1902 + return; 1.1903 + } 1.1904 + 1.1905 + // XXXunf Temporary until bug 371900 is fixed. 1.1906 + let key = document.getElementById("focusSearch").getAttribute("key"); 1.1907 +#ifdef XP_MACOSX 1.1908 + let keyModifier = aEvent.metaKey; 1.1909 +#else 1.1910 + let keyModifier = aEvent.ctrlKey; 1.1911 +#endif 1.1912 + if (String.fromCharCode(aEvent.charCode) == key && keyModifier) { 1.1913 + this.focusSearchBox(); 1.1914 + return; 1.1915 + } 1.1916 + }, 1.1917 + 1.1918 + get shouldShowNavButtons() { 1.1919 + var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor) 1.1920 + .getInterface(Ci.nsIWebNavigation) 1.1921 + .QueryInterface(Ci.nsIDocShellTreeItem); 1.1922 + 1.1923 + // If there is no outer frame then make the buttons visible 1.1924 + if (docshellItem.rootTreeItem == docshellItem) 1.1925 + return true; 1.1926 + 1.1927 + var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor) 1.1928 + .getInterface(Ci.nsIDOMWindow); 1.1929 + var outerDoc = outerWin.document; 1.1930 + var node = outerDoc.getElementById("back-button"); 1.1931 + // If the outer frame has no back-button then make the buttons visible 1.1932 + if (!node) 1.1933 + return true; 1.1934 + 1.1935 + // If the back-button or any of its parents are hidden then make the buttons 1.1936 + // visible 1.1937 + while (node != outerDoc) { 1.1938 + var style = outerWin.getComputedStyle(node, ""); 1.1939 + if (style.display == "none") 1.1940 + return true; 1.1941 + if (style.visibility != "visible") 1.1942 + return true; 1.1943 + node = node.parentNode; 1.1944 + } 1.1945 + 1.1946 + return false; 1.1947 + }, 1.1948 + 1.1949 + get searchQuery() { 1.1950 + return this._search.value; 1.1951 + }, 1.1952 + 1.1953 + set searchQuery(aQuery) { 1.1954 + this._search.value = aQuery; 1.1955 + }, 1.1956 +}; 1.1957 + 1.1958 + 1.1959 +var gDiscoverView = { 1.1960 + node: null, 1.1961 + enabled: true, 1.1962 + // Set to true after the view is first shown. If initialization completes 1.1963 + // after this then it must also load the discover homepage 1.1964 + loaded: false, 1.1965 + _browser: null, 1.1966 + _loading: null, 1.1967 + _error: null, 1.1968 + homepageURL: null, 1.1969 + _loadListeners: [], 1.1970 + 1.1971 + initialize: function gDiscoverView_initialize() { 1.1972 + this.enabled = isDiscoverEnabled(); 1.1973 + if (!this.enabled) { 1.1974 + gCategories.get("addons://discover/").hidden = true; 1.1975 + return; 1.1976 + } 1.1977 + 1.1978 + this.node = document.getElementById("discover-view"); 1.1979 + this._loading = document.getElementById("discover-loading"); 1.1980 + this._error = document.getElementById("discover-error"); 1.1981 + this._browser = document.getElementById("discover-browser"); 1.1982 + 1.1983 + let compatMode = "normal"; 1.1984 + if (!AddonManager.checkCompatibility) 1.1985 + compatMode = "ignore"; 1.1986 + else if (AddonManager.strictCompatibility) 1.1987 + compatMode = "strict"; 1.1988 + 1.1989 + var url = Services.prefs.getCharPref(PREF_DISCOVERURL); 1.1990 + url = url.replace("%COMPATIBILITY_MODE%", compatMode); 1.1991 + url = Services.urlFormatter.formatURL(url); 1.1992 + 1.1993 + var self = this; 1.1994 + 1.1995 + function setURL(aURL) { 1.1996 + try { 1.1997 + self.homepageURL = Services.io.newURI(aURL, null, null); 1.1998 + } catch (e) { 1.1999 + self.showError(); 1.2000 + notifyInitialized(); 1.2001 + return; 1.2002 + } 1.2003 + 1.2004 + self._browser.homePage = self.homepageURL.spec; 1.2005 + self._browser.addProgressListener(self); 1.2006 + 1.2007 + if (self.loaded) 1.2008 + self._loadURL(self.homepageURL.spec, false, notifyInitialized); 1.2009 + else 1.2010 + notifyInitialized(); 1.2011 + } 1.2012 + 1.2013 + if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) { 1.2014 + setURL(url); 1.2015 + return; 1.2016 + } 1.2017 + 1.2018 + gPendingInitializations++; 1.2019 + AddonManager.getAllAddons(function initialize_getAllAddons(aAddons) { 1.2020 + var list = {}; 1.2021 + for (let addon of aAddons) { 1.2022 + var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%", 1.2023 + addon.id); 1.2024 + try { 1.2025 + if (!Services.prefs.getBoolPref(prefName)) 1.2026 + continue; 1.2027 + } catch (e) { } 1.2028 + list[addon.id] = { 1.2029 + name: addon.name, 1.2030 + version: addon.version, 1.2031 + type: addon.type, 1.2032 + userDisabled: addon.userDisabled, 1.2033 + isCompatible: addon.isCompatible, 1.2034 + isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED 1.2035 + } 1.2036 + } 1.2037 + 1.2038 + setURL(url + "#" + JSON.stringify(list)); 1.2039 + }); 1.2040 + }, 1.2041 + 1.2042 + destroy: function gDiscoverView_destroy() { 1.2043 + try { 1.2044 + this._browser.removeProgressListener(this); 1.2045 + } 1.2046 + catch (e) { 1.2047 + // Ignore the case when the listener wasn't already registered 1.2048 + } 1.2049 + }, 1.2050 + 1.2051 + show: function gDiscoverView_show(aParam, aRequest, aState, aIsRefresh) { 1.2052 + gViewController.updateCommands(); 1.2053 + 1.2054 + // If we're being told to load a specific URL then just do that 1.2055 + if (aState && "url" in aState) { 1.2056 + this.loaded = true; 1.2057 + this._loadURL(aState.url); 1.2058 + } 1.2059 + 1.2060 + // If the view has loaded before and still at the homepage (if refreshing), 1.2061 + // and the error page is not visible then there is nothing else to do 1.2062 + if (this.loaded && this.node.selectedPanel != this._error && 1.2063 + (!aIsRefresh || (this._browser.currentURI && 1.2064 + this._browser.currentURI.spec == this._browser.homePage))) { 1.2065 + gViewController.notifyViewChanged(); 1.2066 + return; 1.2067 + } 1.2068 + 1.2069 + this.loaded = true; 1.2070 + 1.2071 + // No homepage means initialization isn't complete, the browser will get 1.2072 + // loaded once initialization is complete 1.2073 + if (!this.homepageURL) { 1.2074 + this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController)); 1.2075 + return; 1.2076 + } 1.2077 + 1.2078 + this._loadURL(this.homepageURL.spec, aIsRefresh, 1.2079 + gViewController.notifyViewChanged.bind(gViewController)); 1.2080 + }, 1.2081 + 1.2082 + canRefresh: function gDiscoverView_canRefresh() { 1.2083 + if (this._browser.currentURI && 1.2084 + this._browser.currentURI.spec == this._browser.homePage) 1.2085 + return false; 1.2086 + return true; 1.2087 + }, 1.2088 + 1.2089 + refresh: function gDiscoverView_refresh(aParam, aRequest, aState) { 1.2090 + this.show(aParam, aRequest, aState, true); 1.2091 + }, 1.2092 + 1.2093 + hide: function gDiscoverView_hide() { }, 1.2094 + 1.2095 + showError: function gDiscoverView_showError() { 1.2096 + this.node.selectedPanel = this._error; 1.2097 + }, 1.2098 + 1.2099 + _loadURL: function gDiscoverView_loadURL(aURL, aKeepHistory, aCallback) { 1.2100 + if (this._browser.currentURI.spec == aURL) { 1.2101 + if (aCallback) 1.2102 + aCallback(); 1.2103 + return; 1.2104 + } 1.2105 + 1.2106 + if (aCallback) 1.2107 + this._loadListeners.push(aCallback); 1.2108 + 1.2109 + var flags = 0; 1.2110 + if (!aKeepHistory) 1.2111 + flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; 1.2112 + 1.2113 + this._browser.loadURIWithFlags(aURL, flags); 1.2114 + }, 1.2115 + 1.2116 + onLocationChange: function gDiscoverView_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { 1.2117 + // Ignore the about:blank load 1.2118 + if (aLocation.spec == "about:blank") 1.2119 + return; 1.2120 + 1.2121 + // When using the real session history the inner-frame will update the 1.2122 + // session history automatically, if using the fake history though it must 1.2123 + // be manually updated 1.2124 + if (gHistory == FakeHistory) { 1.2125 + var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell); 1.2126 + 1.2127 + var state = { 1.2128 + view: "addons://discover/", 1.2129 + url: aLocation.spec 1.2130 + }; 1.2131 + 1.2132 + var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16; 1.2133 + if (docshell.loadType & replaceHistory) 1.2134 + gHistory.replaceState(state); 1.2135 + else 1.2136 + gHistory.pushState(state); 1.2137 + gViewController.lastHistoryIndex = gHistory.index; 1.2138 + } 1.2139 + 1.2140 + gViewController.updateCommands(); 1.2141 + 1.2142 + // If the hostname is the same as the new location's host and either the 1.2143 + // default scheme is insecure or the new location is secure then continue 1.2144 + // with the load 1.2145 + if (aLocation.host == this.homepageURL.host && 1.2146 + (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https"))) 1.2147 + return; 1.2148 + 1.2149 + // Canceling the request will send an error to onStateChange which will show 1.2150 + // the error page 1.2151 + aRequest.cancel(Components.results.NS_BINDING_ABORTED); 1.2152 + }, 1.2153 + 1.2154 + onSecurityChange: function gDiscoverView_onSecurityChange(aWebProgress, aRequest, aState) { 1.2155 + // Don't care about security if the page is not https 1.2156 + if (!this.homepageURL.schemeIs("https")) 1.2157 + return; 1.2158 + 1.2159 + // If the request was secure then it is ok 1.2160 + if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) 1.2161 + return; 1.2162 + 1.2163 + // Canceling the request will send an error to onStateChange which will show 1.2164 + // the error page 1.2165 + aRequest.cancel(Components.results.NS_BINDING_ABORTED); 1.2166 + }, 1.2167 + 1.2168 + onStateChange: function gDiscoverView_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { 1.2169 + let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | 1.2170 + Ci.nsIWebProgressListener.STATE_IS_REQUEST | 1.2171 + Ci.nsIWebProgressListener.STATE_TRANSFERRING; 1.2172 + // Once transferring begins show the content 1.2173 + if (aStateFlags & transferStart) 1.2174 + this.node.selectedPanel = this._browser; 1.2175 + 1.2176 + // Only care about the network events 1.2177 + if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK))) 1.2178 + return; 1.2179 + 1.2180 + // If this is the start of network activity then show the loading page 1.2181 + if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START)) 1.2182 + this.node.selectedPanel = this._loading; 1.2183 + 1.2184 + // Ignore anything except stop events 1.2185 + if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP))) 1.2186 + return; 1.2187 + 1.2188 + // Consider the successful load of about:blank as still loading 1.2189 + if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank") 1.2190 + return; 1.2191 + 1.2192 + // If there was an error loading the page or the new hostname is not the 1.2193 + // same as the default hostname or the default scheme is secure and the new 1.2194 + // scheme is insecure then show the error page 1.2195 + const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021; 1.2196 + if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) || 1.2197 + (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) { 1.2198 + this.showError(); 1.2199 + } else { 1.2200 + // Got a successful load, make sure the browser is visible 1.2201 + this.node.selectedPanel = this._browser; 1.2202 + gViewController.updateCommands(); 1.2203 + } 1.2204 + 1.2205 + var listeners = this._loadListeners; 1.2206 + this._loadListeners = []; 1.2207 + 1.2208 + for (let listener of listeners) 1.2209 + listener(); 1.2210 + }, 1.2211 + 1.2212 + onProgressChange: function gDiscoverView_onProgressChange() { }, 1.2213 + onStatusChange: function gDiscoverView_onStatusChange() { }, 1.2214 + 1.2215 + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, 1.2216 + Ci.nsISupportsWeakReference]), 1.2217 + 1.2218 + getSelectedAddon: function gDiscoverView_getSelectedAddon() null 1.2219 +}; 1.2220 + 1.2221 + 1.2222 +var gCachedAddons = {}; 1.2223 + 1.2224 +var gSearchView = { 1.2225 + node: null, 1.2226 + _filter: null, 1.2227 + _sorters: null, 1.2228 + _loading: null, 1.2229 + _listBox: null, 1.2230 + _emptyNotice: null, 1.2231 + _allResultsLink: null, 1.2232 + _lastQuery: null, 1.2233 + _lastRemoteTotal: 0, 1.2234 + _pendingSearches: 0, 1.2235 + 1.2236 + initialize: function gSearchView_initialize() { 1.2237 + this.node = document.getElementById("search-view"); 1.2238 + this._filter = document.getElementById("search-filter-radiogroup"); 1.2239 + this._sorters = document.getElementById("search-sorters"); 1.2240 + this._sorters.handler = this; 1.2241 + this._loading = document.getElementById("search-loading"); 1.2242 + this._listBox = document.getElementById("search-list"); 1.2243 + this._emptyNotice = document.getElementById("search-list-empty"); 1.2244 + this._allResultsLink = document.getElementById("search-allresults-link"); 1.2245 + 1.2246 + if (!AddonManager.isInstallEnabled("application/x-xpinstall")) 1.2247 + this._filter.hidden = true; 1.2248 + 1.2249 + var self = this; 1.2250 + this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) { 1.2251 + if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { 1.2252 + var item = self._listBox.selectedItem; 1.2253 + if (item) 1.2254 + item.showInDetailView(); 1.2255 + } 1.2256 + }, false); 1.2257 + 1.2258 + this._filter.addEventListener("command", function filter_onCommand() self.updateView(), false); 1.2259 + }, 1.2260 + 1.2261 + shutdown: function gSearchView_shutdown() { 1.2262 + if (AddonRepository.isSearching) 1.2263 + AddonRepository.cancelSearch(); 1.2264 + }, 1.2265 + 1.2266 + get isSearching() { 1.2267 + return this._pendingSearches > 0; 1.2268 + }, 1.2269 + 1.2270 + show: function gSearchView_show(aQuery, aRequest) { 1.2271 + gEventManager.registerInstallListener(this); 1.2272 + 1.2273 + this.showEmptyNotice(false); 1.2274 + this.showAllResultsLink(0); 1.2275 + this.showLoading(true); 1.2276 + this._sorters.showprice = false; 1.2277 + 1.2278 + gHeader.searchQuery = aQuery; 1.2279 + aQuery = aQuery.trim().toLocaleLowerCase(); 1.2280 + if (this._lastQuery == aQuery) { 1.2281 + this.updateView(); 1.2282 + gViewController.notifyViewChanged(); 1.2283 + return; 1.2284 + } 1.2285 + this._lastQuery = aQuery; 1.2286 + 1.2287 + if (AddonRepository.isSearching) 1.2288 + AddonRepository.cancelSearch(); 1.2289 + 1.2290 + while (this._listBox.firstChild.localName == "richlistitem") 1.2291 + this._listBox.removeChild(this._listBox.firstChild); 1.2292 + 1.2293 + var self = this; 1.2294 + gCachedAddons = {}; 1.2295 + this._pendingSearches = 2; 1.2296 + this._sorters.setSort("relevancescore", false); 1.2297 + 1.2298 + var elements = []; 1.2299 + 1.2300 + function createSearchResults(aObjsList, aIsInstall, aIsRemote) { 1.2301 + for (let index in aObjsList) { 1.2302 + let obj = aObjsList[index]; 1.2303 + let score = aObjsList.length - index; 1.2304 + if (!aIsRemote && aQuery.length > 0) { 1.2305 + score = self.getMatchScore(obj, aQuery); 1.2306 + if (score == 0) 1.2307 + continue; 1.2308 + } 1.2309 + 1.2310 + let item = createItem(obj, aIsInstall, aIsRemote); 1.2311 + item.setAttribute("relevancescore", score); 1.2312 + if (aIsRemote) { 1.2313 + gCachedAddons[obj.id] = obj; 1.2314 + if (obj.purchaseURL) 1.2315 + self._sorters.showprice = true; 1.2316 + } 1.2317 + 1.2318 + elements.push(item); 1.2319 + } 1.2320 + } 1.2321 + 1.2322 + function finishSearch(createdCount) { 1.2323 + if (elements.length > 0) { 1.2324 + sortElements(elements, [self._sorters.sortBy], self._sorters.ascending); 1.2325 + for (let element of elements) 1.2326 + self._listBox.insertBefore(element, self._listBox.lastChild); 1.2327 + self.updateListAttributes(); 1.2328 + } 1.2329 + 1.2330 + self._pendingSearches--; 1.2331 + self.updateView(); 1.2332 + 1.2333 + if (!self.isSearching) 1.2334 + gViewController.notifyViewChanged(); 1.2335 + } 1.2336 + 1.2337 + getAddonsAndInstalls(null, function show_getAddonsAndInstalls(aAddons, aInstalls) { 1.2338 + if (gViewController && aRequest != gViewController.currentViewRequest) 1.2339 + return; 1.2340 + 1.2341 + createSearchResults(aAddons, false, false); 1.2342 + createSearchResults(aInstalls, true, false); 1.2343 + finishSearch(); 1.2344 + }); 1.2345 + 1.2346 + var maxRemoteResults = 0; 1.2347 + try { 1.2348 + maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS); 1.2349 + } catch(e) {} 1.2350 + 1.2351 + if (maxRemoteResults <= 0) { 1.2352 + finishSearch(0); 1.2353 + return; 1.2354 + } 1.2355 + 1.2356 + AddonRepository.searchAddons(aQuery, maxRemoteResults, { 1.2357 + searchFailed: function show_SearchFailed() { 1.2358 + if (gViewController && aRequest != gViewController.currentViewRequest) 1.2359 + return; 1.2360 + 1.2361 + self._lastRemoteTotal = 0; 1.2362 + 1.2363 + // XXXunf Better handling of AMO search failure. See bug 579502 1.2364 + finishSearch(0); // Silently fail 1.2365 + }, 1.2366 + 1.2367 + searchSucceeded: function show_SearchSucceeded(aAddonsList, aAddonCount, aTotalResults) { 1.2368 + if (gViewController && aRequest != gViewController.currentViewRequest) 1.2369 + return; 1.2370 + 1.2371 + if (aTotalResults > maxRemoteResults) 1.2372 + self._lastRemoteTotal = aTotalResults; 1.2373 + else 1.2374 + self._lastRemoteTotal = 0; 1.2375 + 1.2376 + var createdCount = createSearchResults(aAddonsList, false, true); 1.2377 + finishSearch(createdCount); 1.2378 + } 1.2379 + }); 1.2380 + }, 1.2381 + 1.2382 + showLoading: function gSearchView_showLoading(aLoading) { 1.2383 + this._loading.hidden = !aLoading; 1.2384 + this._listBox.hidden = aLoading; 1.2385 + }, 1.2386 + 1.2387 + updateView: function gSearchView_updateView() { 1.2388 + var showLocal = this._filter.value == "local"; 1.2389 + 1.2390 + if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall")) 1.2391 + showLocal = true; 1.2392 + 1.2393 + this._listBox.setAttribute("local", showLocal); 1.2394 + this._listBox.setAttribute("remote", !showLocal); 1.2395 + 1.2396 + this.showLoading(this.isSearching && !showLocal); 1.2397 + if (!this.isSearching) { 1.2398 + var isEmpty = true; 1.2399 + var results = this._listBox.getElementsByTagName("richlistitem"); 1.2400 + for (let result of results) { 1.2401 + var isRemote = (result.getAttribute("remote") == "true"); 1.2402 + if ((isRemote && !showLocal) || (!isRemote && showLocal)) { 1.2403 + isEmpty = false; 1.2404 + break; 1.2405 + } 1.2406 + } 1.2407 + 1.2408 + this.showEmptyNotice(isEmpty); 1.2409 + this.showAllResultsLink(this._lastRemoteTotal); 1.2410 + } 1.2411 + 1.2412 + gViewController.updateCommands(); 1.2413 + }, 1.2414 + 1.2415 + hide: function gSearchView_hide() { 1.2416 + gEventManager.unregisterInstallListener(this); 1.2417 + doPendingUninstalls(this._listBox); 1.2418 + }, 1.2419 + 1.2420 + getMatchScore: function gSearchView_getMatchScore(aObj, aQuery) { 1.2421 + var score = 0; 1.2422 + score += this.calculateMatchScore(aObj.name, aQuery, 1.2423 + SEARCH_SCORE_MULTIPLIER_NAME); 1.2424 + score += this.calculateMatchScore(aObj.description, aQuery, 1.2425 + SEARCH_SCORE_MULTIPLIER_DESCRIPTION); 1.2426 + return score; 1.2427 + }, 1.2428 + 1.2429 + calculateMatchScore: function gSearchView_calculateMatchScore(aStr, aQuery, aMultiplier) { 1.2430 + var score = 0; 1.2431 + if (!aStr || aQuery.length == 0) 1.2432 + return score; 1.2433 + 1.2434 + aStr = aStr.trim().toLocaleLowerCase(); 1.2435 + var haystack = aStr.split(/\s+/); 1.2436 + var needles = aQuery.split(/\s+/); 1.2437 + 1.2438 + for (let needle of needles) { 1.2439 + for (let hay of haystack) { 1.2440 + if (hay == needle) { 1.2441 + // matching whole words is best 1.2442 + score += SEARCH_SCORE_MATCH_WHOLEWORD; 1.2443 + } else { 1.2444 + let i = hay.indexOf(needle); 1.2445 + if (i == 0) // matching on word boundries is also good 1.2446 + score += SEARCH_SCORE_MATCH_WORDBOUNDRY; 1.2447 + else if (i > 0) // substring matches not so good 1.2448 + score += SEARCH_SCORE_MATCH_SUBSTRING; 1.2449 + } 1.2450 + } 1.2451 + } 1.2452 + 1.2453 + // give progressively higher score for longer queries, since longer queries 1.2454 + // are more likely to be unique and therefore more relevant. 1.2455 + if (needles.length > 1 && aStr.indexOf(aQuery) != -1) 1.2456 + score += needles.length; 1.2457 + 1.2458 + return score * aMultiplier; 1.2459 + }, 1.2460 + 1.2461 + showEmptyNotice: function gSearchView_showEmptyNotice(aShow) { 1.2462 + this._emptyNotice.hidden = !aShow; 1.2463 + this._listBox.hidden = aShow; 1.2464 + }, 1.2465 + 1.2466 + showAllResultsLink: function gSearchView_showAllResultsLink(aTotalResults) { 1.2467 + if (aTotalResults == 0) { 1.2468 + this._allResultsLink.hidden = true; 1.2469 + return; 1.2470 + } 1.2471 + 1.2472 + var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults"); 1.2473 + linkStr = PluralForm.get(aTotalResults, linkStr); 1.2474 + linkStr = linkStr.replace("#1", aTotalResults); 1.2475 + this._allResultsLink.setAttribute("value", linkStr); 1.2476 + 1.2477 + this._allResultsLink.setAttribute("href", 1.2478 + AddonRepository.getSearchURL(this._lastQuery)); 1.2479 + this._allResultsLink.hidden = false; 1.2480 + }, 1.2481 + 1.2482 + updateListAttributes: function gSearchView_updateListAttributes() { 1.2483 + var item = this._listBox.querySelector("richlistitem[remote='true'][first]"); 1.2484 + if (item) 1.2485 + item.removeAttribute("first"); 1.2486 + item = this._listBox.querySelector("richlistitem[remote='true'][last]"); 1.2487 + if (item) 1.2488 + item.removeAttribute("last"); 1.2489 + var items = this._listBox.querySelectorAll("richlistitem[remote='true']"); 1.2490 + if (items.length > 0) { 1.2491 + items[0].setAttribute("first", true); 1.2492 + items[items.length - 1].setAttribute("last", true); 1.2493 + } 1.2494 + 1.2495 + item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]"); 1.2496 + if (item) 1.2497 + item.removeAttribute("first"); 1.2498 + item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]"); 1.2499 + if (item) 1.2500 + item.removeAttribute("last"); 1.2501 + items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])"); 1.2502 + if (items.length > 0) { 1.2503 + items[0].setAttribute("first", true); 1.2504 + items[items.length - 1].setAttribute("last", true); 1.2505 + } 1.2506 + 1.2507 + }, 1.2508 + 1.2509 + onSortChanged: function gSearchView_onSortChanged(aSortBy, aAscending) { 1.2510 + var footer = this._listBox.lastChild; 1.2511 + this._listBox.removeChild(footer); 1.2512 + 1.2513 + sortList(this._listBox, aSortBy, aAscending); 1.2514 + this.updateListAttributes(); 1.2515 + 1.2516 + this._listBox.appendChild(footer); 1.2517 + }, 1.2518 + 1.2519 + onDownloadCancelled: function gSearchView_onDownloadCancelled(aInstall) { 1.2520 + this.removeInstall(aInstall); 1.2521 + }, 1.2522 + 1.2523 + onInstallCancelled: function gSearchView_onInstallCancelled(aInstall) { 1.2524 + this.removeInstall(aInstall); 1.2525 + }, 1.2526 + 1.2527 + removeInstall: function gSearchView_removeInstall(aInstall) { 1.2528 + for (let item of this._listBox.childNodes) { 1.2529 + if (item.mInstall == aInstall) { 1.2530 + this._listBox.removeChild(item); 1.2531 + return; 1.2532 + } 1.2533 + } 1.2534 + }, 1.2535 + 1.2536 + getSelectedAddon: function gSearchView_getSelectedAddon() { 1.2537 + var item = this._listBox.selectedItem; 1.2538 + if (item) 1.2539 + return item.mAddon; 1.2540 + return null; 1.2541 + }, 1.2542 + 1.2543 + getListItemForID: function gSearchView_getListItemForID(aId) { 1.2544 + var listitem = this._listBox.firstChild; 1.2545 + while (listitem) { 1.2546 + if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId) 1.2547 + return listitem; 1.2548 + listitem = listitem.nextSibling; 1.2549 + } 1.2550 + return null; 1.2551 + } 1.2552 +}; 1.2553 + 1.2554 + 1.2555 +var gListView = { 1.2556 + node: null, 1.2557 + _listBox: null, 1.2558 + _emptyNotice: null, 1.2559 + _type: null, 1.2560 + 1.2561 + initialize: function gListView_initialize() { 1.2562 + this.node = document.getElementById("list-view"); 1.2563 + this._listBox = document.getElementById("addon-list"); 1.2564 + this._emptyNotice = document.getElementById("addon-list-empty"); 1.2565 + 1.2566 + var self = this; 1.2567 + this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) { 1.2568 + if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { 1.2569 + var item = self._listBox.selectedItem; 1.2570 + if (item) 1.2571 + item.showInDetailView(); 1.2572 + } 1.2573 + }, false); 1.2574 + }, 1.2575 + 1.2576 + show: function gListView_show(aType, aRequest) { 1.2577 + if (!(aType in AddonManager.addonTypes)) 1.2578 + throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG); 1.2579 + 1.2580 + this._type = aType; 1.2581 + this.node.setAttribute("type", aType); 1.2582 + this.showEmptyNotice(false); 1.2583 + 1.2584 + while (this._listBox.itemCount > 0) 1.2585 + this._listBox.removeItemAt(0); 1.2586 + 1.2587 + var self = this; 1.2588 + getAddonsAndInstalls(aType, function show_getAddonsAndInstalls(aAddonsList, aInstallsList) { 1.2589 + if (gViewController && aRequest != gViewController.currentViewRequest) 1.2590 + return; 1.2591 + 1.2592 + var elements = []; 1.2593 + 1.2594 + for (let addonItem of aAddonsList) 1.2595 + elements.push(createItem(addonItem)); 1.2596 + 1.2597 + for (let installItem of aInstallsList) 1.2598 + elements.push(createItem(installItem, true)); 1.2599 + 1.2600 + self.showEmptyNotice(elements.length == 0); 1.2601 + if (elements.length > 0) { 1.2602 + sortElements(elements, ["uiState", "name"], true); 1.2603 + for (let element of elements) 1.2604 + self._listBox.appendChild(element); 1.2605 + } 1.2606 + 1.2607 + gEventManager.registerInstallListener(self); 1.2608 + gViewController.updateCommands(); 1.2609 + gViewController.notifyViewChanged(); 1.2610 + }); 1.2611 + }, 1.2612 + 1.2613 + hide: function gListView_hide() { 1.2614 + gEventManager.unregisterInstallListener(this); 1.2615 + doPendingUninstalls(this._listBox); 1.2616 + }, 1.2617 + 1.2618 + showEmptyNotice: function gListView_showEmptyNotice(aShow) { 1.2619 + this._emptyNotice.hidden = !aShow; 1.2620 + }, 1.2621 + 1.2622 + onSortChanged: function gListView_onSortChanged(aSortBy, aAscending) { 1.2623 + sortList(this._listBox, aSortBy, aAscending); 1.2624 + }, 1.2625 + 1.2626 + onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) { 1.2627 + // The existing list item will take care of upgrade installs 1.2628 + if (aExistingAddon) 1.2629 + return; 1.2630 + 1.2631 + this.addItem(aAddon); 1.2632 + }, 1.2633 + 1.2634 + onDownloadStarted: function gListView_onDownloadStarted(aInstall) { 1.2635 + this.addItem(aInstall, true); 1.2636 + }, 1.2637 + 1.2638 + onInstallStarted: function gListView_onInstallStarted(aInstall) { 1.2639 + this.addItem(aInstall, true); 1.2640 + }, 1.2641 + 1.2642 + onDownloadCancelled: function gListView_onDownloadCancelled(aInstall) { 1.2643 + this.removeItem(aInstall, true); 1.2644 + }, 1.2645 + 1.2646 + onInstallCancelled: function gListView_onInstallCancelled(aInstall) { 1.2647 + this.removeItem(aInstall, true); 1.2648 + }, 1.2649 + 1.2650 + onInstallEnded: function gListView_onInstallEnded(aInstall) { 1.2651 + // Remove any install entries for upgrades, their status will appear against 1.2652 + // the existing item 1.2653 + if (aInstall.existingAddon) 1.2654 + this.removeItem(aInstall, true); 1.2655 + 1.2656 + if (aInstall.addon.type == "experiment") { 1.2657 + let item = this.getListItemForID(aInstall.addon.id); 1.2658 + if (item) { 1.2659 + item.endDate = getExperimentEndDate(aInstall.addon); 1.2660 + } 1.2661 + } 1.2662 + }, 1.2663 + 1.2664 + addItem: function gListView_addItem(aObj, aIsInstall) { 1.2665 + if (aObj.type != this._type) 1.2666 + return; 1.2667 + 1.2668 + if (aIsInstall && aObj.existingAddon) 1.2669 + return; 1.2670 + 1.2671 + let prop = aIsInstall ? "mInstall" : "mAddon"; 1.2672 + for (let item of this._listBox.childNodes) { 1.2673 + if (item[prop] == aObj) 1.2674 + return; 1.2675 + } 1.2676 + 1.2677 + let item = createItem(aObj, aIsInstall); 1.2678 + this._listBox.insertBefore(item, this._listBox.firstChild); 1.2679 + this.showEmptyNotice(false); 1.2680 + }, 1.2681 + 1.2682 + removeItem: function gListView_removeItem(aObj, aIsInstall) { 1.2683 + let prop = aIsInstall ? "mInstall" : "mAddon"; 1.2684 + 1.2685 + for (let item of this._listBox.childNodes) { 1.2686 + if (item[prop] == aObj) { 1.2687 + this._listBox.removeChild(item); 1.2688 + this.showEmptyNotice(this._listBox.itemCount == 0); 1.2689 + return; 1.2690 + } 1.2691 + } 1.2692 + }, 1.2693 + 1.2694 + getSelectedAddon: function gListView_getSelectedAddon() { 1.2695 + var item = this._listBox.selectedItem; 1.2696 + if (item) 1.2697 + return item.mAddon; 1.2698 + return null; 1.2699 + }, 1.2700 + 1.2701 + getListItemForID: function gListView_getListItemForID(aId) { 1.2702 + var listitem = this._listBox.firstChild; 1.2703 + while (listitem) { 1.2704 + if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId) 1.2705 + return listitem; 1.2706 + listitem = listitem.nextSibling; 1.2707 + } 1.2708 + return null; 1.2709 + } 1.2710 +}; 1.2711 + 1.2712 + 1.2713 +var gDetailView = { 1.2714 + node: null, 1.2715 + _addon: null, 1.2716 + _loadingTimer: null, 1.2717 + _autoUpdate: null, 1.2718 + 1.2719 + initialize: function gDetailView_initialize() { 1.2720 + this.node = document.getElementById("detail-view"); 1.2721 + 1.2722 + this._autoUpdate = document.getElementById("detail-autoUpdate"); 1.2723 + 1.2724 + var self = this; 1.2725 + this._autoUpdate.addEventListener("command", function autoUpdate_onCommand() { 1.2726 + self._addon.applyBackgroundUpdates = self._autoUpdate.value; 1.2727 + }, true); 1.2728 + }, 1.2729 + 1.2730 + shutdown: function gDetailView_shutdown() { 1.2731 + AddonManager.removeManagerListener(this); 1.2732 + }, 1.2733 + 1.2734 + onUpdateModeChanged: function gDetailView_onUpdateModeChanged() { 1.2735 + this.onPropertyChanged(["applyBackgroundUpdates"]); 1.2736 + }, 1.2737 + 1.2738 + _updateView: function gDetailView_updateView(aAddon, aIsRemote, aScrollToPreferences) { 1.2739 + AddonManager.addManagerListener(this); 1.2740 + this.clearLoading(); 1.2741 + 1.2742 + this._addon = aAddon; 1.2743 + gEventManager.registerAddonListener(this, aAddon.id); 1.2744 + gEventManager.registerInstallListener(this); 1.2745 + 1.2746 + this.node.setAttribute("type", aAddon.type); 1.2747 + 1.2748 + // If the search category isn't selected then make sure to select the 1.2749 + // correct category 1.2750 + if (gCategories.selected != "addons://search/") 1.2751 + gCategories.select("addons://list/" + aAddon.type); 1.2752 + 1.2753 + document.getElementById("detail-name").textContent = aAddon.name; 1.2754 + var icon = aAddon.icon64URL ? aAddon.icon64URL : aAddon.iconURL; 1.2755 + document.getElementById("detail-icon").src = icon ? icon : ""; 1.2756 + document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL); 1.2757 + 1.2758 + var version = document.getElementById("detail-version"); 1.2759 + if (shouldShowVersionNumber(aAddon)) { 1.2760 + version.hidden = false; 1.2761 + version.value = aAddon.version; 1.2762 + } else { 1.2763 + version.hidden = true; 1.2764 + } 1.2765 + 1.2766 + var screenshot = document.getElementById("detail-screenshot"); 1.2767 + if (aAddon.screenshots && aAddon.screenshots.length > 0) { 1.2768 + if (aAddon.screenshots[0].thumbnailURL) { 1.2769 + screenshot.src = aAddon.screenshots[0].thumbnailURL; 1.2770 + screenshot.width = aAddon.screenshots[0].thumbnailWidth; 1.2771 + screenshot.height = aAddon.screenshots[0].thumbnailHeight; 1.2772 + } else { 1.2773 + screenshot.src = aAddon.screenshots[0].url; 1.2774 + screenshot.width = aAddon.screenshots[0].width; 1.2775 + screenshot.height = aAddon.screenshots[0].height; 1.2776 + } 1.2777 + screenshot.setAttribute("loading", "true"); 1.2778 + screenshot.hidden = false; 1.2779 + } else { 1.2780 + screenshot.hidden = true; 1.2781 + } 1.2782 + 1.2783 + var desc = document.getElementById("detail-desc"); 1.2784 + desc.textContent = aAddon.description; 1.2785 + 1.2786 + var fullDesc = document.getElementById("detail-fulldesc"); 1.2787 + if (aAddon.fullDescription) { 1.2788 + fullDesc.textContent = aAddon.fullDescription; 1.2789 + fullDesc.hidden = false; 1.2790 + } else { 1.2791 + fullDesc.hidden = true; 1.2792 + } 1.2793 + 1.2794 + var contributions = document.getElementById("detail-contributions"); 1.2795 + if ("contributionURL" in aAddon && aAddon.contributionURL) { 1.2796 + contributions.hidden = false; 1.2797 + var amount = document.getElementById("detail-contrib-suggested"); 1.2798 + if (aAddon.contributionAmount) { 1.2799 + amount.value = gStrings.ext.formatStringFromName("contributionAmount2", 1.2800 + [aAddon.contributionAmount], 1.2801 + 1); 1.2802 + amount.hidden = false; 1.2803 + } else { 1.2804 + amount.hidden = true; 1.2805 + } 1.2806 + } else { 1.2807 + contributions.hidden = true; 1.2808 + } 1.2809 + 1.2810 + if ("purchaseURL" in aAddon && aAddon.purchaseURL) { 1.2811 + var purchase = document.getElementById("detail-purchase-btn"); 1.2812 + purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label", 1.2813 + [aAddon.purchaseDisplayAmount], 1.2814 + 1); 1.2815 + purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey"); 1.2816 + } 1.2817 + 1.2818 + var updateDateRow = document.getElementById("detail-dateUpdated"); 1.2819 + if (aAddon.updateDate) { 1.2820 + var date = formatDate(aAddon.updateDate); 1.2821 + updateDateRow.value = date; 1.2822 + } else { 1.2823 + updateDateRow.value = null; 1.2824 + } 1.2825 + 1.2826 + // TODO if the add-on was downloaded from releases.mozilla.org link to the 1.2827 + // AMO profile (bug 590344) 1.2828 + if (false) { 1.2829 + document.getElementById("detail-repository-row").hidden = false; 1.2830 + document.getElementById("detail-homepage-row").hidden = true; 1.2831 + var repository = document.getElementById("detail-repository"); 1.2832 + repository.value = aAddon.homepageURL; 1.2833 + repository.href = aAddon.homepageURL; 1.2834 + } else if (aAddon.homepageURL) { 1.2835 + document.getElementById("detail-repository-row").hidden = true; 1.2836 + document.getElementById("detail-homepage-row").hidden = false; 1.2837 + var homepage = document.getElementById("detail-homepage"); 1.2838 + homepage.value = aAddon.homepageURL; 1.2839 + homepage.href = aAddon.homepageURL; 1.2840 + } else { 1.2841 + document.getElementById("detail-repository-row").hidden = true; 1.2842 + document.getElementById("detail-homepage-row").hidden = true; 1.2843 + } 1.2844 + 1.2845 + var rating = document.getElementById("detail-rating"); 1.2846 + if (aAddon.averageRating) { 1.2847 + rating.averageRating = aAddon.averageRating; 1.2848 + rating.hidden = false; 1.2849 + } else { 1.2850 + rating.hidden = true; 1.2851 + } 1.2852 + 1.2853 + var reviews = document.getElementById("detail-reviews"); 1.2854 + if (aAddon.reviewURL) { 1.2855 + var text = gStrings.ext.GetStringFromName("numReviews"); 1.2856 + text = PluralForm.get(aAddon.reviewCount, text) 1.2857 + text = text.replace("#1", aAddon.reviewCount); 1.2858 + reviews.value = text; 1.2859 + reviews.hidden = false; 1.2860 + reviews.href = aAddon.reviewURL; 1.2861 + } else { 1.2862 + reviews.hidden = true; 1.2863 + } 1.2864 + 1.2865 + document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL; 1.2866 + 1.2867 + var sizeRow = document.getElementById("detail-size"); 1.2868 + if (aAddon.size && aIsRemote) { 1.2869 + let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size)); 1.2870 + let formatted = gStrings.dl.GetStringFromName("doneSize"); 1.2871 + formatted = formatted.replace("#1", size).replace("#2", unit); 1.2872 + sizeRow.value = formatted; 1.2873 + } else { 1.2874 + sizeRow.value = null; 1.2875 + } 1.2876 + 1.2877 + var downloadsRow = document.getElementById("detail-downloads"); 1.2878 + if (aAddon.totalDownloads && aIsRemote) { 1.2879 + var downloads = aAddon.totalDownloads; 1.2880 + downloadsRow.value = downloads; 1.2881 + } else { 1.2882 + downloadsRow.value = null; 1.2883 + } 1.2884 + 1.2885 + var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade") && aAddon.id != AddonManager.hotfixID; 1.2886 + document.getElementById("detail-updates-row").hidden = !canUpdate; 1.2887 + 1.2888 + if ("applyBackgroundUpdates" in aAddon) { 1.2889 + this._autoUpdate.hidden = false; 1.2890 + this._autoUpdate.value = aAddon.applyBackgroundUpdates; 1.2891 + let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon); 1.2892 + document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates; 1.2893 + } else { 1.2894 + this._autoUpdate.hidden = true; 1.2895 + document.getElementById("detail-findUpdates-btn").hidden = false; 1.2896 + } 1.2897 + 1.2898 + document.getElementById("detail-prefs-btn").hidden = !aIsRemote && 1.2899 + !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon); 1.2900 + 1.2901 + var gridRows = document.querySelectorAll("#detail-grid rows row"); 1.2902 + let first = true; 1.2903 + for (let gridRow of gridRows) { 1.2904 + if (first && window.getComputedStyle(gridRow, null).getPropertyValue("display") != "none") { 1.2905 + gridRow.setAttribute("first-row", true); 1.2906 + first = false; 1.2907 + } else { 1.2908 + gridRow.removeAttribute("first-row"); 1.2909 + } 1.2910 + } 1.2911 + 1.2912 + if (this._addon.type == "experiment") { 1.2913 + let prefix = "details.experiment."; 1.2914 + let active = this._addon.isActive; 1.2915 + 1.2916 + let stateKey = prefix + "state." + (active ? "active" : "complete"); 1.2917 + let node = document.getElementById("detail-experiment-state"); 1.2918 + node.value = gStrings.ext.GetStringFromName(stateKey); 1.2919 + 1.2920 + let now = Date.now(); 1.2921 + let end = getExperimentEndDate(this._addon); 1.2922 + let days = Math.abs(end - now) / (24 * 60 * 60 * 1000); 1.2923 + 1.2924 + let timeKey = prefix + "time."; 1.2925 + let timeMessage; 1.2926 + if (days < 1) { 1.2927 + timeKey += (active ? "endsToday" : "endedToday"); 1.2928 + timeMessage = gStrings.ext.GetStringFromName(timeKey); 1.2929 + } else { 1.2930 + timeKey += (active ? "daysRemaining" : "daysPassed"); 1.2931 + days = Math.round(days); 1.2932 + let timeString = gStrings.ext.GetStringFromName(timeKey); 1.2933 + timeMessage = PluralForm.get(days, timeString) 1.2934 + .replace("#1", days); 1.2935 + } 1.2936 + 1.2937 + document.getElementById("detail-experiment-time").value = timeMessage; 1.2938 + } 1.2939 + 1.2940 + this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() { 1.2941 + this.updateState(); 1.2942 + gViewController.notifyViewChanged(); 1.2943 + }).bind(this)); 1.2944 + }, 1.2945 + 1.2946 + show: function gDetailView_show(aAddonId, aRequest) { 1.2947 + let index = aAddonId.indexOf("/preferences"); 1.2948 + let scrollToPreferences = false; 1.2949 + if (index >= 0) { 1.2950 + aAddonId = aAddonId.substring(0, index); 1.2951 + scrollToPreferences = true; 1.2952 + } 1.2953 + 1.2954 + var self = this; 1.2955 + this._loadingTimer = setTimeout(function loadTimeOutTimer() { 1.2956 + self.node.setAttribute("loading-extended", true); 1.2957 + }, LOADING_MSG_DELAY); 1.2958 + 1.2959 + var view = gViewController.currentViewId; 1.2960 + 1.2961 + AddonManager.getAddonByID(aAddonId, function show_getAddonByID(aAddon) { 1.2962 + if (gViewController && aRequest != gViewController.currentViewRequest) 1.2963 + return; 1.2964 + 1.2965 + if (aAddon) { 1.2966 + self._updateView(aAddon, false, scrollToPreferences); 1.2967 + return; 1.2968 + } 1.2969 + 1.2970 + // Look for an add-on pending install 1.2971 + AddonManager.getAllInstalls(function show_getAllInstalls(aInstalls) { 1.2972 + for (let install of aInstalls) { 1.2973 + if (install.state == AddonManager.STATE_INSTALLED && 1.2974 + install.addon.id == aAddonId) { 1.2975 + self._updateView(install.addon, false); 1.2976 + return; 1.2977 + } 1.2978 + } 1.2979 + 1.2980 + if (aAddonId in gCachedAddons) { 1.2981 + self._updateView(gCachedAddons[aAddonId], true); 1.2982 + return; 1.2983 + } 1.2984 + 1.2985 + // This might happen due to session restore restoring us back to an 1.2986 + // add-on that doesn't exist but otherwise shouldn't normally happen. 1.2987 + // Either way just revert to the default view. 1.2988 + gViewController.replaceView(VIEW_DEFAULT); 1.2989 + }); 1.2990 + }); 1.2991 + }, 1.2992 + 1.2993 + hide: function gDetailView_hide() { 1.2994 + AddonManager.removeManagerListener(this); 1.2995 + this.clearLoading(); 1.2996 + if (this._addon) { 1.2997 + if (hasInlineOptions(this._addon)) { 1.2998 + Services.obs.notifyObservers(document, 1.2999 + AddonManager.OPTIONS_NOTIFICATION_HIDDEN, 1.3000 + this._addon.id); 1.3001 + } 1.3002 + 1.3003 + gEventManager.unregisterAddonListener(this, this._addon.id); 1.3004 + gEventManager.unregisterInstallListener(this); 1.3005 + this._addon = null; 1.3006 + 1.3007 + // Flush the preferences to disk so they survive any crash 1.3008 + if (this.node.getElementsByTagName("setting").length) 1.3009 + Services.prefs.savePrefFile(null); 1.3010 + } 1.3011 + }, 1.3012 + 1.3013 + updateState: function gDetailView_updateState() { 1.3014 + gViewController.updateCommands(); 1.3015 + 1.3016 + var pending = this._addon.pendingOperations; 1.3017 + if (pending != AddonManager.PENDING_NONE) { 1.3018 + this.node.removeAttribute("notification"); 1.3019 + 1.3020 + var pending = null; 1.3021 + const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall", 1.3022 + "upgrade"]; 1.3023 + for (let op of PENDING_OPERATIONS) { 1.3024 + if (isPending(this._addon, op)) 1.3025 + pending = op; 1.3026 + } 1.3027 + 1.3028 + this.node.setAttribute("pending", pending); 1.3029 + document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName( 1.3030 + "details.notification." + pending, 1.3031 + [this._addon.name, gStrings.brandShortName], 2 1.3032 + ); 1.3033 + } else { 1.3034 + this.node.removeAttribute("pending"); 1.3035 + 1.3036 + if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) { 1.3037 + this.node.setAttribute("notification", "error"); 1.3038 + document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( 1.3039 + "details.notification.blocked", 1.3040 + [this._addon.name], 1 1.3041 + ); 1.3042 + var errorLink = document.getElementById("detail-error-link"); 1.3043 + errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link"); 1.3044 + errorLink.href = this._addon.blocklistURL; 1.3045 + errorLink.hidden = false; 1.3046 + } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility || 1.3047 + (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) { 1.3048 + this.node.setAttribute("notification", "warning"); 1.3049 + document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( 1.3050 + "details.notification.incompatible", 1.3051 + [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3 1.3052 + ); 1.3053 + document.getElementById("detail-warning-link").hidden = true; 1.3054 + } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) { 1.3055 + this.node.setAttribute("notification", "warning"); 1.3056 + document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( 1.3057 + "details.notification.softblocked", 1.3058 + [this._addon.name], 1 1.3059 + ); 1.3060 + var warningLink = document.getElementById("detail-warning-link"); 1.3061 + warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link"); 1.3062 + warningLink.href = this._addon.blocklistURL; 1.3063 + warningLink.hidden = false; 1.3064 + } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) { 1.3065 + this.node.setAttribute("notification", "warning"); 1.3066 + document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( 1.3067 + "details.notification.outdated", 1.3068 + [this._addon.name], 1 1.3069 + ); 1.3070 + var warningLink = document.getElementById("detail-warning-link"); 1.3071 + warningLink.value = gStrings.ext.GetStringFromName("details.notification.outdated.link"); 1.3072 + warningLink.href = Services.urlFormatter.formatURLPref("plugins.update.url"); 1.3073 + warningLink.hidden = false; 1.3074 + } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) { 1.3075 + this.node.setAttribute("notification", "error"); 1.3076 + document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( 1.3077 + "details.notification.vulnerableUpdatable", 1.3078 + [this._addon.name], 1 1.3079 + ); 1.3080 + var errorLink = document.getElementById("detail-error-link"); 1.3081 + errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link"); 1.3082 + errorLink.href = this._addon.blocklistURL; 1.3083 + errorLink.hidden = false; 1.3084 + } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { 1.3085 + this.node.setAttribute("notification", "error"); 1.3086 + document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( 1.3087 + "details.notification.vulnerableNoUpdate", 1.3088 + [this._addon.name], 1 1.3089 + ); 1.3090 + var errorLink = document.getElementById("detail-error-link"); 1.3091 + errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link"); 1.3092 + errorLink.href = this._addon.blocklistURL; 1.3093 + errorLink.hidden = false; 1.3094 + } else { 1.3095 + this.node.removeAttribute("notification"); 1.3096 + } 1.3097 + } 1.3098 + 1.3099 + let menulist = document.getElementById("detail-state-menulist"); 1.3100 + let addonType = AddonManager.addonTypes[this._addon.type]; 1.3101 + if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE && 1.3102 + (hasPermission(this._addon, "ask_to_activate") || 1.3103 + hasPermission(this._addon, "enable") || 1.3104 + hasPermission(this._addon, "disable"))) { 1.3105 + let askItem = document.getElementById("detail-ask-to-activate-menuitem"); 1.3106 + let alwaysItem = document.getElementById("detail-always-activate-menuitem"); 1.3107 + let neverItem = document.getElementById("detail-never-activate-menuitem"); 1.3108 + if (this._addon.userDisabled === true) { 1.3109 + menulist.selectedItem = neverItem; 1.3110 + } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) { 1.3111 + menulist.selectedItem = askItem; 1.3112 + } else { 1.3113 + menulist.selectedItem = alwaysItem; 1.3114 + } 1.3115 + menulist.hidden = false; 1.3116 + } else { 1.3117 + menulist.hidden = true; 1.3118 + } 1.3119 + 1.3120 + this.node.setAttribute("active", this._addon.isActive); 1.3121 + }, 1.3122 + 1.3123 + clearLoading: function gDetailView_clearLoading() { 1.3124 + if (this._loadingTimer) { 1.3125 + clearTimeout(this._loadingTimer); 1.3126 + this._loadingTimer = null; 1.3127 + } 1.3128 + 1.3129 + this.node.removeAttribute("loading-extended"); 1.3130 + }, 1.3131 + 1.3132 + emptySettingsRows: function gDetailView_emptySettingsRows() { 1.3133 + var lastRow = document.getElementById("detail-downloads"); 1.3134 + var rows = lastRow.parentNode; 1.3135 + while (lastRow.nextSibling) 1.3136 + rows.removeChild(rows.lastChild); 1.3137 + }, 1.3138 + 1.3139 + fillSettingsRows: function gDetailView_fillSettingsRows(aScrollToPreferences, aCallback) { 1.3140 + this.emptySettingsRows(); 1.3141 + if (!hasInlineOptions(this._addon)) { 1.3142 + if (aCallback) 1.3143 + aCallback(); 1.3144 + return; 1.3145 + } 1.3146 + 1.3147 + // This function removes and returns the text content of aNode without 1.3148 + // removing any child elements. Removing the text nodes ensures any XBL 1.3149 + // bindings apply properly. 1.3150 + function stripTextNodes(aNode) { 1.3151 + var text = ''; 1.3152 + for (var i = 0; i < aNode.childNodes.length; i++) { 1.3153 + if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { 1.3154 + text += aNode.childNodes[i].textContent; 1.3155 + aNode.removeChild(aNode.childNodes[i--]); 1.3156 + } else { 1.3157 + text += stripTextNodes(aNode.childNodes[i]); 1.3158 + } 1.3159 + } 1.3160 + return text; 1.3161 + } 1.3162 + 1.3163 + var rows = document.getElementById("detail-downloads").parentNode; 1.3164 + 1.3165 + try { 1.3166 + var xhr = new XMLHttpRequest(); 1.3167 + xhr.open("GET", this._addon.optionsURL, true); 1.3168 + xhr.responseType = "xml"; 1.3169 + xhr.onload = (function fillSettingsRows_onload() { 1.3170 + var xml = xhr.responseXML; 1.3171 + var settings = xml.querySelectorAll(":root > setting"); 1.3172 + 1.3173 + var firstSetting = null; 1.3174 + for (var setting of settings) { 1.3175 + 1.3176 + var desc = stripTextNodes(setting).trim(); 1.3177 + if (!setting.hasAttribute("desc")) 1.3178 + setting.setAttribute("desc", desc); 1.3179 + 1.3180 + var type = setting.getAttribute("type"); 1.3181 + if (type == "file" || type == "directory") 1.3182 + setting.setAttribute("fullpath", "true"); 1.3183 + 1.3184 + setting = document.importNode(setting, true); 1.3185 + var style = setting.getAttribute("style"); 1.3186 + if (style) { 1.3187 + setting.removeAttribute("style"); 1.3188 + setting.setAttribute("style", style); 1.3189 + } 1.3190 + 1.3191 + rows.appendChild(setting); 1.3192 + var visible = window.getComputedStyle(setting, null).getPropertyValue("display") != "none"; 1.3193 + if (!firstSetting && visible) { 1.3194 + setting.setAttribute("first-row", true); 1.3195 + firstSetting = setting; 1.3196 + } 1.3197 + } 1.3198 + 1.3199 + // Ensure the page has loaded and force the XBL bindings to be synchronously applied, 1.3200 + // then notify observers. 1.3201 + if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) { 1.3202 + gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() { 1.3203 + gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false); 1.3204 + if (firstSetting) 1.3205 + firstSetting.clientTop; 1.3206 + Services.obs.notifyObservers(document, 1.3207 + AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, 1.3208 + gDetailView._addon.id); 1.3209 + if (aScrollToPreferences) 1.3210 + gDetailView.scrollToPreferencesRows(); 1.3211 + }, false); 1.3212 + } else { 1.3213 + if (firstSetting) 1.3214 + firstSetting.clientTop; 1.3215 + Services.obs.notifyObservers(document, 1.3216 + AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, 1.3217 + this._addon.id); 1.3218 + if (aScrollToPreferences) 1.3219 + gDetailView.scrollToPreferencesRows(); 1.3220 + } 1.3221 + if (aCallback) 1.3222 + aCallback(); 1.3223 + }).bind(this); 1.3224 + xhr.onerror = function fillSettingsRows_onerror(aEvent) { 1.3225 + Cu.reportError("Error " + aEvent.target.status + 1.3226 + " occurred while receiving " + this._addon.optionsURL); 1.3227 + if (aCallback) 1.3228 + aCallback(); 1.3229 + }; 1.3230 + xhr.send(); 1.3231 + } catch(e) { 1.3232 + Cu.reportError(e); 1.3233 + if (aCallback) 1.3234 + aCallback(); 1.3235 + } 1.3236 + }, 1.3237 + 1.3238 + scrollToPreferencesRows: function gDetailView_scrollToPreferencesRows() { 1.3239 + // We find this row, rather than remembering it from above, 1.3240 + // in case it has been changed by the observers. 1.3241 + let firstRow = gDetailView.node.querySelector('setting[first-row="true"]'); 1.3242 + if (firstRow) { 1.3243 + let top = firstRow.boxObject.y; 1.3244 + top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top")); 1.3245 + 1.3246 + let detailViewBoxObject = gDetailView.node.boxObject; 1.3247 + top -= detailViewBoxObject.y; 1.3248 + 1.3249 + detailViewBoxObject.QueryInterface(Ci.nsIScrollBoxObject); 1.3250 + detailViewBoxObject.scrollTo(0, top); 1.3251 + } 1.3252 + }, 1.3253 + 1.3254 + getSelectedAddon: function gDetailView_getSelectedAddon() { 1.3255 + return this._addon; 1.3256 + }, 1.3257 + 1.3258 + onEnabling: function gDetailView_onEnabling() { 1.3259 + this.updateState(); 1.3260 + }, 1.3261 + 1.3262 + onEnabled: function gDetailView_onEnabled() { 1.3263 + this.updateState(); 1.3264 + this.fillSettingsRows(); 1.3265 + }, 1.3266 + 1.3267 + onDisabling: function gDetailView_onDisabling(aNeedsRestart) { 1.3268 + this.updateState(); 1.3269 + if (!aNeedsRestart && hasInlineOptions(this._addon)) { 1.3270 + Services.obs.notifyObservers(document, 1.3271 + AddonManager.OPTIONS_NOTIFICATION_HIDDEN, 1.3272 + this._addon.id); 1.3273 + } 1.3274 + }, 1.3275 + 1.3276 + onDisabled: function gDetailView_onDisabled() { 1.3277 + this.updateState(); 1.3278 + this.emptySettingsRows(); 1.3279 + }, 1.3280 + 1.3281 + onUninstalling: function gDetailView_onUninstalling() { 1.3282 + this.updateState(); 1.3283 + }, 1.3284 + 1.3285 + onUninstalled: function gDetailView_onUninstalled() { 1.3286 + gViewController.popState(); 1.3287 + }, 1.3288 + 1.3289 + onOperationCancelled: function gDetailView_onOperationCancelled() { 1.3290 + this.updateState(); 1.3291 + }, 1.3292 + 1.3293 + onPropertyChanged: function gDetailView_onPropertyChanged(aProperties) { 1.3294 + if (aProperties.indexOf("applyBackgroundUpdates") != -1) { 1.3295 + this._autoUpdate.value = this._addon.applyBackgroundUpdates; 1.3296 + let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon); 1.3297 + document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates; 1.3298 + } 1.3299 + 1.3300 + if (aProperties.indexOf("appDisabled") != -1 || 1.3301 + aProperties.indexOf("userDisabled") != -1) 1.3302 + this.updateState(); 1.3303 + }, 1.3304 + 1.3305 + onExternalInstall: function gDetailView_onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) { 1.3306 + // Only care about upgrades for the currently displayed add-on 1.3307 + if (!aExistingAddon || aExistingAddon.id != this._addon.id) 1.3308 + return; 1.3309 + 1.3310 + if (!aNeedsRestart) 1.3311 + this._updateView(aAddon, false); 1.3312 + else 1.3313 + this.updateState(); 1.3314 + }, 1.3315 + 1.3316 + onInstallCancelled: function gDetailView_onInstallCancelled(aInstall) { 1.3317 + if (aInstall.addon.id == this._addon.id) 1.3318 + gViewController.popState(); 1.3319 + } 1.3320 +}; 1.3321 + 1.3322 + 1.3323 +var gUpdatesView = { 1.3324 + node: null, 1.3325 + _listBox: null, 1.3326 + _emptyNotice: null, 1.3327 + _sorters: null, 1.3328 + _updateSelected: null, 1.3329 + _categoryItem: null, 1.3330 + 1.3331 + initialize: function gUpdatesView_initialize() { 1.3332 + this.node = document.getElementById("updates-view"); 1.3333 + this._listBox = document.getElementById("updates-list"); 1.3334 + this._emptyNotice = document.getElementById("updates-list-empty"); 1.3335 + this._sorters = document.getElementById("updates-sorters"); 1.3336 + this._sorters.handler = this; 1.3337 + 1.3338 + this._categoryItem = gCategories.get("addons://updates/available"); 1.3339 + 1.3340 + this._updateSelected = document.getElementById("update-selected-btn"); 1.3341 + this._updateSelected.addEventListener("command", function updateSelected_onCommand() { 1.3342 + gUpdatesView.installSelected(); 1.3343 + }, false); 1.3344 + 1.3345 + this.updateAvailableCount(true); 1.3346 + 1.3347 + AddonManager.addAddonListener(this); 1.3348 + AddonManager.addInstallListener(this); 1.3349 + }, 1.3350 + 1.3351 + shutdown: function gUpdatesView_shutdown() { 1.3352 + AddonManager.removeAddonListener(this); 1.3353 + AddonManager.removeInstallListener(this); 1.3354 + }, 1.3355 + 1.3356 + show: function gUpdatesView_show(aType, aRequest) { 1.3357 + document.getElementById("empty-availableUpdates-msg").hidden = aType != "available"; 1.3358 + document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent"; 1.3359 + this.showEmptyNotice(false); 1.3360 + 1.3361 + while (this._listBox.itemCount > 0) 1.3362 + this._listBox.removeItemAt(0); 1.3363 + 1.3364 + this.node.setAttribute("updatetype", aType); 1.3365 + if (aType == "recent") 1.3366 + this._showRecentUpdates(aRequest); 1.3367 + else 1.3368 + this._showAvailableUpdates(false, aRequest); 1.3369 + }, 1.3370 + 1.3371 + hide: function gUpdatesView_hide() { 1.3372 + this._updateSelected.hidden = true; 1.3373 + this._categoryItem.disabled = this._categoryItem.badgeCount == 0; 1.3374 + doPendingUninstalls(this._listBox); 1.3375 + }, 1.3376 + 1.3377 + _showRecentUpdates: function gUpdatesView_showRecentUpdates(aRequest) { 1.3378 + var self = this; 1.3379 + AddonManager.getAllAddons(function showRecentUpdates_getAllAddons(aAddonsList) { 1.3380 + if (gViewController && aRequest != gViewController.currentViewRequest) 1.3381 + return; 1.3382 + 1.3383 + var elements = []; 1.3384 + let threshold = Date.now() - UPDATES_RECENT_TIMESPAN; 1.3385 + for (let addon of aAddonsList) { 1.3386 + if (!addon.updateDate || addon.updateDate.getTime() < threshold) 1.3387 + continue; 1.3388 + 1.3389 + elements.push(createItem(addon)); 1.3390 + } 1.3391 + 1.3392 + self.showEmptyNotice(elements.length == 0); 1.3393 + if (elements.length > 0) { 1.3394 + sortElements(elements, [self._sorters.sortBy], self._sorters.ascending); 1.3395 + for (let element of elements) 1.3396 + self._listBox.appendChild(element); 1.3397 + } 1.3398 + 1.3399 + gViewController.notifyViewChanged(); 1.3400 + }); 1.3401 + }, 1.3402 + 1.3403 + _showAvailableUpdates: function gUpdatesView_showAvailableUpdates(aIsRefresh, aRequest) { 1.3404 + /* Disable the Update Selected button so it can't get clicked 1.3405 + before everything is initialized asynchronously. 1.3406 + It will get re-enabled by maybeDisableUpdateSelected(). */ 1.3407 + this._updateSelected.disabled = true; 1.3408 + 1.3409 + var self = this; 1.3410 + AddonManager.getAllInstalls(function showAvailableUpdates_getAllInstalls(aInstallsList) { 1.3411 + if (!aIsRefresh && gViewController && aRequest && 1.3412 + aRequest != gViewController.currentViewRequest) 1.3413 + return; 1.3414 + 1.3415 + if (aIsRefresh) { 1.3416 + self.showEmptyNotice(false); 1.3417 + self._updateSelected.hidden = true; 1.3418 + 1.3419 + while (self._listBox.itemCount > 0) 1.3420 + self._listBox.removeItemAt(0); 1.3421 + } 1.3422 + 1.3423 + var elements = []; 1.3424 + 1.3425 + for (let install of aInstallsList) { 1.3426 + if (!self.isManualUpdate(install)) 1.3427 + continue; 1.3428 + 1.3429 + let item = createItem(install.existingAddon); 1.3430 + item.setAttribute("upgrade", true); 1.3431 + item.addEventListener("IncludeUpdateChanged", function item_onIncludeUpdateChanged() { 1.3432 + self.maybeDisableUpdateSelected(); 1.3433 + }, false); 1.3434 + elements.push(item); 1.3435 + } 1.3436 + 1.3437 + self.showEmptyNotice(elements.length == 0); 1.3438 + if (elements.length > 0) { 1.3439 + self._updateSelected.hidden = false; 1.3440 + sortElements(elements, [self._sorters.sortBy], self._sorters.ascending); 1.3441 + for (let element of elements) 1.3442 + self._listBox.appendChild(element); 1.3443 + } 1.3444 + 1.3445 + // ensure badge count is in sync 1.3446 + self._categoryItem.badgeCount = self._listBox.itemCount; 1.3447 + 1.3448 + gViewController.notifyViewChanged(); 1.3449 + }); 1.3450 + }, 1.3451 + 1.3452 + showEmptyNotice: function gUpdatesView_showEmptyNotice(aShow) { 1.3453 + this._emptyNotice.hidden = !aShow; 1.3454 + }, 1.3455 + 1.3456 + isManualUpdate: function gUpdatesView_isManualUpdate(aInstall, aOnlyAvailable) { 1.3457 + var isManual = aInstall.existingAddon && 1.3458 + !AddonManager.shouldAutoUpdate(aInstall.existingAddon); 1.3459 + if (isManual && aOnlyAvailable) 1.3460 + return isInState(aInstall, "available"); 1.3461 + return isManual; 1.3462 + }, 1.3463 + 1.3464 + maybeRefresh: function gUpdatesView_maybeRefresh() { 1.3465 + if (gViewController.currentViewId == "addons://updates/available") 1.3466 + this._showAvailableUpdates(true); 1.3467 + this.updateAvailableCount(); 1.3468 + }, 1.3469 + 1.3470 + updateAvailableCount: function gUpdatesView_updateAvailableCount(aInitializing) { 1.3471 + if (aInitializing) 1.3472 + gPendingInitializations++; 1.3473 + var self = this; 1.3474 + AddonManager.getAllInstalls(function updateAvailableCount_getAllInstalls(aInstallsList) { 1.3475 + var count = aInstallsList.filter(function installListFilter(aInstall) { 1.3476 + return self.isManualUpdate(aInstall, true); 1.3477 + }).length; 1.3478 + self._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" && 1.3479 + count == 0; 1.3480 + self._categoryItem.badgeCount = count; 1.3481 + if (aInitializing) 1.3482 + notifyInitialized(); 1.3483 + }); 1.3484 + }, 1.3485 + 1.3486 + maybeDisableUpdateSelected: function gUpdatesView_maybeDisableUpdateSelected() { 1.3487 + for (let item of this._listBox.childNodes) { 1.3488 + if (item.includeUpdate) { 1.3489 + this._updateSelected.disabled = false; 1.3490 + return; 1.3491 + } 1.3492 + } 1.3493 + this._updateSelected.disabled = true; 1.3494 + }, 1.3495 + 1.3496 + installSelected: function gUpdatesView_installSelected() { 1.3497 + for (let item of this._listBox.childNodes) { 1.3498 + if (item.includeUpdate) 1.3499 + item.upgrade(); 1.3500 + } 1.3501 + 1.3502 + this._updateSelected.disabled = true; 1.3503 + }, 1.3504 + 1.3505 + getSelectedAddon: function gUpdatesView_getSelectedAddon() { 1.3506 + var item = this._listBox.selectedItem; 1.3507 + if (item) 1.3508 + return item.mAddon; 1.3509 + return null; 1.3510 + }, 1.3511 + 1.3512 + getListItemForID: function gUpdatesView_getListItemForID(aId) { 1.3513 + var listitem = this._listBox.firstChild; 1.3514 + while (listitem) { 1.3515 + if (listitem.mAddon.id == aId) 1.3516 + return listitem; 1.3517 + listitem = listitem.nextSibling; 1.3518 + } 1.3519 + return null; 1.3520 + }, 1.3521 + 1.3522 + onSortChanged: function gUpdatesView_onSortChanged(aSortBy, aAscending) { 1.3523 + sortList(this._listBox, aSortBy, aAscending); 1.3524 + }, 1.3525 + 1.3526 + onNewInstall: function gUpdatesView_onNewInstall(aInstall) { 1.3527 + if (!this.isManualUpdate(aInstall)) 1.3528 + return; 1.3529 + this.maybeRefresh(); 1.3530 + }, 1.3531 + 1.3532 + onInstallStarted: function gUpdatesView_onInstallStarted(aInstall) { 1.3533 + this.updateAvailableCount(); 1.3534 + }, 1.3535 + 1.3536 + onInstallCancelled: function gUpdatesView_onInstallCancelled(aInstall) { 1.3537 + if (!this.isManualUpdate(aInstall)) 1.3538 + return; 1.3539 + this.maybeRefresh(); 1.3540 + }, 1.3541 + 1.3542 + onPropertyChanged: function gUpdatesView_onPropertyChanged(aAddon, aProperties) { 1.3543 + if (aProperties.indexOf("applyBackgroundUpdates") != -1) 1.3544 + this.updateAvailableCount(); 1.3545 + } 1.3546 +}; 1.3547 + 1.3548 +function debuggingPrefChanged() { 1.3549 + gViewController.updateState(); 1.3550 + gViewController.updateCommands(); 1.3551 + gViewController.notifyViewChanged(); 1.3552 +} 1.3553 + 1.3554 +var gDragDrop = { 1.3555 + onDragOver: function gDragDrop_onDragOver(aEvent) { 1.3556 + var types = aEvent.dataTransfer.types; 1.3557 + if (types.contains("text/uri-list") || 1.3558 + types.contains("text/x-moz-url") || 1.3559 + types.contains("application/x-moz-file")) 1.3560 + aEvent.preventDefault(); 1.3561 + }, 1.3562 + 1.3563 + onDrop: function gDragDrop_onDrop(aEvent) { 1.3564 + var dataTransfer = aEvent.dataTransfer; 1.3565 + var urls = []; 1.3566 + 1.3567 + // Convert every dropped item into a url 1.3568 + for (var i = 0; i < dataTransfer.mozItemCount; i++) { 1.3569 + var url = dataTransfer.mozGetDataAt("text/uri-list", i); 1.3570 + if (url) { 1.3571 + urls.push(url); 1.3572 + continue; 1.3573 + } 1.3574 + 1.3575 + url = dataTransfer.mozGetDataAt("text/x-moz-url", i); 1.3576 + if (url) { 1.3577 + urls.push(url.split("\n")[0]); 1.3578 + continue; 1.3579 + } 1.3580 + 1.3581 + var file = dataTransfer.mozGetDataAt("application/x-moz-file", i); 1.3582 + if (file) { 1.3583 + urls.push(Services.io.newFileURI(file).spec); 1.3584 + continue; 1.3585 + } 1.3586 + } 1.3587 + 1.3588 + var pos = 0; 1.3589 + var installs = []; 1.3590 + 1.3591 + function buildNextInstall() { 1.3592 + if (pos == urls.length) { 1.3593 + if (installs.length > 0) { 1.3594 + // Display the normal install confirmation for the installs 1.3595 + AddonManager.installAddonsFromWebpage("application/x-xpinstall", 1.3596 + window, null, installs); 1.3597 + } 1.3598 + return; 1.3599 + } 1.3600 + 1.3601 + AddonManager.getInstallForURL(urls[pos++], function onDrop_getInstallForURL(aInstall) { 1.3602 + installs.push(aInstall); 1.3603 + buildNextInstall(); 1.3604 + }, "application/x-xpinstall"); 1.3605 + } 1.3606 + 1.3607 + buildNextInstall(); 1.3608 + 1.3609 + aEvent.preventDefault(); 1.3610 + } 1.3611 +};