michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/DownloadUtils.jsm"); michael@0: Cu.import("resource://gre/modules/AddonManager.jsm"); michael@0: Cu.import("resource://gre/modules/addons/AddonRepository.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function () { michael@0: return Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {}). michael@0: BrowserToolboxProcess; michael@0: }); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Experiments", michael@0: "resource:///modules/experiments/Experiments.jsm"); michael@0: michael@0: const PREF_DISCOVERURL = "extensions.webservice.discoverURL"; michael@0: const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane"; michael@0: const PREF_XPI_ENABLED = "xpinstall.enabled"; michael@0: const PREF_MAXRESULTS = "extensions.getAddons.maxResults"; michael@0: const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled"; michael@0: const PREF_GETADDONS_CACHE_ID_ENABLED = "extensions.%ID%.getAddons.cache.enabled"; michael@0: const PREF_UI_TYPE_HIDDEN = "extensions.ui.%TYPE%.hidden"; michael@0: const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory"; michael@0: const PREF_ADDON_DEBUGGING_ENABLED = "devtools.chrome.enabled"; michael@0: const PREF_REMOTE_DEBUGGING_ENABLED = "devtools.debugger.remote-enabled"; michael@0: michael@0: const LOADING_MSG_DELAY = 100; michael@0: michael@0: const SEARCH_SCORE_MULTIPLIER_NAME = 2; michael@0: const SEARCH_SCORE_MULTIPLIER_DESCRIPTION = 2; michael@0: michael@0: // Use integers so search scores are sortable by nsIXULSortService michael@0: const SEARCH_SCORE_MATCH_WHOLEWORD = 10; michael@0: const SEARCH_SCORE_MATCH_WORDBOUNDRY = 6; michael@0: const SEARCH_SCORE_MATCH_SUBSTRING = 3; michael@0: michael@0: const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds) michael@0: const UPDATES_RELEASENOTES_TRANSFORMFILE = "chrome://mozapps/content/extensions/updateinfo.xsl"; michael@0: michael@0: const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" michael@0: michael@0: const VIEW_DEFAULT = "addons://discover/"; michael@0: michael@0: var gStrings = {}; michael@0: XPCOMUtils.defineLazyServiceGetter(gStrings, "bundleSvc", michael@0: "@mozilla.org/intl/stringbundle;1", michael@0: "nsIStringBundleService"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(gStrings, "brand", function brandLazyGetter() { michael@0: return this.bundleSvc.createBundle("chrome://branding/locale/brand.properties"); michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(gStrings, "ext", function extLazyGetter() { michael@0: return this.bundleSvc.createBundle("chrome://mozapps/locale/extensions/extensions.properties"); michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(gStrings, "dl", function dlLazyGetter() { michael@0: return this.bundleSvc.createBundle("chrome://mozapps/locale/downloads/downloads.properties"); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyGetter(gStrings, "brandShortName", function brandShortNameLazyGetter() { michael@0: return this.brand.GetStringFromName("brandShortName"); michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(gStrings, "appVersion", function appVersionLazyGetter() { michael@0: return Services.appinfo.version; michael@0: }); michael@0: michael@0: document.addEventListener("load", initialize, true); michael@0: window.addEventListener("unload", shutdown, false); michael@0: michael@0: var gPendingInitializations = 1; michael@0: this.__defineGetter__("gIsInitializing", function gIsInitializingGetter() gPendingInitializations > 0); michael@0: michael@0: function initialize(event) { michael@0: // XXXbz this listener gets _all_ load events for all nodes in the michael@0: // document... but relies on not being called "too early". michael@0: if (event.target instanceof XMLStylesheetProcessingInstruction) { michael@0: return; michael@0: } michael@0: document.removeEventListener("load", initialize, true); michael@0: michael@0: let globalCommandSet = document.getElementById("globalCommandSet"); michael@0: globalCommandSet.addEventListener("command", function(event) { michael@0: gViewController.doCommand(event.target.id); michael@0: }); michael@0: michael@0: let viewCommandSet = document.getElementById("viewCommandSet"); michael@0: viewCommandSet.addEventListener("commandupdate", function(event) { michael@0: gViewController.updateCommands(); michael@0: }); michael@0: viewCommandSet.addEventListener("command", function(event) { michael@0: gViewController.doCommand(event.target.id); michael@0: }); michael@0: michael@0: let detailScreenshot = document.getElementById("detail-screenshot"); michael@0: detailScreenshot.addEventListener("load", function(event) { michael@0: this.removeAttribute("loading"); michael@0: }); michael@0: detailScreenshot.addEventListener("error", function(event) { michael@0: this.setAttribute("loading", "error"); michael@0: }); michael@0: michael@0: let addonPage = document.getElementById("addons-page"); michael@0: addonPage.addEventListener("dragenter", function(event) { michael@0: gDragDrop.onDragOver(event); michael@0: }); michael@0: addonPage.addEventListener("dragover", function(event) { michael@0: gDragDrop.onDragOver(event); michael@0: }); michael@0: addonPage.addEventListener("drop", function(event) { michael@0: gDragDrop.onDrop(event); michael@0: }); michael@0: addonPage.addEventListener("keypress", function(event) { michael@0: gHeader.onKeyPress(event); michael@0: }); michael@0: michael@0: gViewController.initialize(); michael@0: gCategories.initialize(); michael@0: gHeader.initialize(); michael@0: gEventManager.initialize(); michael@0: Services.obs.addObserver(sendEMPong, "EM-ping", false); michael@0: Services.obs.notifyObservers(window, "EM-loaded", ""); michael@0: michael@0: // If the initial view has already been selected (by a call to loadView from michael@0: // the above notifications) then bail out now michael@0: if (gViewController.initialViewSelected) michael@0: return; michael@0: michael@0: // If there is a history state to restore then use that michael@0: if (window.history.state) { michael@0: gViewController.updateState(window.history.state); michael@0: return; michael@0: } michael@0: michael@0: // Default to the last selected category michael@0: var view = gCategories.node.value; michael@0: michael@0: // Allow passing in a view through the window arguments michael@0: if ("arguments" in window && window.arguments.length > 0 && michael@0: window.arguments[0] !== null && "view" in window.arguments[0]) { michael@0: view = window.arguments[0].view; michael@0: } michael@0: michael@0: gViewController.loadInitialView(view); michael@0: michael@0: Services.prefs.addObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged, false); michael@0: Services.prefs.addObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged, false); michael@0: } michael@0: michael@0: function notifyInitialized() { michael@0: if (!gIsInitializing) michael@0: return; michael@0: michael@0: gPendingInitializations--; michael@0: if (!gIsInitializing) { michael@0: var event = document.createEvent("Events"); michael@0: event.initEvent("Initialized", true, true); michael@0: document.dispatchEvent(event); michael@0: } michael@0: } michael@0: michael@0: function shutdown() { michael@0: gCategories.shutdown(); michael@0: gSearchView.shutdown(); michael@0: gEventManager.shutdown(); michael@0: gViewController.shutdown(); michael@0: Services.obs.removeObserver(sendEMPong, "EM-ping"); michael@0: Services.prefs.removeObserver(PREF_ADDON_DEBUGGING_ENABLED, debuggingPrefChanged); michael@0: Services.prefs.removeObserver(PREF_REMOTE_DEBUGGING_ENABLED, debuggingPrefChanged); michael@0: } michael@0: michael@0: function sendEMPong(aSubject, aTopic, aData) { michael@0: Services.obs.notifyObservers(window, "EM-pong", ""); michael@0: } michael@0: michael@0: // Used by external callers to load a specific view into the manager michael@0: function loadView(aViewId) { michael@0: if (!gViewController.initialViewSelected) { michael@0: // The caller opened the window and immediately loaded the view so it michael@0: // should be the initial history entry michael@0: michael@0: gViewController.loadInitialView(aViewId); michael@0: } else { michael@0: gViewController.loadView(aViewId); michael@0: } michael@0: } michael@0: michael@0: function isDiscoverEnabled() { michael@0: if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID) michael@0: return false; michael@0: michael@0: try { michael@0: if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED)) michael@0: return false; michael@0: } catch (e) {} michael@0: michael@0: try { michael@0: if (!Services.prefs.getBoolPref(PREF_XPI_ENABLED)) michael@0: return false; michael@0: } catch (e) {} michael@0: michael@0: return true; michael@0: } michael@0: michael@0: function getExperimentEndDate(aAddon) { michael@0: if (!("@mozilla.org/browser/experiments-service;1" in Cc)) { michael@0: return 0; michael@0: } michael@0: michael@0: if (!aAddon.isActive) { michael@0: return aAddon.endDate; michael@0: } michael@0: michael@0: let experiment = Experiments.instance().getActiveExperiment(); michael@0: if (!experiment) { michael@0: return 0; michael@0: } michael@0: michael@0: return experiment.endDate; michael@0: } michael@0: michael@0: /** michael@0: * Obtain the main DOMWindow for the current context. michael@0: */ michael@0: function getMainWindow() { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem) michael@0: .rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow); michael@0: } michael@0: michael@0: /** michael@0: * Obtain the DOMWindow that can open a preferences pane. michael@0: * michael@0: * This is essentially "get the browser chrome window" with the added check michael@0: * that the supposed browser chrome window is capable of opening a preferences michael@0: * pane. michael@0: * michael@0: * This may return null if we can't find the browser chrome window. michael@0: */ michael@0: function getMainWindowWithPreferencesPane() { michael@0: let mainWindow = getMainWindow(); michael@0: if (mainWindow && "openAdvancedPreferences" in mainWindow) { michael@0: return mainWindow; michael@0: } else { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * A wrapper around the HTML5 session history service that allows the browser michael@0: * back/forward controls to work within the manager michael@0: */ michael@0: var HTML5History = { michael@0: get index() { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .sessionHistory.index; michael@0: }, michael@0: michael@0: get canGoBack() { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .canGoBack; michael@0: }, michael@0: michael@0: get canGoForward() { michael@0: return window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .canGoForward; michael@0: }, michael@0: michael@0: back: function HTML5History_back() { michael@0: window.history.back(); michael@0: gViewController.updateCommand("cmd_back"); michael@0: gViewController.updateCommand("cmd_forward"); michael@0: }, michael@0: michael@0: forward: function HTML5History_forward() { michael@0: window.history.forward(); michael@0: gViewController.updateCommand("cmd_back"); michael@0: gViewController.updateCommand("cmd_forward"); michael@0: }, michael@0: michael@0: pushState: function HTML5History_pushState(aState) { michael@0: window.history.pushState(aState, document.title); michael@0: }, michael@0: michael@0: replaceState: function HTML5History_replaceState(aState) { michael@0: window.history.replaceState(aState, document.title); michael@0: }, michael@0: michael@0: popState: function HTML5History_popState() { michael@0: function onStatePopped(aEvent) { michael@0: window.removeEventListener("popstate", onStatePopped, true); michael@0: // TODO To ensure we can't go forward again we put an additional entry michael@0: // for the current state into the history. Ideally we would just strip michael@0: // the history but there doesn't seem to be a way to do that. Bug 590661 michael@0: window.history.pushState(aEvent.state, document.title); michael@0: } michael@0: window.addEventListener("popstate", onStatePopped, true); michael@0: window.history.back(); michael@0: gViewController.updateCommand("cmd_back"); michael@0: gViewController.updateCommand("cmd_forward"); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * A wrapper around a fake history service michael@0: */ michael@0: var FakeHistory = { michael@0: pos: 0, michael@0: states: [null], michael@0: michael@0: get index() { michael@0: return this.pos; michael@0: }, michael@0: michael@0: get canGoBack() { michael@0: return this.pos > 0; michael@0: }, michael@0: michael@0: get canGoForward() { michael@0: return (this.pos + 1) < this.states.length; michael@0: }, michael@0: michael@0: back: function FakeHistory_back() { michael@0: if (this.pos == 0) michael@0: throw Components.Exception("Cannot go back from this point"); michael@0: michael@0: this.pos--; michael@0: gViewController.updateState(this.states[this.pos]); michael@0: gViewController.updateCommand("cmd_back"); michael@0: gViewController.updateCommand("cmd_forward"); michael@0: }, michael@0: michael@0: forward: function FakeHistory_forward() { michael@0: if ((this.pos + 1) >= this.states.length) michael@0: throw Components.Exception("Cannot go forward from this point"); michael@0: michael@0: this.pos++; michael@0: gViewController.updateState(this.states[this.pos]); michael@0: gViewController.updateCommand("cmd_back"); michael@0: gViewController.updateCommand("cmd_forward"); michael@0: }, michael@0: michael@0: pushState: function FakeHistory_pushState(aState) { michael@0: this.pos++; michael@0: this.states.splice(this.pos, this.states.length); michael@0: this.states.push(aState); michael@0: }, michael@0: michael@0: replaceState: function FakeHistory_replaceState(aState) { michael@0: this.states[this.pos] = aState; michael@0: }, michael@0: michael@0: popState: function FakeHistory_popState() { michael@0: if (this.pos == 0) michael@0: throw Components.Exception("Cannot popState from this view"); michael@0: michael@0: this.states.splice(this.pos, this.states.length); michael@0: this.pos--; michael@0: michael@0: gViewController.updateState(this.states[this.pos]); michael@0: gViewController.updateCommand("cmd_back"); michael@0: gViewController.updateCommand("cmd_forward"); michael@0: } michael@0: }; michael@0: michael@0: // If the window has a session history then use the HTML5 History wrapper michael@0: // otherwise use our fake history implementation michael@0: if (window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .sessionHistory) { michael@0: var gHistory = HTML5History; michael@0: } michael@0: else { michael@0: gHistory = FakeHistory; michael@0: } michael@0: michael@0: var gEventManager = { michael@0: _listeners: {}, michael@0: _installListeners: [], michael@0: michael@0: initialize: function gEM_initialize() { michael@0: var self = this; michael@0: const ADDON_EVENTS = ["onEnabling", "onEnabled", "onDisabling", michael@0: "onDisabled", "onUninstalling", "onUninstalled", michael@0: "onInstalled", "onOperationCancelled", michael@0: "onUpdateAvailable", "onUpdateFinished", michael@0: "onCompatibilityUpdateAvailable", michael@0: "onPropertyChanged"]; michael@0: for (let evt of ADDON_EVENTS) { michael@0: let event = evt; michael@0: self[event] = function initialize_delegateAddonEvent(...aArgs) { michael@0: self.delegateAddonEvent(event, aArgs); michael@0: }; michael@0: } michael@0: michael@0: const INSTALL_EVENTS = ["onNewInstall", "onDownloadStarted", michael@0: "onDownloadEnded", "onDownloadFailed", michael@0: "onDownloadProgress", "onDownloadCancelled", michael@0: "onInstallStarted", "onInstallEnded", michael@0: "onInstallFailed", "onInstallCancelled", michael@0: "onExternalInstall"]; michael@0: for (let evt of INSTALL_EVENTS) { michael@0: let event = evt; michael@0: self[event] = function initialize_delegateInstallEvent(...aArgs) { michael@0: self.delegateInstallEvent(event, aArgs); michael@0: }; michael@0: } michael@0: michael@0: AddonManager.addManagerListener(this); michael@0: AddonManager.addInstallListener(this); michael@0: AddonManager.addAddonListener(this); michael@0: michael@0: this.refreshGlobalWarning(); michael@0: this.refreshAutoUpdateDefault(); michael@0: michael@0: var contextMenu = document.getElementById("addonitem-popup"); michael@0: contextMenu.addEventListener("popupshowing", function contextMenu_onPopupshowing() { michael@0: var addon = gViewController.currentViewObj.getSelectedAddon(); michael@0: contextMenu.setAttribute("addontype", addon.type); michael@0: michael@0: var menuSep = document.getElementById("addonitem-menuseparator"); michael@0: var countEnabledMenuCmds = 0; michael@0: for (let child of contextMenu.children) { michael@0: if (child.nodeName == "menuitem" && michael@0: gViewController.isCommandEnabled(child.command)) { michael@0: countEnabledMenuCmds++; michael@0: } michael@0: } michael@0: michael@0: // with only one menu item, we hide the menu separator michael@0: menuSep.hidden = (countEnabledMenuCmds <= 1); michael@0: michael@0: }, false); michael@0: }, michael@0: michael@0: shutdown: function gEM_shutdown() { michael@0: AddonManager.removeManagerListener(this); michael@0: AddonManager.removeInstallListener(this); michael@0: AddonManager.removeAddonListener(this); michael@0: }, michael@0: michael@0: registerAddonListener: function gEM_registerAddonListener(aListener, aAddonId) { michael@0: if (!(aAddonId in this._listeners)) michael@0: this._listeners[aAddonId] = []; michael@0: else if (this._listeners[aAddonId].indexOf(aListener) != -1) michael@0: return; michael@0: this._listeners[aAddonId].push(aListener); michael@0: }, michael@0: michael@0: unregisterAddonListener: function gEM_unregisterAddonListener(aListener, aAddonId) { michael@0: if (!(aAddonId in this._listeners)) michael@0: return; michael@0: var index = this._listeners[aAddonId].indexOf(aListener); michael@0: if (index == -1) michael@0: return; michael@0: this._listeners[aAddonId].splice(index, 1); michael@0: }, michael@0: michael@0: registerInstallListener: function gEM_registerInstallListener(aListener) { michael@0: if (this._installListeners.indexOf(aListener) != -1) michael@0: return; michael@0: this._installListeners.push(aListener); michael@0: }, michael@0: michael@0: unregisterInstallListener: function gEM_unregisterInstallListener(aListener) { michael@0: var i = this._installListeners.indexOf(aListener); michael@0: if (i == -1) michael@0: return; michael@0: this._installListeners.splice(i, 1); michael@0: }, michael@0: michael@0: delegateAddonEvent: function gEM_delegateAddonEvent(aEvent, aParams) { michael@0: var addon = aParams.shift(); michael@0: if (!(addon.id in this._listeners)) michael@0: return; michael@0: michael@0: var listeners = this._listeners[addon.id]; michael@0: for (let listener of listeners) { michael@0: if (!(aEvent in listener)) michael@0: continue; michael@0: try { michael@0: listener[aEvent].apply(listener, aParams); michael@0: } catch(e) { michael@0: // this shouldn't be fatal michael@0: Cu.reportError(e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: delegateInstallEvent: function gEM_delegateInstallEvent(aEvent, aParams) { michael@0: var existingAddon = aEvent == "onExternalInstall" ? aParams[1] : aParams[0].existingAddon; michael@0: // If the install is an update then send the event to all listeners michael@0: // registered for the existing add-on michael@0: if (existingAddon) michael@0: this.delegateAddonEvent(aEvent, [existingAddon].concat(aParams)); michael@0: michael@0: for (let listener of this._installListeners) { michael@0: if (!(aEvent in listener)) michael@0: continue; michael@0: try { michael@0: listener[aEvent].apply(listener, aParams); michael@0: } catch(e) { michael@0: // this shouldn't be fatal michael@0: Cu.reportError(e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: refreshGlobalWarning: function gEM_refreshGlobalWarning() { michael@0: var page = document.getElementById("addons-page"); michael@0: michael@0: if (Services.appinfo.inSafeMode) { michael@0: page.setAttribute("warning", "safemode"); michael@0: return; michael@0: } michael@0: michael@0: if (AddonManager.checkUpdateSecurityDefault && michael@0: !AddonManager.checkUpdateSecurity) { michael@0: page.setAttribute("warning", "updatesecurity"); michael@0: return; michael@0: } michael@0: michael@0: if (!AddonManager.checkCompatibility) { michael@0: page.setAttribute("warning", "checkcompatibility"); michael@0: return; michael@0: } michael@0: michael@0: page.removeAttribute("warning"); michael@0: }, michael@0: michael@0: refreshAutoUpdateDefault: function gEM_refreshAutoUpdateDefault() { michael@0: var updateEnabled = AddonManager.updateEnabled; michael@0: var autoUpdateDefault = AddonManager.autoUpdateDefault; michael@0: michael@0: // The checkbox needs to reflect that both prefs need to be true michael@0: // for updates to be checked for and applied automatically michael@0: document.getElementById("utils-autoUpdateDefault") michael@0: .setAttribute("checked", updateEnabled && autoUpdateDefault); michael@0: michael@0: document.getElementById("utils-resetAddonUpdatesToAutomatic").hidden = !autoUpdateDefault; michael@0: document.getElementById("utils-resetAddonUpdatesToManual").hidden = autoUpdateDefault; michael@0: }, michael@0: michael@0: onCompatibilityModeChanged: function gEM_onCompatibilityModeChanged() { michael@0: this.refreshGlobalWarning(); michael@0: }, michael@0: michael@0: onCheckUpdateSecurityChanged: function gEM_onCheckUpdateSecurityChanged() { michael@0: this.refreshGlobalWarning(); michael@0: }, michael@0: michael@0: onUpdateModeChanged: function gEM_onUpdateModeChanged() { michael@0: this.refreshAutoUpdateDefault(); michael@0: } michael@0: }; michael@0: michael@0: michael@0: var gViewController = { michael@0: viewPort: null, michael@0: currentViewId: "", michael@0: currentViewObj: null, michael@0: currentViewRequest: 0, michael@0: viewObjects: {}, michael@0: viewChangeCallback: null, michael@0: initialViewSelected: false, michael@0: lastHistoryIndex: -1, michael@0: michael@0: initialize: function gVC_initialize() { michael@0: this.viewPort = document.getElementById("view-port"); michael@0: michael@0: this.viewObjects["search"] = gSearchView; michael@0: this.viewObjects["discover"] = gDiscoverView; michael@0: this.viewObjects["list"] = gListView; michael@0: this.viewObjects["detail"] = gDetailView; michael@0: this.viewObjects["updates"] = gUpdatesView; michael@0: michael@0: for each (let view in this.viewObjects) michael@0: view.initialize(); michael@0: michael@0: window.controllers.appendController(this); michael@0: michael@0: window.addEventListener("popstate", michael@0: function window_onStatePopped(e) { michael@0: gViewController.updateState(e.state); michael@0: }, michael@0: false); michael@0: }, michael@0: michael@0: shutdown: function gVC_shutdown() { michael@0: if (this.currentViewObj) michael@0: this.currentViewObj.hide(); michael@0: this.currentViewRequest = 0; michael@0: michael@0: for each(let view in this.viewObjects) { michael@0: if ("shutdown" in view) { michael@0: try { michael@0: view.shutdown(); michael@0: } catch(e) { michael@0: // this shouldn't be fatal michael@0: Cu.reportError(e); michael@0: } michael@0: } michael@0: } michael@0: michael@0: window.controllers.removeController(this); michael@0: }, michael@0: michael@0: updateState: function gVC_updateState(state) { michael@0: try { michael@0: this.loadViewInternal(state.view, state.previousView, state); michael@0: this.lastHistoryIndex = gHistory.index; michael@0: } michael@0: catch (e) { michael@0: // The attempt to load the view failed, try moving further along history michael@0: if (this.lastHistoryIndex > gHistory.index) { michael@0: if (gHistory.canGoBack) michael@0: gHistory.back(); michael@0: else michael@0: gViewController.replaceView(VIEW_DEFAULT); michael@0: } else { michael@0: if (gHistory.canGoForward) michael@0: gHistory.forward(); michael@0: else michael@0: gViewController.replaceView(VIEW_DEFAULT); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: parseViewId: function gVC_parseViewId(aViewId) { michael@0: var matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/; michael@0: var [,viewType, viewParam] = aViewId.match(matchRegex) || []; michael@0: return {type: viewType, param: decodeURIComponent(viewParam)}; michael@0: }, michael@0: michael@0: get isLoading() { michael@0: return !this.currentViewObj || this.currentViewObj.node.hasAttribute("loading"); michael@0: }, michael@0: michael@0: loadView: function gVC_loadView(aViewId) { michael@0: var isRefresh = false; michael@0: if (aViewId == this.currentViewId) { michael@0: if (this.isLoading) michael@0: return; michael@0: if (!("refresh" in this.currentViewObj)) michael@0: return; michael@0: if (!this.currentViewObj.canRefresh()) michael@0: return; michael@0: isRefresh = true; michael@0: } michael@0: michael@0: var state = { michael@0: view: aViewId, michael@0: previousView: this.currentViewId michael@0: }; michael@0: if (!isRefresh) { michael@0: gHistory.pushState(state); michael@0: this.lastHistoryIndex = gHistory.index; michael@0: } michael@0: this.loadViewInternal(aViewId, this.currentViewId, state); michael@0: }, michael@0: michael@0: // Replaces the existing view with a new one, rewriting the current history michael@0: // entry to match. michael@0: replaceView: function gVC_replaceView(aViewId) { michael@0: if (aViewId == this.currentViewId) michael@0: return; michael@0: michael@0: var state = { michael@0: view: aViewId, michael@0: previousView: null michael@0: }; michael@0: gHistory.replaceState(state); michael@0: this.loadViewInternal(aViewId, null, state); michael@0: }, michael@0: michael@0: loadInitialView: function gVC_loadInitialView(aViewId) { michael@0: var state = { michael@0: view: aViewId, michael@0: previousView: null michael@0: }; michael@0: gHistory.replaceState(state); michael@0: michael@0: this.loadViewInternal(aViewId, null, state); michael@0: this.initialViewSelected = true; michael@0: notifyInitialized(); michael@0: }, michael@0: michael@0: loadViewInternal: function gVC_loadViewInternal(aViewId, aPreviousView, aState) { michael@0: var view = this.parseViewId(aViewId); michael@0: michael@0: if (!view.type || !(view.type in this.viewObjects)) michael@0: throw Components.Exception("Invalid view: " + view.type); michael@0: michael@0: var viewObj = this.viewObjects[view.type]; michael@0: if (!viewObj.node) michael@0: throw Components.Exception("Root node doesn't exist for '" + view.type + "' view"); michael@0: michael@0: if (this.currentViewObj && aViewId != aPreviousView) { michael@0: try { michael@0: let canHide = this.currentViewObj.hide(); michael@0: if (canHide === false) michael@0: return; michael@0: this.viewPort.selectedPanel.removeAttribute("loading"); michael@0: } catch (e) { michael@0: // this shouldn't be fatal michael@0: Cu.reportError(e); michael@0: } michael@0: } michael@0: michael@0: gCategories.select(aViewId, aPreviousView); michael@0: michael@0: this.currentViewId = aViewId; michael@0: this.currentViewObj = viewObj; michael@0: michael@0: this.viewPort.selectedPanel = this.currentViewObj.node; michael@0: this.viewPort.selectedPanel.setAttribute("loading", "true"); michael@0: this.currentViewObj.node.focus(); michael@0: michael@0: if (aViewId == aPreviousView) michael@0: this.currentViewObj.refresh(view.param, ++this.currentViewRequest, aState); michael@0: else michael@0: this.currentViewObj.show(view.param, ++this.currentViewRequest, aState); michael@0: }, michael@0: michael@0: // Moves back in the document history and removes the current history entry michael@0: popState: function gVC_popState(aCallback) { michael@0: this.viewChangeCallback = aCallback; michael@0: gHistory.popState(); michael@0: }, michael@0: michael@0: notifyViewChanged: function gVC_notifyViewChanged() { michael@0: this.viewPort.selectedPanel.removeAttribute("loading"); michael@0: michael@0: if (this.viewChangeCallback) { michael@0: this.viewChangeCallback(); michael@0: this.viewChangeCallback = null; michael@0: } michael@0: michael@0: var event = document.createEvent("Events"); michael@0: event.initEvent("ViewChanged", true, true); michael@0: this.currentViewObj.node.dispatchEvent(event); michael@0: }, michael@0: michael@0: commands: { michael@0: cmd_back: { michael@0: isEnabled: function cmd_back_isEnabled() { michael@0: return gHistory.canGoBack; michael@0: }, michael@0: doCommand: function cmd_back_doCommand() { michael@0: gHistory.back(); michael@0: } michael@0: }, michael@0: michael@0: cmd_forward: { michael@0: isEnabled: function cmd_forward_isEnabled() { michael@0: return gHistory.canGoForward; michael@0: }, michael@0: doCommand: function cmd_forward_doCommand() { michael@0: gHistory.forward(); michael@0: } michael@0: }, michael@0: michael@0: cmd_focusSearch: { michael@0: isEnabled: () => true, michael@0: doCommand: function cmd_focusSearch_doCommand() { michael@0: gHeader.focusSearchBox(); michael@0: } michael@0: }, michael@0: michael@0: cmd_restartApp: { michael@0: isEnabled: function cmd_restartApp_isEnabled() true, michael@0: doCommand: function cmd_restartApp_doCommand() { michael@0: let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]. michael@0: createInstance(Ci.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(cancelQuit, "quit-application-requested", michael@0: "restart"); michael@0: if (cancelQuit.data) michael@0: return; // somebody canceled our quit request michael@0: michael@0: let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]. michael@0: getService(Ci.nsIAppStartup); michael@0: appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart); michael@0: } michael@0: }, michael@0: michael@0: cmd_enableCheckCompatibility: { michael@0: isEnabled: function cmd_enableCheckCompatibility_isEnabled() true, michael@0: doCommand: function cmd_enableCheckCompatibility_doCommand() { michael@0: AddonManager.checkCompatibility = true; michael@0: } michael@0: }, michael@0: michael@0: cmd_enableUpdateSecurity: { michael@0: isEnabled: function cmd_enableUpdateSecurity_isEnabled() true, michael@0: doCommand: function cmd_enableUpdateSecurity_doCommand() { michael@0: AddonManager.checkUpdateSecurity = true; michael@0: } michael@0: }, michael@0: michael@0: cmd_pluginCheck: { michael@0: isEnabled: function cmd_pluginCheck_isEnabled() true, michael@0: doCommand: function cmd_pluginCheck_doCommand() { michael@0: openURL(Services.urlFormatter.formatURLPref("plugins.update.url")); michael@0: } michael@0: }, michael@0: michael@0: cmd_toggleAutoUpdateDefault: { michael@0: isEnabled: function cmd_toggleAutoUpdateDefault_isEnabled() true, michael@0: doCommand: function cmd_toggleAutoUpdateDefault_doCommand() { michael@0: if (!AddonManager.updateEnabled || !AddonManager.autoUpdateDefault) { michael@0: // One or both of the prefs is false, i.e. the checkbox is not checked. michael@0: // Now toggle both to true. If the user wants us to auto-update michael@0: // add-ons, we also need to auto-check for updates. michael@0: AddonManager.updateEnabled = true; michael@0: AddonManager.autoUpdateDefault = true; michael@0: } else { michael@0: // Both prefs are true, i.e. the checkbox is checked. michael@0: // Toggle the auto pref to false, but don't touch the enabled check. michael@0: AddonManager.autoUpdateDefault = false; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: cmd_resetAddonAutoUpdate: { michael@0: isEnabled: function cmd_resetAddonAutoUpdate_isEnabled() true, michael@0: doCommand: function cmd_resetAddonAutoUpdate_doCommand() { michael@0: AddonManager.getAllAddons(function cmd_resetAddonAutoUpdate_getAllAddons(aAddonList) { michael@0: for (let addon of aAddonList) { michael@0: if ("applyBackgroundUpdates" in addon) michael@0: addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT; michael@0: } michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: cmd_goToDiscoverPane: { michael@0: isEnabled: function cmd_goToDiscoverPane_isEnabled() { michael@0: return gDiscoverView.enabled; michael@0: }, michael@0: doCommand: function cmd_goToDiscoverPane_doCommand() { michael@0: gViewController.loadView("addons://discover/"); michael@0: } michael@0: }, michael@0: michael@0: cmd_goToRecentUpdates: { michael@0: isEnabled: function cmd_goToRecentUpdates_isEnabled() true, michael@0: doCommand: function cmd_goToRecentUpdates_doCommand() { michael@0: gViewController.loadView("addons://updates/recent"); michael@0: } michael@0: }, michael@0: michael@0: cmd_goToAvailableUpdates: { michael@0: isEnabled: function cmd_goToAvailableUpdates_isEnabled() true, michael@0: doCommand: function cmd_goToAvailableUpdates_doCommand() { michael@0: gViewController.loadView("addons://updates/available"); michael@0: } michael@0: }, michael@0: michael@0: cmd_showItemDetails: { michael@0: isEnabled: function cmd_showItemDetails_isEnabled(aAddon) { michael@0: return !!aAddon && (gViewController.currentViewObj != gDetailView); michael@0: }, michael@0: doCommand: function cmd_showItemDetails_doCommand(aAddon, aScrollToPreferences) { michael@0: gViewController.loadView("addons://detail/" + michael@0: encodeURIComponent(aAddon.id) + michael@0: (aScrollToPreferences ? "/preferences" : "")); michael@0: } michael@0: }, michael@0: michael@0: cmd_findAllUpdates: { michael@0: inProgress: false, michael@0: isEnabled: function cmd_findAllUpdates_isEnabled() !this.inProgress, michael@0: doCommand: function cmd_findAllUpdates_doCommand() { michael@0: this.inProgress = true; michael@0: gViewController.updateCommand("cmd_findAllUpdates"); michael@0: document.getElementById("updates-noneFound").hidden = true; michael@0: document.getElementById("updates-progress").hidden = false; michael@0: document.getElementById("updates-manualUpdatesFound-btn").hidden = true; michael@0: michael@0: var pendingChecks = 0; michael@0: var numUpdated = 0; michael@0: var numManualUpdates = 0; michael@0: var restartNeeded = false; michael@0: var self = this; michael@0: michael@0: function updateStatus() { michael@0: if (pendingChecks > 0) michael@0: return; michael@0: michael@0: self.inProgress = false; michael@0: gViewController.updateCommand("cmd_findAllUpdates"); michael@0: document.getElementById("updates-progress").hidden = true; michael@0: gUpdatesView.maybeRefresh(); michael@0: michael@0: if (numManualUpdates > 0 && numUpdated == 0) { michael@0: document.getElementById("updates-manualUpdatesFound-btn").hidden = false; michael@0: return; michael@0: } michael@0: michael@0: if (numUpdated == 0) { michael@0: document.getElementById("updates-noneFound").hidden = false; michael@0: return; michael@0: } michael@0: michael@0: if (restartNeeded) { michael@0: document.getElementById("updates-downloaded").hidden = false; michael@0: document.getElementById("updates-restart-btn").hidden = false; michael@0: } else { michael@0: document.getElementById("updates-installed").hidden = false; michael@0: } michael@0: } michael@0: michael@0: var updateInstallListener = { michael@0: onDownloadFailed: function cmd_findAllUpdates_downloadFailed() { michael@0: pendingChecks--; michael@0: updateStatus(); michael@0: }, michael@0: onInstallFailed: function cmd_findAllUpdates_installFailed() { michael@0: pendingChecks--; michael@0: updateStatus(); michael@0: }, michael@0: onInstallEnded: function cmd_findAllUpdates_installEnded(aInstall, aAddon) { michael@0: pendingChecks--; michael@0: numUpdated++; michael@0: if (isPending(aInstall.existingAddon, "upgrade")) michael@0: restartNeeded = true; michael@0: updateStatus(); michael@0: } michael@0: }; michael@0: michael@0: var updateCheckListener = { michael@0: onUpdateAvailable: function cmd_findAllUpdates_updateAvailable(aAddon, aInstall) { michael@0: gEventManager.delegateAddonEvent("onUpdateAvailable", michael@0: [aAddon, aInstall]); michael@0: if (AddonManager.shouldAutoUpdate(aAddon)) { michael@0: aInstall.addListener(updateInstallListener); michael@0: aInstall.install(); michael@0: } else { michael@0: pendingChecks--; michael@0: numManualUpdates++; michael@0: updateStatus(); michael@0: } michael@0: }, michael@0: onNoUpdateAvailable: function cmd_findAllUpdates_noUpdateAvailable(aAddon) { michael@0: pendingChecks--; michael@0: updateStatus(); michael@0: }, michael@0: onUpdateFinished: function cmd_findAllUpdates_updateFinished(aAddon, aError) { michael@0: gEventManager.delegateAddonEvent("onUpdateFinished", michael@0: [aAddon, aError]); michael@0: } michael@0: }; michael@0: michael@0: AddonManager.getAddonsByTypes(null, function cmd_findAllUpdates_getAddonsByTypes(aAddonList) { michael@0: for (let addon of aAddonList) { michael@0: if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) { michael@0: pendingChecks++; michael@0: addon.findUpdates(updateCheckListener, michael@0: AddonManager.UPDATE_WHEN_USER_REQUESTED); michael@0: } michael@0: } michael@0: michael@0: if (pendingChecks == 0) michael@0: updateStatus(); michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: cmd_findItemUpdates: { michael@0: isEnabled: function cmd_findItemUpdates_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: return hasPermission(aAddon, "upgrade"); michael@0: }, michael@0: doCommand: function cmd_findItemUpdates_doCommand(aAddon) { michael@0: var listener = { michael@0: onUpdateAvailable: function cmd_findItemUpdates_updateAvailable(aAddon, aInstall) { michael@0: gEventManager.delegateAddonEvent("onUpdateAvailable", michael@0: [aAddon, aInstall]); michael@0: if (AddonManager.shouldAutoUpdate(aAddon)) michael@0: aInstall.install(); michael@0: }, michael@0: onNoUpdateAvailable: function cmd_findItemUpdates_noUpdateAvailable(aAddon) { michael@0: gEventManager.delegateAddonEvent("onNoUpdateAvailable", michael@0: [aAddon]); michael@0: } michael@0: }; michael@0: gEventManager.delegateAddonEvent("onCheckingUpdate", [aAddon]); michael@0: aAddon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED); michael@0: } michael@0: }, michael@0: michael@0: cmd_debugItem: { michael@0: doCommand: function cmd_debugItem_doCommand(aAddon) { michael@0: BrowserToolboxProcess.init({ addonID: aAddon.id }); michael@0: }, michael@0: michael@0: isEnabled: function cmd_debugItem_isEnabled(aAddon) { michael@0: let debuggerEnabled = Services.prefs. michael@0: getBoolPref(PREF_ADDON_DEBUGGING_ENABLED); michael@0: let remoteEnabled = Services.prefs. michael@0: getBoolPref(PREF_REMOTE_DEBUGGING_ENABLED); michael@0: return aAddon && aAddon.isDebuggable && debuggerEnabled && remoteEnabled; michael@0: } michael@0: }, michael@0: michael@0: cmd_showItemPreferences: { michael@0: isEnabled: function cmd_showItemPreferences_isEnabled(aAddon) { michael@0: if (!aAddon || !aAddon.isActive || !aAddon.optionsURL) michael@0: return false; michael@0: if (gViewController.currentViewObj == gDetailView && michael@0: aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) { michael@0: return false; michael@0: } michael@0: if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO) michael@0: return false; michael@0: return true; michael@0: }, michael@0: doCommand: function cmd_showItemPreferences_doCommand(aAddon) { michael@0: if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE) { michael@0: gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true); michael@0: return; michael@0: } michael@0: var optionsURL = aAddon.optionsURL; michael@0: if (aAddon.optionsType == AddonManager.OPTIONS_TYPE_TAB && michael@0: openOptionsInTab(optionsURL)) { michael@0: return; michael@0: } michael@0: var windows = Services.wm.getEnumerator(null); michael@0: while (windows.hasMoreElements()) { michael@0: var win = windows.getNext(); michael@0: if (win.closed) { michael@0: continue; michael@0: } michael@0: if (win.document.documentURI == optionsURL) { michael@0: win.focus(); michael@0: return; michael@0: } michael@0: } michael@0: var features = "chrome,titlebar,toolbar,centerscreen"; michael@0: try { michael@0: var instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply"); michael@0: features += instantApply ? ",dialog=no" : ",modal"; michael@0: } catch (e) { michael@0: features += ",modal"; michael@0: } michael@0: openDialog(optionsURL, "", features); michael@0: } michael@0: }, michael@0: michael@0: cmd_showItemAbout: { michael@0: isEnabled: function cmd_showItemAbout_isEnabled(aAddon) { michael@0: // XXXunf This may be applicable to install items too. See bug 561260 michael@0: return !!aAddon; michael@0: }, michael@0: doCommand: function cmd_showItemAbout_doCommand(aAddon) { michael@0: var aboutURL = aAddon.aboutURL; michael@0: if (aboutURL) michael@0: openDialog(aboutURL, "", "chrome,centerscreen,modal", aAddon); michael@0: else michael@0: openDialog("chrome://mozapps/content/extensions/about.xul", michael@0: "", "chrome,centerscreen,modal", aAddon); michael@0: } michael@0: }, michael@0: michael@0: cmd_enableItem: { michael@0: isEnabled: function cmd_enableItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: let addonType = AddonManager.addonTypes[aAddon.type]; michael@0: return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && michael@0: hasPermission(aAddon, "enable")); michael@0: }, michael@0: doCommand: function cmd_enableItem_doCommand(aAddon) { michael@0: aAddon.userDisabled = false; michael@0: }, michael@0: getTooltip: function cmd_enableItem_getTooltip(aAddon) { michael@0: if (!aAddon) michael@0: return ""; michael@0: if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_ENABLE) michael@0: return gStrings.ext.GetStringFromName("enableAddonRestartRequiredTooltip"); michael@0: return gStrings.ext.GetStringFromName("enableAddonTooltip"); michael@0: } michael@0: }, michael@0: michael@0: cmd_disableItem: { michael@0: isEnabled: function cmd_disableItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: let addonType = AddonManager.addonTypes[aAddon.type]; michael@0: return (!(addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && michael@0: hasPermission(aAddon, "disable")); michael@0: }, michael@0: doCommand: function cmd_disableItem_doCommand(aAddon) { michael@0: aAddon.userDisabled = true; michael@0: }, michael@0: getTooltip: function cmd_disableItem_getTooltip(aAddon) { michael@0: if (!aAddon) michael@0: return ""; michael@0: if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_DISABLE) michael@0: return gStrings.ext.GetStringFromName("disableAddonRestartRequiredTooltip"); michael@0: return gStrings.ext.GetStringFromName("disableAddonTooltip"); michael@0: } michael@0: }, michael@0: michael@0: cmd_installItem: { michael@0: isEnabled: function cmd_installItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: return aAddon.install && aAddon.install.state == AddonManager.STATE_AVAILABLE; michael@0: }, michael@0: doCommand: function cmd_installItem_doCommand(aAddon) { michael@0: function doInstall() { michael@0: gViewController.currentViewObj.getListItemForID(aAddon.id)._installStatus.installRemote(); michael@0: } michael@0: michael@0: if (gViewController.currentViewObj == gDetailView) michael@0: gViewController.popState(doInstall); michael@0: else michael@0: doInstall(); michael@0: } michael@0: }, michael@0: michael@0: cmd_purchaseItem: { michael@0: isEnabled: function cmd_purchaseItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: return !!aAddon.purchaseURL; michael@0: }, michael@0: doCommand: function cmd_purchaseItem_doCommand(aAddon) { michael@0: openURL(aAddon.purchaseURL); michael@0: } michael@0: }, michael@0: michael@0: cmd_uninstallItem: { michael@0: isEnabled: function cmd_uninstallItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: return hasPermission(aAddon, "uninstall"); michael@0: }, michael@0: doCommand: function cmd_uninstallItem_doCommand(aAddon) { michael@0: if (gViewController.currentViewObj != gDetailView) { michael@0: aAddon.uninstall(); michael@0: return; michael@0: } michael@0: michael@0: gViewController.popState(function cmd_uninstallItem_popState() { michael@0: gViewController.currentViewObj.getListItemForID(aAddon.id).uninstall(); michael@0: }); michael@0: }, michael@0: getTooltip: function cmd_uninstallItem_getTooltip(aAddon) { michael@0: if (!aAddon) michael@0: return ""; michael@0: if (aAddon.operationsRequiringRestart & AddonManager.OP_NEEDS_RESTART_UNINSTALL) michael@0: return gStrings.ext.GetStringFromName("uninstallAddonRestartRequiredTooltip"); michael@0: return gStrings.ext.GetStringFromName("uninstallAddonTooltip"); michael@0: } michael@0: }, michael@0: michael@0: cmd_cancelUninstallItem: { michael@0: isEnabled: function cmd_cancelUninstallItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: return isPending(aAddon, "uninstall"); michael@0: }, michael@0: doCommand: function cmd_cancelUninstallItem_doCommand(aAddon) { michael@0: aAddon.cancelUninstall(); michael@0: } michael@0: }, michael@0: michael@0: cmd_installFromFile: { michael@0: isEnabled: function cmd_installFromFile_isEnabled() true, michael@0: doCommand: function cmd_installFromFile_doCommand() { michael@0: const nsIFilePicker = Ci.nsIFilePicker; michael@0: var fp = Cc["@mozilla.org/filepicker;1"] michael@0: .createInstance(nsIFilePicker); michael@0: fp.init(window, michael@0: gStrings.ext.GetStringFromName("installFromFile.dialogTitle"), michael@0: nsIFilePicker.modeOpenMultiple); michael@0: try { michael@0: fp.appendFilter(gStrings.ext.GetStringFromName("installFromFile.filterName"), michael@0: "*.xpi;*.jar"); michael@0: fp.appendFilters(nsIFilePicker.filterAll); michael@0: } catch (e) { } michael@0: michael@0: if (fp.show() != nsIFilePicker.returnOK) michael@0: return; michael@0: michael@0: var files = fp.files; michael@0: var installs = []; michael@0: michael@0: function buildNextInstall() { michael@0: if (!files.hasMoreElements()) { michael@0: if (installs.length > 0) { michael@0: // Display the normal install confirmation for the installs michael@0: AddonManager.installAddonsFromWebpage("application/x-xpinstall", michael@0: window, null, installs); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: var file = files.getNext(); michael@0: AddonManager.getInstallForFile(file, function cmd_installFromFile_getInstallForFile(aInstall) { michael@0: installs.push(aInstall); michael@0: buildNextInstall(); michael@0: }); michael@0: } michael@0: michael@0: buildNextInstall(); michael@0: } michael@0: }, michael@0: michael@0: cmd_cancelOperation: { michael@0: isEnabled: function cmd_cancelOperation_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: return aAddon.pendingOperations != AddonManager.PENDING_NONE; michael@0: }, michael@0: doCommand: function cmd_cancelOperation_doCommand(aAddon) { michael@0: if (isPending(aAddon, "install")) { michael@0: aAddon.install.cancel(); michael@0: } else if (isPending(aAddon, "upgrade")) { michael@0: aAddon.pendingUpgrade.install.cancel(); michael@0: } else if (isPending(aAddon, "uninstall")) { michael@0: aAddon.cancelUninstall(); michael@0: } else if (isPending(aAddon, "enable")) { michael@0: aAddon.userDisabled = true; michael@0: } else if (isPending(aAddon, "disable")) { michael@0: aAddon.userDisabled = false; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: cmd_contribute: { michael@0: isEnabled: function cmd_contribute_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: return ("contributionURL" in aAddon && aAddon.contributionURL); michael@0: }, michael@0: doCommand: function cmd_contribute_doCommand(aAddon) { michael@0: openURL(aAddon.contributionURL); michael@0: } michael@0: }, michael@0: michael@0: cmd_askToActivateItem: { michael@0: isEnabled: function cmd_askToActivateItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: let addonType = AddonManager.addonTypes[aAddon.type]; michael@0: return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && michael@0: hasPermission(aAddon, "ask_to_activate")); michael@0: }, michael@0: doCommand: function cmd_askToActivateItem_doCommand(aAddon) { michael@0: aAddon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE; michael@0: } michael@0: }, michael@0: michael@0: cmd_alwaysActivateItem: { michael@0: isEnabled: function cmd_alwaysActivateItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: let addonType = AddonManager.addonTypes[aAddon.type]; michael@0: return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && michael@0: hasPermission(aAddon, "enable")); michael@0: }, michael@0: doCommand: function cmd_alwaysActivateItem_doCommand(aAddon) { michael@0: aAddon.userDisabled = false; michael@0: } michael@0: }, michael@0: michael@0: cmd_neverActivateItem: { michael@0: isEnabled: function cmd_neverActivateItem_isEnabled(aAddon) { michael@0: if (!aAddon) michael@0: return false; michael@0: let addonType = AddonManager.addonTypes[aAddon.type]; michael@0: return ((addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE) && michael@0: hasPermission(aAddon, "disable")); michael@0: }, michael@0: doCommand: function cmd_neverActivateItem_doCommand(aAddon) { michael@0: aAddon.userDisabled = true; michael@0: } michael@0: }, michael@0: michael@0: cmd_experimentsLearnMore: { michael@0: isEnabled: function cmd_experimentsLearnMore_isEnabled() { michael@0: let mainWindow = getMainWindow(); michael@0: return mainWindow && "switchToTabHavingURI" in mainWindow; michael@0: }, michael@0: doCommand: function cmd_experimentsLearnMore_doCommand() { michael@0: let url = Services.prefs.getCharPref("toolkit.telemetry.infoURL"); michael@0: openOptionsInTab(url); michael@0: }, michael@0: }, michael@0: michael@0: cmd_experimentsOpenTelemetryPreferences: { michael@0: isEnabled: function cmd_experimentsOpenTelemetryPreferences_isEnabled() { michael@0: return !!getMainWindowWithPreferencesPane(); michael@0: }, michael@0: doCommand: function cmd_experimentsOpenTelemetryPreferences_doCommand() { michael@0: let mainWindow = getMainWindowWithPreferencesPane(); michael@0: mainWindow.openAdvancedPreferences("dataChoicesTab"); michael@0: }, michael@0: }, michael@0: }, michael@0: michael@0: supportsCommand: function gVC_supportsCommand(aCommand) { michael@0: return (aCommand in this.commands); michael@0: }, michael@0: michael@0: isCommandEnabled: function gVC_isCommandEnabled(aCommand) { michael@0: if (!this.supportsCommand(aCommand)) michael@0: return false; michael@0: var addon = this.currentViewObj.getSelectedAddon(); michael@0: return this.commands[aCommand].isEnabled(addon); michael@0: }, michael@0: michael@0: updateCommands: function gVC_updateCommands() { michael@0: // wait until the view is initialized michael@0: if (!this.currentViewObj) michael@0: return; michael@0: var addon = this.currentViewObj.getSelectedAddon(); michael@0: for (let commandId in this.commands) michael@0: this.updateCommand(commandId, addon); michael@0: }, michael@0: michael@0: updateCommand: function gVC_updateCommand(aCommandId, aAddon) { michael@0: if (typeof aAddon == "undefined") michael@0: aAddon = this.currentViewObj.getSelectedAddon(); michael@0: var cmd = this.commands[aCommandId]; michael@0: var cmdElt = document.getElementById(aCommandId); michael@0: cmdElt.setAttribute("disabled", !cmd.isEnabled(aAddon)); michael@0: if ("getTooltip" in cmd) { michael@0: let tooltip = cmd.getTooltip(aAddon); michael@0: if (tooltip) michael@0: cmdElt.setAttribute("tooltiptext", tooltip); michael@0: else michael@0: cmdElt.removeAttribute("tooltiptext"); michael@0: } michael@0: }, michael@0: michael@0: doCommand: function gVC_doCommand(aCommand, aAddon) { michael@0: if (!this.supportsCommand(aCommand)) michael@0: return; michael@0: var cmd = this.commands[aCommand]; michael@0: if (!aAddon) michael@0: aAddon = this.currentViewObj.getSelectedAddon(); michael@0: if (!cmd.isEnabled(aAddon)) michael@0: return; michael@0: cmd.doCommand(aAddon); michael@0: }, michael@0: michael@0: onEvent: function gVC_onEvent() {} michael@0: }; michael@0: michael@0: function hasInlineOptions(aAddon) { michael@0: return (aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE || michael@0: aAddon.optionsType == AddonManager.OPTIONS_TYPE_INLINE_INFO); michael@0: } michael@0: michael@0: function openOptionsInTab(optionsURL) { michael@0: let mainWindow = getMainWindow(); michael@0: if ("switchToTabHavingURI" in mainWindow) { michael@0: mainWindow.switchToTabHavingURI(optionsURL, true); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: function formatDate(aDate) { michael@0: return Cc["@mozilla.org/intl/scriptabledateformat;1"] michael@0: .getService(Ci.nsIScriptableDateFormat) michael@0: .FormatDate("", michael@0: Ci.nsIScriptableDateFormat.dateFormatLong, michael@0: aDate.getFullYear(), michael@0: aDate.getMonth() + 1, michael@0: aDate.getDate() michael@0: ); michael@0: } michael@0: michael@0: michael@0: function hasPermission(aAddon, aPerm) { michael@0: var perm = AddonManager["PERM_CAN_" + aPerm.toUpperCase()]; michael@0: return !!(aAddon.permissions & perm); michael@0: } michael@0: michael@0: michael@0: function isPending(aAddon, aAction) { michael@0: var action = AddonManager["PENDING_" + aAction.toUpperCase()]; michael@0: return !!(aAddon.pendingOperations & action); michael@0: } michael@0: michael@0: function isInState(aInstall, aState) { michael@0: var state = AddonManager["STATE_" + aState.toUpperCase()]; michael@0: return aInstall.state == state; michael@0: } michael@0: michael@0: function shouldShowVersionNumber(aAddon) { michael@0: if (!aAddon.version) michael@0: return false; michael@0: michael@0: // The version number is hidden for lightweight themes. michael@0: if (aAddon.type == "theme") michael@0: return !/@personas\.mozilla\.org$/.test(aAddon.id); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: function createItem(aObj, aIsInstall, aIsRemote) { michael@0: let item = document.createElement("richlistitem"); michael@0: michael@0: item.setAttribute("class", "addon addon-view"); michael@0: item.setAttribute("name", aObj.name); michael@0: item.setAttribute("type", aObj.type); michael@0: item.setAttribute("remote", !!aIsRemote); michael@0: michael@0: if (aIsInstall) { michael@0: item.mInstall = aObj; michael@0: michael@0: if (aObj.state != AddonManager.STATE_INSTALLED) { michael@0: item.setAttribute("status", "installing"); michael@0: return item; michael@0: } michael@0: aObj = aObj.addon; michael@0: } michael@0: michael@0: item.mAddon = aObj; michael@0: michael@0: item.setAttribute("status", "installed"); michael@0: michael@0: // set only attributes needed for sorting and XBL binding, michael@0: // the binding handles the rest michael@0: item.setAttribute("value", aObj.id); michael@0: michael@0: if (aObj.type == "experiment") { michael@0: item.endDate = getExperimentEndDate(aObj); michael@0: } michael@0: michael@0: return item; michael@0: } michael@0: michael@0: function sortElements(aElements, aSortBy, aAscending) { michael@0: // aSortBy is an Array of attributes to sort by, in decending michael@0: // order of priority. michael@0: michael@0: const DATE_FIELDS = ["updateDate"]; michael@0: const NUMERIC_FIELDS = ["size", "relevancescore", "purchaseAmount"]; michael@0: michael@0: // We're going to group add-ons into the following buckets: michael@0: // michael@0: // enabledInstalled michael@0: // * Enabled michael@0: // * Incompatible but enabled because compatibility checking is off michael@0: // * Waiting to be installed michael@0: // * Waiting to be enabled michael@0: // michael@0: // pendingDisable michael@0: // * Waiting to be disabled michael@0: // michael@0: // pendingUninstall michael@0: // * Waiting to be removed michael@0: // michael@0: // disabledIncompatibleBlocked michael@0: // * Disabled michael@0: // * Incompatible michael@0: // * Blocklisted michael@0: michael@0: const UISTATE_ORDER = ["enabled", "pendingDisable", "pendingUninstall", michael@0: "disabled"]; michael@0: michael@0: function dateCompare(a, b) { michael@0: var aTime = a.getTime(); michael@0: var bTime = b.getTime(); michael@0: if (aTime < bTime) michael@0: return -1; michael@0: if (aTime > bTime) michael@0: return 1; michael@0: return 0; michael@0: } michael@0: michael@0: function numberCompare(a, b) { michael@0: return a - b; michael@0: } michael@0: michael@0: function stringCompare(a, b) { michael@0: return a.localeCompare(b); michael@0: } michael@0: michael@0: function uiStateCompare(a, b) { michael@0: // If we're in descending order, swap a and b, because michael@0: // we don't ever want to have descending uiStates michael@0: if (!aAscending) michael@0: [a, b] = [b, a]; michael@0: michael@0: return (UISTATE_ORDER.indexOf(a) - UISTATE_ORDER.indexOf(b)); michael@0: } michael@0: michael@0: function getValue(aObj, aKey) { michael@0: if (!aObj) michael@0: return null; michael@0: michael@0: if (aObj.hasAttribute(aKey)) michael@0: return aObj.getAttribute(aKey); michael@0: michael@0: var addon = aObj.mAddon || aObj.mInstall; michael@0: if (!addon) michael@0: return null; michael@0: michael@0: if (aKey == "uiState") { michael@0: if (addon.pendingOperations == AddonManager.PENDING_DISABLE) michael@0: return "pendingDisable"; michael@0: if (addon.pendingOperations == AddonManager.PENDING_UNINSTALL) michael@0: return "pendingUninstall"; michael@0: if (!addon.isActive && michael@0: (addon.pendingOperations != AddonManager.PENDING_ENABLE && michael@0: addon.pendingOperations != AddonManager.PENDING_INSTALL)) michael@0: return "disabled"; michael@0: else michael@0: return "enabled"; michael@0: } michael@0: michael@0: return addon[aKey]; michael@0: } michael@0: michael@0: // aSortFuncs will hold the sorting functions that we'll michael@0: // use per element, in the correct order. michael@0: var aSortFuncs = []; michael@0: michael@0: for (let i = 0; i < aSortBy.length; i++) { michael@0: var sortBy = aSortBy[i]; michael@0: michael@0: aSortFuncs[i] = stringCompare; michael@0: michael@0: if (sortBy == "uiState") michael@0: aSortFuncs[i] = uiStateCompare; michael@0: else if (DATE_FIELDS.indexOf(sortBy) != -1) michael@0: aSortFuncs[i] = dateCompare; michael@0: else if (NUMERIC_FIELDS.indexOf(sortBy) != -1) michael@0: aSortFuncs[i] = numberCompare; michael@0: } michael@0: michael@0: michael@0: aElements.sort(function elementsSort(a, b) { michael@0: if (!aAscending) michael@0: [a, b] = [b, a]; michael@0: michael@0: for (let i = 0; i < aSortFuncs.length; i++) { michael@0: var sortBy = aSortBy[i]; michael@0: var aValue = getValue(a, sortBy); michael@0: var bValue = getValue(b, sortBy); michael@0: michael@0: if (!aValue && !bValue) michael@0: return 0; michael@0: if (!aValue) michael@0: return -1; michael@0: if (!bValue) michael@0: return 1; michael@0: if (aValue != bValue) { michael@0: var result = aSortFuncs[i](aValue, bValue); michael@0: michael@0: if (result != 0) michael@0: return result; michael@0: } michael@0: } michael@0: michael@0: // If we got here, then all values of a and b michael@0: // must have been equal. michael@0: return 0; michael@0: michael@0: }); michael@0: } michael@0: michael@0: function sortList(aList, aSortBy, aAscending) { michael@0: var elements = Array.slice(aList.childNodes, 0); michael@0: sortElements(elements, [aSortBy], aAscending); michael@0: michael@0: while (aList.listChild) michael@0: aList.removeChild(aList.lastChild); michael@0: michael@0: for (let element of elements) michael@0: aList.appendChild(element); michael@0: } michael@0: michael@0: function getAddonsAndInstalls(aType, aCallback) { michael@0: let addons = null, installs = null; michael@0: let types = (aType != null) ? [aType] : null; michael@0: michael@0: AddonManager.getAddonsByTypes(types, function getAddonsAndInstalls_getAddonsByTypes(aAddonsList) { michael@0: addons = aAddonsList; michael@0: if (installs != null) michael@0: aCallback(addons, installs); michael@0: }); michael@0: michael@0: AddonManager.getInstallsByTypes(types, function getAddonsAndInstalls_getInstallsByTypes(aInstallsList) { michael@0: // skip over upgrade installs and non-active installs michael@0: installs = aInstallsList.filter(function installsFilter(aInstall) { michael@0: return !(aInstall.existingAddon || michael@0: aInstall.state == AddonManager.STATE_AVAILABLE); michael@0: }); michael@0: michael@0: if (addons != null) michael@0: aCallback(addons, installs) michael@0: }); michael@0: } michael@0: michael@0: function doPendingUninstalls(aListBox) { michael@0: // Uninstalling add-ons can mutate the list so find the add-ons first then michael@0: // uninstall them michael@0: var items = []; michael@0: var listitem = aListBox.firstChild; michael@0: while (listitem) { michael@0: if (listitem.getAttribute("pending") == "uninstall" && michael@0: !listitem.isPending("uninstall")) michael@0: items.push(listitem.mAddon); michael@0: listitem = listitem.nextSibling; michael@0: } michael@0: michael@0: for (let addon of items) michael@0: addon.uninstall(); michael@0: } michael@0: michael@0: var gCategories = { michael@0: node: null, michael@0: _search: null, michael@0: michael@0: initialize: function gCategories_initialize() { michael@0: this.node = document.getElementById("categories"); michael@0: this._search = this.get("addons://search/"); michael@0: michael@0: var types = AddonManager.addonTypes; michael@0: for (var type in types) michael@0: this.onTypeAdded(types[type]); michael@0: michael@0: AddonManager.addTypeListener(this); michael@0: michael@0: try { michael@0: this.node.value = Services.prefs.getCharPref(PREF_UI_LASTCATEGORY); michael@0: } catch (e) { } michael@0: michael@0: // If there was no last view or no existing category matched the last view michael@0: // then the list will default to selecting the search category and we never michael@0: // want to show that as the first view so switch to the default category michael@0: if (!this.node.selectedItem || this.node.selectedItem == this._search) michael@0: this.node.value = VIEW_DEFAULT; michael@0: michael@0: var self = this; michael@0: this.node.addEventListener("select", function node_onSelected() { michael@0: self.maybeHideSearch(); michael@0: gViewController.loadView(self.node.selectedItem.value); michael@0: }, false); michael@0: michael@0: this.node.addEventListener("click", function node_onClicked(aEvent) { michael@0: var selectedItem = self.node.selectedItem; michael@0: if (aEvent.target.localName == "richlistitem" && michael@0: aEvent.target == selectedItem) { michael@0: var viewId = selectedItem.value; michael@0: michael@0: if (gViewController.parseViewId(viewId).type == "search") { michael@0: viewId += encodeURIComponent(gHeader.searchQuery); michael@0: } michael@0: michael@0: gViewController.loadView(viewId); michael@0: } michael@0: }, false); michael@0: }, michael@0: michael@0: shutdown: function gCategories_shutdown() { michael@0: AddonManager.removeTypeListener(this); michael@0: }, michael@0: michael@0: _insertCategory: function gCategories_insertCategory(aId, aName, aView, aPriority, aStartHidden) { michael@0: // If this category already exists then don't re-add it michael@0: if (document.getElementById("category-" + aId)) michael@0: return; michael@0: michael@0: var category = document.createElement("richlistitem"); michael@0: category.setAttribute("id", "category-" + aId); michael@0: category.setAttribute("value", aView); michael@0: category.setAttribute("class", "category"); michael@0: category.setAttribute("name", aName); michael@0: category.setAttribute("tooltiptext", aName); michael@0: category.setAttribute("priority", aPriority); michael@0: category.setAttribute("hidden", aStartHidden); michael@0: michael@0: var node; michael@0: for (node of this.node.children) { michael@0: var nodePriority = parseInt(node.getAttribute("priority")); michael@0: // If the new type's priority is higher than this one then this is the michael@0: // insertion point michael@0: if (aPriority < nodePriority) michael@0: break; michael@0: // If the new type's priority is lower than this one then this is isn't michael@0: // the insertion point michael@0: if (aPriority > nodePriority) michael@0: continue; michael@0: // If the priorities are equal and the new type's name is earlier michael@0: // alphabetically then this is the insertion point michael@0: if (String.localeCompare(aName, node.getAttribute("name")) < 0) michael@0: break; michael@0: } michael@0: michael@0: this.node.insertBefore(category, node); michael@0: }, michael@0: michael@0: _removeCategory: function gCategories_removeCategory(aId) { michael@0: var category = document.getElementById("category-" + aId); michael@0: if (!category) michael@0: return; michael@0: michael@0: // If this category is currently selected then switch to the default view michael@0: if (this.node.selectedItem == category) michael@0: gViewController.replaceView(VIEW_DEFAULT); michael@0: michael@0: this.node.removeChild(category); michael@0: }, michael@0: michael@0: onTypeAdded: function gCategories_onTypeAdded(aType) { michael@0: // Ignore types that we don't have a view object for michael@0: if (!(aType.viewType in gViewController.viewObjects)) michael@0: return; michael@0: michael@0: var aViewId = "addons://" + aType.viewType + "/" + aType.id; michael@0: michael@0: var startHidden = false; michael@0: if (aType.flags & AddonManager.TYPE_UI_HIDE_EMPTY) { michael@0: var prefName = PREF_UI_TYPE_HIDDEN.replace("%TYPE%", aType.id); michael@0: try { michael@0: startHidden = Services.prefs.getBoolPref(prefName); michael@0: } michael@0: catch (e) { michael@0: // Default to hidden michael@0: startHidden = true; michael@0: } michael@0: michael@0: var self = this; michael@0: gPendingInitializations++; michael@0: getAddonsAndInstalls(aType.id, function onTypeAdded_getAddonsAndInstalls(aAddonsList, aInstallsList) { michael@0: var hidden = (aAddonsList.length == 0 && aInstallsList.length == 0); michael@0: var item = self.get(aViewId); michael@0: michael@0: // Don't load view that is becoming hidden michael@0: if (hidden && aViewId == gViewController.currentViewId) michael@0: gViewController.loadView(VIEW_DEFAULT); michael@0: michael@0: item.hidden = hidden; michael@0: Services.prefs.setBoolPref(prefName, hidden); michael@0: michael@0: if (aAddonsList.length > 0 || aInstallsList.length > 0) { michael@0: notifyInitialized(); michael@0: return; michael@0: } michael@0: michael@0: gEventManager.registerInstallListener({ michael@0: onDownloadStarted: function gCategories_onDownloadStarted(aInstall) { michael@0: this._maybeShowCategory(aInstall); michael@0: }, michael@0: michael@0: onInstallStarted: function gCategories_onInstallStarted(aInstall) { michael@0: this._maybeShowCategory(aInstall); michael@0: }, michael@0: michael@0: onInstallEnded: function gCategories_onInstallEnded(aInstall, aAddon) { michael@0: this._maybeShowCategory(aAddon); michael@0: }, michael@0: michael@0: onExternalInstall: function gCategories_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) { michael@0: this._maybeShowCategory(aAddon); michael@0: }, michael@0: michael@0: _maybeShowCategory: function gCategories_maybeShowCategory(aAddon) { michael@0: if (aType.id == aAddon.type) { michael@0: self.get(aViewId).hidden = false; michael@0: Services.prefs.setBoolPref(prefName, false); michael@0: gEventManager.unregisterInstallListener(this); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: notifyInitialized(); michael@0: }); michael@0: } michael@0: michael@0: this._insertCategory(aType.id, aType.name, aViewId, aType.uiPriority, michael@0: startHidden); michael@0: }, michael@0: michael@0: onTypeRemoved: function gCategories_onTypeRemoved(aType) { michael@0: this._removeCategory(aType.id); michael@0: }, michael@0: michael@0: get selected() { michael@0: return this.node.selectedItem ? this.node.selectedItem.value : null; michael@0: }, michael@0: michael@0: select: function gCategories_select(aId, aPreviousView) { michael@0: var view = gViewController.parseViewId(aId); michael@0: if (view.type == "detail" && aPreviousView) { michael@0: aId = aPreviousView; michael@0: view = gViewController.parseViewId(aPreviousView); michael@0: } michael@0: michael@0: Services.prefs.setCharPref(PREF_UI_LASTCATEGORY, aId); michael@0: michael@0: if (this.node.selectedItem && michael@0: this.node.selectedItem.value == aId) { michael@0: this.node.selectedItem.hidden = false; michael@0: this.node.selectedItem.disabled = false; michael@0: return; michael@0: } michael@0: michael@0: if (view.type == "search") michael@0: var item = this._search; michael@0: else michael@0: var item = this.get(aId); michael@0: michael@0: if (item) { michael@0: item.hidden = false; michael@0: item.disabled = false; michael@0: this.node.suppressOnSelect = true; michael@0: this.node.selectedItem = item; michael@0: this.node.suppressOnSelect = false; michael@0: this.node.ensureElementIsVisible(item); michael@0: michael@0: this.maybeHideSearch(); michael@0: } michael@0: }, michael@0: michael@0: get: function gCategories_get(aId) { michael@0: var items = document.getElementsByAttribute("value", aId); michael@0: if (items.length) michael@0: return items[0]; michael@0: return null; michael@0: }, michael@0: michael@0: setBadge: function gCategories_setBadge(aId, aCount) { michael@0: let item = this.get(aId); michael@0: if (item) michael@0: item.badgeCount = aCount; michael@0: }, michael@0: michael@0: maybeHideSearch: function gCategories_maybeHideSearch() { michael@0: var view = gViewController.parseViewId(this.node.selectedItem.value); michael@0: this._search.disabled = view.type != "search"; michael@0: } michael@0: }; michael@0: michael@0: michael@0: var gHeader = { michael@0: _search: null, michael@0: _dest: "", michael@0: michael@0: initialize: function gHeader_initialize() { michael@0: this._search = document.getElementById("header-search"); michael@0: michael@0: this._search.addEventListener("command", function search_onCommand(aEvent) { michael@0: var query = aEvent.target.value; michael@0: if (query.length == 0) michael@0: return; michael@0: michael@0: gViewController.loadView("addons://search/" + encodeURIComponent(query)); michael@0: }, false); michael@0: michael@0: function updateNavButtonVisibility() { michael@0: var shouldShow = gHeader.shouldShowNavButtons; michael@0: document.getElementById("back-btn").hidden = !shouldShow; michael@0: document.getElementById("forward-btn").hidden = !shouldShow; michael@0: } michael@0: michael@0: window.addEventListener("focus", function window_onFocus(aEvent) { michael@0: if (aEvent.target == window) michael@0: updateNavButtonVisibility(); michael@0: }, false); michael@0: michael@0: updateNavButtonVisibility(); michael@0: }, michael@0: michael@0: focusSearchBox: function gHeader_focusSearchBox() { michael@0: this._search.focus(); michael@0: }, michael@0: michael@0: onKeyPress: function gHeader_onKeyPress(aEvent) { michael@0: if (String.fromCharCode(aEvent.charCode) == "/") { michael@0: this.focusSearchBox(); michael@0: return; michael@0: } michael@0: michael@0: // XXXunf Temporary until bug 371900 is fixed. michael@0: let key = document.getElementById("focusSearch").getAttribute("key"); michael@0: #ifdef XP_MACOSX michael@0: let keyModifier = aEvent.metaKey; michael@0: #else michael@0: let keyModifier = aEvent.ctrlKey; michael@0: #endif michael@0: if (String.fromCharCode(aEvent.charCode) == key && keyModifier) { michael@0: this.focusSearchBox(); michael@0: return; michael@0: } michael@0: }, michael@0: michael@0: get shouldShowNavButtons() { michael@0: var docshellItem = window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem); michael@0: michael@0: // If there is no outer frame then make the buttons visible michael@0: if (docshellItem.rootTreeItem == docshellItem) michael@0: return true; michael@0: michael@0: var outerWin = docshellItem.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindow); michael@0: var outerDoc = outerWin.document; michael@0: var node = outerDoc.getElementById("back-button"); michael@0: // If the outer frame has no back-button then make the buttons visible michael@0: if (!node) michael@0: return true; michael@0: michael@0: // If the back-button or any of its parents are hidden then make the buttons michael@0: // visible michael@0: while (node != outerDoc) { michael@0: var style = outerWin.getComputedStyle(node, ""); michael@0: if (style.display == "none") michael@0: return true; michael@0: if (style.visibility != "visible") michael@0: return true; michael@0: node = node.parentNode; michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: get searchQuery() { michael@0: return this._search.value; michael@0: }, michael@0: michael@0: set searchQuery(aQuery) { michael@0: this._search.value = aQuery; michael@0: }, michael@0: }; michael@0: michael@0: michael@0: var gDiscoverView = { michael@0: node: null, michael@0: enabled: true, michael@0: // Set to true after the view is first shown. If initialization completes michael@0: // after this then it must also load the discover homepage michael@0: loaded: false, michael@0: _browser: null, michael@0: _loading: null, michael@0: _error: null, michael@0: homepageURL: null, michael@0: _loadListeners: [], michael@0: michael@0: initialize: function gDiscoverView_initialize() { michael@0: this.enabled = isDiscoverEnabled(); michael@0: if (!this.enabled) { michael@0: gCategories.get("addons://discover/").hidden = true; michael@0: return; michael@0: } michael@0: michael@0: this.node = document.getElementById("discover-view"); michael@0: this._loading = document.getElementById("discover-loading"); michael@0: this._error = document.getElementById("discover-error"); michael@0: this._browser = document.getElementById("discover-browser"); michael@0: michael@0: let compatMode = "normal"; michael@0: if (!AddonManager.checkCompatibility) michael@0: compatMode = "ignore"; michael@0: else if (AddonManager.strictCompatibility) michael@0: compatMode = "strict"; michael@0: michael@0: var url = Services.prefs.getCharPref(PREF_DISCOVERURL); michael@0: url = url.replace("%COMPATIBILITY_MODE%", compatMode); michael@0: url = Services.urlFormatter.formatURL(url); michael@0: michael@0: var self = this; michael@0: michael@0: function setURL(aURL) { michael@0: try { michael@0: self.homepageURL = Services.io.newURI(aURL, null, null); michael@0: } catch (e) { michael@0: self.showError(); michael@0: notifyInitialized(); michael@0: return; michael@0: } michael@0: michael@0: self._browser.homePage = self.homepageURL.spec; michael@0: self._browser.addProgressListener(self); michael@0: michael@0: if (self.loaded) michael@0: self._loadURL(self.homepageURL.spec, false, notifyInitialized); michael@0: else michael@0: notifyInitialized(); michael@0: } michael@0: michael@0: if (Services.prefs.getBoolPref(PREF_GETADDONS_CACHE_ENABLED) == false) { michael@0: setURL(url); michael@0: return; michael@0: } michael@0: michael@0: gPendingInitializations++; michael@0: AddonManager.getAllAddons(function initialize_getAllAddons(aAddons) { michael@0: var list = {}; michael@0: for (let addon of aAddons) { michael@0: var prefName = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%", michael@0: addon.id); michael@0: try { michael@0: if (!Services.prefs.getBoolPref(prefName)) michael@0: continue; michael@0: } catch (e) { } michael@0: list[addon.id] = { michael@0: name: addon.name, michael@0: version: addon.version, michael@0: type: addon.type, michael@0: userDisabled: addon.userDisabled, michael@0: isCompatible: addon.isCompatible, michael@0: isBlocklisted: addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED michael@0: } michael@0: } michael@0: michael@0: setURL(url + "#" + JSON.stringify(list)); michael@0: }); michael@0: }, michael@0: michael@0: destroy: function gDiscoverView_destroy() { michael@0: try { michael@0: this._browser.removeProgressListener(this); michael@0: } michael@0: catch (e) { michael@0: // Ignore the case when the listener wasn't already registered michael@0: } michael@0: }, michael@0: michael@0: show: function gDiscoverView_show(aParam, aRequest, aState, aIsRefresh) { michael@0: gViewController.updateCommands(); michael@0: michael@0: // If we're being told to load a specific URL then just do that michael@0: if (aState && "url" in aState) { michael@0: this.loaded = true; michael@0: this._loadURL(aState.url); michael@0: } michael@0: michael@0: // If the view has loaded before and still at the homepage (if refreshing), michael@0: // and the error page is not visible then there is nothing else to do michael@0: if (this.loaded && this.node.selectedPanel != this._error && michael@0: (!aIsRefresh || (this._browser.currentURI && michael@0: this._browser.currentURI.spec == this._browser.homePage))) { michael@0: gViewController.notifyViewChanged(); michael@0: return; michael@0: } michael@0: michael@0: this.loaded = true; michael@0: michael@0: // No homepage means initialization isn't complete, the browser will get michael@0: // loaded once initialization is complete michael@0: if (!this.homepageURL) { michael@0: this._loadListeners.push(gViewController.notifyViewChanged.bind(gViewController)); michael@0: return; michael@0: } michael@0: michael@0: this._loadURL(this.homepageURL.spec, aIsRefresh, michael@0: gViewController.notifyViewChanged.bind(gViewController)); michael@0: }, michael@0: michael@0: canRefresh: function gDiscoverView_canRefresh() { michael@0: if (this._browser.currentURI && michael@0: this._browser.currentURI.spec == this._browser.homePage) michael@0: return false; michael@0: return true; michael@0: }, michael@0: michael@0: refresh: function gDiscoverView_refresh(aParam, aRequest, aState) { michael@0: this.show(aParam, aRequest, aState, true); michael@0: }, michael@0: michael@0: hide: function gDiscoverView_hide() { }, michael@0: michael@0: showError: function gDiscoverView_showError() { michael@0: this.node.selectedPanel = this._error; michael@0: }, michael@0: michael@0: _loadURL: function gDiscoverView_loadURL(aURL, aKeepHistory, aCallback) { michael@0: if (this._browser.currentURI.spec == aURL) { michael@0: if (aCallback) michael@0: aCallback(); michael@0: return; michael@0: } michael@0: michael@0: if (aCallback) michael@0: this._loadListeners.push(aCallback); michael@0: michael@0: var flags = 0; michael@0: if (!aKeepHistory) michael@0: flags |= Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; michael@0: michael@0: this._browser.loadURIWithFlags(aURL, flags); michael@0: }, michael@0: michael@0: onLocationChange: function gDiscoverView_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) { michael@0: // Ignore the about:blank load michael@0: if (aLocation.spec == "about:blank") michael@0: return; michael@0: michael@0: // When using the real session history the inner-frame will update the michael@0: // session history automatically, if using the fake history though it must michael@0: // be manually updated michael@0: if (gHistory == FakeHistory) { michael@0: var docshell = aWebProgress.QueryInterface(Ci.nsIDocShell); michael@0: michael@0: var state = { michael@0: view: "addons://discover/", michael@0: url: aLocation.spec michael@0: }; michael@0: michael@0: var replaceHistory = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY << 16; michael@0: if (docshell.loadType & replaceHistory) michael@0: gHistory.replaceState(state); michael@0: else michael@0: gHistory.pushState(state); michael@0: gViewController.lastHistoryIndex = gHistory.index; michael@0: } michael@0: michael@0: gViewController.updateCommands(); michael@0: michael@0: // If the hostname is the same as the new location's host and either the michael@0: // default scheme is insecure or the new location is secure then continue michael@0: // with the load michael@0: if (aLocation.host == this.homepageURL.host && michael@0: (!this.homepageURL.schemeIs("https") || aLocation.schemeIs("https"))) michael@0: return; michael@0: michael@0: // Canceling the request will send an error to onStateChange which will show michael@0: // the error page michael@0: aRequest.cancel(Components.results.NS_BINDING_ABORTED); michael@0: }, michael@0: michael@0: onSecurityChange: function gDiscoverView_onSecurityChange(aWebProgress, aRequest, aState) { michael@0: // Don't care about security if the page is not https michael@0: if (!this.homepageURL.schemeIs("https")) michael@0: return; michael@0: michael@0: // If the request was secure then it is ok michael@0: if (aState & Ci.nsIWebProgressListener.STATE_IS_SECURE) michael@0: return; michael@0: michael@0: // Canceling the request will send an error to onStateChange which will show michael@0: // the error page michael@0: aRequest.cancel(Components.results.NS_BINDING_ABORTED); michael@0: }, michael@0: michael@0: onStateChange: function gDiscoverView_onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) { michael@0: let transferStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | michael@0: Ci.nsIWebProgressListener.STATE_IS_REQUEST | michael@0: Ci.nsIWebProgressListener.STATE_TRANSFERRING; michael@0: // Once transferring begins show the content michael@0: if (aStateFlags & transferStart) michael@0: this.node.selectedPanel = this._browser; michael@0: michael@0: // Only care about the network events michael@0: if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_IS_NETWORK))) michael@0: return; michael@0: michael@0: // If this is the start of network activity then show the loading page michael@0: if (aStateFlags & (Ci.nsIWebProgressListener.STATE_START)) michael@0: this.node.selectedPanel = this._loading; michael@0: michael@0: // Ignore anything except stop events michael@0: if (!(aStateFlags & (Ci.nsIWebProgressListener.STATE_STOP))) michael@0: return; michael@0: michael@0: // Consider the successful load of about:blank as still loading michael@0: if (aRequest instanceof Ci.nsIChannel && aRequest.URI.spec == "about:blank") michael@0: return; michael@0: michael@0: // If there was an error loading the page or the new hostname is not the michael@0: // same as the default hostname or the default scheme is secure and the new michael@0: // scheme is insecure then show the error page michael@0: const NS_ERROR_PARSED_DATA_CACHED = 0x805D0021; michael@0: if (!(Components.isSuccessCode(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED) || michael@0: (aRequest && aRequest instanceof Ci.nsIHttpChannel && !aRequest.requestSucceeded)) { michael@0: this.showError(); michael@0: } else { michael@0: // Got a successful load, make sure the browser is visible michael@0: this.node.selectedPanel = this._browser; michael@0: gViewController.updateCommands(); michael@0: } michael@0: michael@0: var listeners = this._loadListeners; michael@0: this._loadListeners = []; michael@0: michael@0: for (let listener of listeners) michael@0: listener(); michael@0: }, michael@0: michael@0: onProgressChange: function gDiscoverView_onProgressChange() { }, michael@0: onStatusChange: function gDiscoverView_onStatusChange() { }, michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, michael@0: Ci.nsISupportsWeakReference]), michael@0: michael@0: getSelectedAddon: function gDiscoverView_getSelectedAddon() null michael@0: }; michael@0: michael@0: michael@0: var gCachedAddons = {}; michael@0: michael@0: var gSearchView = { michael@0: node: null, michael@0: _filter: null, michael@0: _sorters: null, michael@0: _loading: null, michael@0: _listBox: null, michael@0: _emptyNotice: null, michael@0: _allResultsLink: null, michael@0: _lastQuery: null, michael@0: _lastRemoteTotal: 0, michael@0: _pendingSearches: 0, michael@0: michael@0: initialize: function gSearchView_initialize() { michael@0: this.node = document.getElementById("search-view"); michael@0: this._filter = document.getElementById("search-filter-radiogroup"); michael@0: this._sorters = document.getElementById("search-sorters"); michael@0: this._sorters.handler = this; michael@0: this._loading = document.getElementById("search-loading"); michael@0: this._listBox = document.getElementById("search-list"); michael@0: this._emptyNotice = document.getElementById("search-list-empty"); michael@0: this._allResultsLink = document.getElementById("search-allresults-link"); michael@0: michael@0: if (!AddonManager.isInstallEnabled("application/x-xpinstall")) michael@0: this._filter.hidden = true; michael@0: michael@0: var self = this; michael@0: this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) { michael@0: if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { michael@0: var item = self._listBox.selectedItem; michael@0: if (item) michael@0: item.showInDetailView(); michael@0: } michael@0: }, false); michael@0: michael@0: this._filter.addEventListener("command", function filter_onCommand() self.updateView(), false); michael@0: }, michael@0: michael@0: shutdown: function gSearchView_shutdown() { michael@0: if (AddonRepository.isSearching) michael@0: AddonRepository.cancelSearch(); michael@0: }, michael@0: michael@0: get isSearching() { michael@0: return this._pendingSearches > 0; michael@0: }, michael@0: michael@0: show: function gSearchView_show(aQuery, aRequest) { michael@0: gEventManager.registerInstallListener(this); michael@0: michael@0: this.showEmptyNotice(false); michael@0: this.showAllResultsLink(0); michael@0: this.showLoading(true); michael@0: this._sorters.showprice = false; michael@0: michael@0: gHeader.searchQuery = aQuery; michael@0: aQuery = aQuery.trim().toLocaleLowerCase(); michael@0: if (this._lastQuery == aQuery) { michael@0: this.updateView(); michael@0: gViewController.notifyViewChanged(); michael@0: return; michael@0: } michael@0: this._lastQuery = aQuery; michael@0: michael@0: if (AddonRepository.isSearching) michael@0: AddonRepository.cancelSearch(); michael@0: michael@0: while (this._listBox.firstChild.localName == "richlistitem") michael@0: this._listBox.removeChild(this._listBox.firstChild); michael@0: michael@0: var self = this; michael@0: gCachedAddons = {}; michael@0: this._pendingSearches = 2; michael@0: this._sorters.setSort("relevancescore", false); michael@0: michael@0: var elements = []; michael@0: michael@0: function createSearchResults(aObjsList, aIsInstall, aIsRemote) { michael@0: for (let index in aObjsList) { michael@0: let obj = aObjsList[index]; michael@0: let score = aObjsList.length - index; michael@0: if (!aIsRemote && aQuery.length > 0) { michael@0: score = self.getMatchScore(obj, aQuery); michael@0: if (score == 0) michael@0: continue; michael@0: } michael@0: michael@0: let item = createItem(obj, aIsInstall, aIsRemote); michael@0: item.setAttribute("relevancescore", score); michael@0: if (aIsRemote) { michael@0: gCachedAddons[obj.id] = obj; michael@0: if (obj.purchaseURL) michael@0: self._sorters.showprice = true; michael@0: } michael@0: michael@0: elements.push(item); michael@0: } michael@0: } michael@0: michael@0: function finishSearch(createdCount) { michael@0: if (elements.length > 0) { michael@0: sortElements(elements, [self._sorters.sortBy], self._sorters.ascending); michael@0: for (let element of elements) michael@0: self._listBox.insertBefore(element, self._listBox.lastChild); michael@0: self.updateListAttributes(); michael@0: } michael@0: michael@0: self._pendingSearches--; michael@0: self.updateView(); michael@0: michael@0: if (!self.isSearching) michael@0: gViewController.notifyViewChanged(); michael@0: } michael@0: michael@0: getAddonsAndInstalls(null, function show_getAddonsAndInstalls(aAddons, aInstalls) { michael@0: if (gViewController && aRequest != gViewController.currentViewRequest) michael@0: return; michael@0: michael@0: createSearchResults(aAddons, false, false); michael@0: createSearchResults(aInstalls, true, false); michael@0: finishSearch(); michael@0: }); michael@0: michael@0: var maxRemoteResults = 0; michael@0: try { michael@0: maxRemoteResults = Services.prefs.getIntPref(PREF_MAXRESULTS); michael@0: } catch(e) {} michael@0: michael@0: if (maxRemoteResults <= 0) { michael@0: finishSearch(0); michael@0: return; michael@0: } michael@0: michael@0: AddonRepository.searchAddons(aQuery, maxRemoteResults, { michael@0: searchFailed: function show_SearchFailed() { michael@0: if (gViewController && aRequest != gViewController.currentViewRequest) michael@0: return; michael@0: michael@0: self._lastRemoteTotal = 0; michael@0: michael@0: // XXXunf Better handling of AMO search failure. See bug 579502 michael@0: finishSearch(0); // Silently fail michael@0: }, michael@0: michael@0: searchSucceeded: function show_SearchSucceeded(aAddonsList, aAddonCount, aTotalResults) { michael@0: if (gViewController && aRequest != gViewController.currentViewRequest) michael@0: return; michael@0: michael@0: if (aTotalResults > maxRemoteResults) michael@0: self._lastRemoteTotal = aTotalResults; michael@0: else michael@0: self._lastRemoteTotal = 0; michael@0: michael@0: var createdCount = createSearchResults(aAddonsList, false, true); michael@0: finishSearch(createdCount); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: showLoading: function gSearchView_showLoading(aLoading) { michael@0: this._loading.hidden = !aLoading; michael@0: this._listBox.hidden = aLoading; michael@0: }, michael@0: michael@0: updateView: function gSearchView_updateView() { michael@0: var showLocal = this._filter.value == "local"; michael@0: michael@0: if (!showLocal && !AddonManager.isInstallEnabled("application/x-xpinstall")) michael@0: showLocal = true; michael@0: michael@0: this._listBox.setAttribute("local", showLocal); michael@0: this._listBox.setAttribute("remote", !showLocal); michael@0: michael@0: this.showLoading(this.isSearching && !showLocal); michael@0: if (!this.isSearching) { michael@0: var isEmpty = true; michael@0: var results = this._listBox.getElementsByTagName("richlistitem"); michael@0: for (let result of results) { michael@0: var isRemote = (result.getAttribute("remote") == "true"); michael@0: if ((isRemote && !showLocal) || (!isRemote && showLocal)) { michael@0: isEmpty = false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: this.showEmptyNotice(isEmpty); michael@0: this.showAllResultsLink(this._lastRemoteTotal); michael@0: } michael@0: michael@0: gViewController.updateCommands(); michael@0: }, michael@0: michael@0: hide: function gSearchView_hide() { michael@0: gEventManager.unregisterInstallListener(this); michael@0: doPendingUninstalls(this._listBox); michael@0: }, michael@0: michael@0: getMatchScore: function gSearchView_getMatchScore(aObj, aQuery) { michael@0: var score = 0; michael@0: score += this.calculateMatchScore(aObj.name, aQuery, michael@0: SEARCH_SCORE_MULTIPLIER_NAME); michael@0: score += this.calculateMatchScore(aObj.description, aQuery, michael@0: SEARCH_SCORE_MULTIPLIER_DESCRIPTION); michael@0: return score; michael@0: }, michael@0: michael@0: calculateMatchScore: function gSearchView_calculateMatchScore(aStr, aQuery, aMultiplier) { michael@0: var score = 0; michael@0: if (!aStr || aQuery.length == 0) michael@0: return score; michael@0: michael@0: aStr = aStr.trim().toLocaleLowerCase(); michael@0: var haystack = aStr.split(/\s+/); michael@0: var needles = aQuery.split(/\s+/); michael@0: michael@0: for (let needle of needles) { michael@0: for (let hay of haystack) { michael@0: if (hay == needle) { michael@0: // matching whole words is best michael@0: score += SEARCH_SCORE_MATCH_WHOLEWORD; michael@0: } else { michael@0: let i = hay.indexOf(needle); michael@0: if (i == 0) // matching on word boundries is also good michael@0: score += SEARCH_SCORE_MATCH_WORDBOUNDRY; michael@0: else if (i > 0) // substring matches not so good michael@0: score += SEARCH_SCORE_MATCH_SUBSTRING; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // give progressively higher score for longer queries, since longer queries michael@0: // are more likely to be unique and therefore more relevant. michael@0: if (needles.length > 1 && aStr.indexOf(aQuery) != -1) michael@0: score += needles.length; michael@0: michael@0: return score * aMultiplier; michael@0: }, michael@0: michael@0: showEmptyNotice: function gSearchView_showEmptyNotice(aShow) { michael@0: this._emptyNotice.hidden = !aShow; michael@0: this._listBox.hidden = aShow; michael@0: }, michael@0: michael@0: showAllResultsLink: function gSearchView_showAllResultsLink(aTotalResults) { michael@0: if (aTotalResults == 0) { michael@0: this._allResultsLink.hidden = true; michael@0: return; michael@0: } michael@0: michael@0: var linkStr = gStrings.ext.GetStringFromName("showAllSearchResults"); michael@0: linkStr = PluralForm.get(aTotalResults, linkStr); michael@0: linkStr = linkStr.replace("#1", aTotalResults); michael@0: this._allResultsLink.setAttribute("value", linkStr); michael@0: michael@0: this._allResultsLink.setAttribute("href", michael@0: AddonRepository.getSearchURL(this._lastQuery)); michael@0: this._allResultsLink.hidden = false; michael@0: }, michael@0: michael@0: updateListAttributes: function gSearchView_updateListAttributes() { michael@0: var item = this._listBox.querySelector("richlistitem[remote='true'][first]"); michael@0: if (item) michael@0: item.removeAttribute("first"); michael@0: item = this._listBox.querySelector("richlistitem[remote='true'][last]"); michael@0: if (item) michael@0: item.removeAttribute("last"); michael@0: var items = this._listBox.querySelectorAll("richlistitem[remote='true']"); michael@0: if (items.length > 0) { michael@0: items[0].setAttribute("first", true); michael@0: items[items.length - 1].setAttribute("last", true); michael@0: } michael@0: michael@0: item = this._listBox.querySelector("richlistitem:not([remote='true'])[first]"); michael@0: if (item) michael@0: item.removeAttribute("first"); michael@0: item = this._listBox.querySelector("richlistitem:not([remote='true'])[last]"); michael@0: if (item) michael@0: item.removeAttribute("last"); michael@0: items = this._listBox.querySelectorAll("richlistitem:not([remote='true'])"); michael@0: if (items.length > 0) { michael@0: items[0].setAttribute("first", true); michael@0: items[items.length - 1].setAttribute("last", true); michael@0: } michael@0: michael@0: }, michael@0: michael@0: onSortChanged: function gSearchView_onSortChanged(aSortBy, aAscending) { michael@0: var footer = this._listBox.lastChild; michael@0: this._listBox.removeChild(footer); michael@0: michael@0: sortList(this._listBox, aSortBy, aAscending); michael@0: this.updateListAttributes(); michael@0: michael@0: this._listBox.appendChild(footer); michael@0: }, michael@0: michael@0: onDownloadCancelled: function gSearchView_onDownloadCancelled(aInstall) { michael@0: this.removeInstall(aInstall); michael@0: }, michael@0: michael@0: onInstallCancelled: function gSearchView_onInstallCancelled(aInstall) { michael@0: this.removeInstall(aInstall); michael@0: }, michael@0: michael@0: removeInstall: function gSearchView_removeInstall(aInstall) { michael@0: for (let item of this._listBox.childNodes) { michael@0: if (item.mInstall == aInstall) { michael@0: this._listBox.removeChild(item); michael@0: return; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: getSelectedAddon: function gSearchView_getSelectedAddon() { michael@0: var item = this._listBox.selectedItem; michael@0: if (item) michael@0: return item.mAddon; michael@0: return null; michael@0: }, michael@0: michael@0: getListItemForID: function gSearchView_getListItemForID(aId) { michael@0: var listitem = this._listBox.firstChild; michael@0: while (listitem) { michael@0: if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId) michael@0: return listitem; michael@0: listitem = listitem.nextSibling; michael@0: } michael@0: return null; michael@0: } michael@0: }; michael@0: michael@0: michael@0: var gListView = { michael@0: node: null, michael@0: _listBox: null, michael@0: _emptyNotice: null, michael@0: _type: null, michael@0: michael@0: initialize: function gListView_initialize() { michael@0: this.node = document.getElementById("list-view"); michael@0: this._listBox = document.getElementById("addon-list"); michael@0: this._emptyNotice = document.getElementById("addon-list-empty"); michael@0: michael@0: var self = this; michael@0: this._listBox.addEventListener("keydown", function listbox_onKeydown(aEvent) { michael@0: if (aEvent.keyCode == aEvent.DOM_VK_RETURN) { michael@0: var item = self._listBox.selectedItem; michael@0: if (item) michael@0: item.showInDetailView(); michael@0: } michael@0: }, false); michael@0: }, michael@0: michael@0: show: function gListView_show(aType, aRequest) { michael@0: if (!(aType in AddonManager.addonTypes)) michael@0: throw Components.Exception("Attempting to show unknown type " + aType, Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: this._type = aType; michael@0: this.node.setAttribute("type", aType); michael@0: this.showEmptyNotice(false); michael@0: michael@0: while (this._listBox.itemCount > 0) michael@0: this._listBox.removeItemAt(0); michael@0: michael@0: var self = this; michael@0: getAddonsAndInstalls(aType, function show_getAddonsAndInstalls(aAddonsList, aInstallsList) { michael@0: if (gViewController && aRequest != gViewController.currentViewRequest) michael@0: return; michael@0: michael@0: var elements = []; michael@0: michael@0: for (let addonItem of aAddonsList) michael@0: elements.push(createItem(addonItem)); michael@0: michael@0: for (let installItem of aInstallsList) michael@0: elements.push(createItem(installItem, true)); michael@0: michael@0: self.showEmptyNotice(elements.length == 0); michael@0: if (elements.length > 0) { michael@0: sortElements(elements, ["uiState", "name"], true); michael@0: for (let element of elements) michael@0: self._listBox.appendChild(element); michael@0: } michael@0: michael@0: gEventManager.registerInstallListener(self); michael@0: gViewController.updateCommands(); michael@0: gViewController.notifyViewChanged(); michael@0: }); michael@0: }, michael@0: michael@0: hide: function gListView_hide() { michael@0: gEventManager.unregisterInstallListener(this); michael@0: doPendingUninstalls(this._listBox); michael@0: }, michael@0: michael@0: showEmptyNotice: function gListView_showEmptyNotice(aShow) { michael@0: this._emptyNotice.hidden = !aShow; michael@0: }, michael@0: michael@0: onSortChanged: function gListView_onSortChanged(aSortBy, aAscending) { michael@0: sortList(this._listBox, aSortBy, aAscending); michael@0: }, michael@0: michael@0: onExternalInstall: function gListView_onExternalInstall(aAddon, aExistingAddon, aRequiresRestart) { michael@0: // The existing list item will take care of upgrade installs michael@0: if (aExistingAddon) michael@0: return; michael@0: michael@0: this.addItem(aAddon); michael@0: }, michael@0: michael@0: onDownloadStarted: function gListView_onDownloadStarted(aInstall) { michael@0: this.addItem(aInstall, true); michael@0: }, michael@0: michael@0: onInstallStarted: function gListView_onInstallStarted(aInstall) { michael@0: this.addItem(aInstall, true); michael@0: }, michael@0: michael@0: onDownloadCancelled: function gListView_onDownloadCancelled(aInstall) { michael@0: this.removeItem(aInstall, true); michael@0: }, michael@0: michael@0: onInstallCancelled: function gListView_onInstallCancelled(aInstall) { michael@0: this.removeItem(aInstall, true); michael@0: }, michael@0: michael@0: onInstallEnded: function gListView_onInstallEnded(aInstall) { michael@0: // Remove any install entries for upgrades, their status will appear against michael@0: // the existing item michael@0: if (aInstall.existingAddon) michael@0: this.removeItem(aInstall, true); michael@0: michael@0: if (aInstall.addon.type == "experiment") { michael@0: let item = this.getListItemForID(aInstall.addon.id); michael@0: if (item) { michael@0: item.endDate = getExperimentEndDate(aInstall.addon); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: addItem: function gListView_addItem(aObj, aIsInstall) { michael@0: if (aObj.type != this._type) michael@0: return; michael@0: michael@0: if (aIsInstall && aObj.existingAddon) michael@0: return; michael@0: michael@0: let prop = aIsInstall ? "mInstall" : "mAddon"; michael@0: for (let item of this._listBox.childNodes) { michael@0: if (item[prop] == aObj) michael@0: return; michael@0: } michael@0: michael@0: let item = createItem(aObj, aIsInstall); michael@0: this._listBox.insertBefore(item, this._listBox.firstChild); michael@0: this.showEmptyNotice(false); michael@0: }, michael@0: michael@0: removeItem: function gListView_removeItem(aObj, aIsInstall) { michael@0: let prop = aIsInstall ? "mInstall" : "mAddon"; michael@0: michael@0: for (let item of this._listBox.childNodes) { michael@0: if (item[prop] == aObj) { michael@0: this._listBox.removeChild(item); michael@0: this.showEmptyNotice(this._listBox.itemCount == 0); michael@0: return; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: getSelectedAddon: function gListView_getSelectedAddon() { michael@0: var item = this._listBox.selectedItem; michael@0: if (item) michael@0: return item.mAddon; michael@0: return null; michael@0: }, michael@0: michael@0: getListItemForID: function gListView_getListItemForID(aId) { michael@0: var listitem = this._listBox.firstChild; michael@0: while (listitem) { michael@0: if (listitem.getAttribute("status") == "installed" && listitem.mAddon.id == aId) michael@0: return listitem; michael@0: listitem = listitem.nextSibling; michael@0: } michael@0: return null; michael@0: } michael@0: }; michael@0: michael@0: michael@0: var gDetailView = { michael@0: node: null, michael@0: _addon: null, michael@0: _loadingTimer: null, michael@0: _autoUpdate: null, michael@0: michael@0: initialize: function gDetailView_initialize() { michael@0: this.node = document.getElementById("detail-view"); michael@0: michael@0: this._autoUpdate = document.getElementById("detail-autoUpdate"); michael@0: michael@0: var self = this; michael@0: this._autoUpdate.addEventListener("command", function autoUpdate_onCommand() { michael@0: self._addon.applyBackgroundUpdates = self._autoUpdate.value; michael@0: }, true); michael@0: }, michael@0: michael@0: shutdown: function gDetailView_shutdown() { michael@0: AddonManager.removeManagerListener(this); michael@0: }, michael@0: michael@0: onUpdateModeChanged: function gDetailView_onUpdateModeChanged() { michael@0: this.onPropertyChanged(["applyBackgroundUpdates"]); michael@0: }, michael@0: michael@0: _updateView: function gDetailView_updateView(aAddon, aIsRemote, aScrollToPreferences) { michael@0: AddonManager.addManagerListener(this); michael@0: this.clearLoading(); michael@0: michael@0: this._addon = aAddon; michael@0: gEventManager.registerAddonListener(this, aAddon.id); michael@0: gEventManager.registerInstallListener(this); michael@0: michael@0: this.node.setAttribute("type", aAddon.type); michael@0: michael@0: // If the search category isn't selected then make sure to select the michael@0: // correct category michael@0: if (gCategories.selected != "addons://search/") michael@0: gCategories.select("addons://list/" + aAddon.type); michael@0: michael@0: document.getElementById("detail-name").textContent = aAddon.name; michael@0: var icon = aAddon.icon64URL ? aAddon.icon64URL : aAddon.iconURL; michael@0: document.getElementById("detail-icon").src = icon ? icon : ""; michael@0: document.getElementById("detail-creator").setCreator(aAddon.creator, aAddon.homepageURL); michael@0: michael@0: var version = document.getElementById("detail-version"); michael@0: if (shouldShowVersionNumber(aAddon)) { michael@0: version.hidden = false; michael@0: version.value = aAddon.version; michael@0: } else { michael@0: version.hidden = true; michael@0: } michael@0: michael@0: var screenshot = document.getElementById("detail-screenshot"); michael@0: if (aAddon.screenshots && aAddon.screenshots.length > 0) { michael@0: if (aAddon.screenshots[0].thumbnailURL) { michael@0: screenshot.src = aAddon.screenshots[0].thumbnailURL; michael@0: screenshot.width = aAddon.screenshots[0].thumbnailWidth; michael@0: screenshot.height = aAddon.screenshots[0].thumbnailHeight; michael@0: } else { michael@0: screenshot.src = aAddon.screenshots[0].url; michael@0: screenshot.width = aAddon.screenshots[0].width; michael@0: screenshot.height = aAddon.screenshots[0].height; michael@0: } michael@0: screenshot.setAttribute("loading", "true"); michael@0: screenshot.hidden = false; michael@0: } else { michael@0: screenshot.hidden = true; michael@0: } michael@0: michael@0: var desc = document.getElementById("detail-desc"); michael@0: desc.textContent = aAddon.description; michael@0: michael@0: var fullDesc = document.getElementById("detail-fulldesc"); michael@0: if (aAddon.fullDescription) { michael@0: fullDesc.textContent = aAddon.fullDescription; michael@0: fullDesc.hidden = false; michael@0: } else { michael@0: fullDesc.hidden = true; michael@0: } michael@0: michael@0: var contributions = document.getElementById("detail-contributions"); michael@0: if ("contributionURL" in aAddon && aAddon.contributionURL) { michael@0: contributions.hidden = false; michael@0: var amount = document.getElementById("detail-contrib-suggested"); michael@0: if (aAddon.contributionAmount) { michael@0: amount.value = gStrings.ext.formatStringFromName("contributionAmount2", michael@0: [aAddon.contributionAmount], michael@0: 1); michael@0: amount.hidden = false; michael@0: } else { michael@0: amount.hidden = true; michael@0: } michael@0: } else { michael@0: contributions.hidden = true; michael@0: } michael@0: michael@0: if ("purchaseURL" in aAddon && aAddon.purchaseURL) { michael@0: var purchase = document.getElementById("detail-purchase-btn"); michael@0: purchase.label = gStrings.ext.formatStringFromName("cmd.purchaseAddon.label", michael@0: [aAddon.purchaseDisplayAmount], michael@0: 1); michael@0: purchase.accesskey = gStrings.ext.GetStringFromName("cmd.purchaseAddon.accesskey"); michael@0: } michael@0: michael@0: var updateDateRow = document.getElementById("detail-dateUpdated"); michael@0: if (aAddon.updateDate) { michael@0: var date = formatDate(aAddon.updateDate); michael@0: updateDateRow.value = date; michael@0: } else { michael@0: updateDateRow.value = null; michael@0: } michael@0: michael@0: // TODO if the add-on was downloaded from releases.mozilla.org link to the michael@0: // AMO profile (bug 590344) michael@0: if (false) { michael@0: document.getElementById("detail-repository-row").hidden = false; michael@0: document.getElementById("detail-homepage-row").hidden = true; michael@0: var repository = document.getElementById("detail-repository"); michael@0: repository.value = aAddon.homepageURL; michael@0: repository.href = aAddon.homepageURL; michael@0: } else if (aAddon.homepageURL) { michael@0: document.getElementById("detail-repository-row").hidden = true; michael@0: document.getElementById("detail-homepage-row").hidden = false; michael@0: var homepage = document.getElementById("detail-homepage"); michael@0: homepage.value = aAddon.homepageURL; michael@0: homepage.href = aAddon.homepageURL; michael@0: } else { michael@0: document.getElementById("detail-repository-row").hidden = true; michael@0: document.getElementById("detail-homepage-row").hidden = true; michael@0: } michael@0: michael@0: var rating = document.getElementById("detail-rating"); michael@0: if (aAddon.averageRating) { michael@0: rating.averageRating = aAddon.averageRating; michael@0: rating.hidden = false; michael@0: } else { michael@0: rating.hidden = true; michael@0: } michael@0: michael@0: var reviews = document.getElementById("detail-reviews"); michael@0: if (aAddon.reviewURL) { michael@0: var text = gStrings.ext.GetStringFromName("numReviews"); michael@0: text = PluralForm.get(aAddon.reviewCount, text) michael@0: text = text.replace("#1", aAddon.reviewCount); michael@0: reviews.value = text; michael@0: reviews.hidden = false; michael@0: reviews.href = aAddon.reviewURL; michael@0: } else { michael@0: reviews.hidden = true; michael@0: } michael@0: michael@0: document.getElementById("detail-rating-row").hidden = !aAddon.averageRating && !aAddon.reviewURL; michael@0: michael@0: var sizeRow = document.getElementById("detail-size"); michael@0: if (aAddon.size && aIsRemote) { michael@0: let [size, unit] = DownloadUtils.convertByteUnits(parseInt(aAddon.size)); michael@0: let formatted = gStrings.dl.GetStringFromName("doneSize"); michael@0: formatted = formatted.replace("#1", size).replace("#2", unit); michael@0: sizeRow.value = formatted; michael@0: } else { michael@0: sizeRow.value = null; michael@0: } michael@0: michael@0: var downloadsRow = document.getElementById("detail-downloads"); michael@0: if (aAddon.totalDownloads && aIsRemote) { michael@0: var downloads = aAddon.totalDownloads; michael@0: downloadsRow.value = downloads; michael@0: } else { michael@0: downloadsRow.value = null; michael@0: } michael@0: michael@0: var canUpdate = !aIsRemote && hasPermission(aAddon, "upgrade") && aAddon.id != AddonManager.hotfixID; michael@0: document.getElementById("detail-updates-row").hidden = !canUpdate; michael@0: michael@0: if ("applyBackgroundUpdates" in aAddon) { michael@0: this._autoUpdate.hidden = false; michael@0: this._autoUpdate.value = aAddon.applyBackgroundUpdates; michael@0: let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon); michael@0: document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates; michael@0: } else { michael@0: this._autoUpdate.hidden = true; michael@0: document.getElementById("detail-findUpdates-btn").hidden = false; michael@0: } michael@0: michael@0: document.getElementById("detail-prefs-btn").hidden = !aIsRemote && michael@0: !gViewController.commands.cmd_showItemPreferences.isEnabled(aAddon); michael@0: michael@0: var gridRows = document.querySelectorAll("#detail-grid rows row"); michael@0: let first = true; michael@0: for (let gridRow of gridRows) { michael@0: if (first && window.getComputedStyle(gridRow, null).getPropertyValue("display") != "none") { michael@0: gridRow.setAttribute("first-row", true); michael@0: first = false; michael@0: } else { michael@0: gridRow.removeAttribute("first-row"); michael@0: } michael@0: } michael@0: michael@0: if (this._addon.type == "experiment") { michael@0: let prefix = "details.experiment."; michael@0: let active = this._addon.isActive; michael@0: michael@0: let stateKey = prefix + "state." + (active ? "active" : "complete"); michael@0: let node = document.getElementById("detail-experiment-state"); michael@0: node.value = gStrings.ext.GetStringFromName(stateKey); michael@0: michael@0: let now = Date.now(); michael@0: let end = getExperimentEndDate(this._addon); michael@0: let days = Math.abs(end - now) / (24 * 60 * 60 * 1000); michael@0: michael@0: let timeKey = prefix + "time."; michael@0: let timeMessage; michael@0: if (days < 1) { michael@0: timeKey += (active ? "endsToday" : "endedToday"); michael@0: timeMessage = gStrings.ext.GetStringFromName(timeKey); michael@0: } else { michael@0: timeKey += (active ? "daysRemaining" : "daysPassed"); michael@0: days = Math.round(days); michael@0: let timeString = gStrings.ext.GetStringFromName(timeKey); michael@0: timeMessage = PluralForm.get(days, timeString) michael@0: .replace("#1", days); michael@0: } michael@0: michael@0: document.getElementById("detail-experiment-time").value = timeMessage; michael@0: } michael@0: michael@0: this.fillSettingsRows(aScrollToPreferences, (function updateView_fillSettingsRows() { michael@0: this.updateState(); michael@0: gViewController.notifyViewChanged(); michael@0: }).bind(this)); michael@0: }, michael@0: michael@0: show: function gDetailView_show(aAddonId, aRequest) { michael@0: let index = aAddonId.indexOf("/preferences"); michael@0: let scrollToPreferences = false; michael@0: if (index >= 0) { michael@0: aAddonId = aAddonId.substring(0, index); michael@0: scrollToPreferences = true; michael@0: } michael@0: michael@0: var self = this; michael@0: this._loadingTimer = setTimeout(function loadTimeOutTimer() { michael@0: self.node.setAttribute("loading-extended", true); michael@0: }, LOADING_MSG_DELAY); michael@0: michael@0: var view = gViewController.currentViewId; michael@0: michael@0: AddonManager.getAddonByID(aAddonId, function show_getAddonByID(aAddon) { michael@0: if (gViewController && aRequest != gViewController.currentViewRequest) michael@0: return; michael@0: michael@0: if (aAddon) { michael@0: self._updateView(aAddon, false, scrollToPreferences); michael@0: return; michael@0: } michael@0: michael@0: // Look for an add-on pending install michael@0: AddonManager.getAllInstalls(function show_getAllInstalls(aInstalls) { michael@0: for (let install of aInstalls) { michael@0: if (install.state == AddonManager.STATE_INSTALLED && michael@0: install.addon.id == aAddonId) { michael@0: self._updateView(install.addon, false); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (aAddonId in gCachedAddons) { michael@0: self._updateView(gCachedAddons[aAddonId], true); michael@0: return; michael@0: } michael@0: michael@0: // This might happen due to session restore restoring us back to an michael@0: // add-on that doesn't exist but otherwise shouldn't normally happen. michael@0: // Either way just revert to the default view. michael@0: gViewController.replaceView(VIEW_DEFAULT); michael@0: }); michael@0: }); michael@0: }, michael@0: michael@0: hide: function gDetailView_hide() { michael@0: AddonManager.removeManagerListener(this); michael@0: this.clearLoading(); michael@0: if (this._addon) { michael@0: if (hasInlineOptions(this._addon)) { michael@0: Services.obs.notifyObservers(document, michael@0: AddonManager.OPTIONS_NOTIFICATION_HIDDEN, michael@0: this._addon.id); michael@0: } michael@0: michael@0: gEventManager.unregisterAddonListener(this, this._addon.id); michael@0: gEventManager.unregisterInstallListener(this); michael@0: this._addon = null; michael@0: michael@0: // Flush the preferences to disk so they survive any crash michael@0: if (this.node.getElementsByTagName("setting").length) michael@0: Services.prefs.savePrefFile(null); michael@0: } michael@0: }, michael@0: michael@0: updateState: function gDetailView_updateState() { michael@0: gViewController.updateCommands(); michael@0: michael@0: var pending = this._addon.pendingOperations; michael@0: if (pending != AddonManager.PENDING_NONE) { michael@0: this.node.removeAttribute("notification"); michael@0: michael@0: var pending = null; michael@0: const PENDING_OPERATIONS = ["enable", "disable", "install", "uninstall", michael@0: "upgrade"]; michael@0: for (let op of PENDING_OPERATIONS) { michael@0: if (isPending(this._addon, op)) michael@0: pending = op; michael@0: } michael@0: michael@0: this.node.setAttribute("pending", pending); michael@0: document.getElementById("detail-pending").textContent = gStrings.ext.formatStringFromName( michael@0: "details.notification." + pending, michael@0: [this._addon.name, gStrings.brandShortName], 2 michael@0: ); michael@0: } else { michael@0: this.node.removeAttribute("pending"); michael@0: michael@0: if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) { michael@0: this.node.setAttribute("notification", "error"); michael@0: document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( michael@0: "details.notification.blocked", michael@0: [this._addon.name], 1 michael@0: ); michael@0: var errorLink = document.getElementById("detail-error-link"); michael@0: errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link"); michael@0: errorLink.href = this._addon.blocklistURL; michael@0: errorLink.hidden = false; michael@0: } else if (!this._addon.isCompatible && (AddonManager.checkCompatibility || michael@0: (this._addon.blocklistState != Ci.nsIBlocklistService.STATE_SOFTBLOCKED))) { michael@0: this.node.setAttribute("notification", "warning"); michael@0: document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( michael@0: "details.notification.incompatible", michael@0: [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3 michael@0: ); michael@0: document.getElementById("detail-warning-link").hidden = true; michael@0: } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) { michael@0: this.node.setAttribute("notification", "warning"); michael@0: document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( michael@0: "details.notification.softblocked", michael@0: [this._addon.name], 1 michael@0: ); michael@0: var warningLink = document.getElementById("detail-warning-link"); michael@0: warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link"); michael@0: warningLink.href = this._addon.blocklistURL; michael@0: warningLink.hidden = false; michael@0: } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) { michael@0: this.node.setAttribute("notification", "warning"); michael@0: document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName( michael@0: "details.notification.outdated", michael@0: [this._addon.name], 1 michael@0: ); michael@0: var warningLink = document.getElementById("detail-warning-link"); michael@0: warningLink.value = gStrings.ext.GetStringFromName("details.notification.outdated.link"); michael@0: warningLink.href = Services.urlFormatter.formatURLPref("plugins.update.url"); michael@0: warningLink.hidden = false; michael@0: } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) { michael@0: this.node.setAttribute("notification", "error"); michael@0: document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( michael@0: "details.notification.vulnerableUpdatable", michael@0: [this._addon.name], 1 michael@0: ); michael@0: var errorLink = document.getElementById("detail-error-link"); michael@0: errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableUpdatable.link"); michael@0: errorLink.href = this._addon.blocklistURL; michael@0: errorLink.hidden = false; michael@0: } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) { michael@0: this.node.setAttribute("notification", "error"); michael@0: document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName( michael@0: "details.notification.vulnerableNoUpdate", michael@0: [this._addon.name], 1 michael@0: ); michael@0: var errorLink = document.getElementById("detail-error-link"); michael@0: errorLink.value = gStrings.ext.GetStringFromName("details.notification.vulnerableNoUpdate.link"); michael@0: errorLink.href = this._addon.blocklistURL; michael@0: errorLink.hidden = false; michael@0: } else { michael@0: this.node.removeAttribute("notification"); michael@0: } michael@0: } michael@0: michael@0: let menulist = document.getElementById("detail-state-menulist"); michael@0: let addonType = AddonManager.addonTypes[this._addon.type]; michael@0: if (addonType.flags & AddonManager.TYPE_SUPPORTS_ASK_TO_ACTIVATE && michael@0: (hasPermission(this._addon, "ask_to_activate") || michael@0: hasPermission(this._addon, "enable") || michael@0: hasPermission(this._addon, "disable"))) { michael@0: let askItem = document.getElementById("detail-ask-to-activate-menuitem"); michael@0: let alwaysItem = document.getElementById("detail-always-activate-menuitem"); michael@0: let neverItem = document.getElementById("detail-never-activate-menuitem"); michael@0: if (this._addon.userDisabled === true) { michael@0: menulist.selectedItem = neverItem; michael@0: } else if (this._addon.userDisabled == AddonManager.STATE_ASK_TO_ACTIVATE) { michael@0: menulist.selectedItem = askItem; michael@0: } else { michael@0: menulist.selectedItem = alwaysItem; michael@0: } michael@0: menulist.hidden = false; michael@0: } else { michael@0: menulist.hidden = true; michael@0: } michael@0: michael@0: this.node.setAttribute("active", this._addon.isActive); michael@0: }, michael@0: michael@0: clearLoading: function gDetailView_clearLoading() { michael@0: if (this._loadingTimer) { michael@0: clearTimeout(this._loadingTimer); michael@0: this._loadingTimer = null; michael@0: } michael@0: michael@0: this.node.removeAttribute("loading-extended"); michael@0: }, michael@0: michael@0: emptySettingsRows: function gDetailView_emptySettingsRows() { michael@0: var lastRow = document.getElementById("detail-downloads"); michael@0: var rows = lastRow.parentNode; michael@0: while (lastRow.nextSibling) michael@0: rows.removeChild(rows.lastChild); michael@0: }, michael@0: michael@0: fillSettingsRows: function gDetailView_fillSettingsRows(aScrollToPreferences, aCallback) { michael@0: this.emptySettingsRows(); michael@0: if (!hasInlineOptions(this._addon)) { michael@0: if (aCallback) michael@0: aCallback(); michael@0: return; michael@0: } michael@0: michael@0: // This function removes and returns the text content of aNode without michael@0: // removing any child elements. Removing the text nodes ensures any XBL michael@0: // bindings apply properly. michael@0: function stripTextNodes(aNode) { michael@0: var text = ''; michael@0: for (var i = 0; i < aNode.childNodes.length; i++) { michael@0: if (aNode.childNodes[i].nodeType != document.ELEMENT_NODE) { michael@0: text += aNode.childNodes[i].textContent; michael@0: aNode.removeChild(aNode.childNodes[i--]); michael@0: } else { michael@0: text += stripTextNodes(aNode.childNodes[i]); michael@0: } michael@0: } michael@0: return text; michael@0: } michael@0: michael@0: var rows = document.getElementById("detail-downloads").parentNode; michael@0: michael@0: try { michael@0: var xhr = new XMLHttpRequest(); michael@0: xhr.open("GET", this._addon.optionsURL, true); michael@0: xhr.responseType = "xml"; michael@0: xhr.onload = (function fillSettingsRows_onload() { michael@0: var xml = xhr.responseXML; michael@0: var settings = xml.querySelectorAll(":root > setting"); michael@0: michael@0: var firstSetting = null; michael@0: for (var setting of settings) { michael@0: michael@0: var desc = stripTextNodes(setting).trim(); michael@0: if (!setting.hasAttribute("desc")) michael@0: setting.setAttribute("desc", desc); michael@0: michael@0: var type = setting.getAttribute("type"); michael@0: if (type == "file" || type == "directory") michael@0: setting.setAttribute("fullpath", "true"); michael@0: michael@0: setting = document.importNode(setting, true); michael@0: var style = setting.getAttribute("style"); michael@0: if (style) { michael@0: setting.removeAttribute("style"); michael@0: setting.setAttribute("style", style); michael@0: } michael@0: michael@0: rows.appendChild(setting); michael@0: var visible = window.getComputedStyle(setting, null).getPropertyValue("display") != "none"; michael@0: if (!firstSetting && visible) { michael@0: setting.setAttribute("first-row", true); michael@0: firstSetting = setting; michael@0: } michael@0: } michael@0: michael@0: // Ensure the page has loaded and force the XBL bindings to be synchronously applied, michael@0: // then notify observers. michael@0: if (gViewController.viewPort.selectedPanel.hasAttribute("loading")) { michael@0: gDetailView.node.addEventListener("ViewChanged", function viewChangedEventListener() { michael@0: gDetailView.node.removeEventListener("ViewChanged", viewChangedEventListener, false); michael@0: if (firstSetting) michael@0: firstSetting.clientTop; michael@0: Services.obs.notifyObservers(document, michael@0: AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, michael@0: gDetailView._addon.id); michael@0: if (aScrollToPreferences) michael@0: gDetailView.scrollToPreferencesRows(); michael@0: }, false); michael@0: } else { michael@0: if (firstSetting) michael@0: firstSetting.clientTop; michael@0: Services.obs.notifyObservers(document, michael@0: AddonManager.OPTIONS_NOTIFICATION_DISPLAYED, michael@0: this._addon.id); michael@0: if (aScrollToPreferences) michael@0: gDetailView.scrollToPreferencesRows(); michael@0: } michael@0: if (aCallback) michael@0: aCallback(); michael@0: }).bind(this); michael@0: xhr.onerror = function fillSettingsRows_onerror(aEvent) { michael@0: Cu.reportError("Error " + aEvent.target.status + michael@0: " occurred while receiving " + this._addon.optionsURL); michael@0: if (aCallback) michael@0: aCallback(); michael@0: }; michael@0: xhr.send(); michael@0: } catch(e) { michael@0: Cu.reportError(e); michael@0: if (aCallback) michael@0: aCallback(); michael@0: } michael@0: }, michael@0: michael@0: scrollToPreferencesRows: function gDetailView_scrollToPreferencesRows() { michael@0: // We find this row, rather than remembering it from above, michael@0: // in case it has been changed by the observers. michael@0: let firstRow = gDetailView.node.querySelector('setting[first-row="true"]'); michael@0: if (firstRow) { michael@0: let top = firstRow.boxObject.y; michael@0: top -= parseInt(window.getComputedStyle(firstRow, null).getPropertyValue("margin-top")); michael@0: michael@0: let detailViewBoxObject = gDetailView.node.boxObject; michael@0: top -= detailViewBoxObject.y; michael@0: michael@0: detailViewBoxObject.QueryInterface(Ci.nsIScrollBoxObject); michael@0: detailViewBoxObject.scrollTo(0, top); michael@0: } michael@0: }, michael@0: michael@0: getSelectedAddon: function gDetailView_getSelectedAddon() { michael@0: return this._addon; michael@0: }, michael@0: michael@0: onEnabling: function gDetailView_onEnabling() { michael@0: this.updateState(); michael@0: }, michael@0: michael@0: onEnabled: function gDetailView_onEnabled() { michael@0: this.updateState(); michael@0: this.fillSettingsRows(); michael@0: }, michael@0: michael@0: onDisabling: function gDetailView_onDisabling(aNeedsRestart) { michael@0: this.updateState(); michael@0: if (!aNeedsRestart && hasInlineOptions(this._addon)) { michael@0: Services.obs.notifyObservers(document, michael@0: AddonManager.OPTIONS_NOTIFICATION_HIDDEN, michael@0: this._addon.id); michael@0: } michael@0: }, michael@0: michael@0: onDisabled: function gDetailView_onDisabled() { michael@0: this.updateState(); michael@0: this.emptySettingsRows(); michael@0: }, michael@0: michael@0: onUninstalling: function gDetailView_onUninstalling() { michael@0: this.updateState(); michael@0: }, michael@0: michael@0: onUninstalled: function gDetailView_onUninstalled() { michael@0: gViewController.popState(); michael@0: }, michael@0: michael@0: onOperationCancelled: function gDetailView_onOperationCancelled() { michael@0: this.updateState(); michael@0: }, michael@0: michael@0: onPropertyChanged: function gDetailView_onPropertyChanged(aProperties) { michael@0: if (aProperties.indexOf("applyBackgroundUpdates") != -1) { michael@0: this._autoUpdate.value = this._addon.applyBackgroundUpdates; michael@0: let hideFindUpdates = AddonManager.shouldAutoUpdate(this._addon); michael@0: document.getElementById("detail-findUpdates-btn").hidden = hideFindUpdates; michael@0: } michael@0: michael@0: if (aProperties.indexOf("appDisabled") != -1 || michael@0: aProperties.indexOf("userDisabled") != -1) michael@0: this.updateState(); michael@0: }, michael@0: michael@0: onExternalInstall: function gDetailView_onExternalInstall(aAddon, aExistingAddon, aNeedsRestart) { michael@0: // Only care about upgrades for the currently displayed add-on michael@0: if (!aExistingAddon || aExistingAddon.id != this._addon.id) michael@0: return; michael@0: michael@0: if (!aNeedsRestart) michael@0: this._updateView(aAddon, false); michael@0: else michael@0: this.updateState(); michael@0: }, michael@0: michael@0: onInstallCancelled: function gDetailView_onInstallCancelled(aInstall) { michael@0: if (aInstall.addon.id == this._addon.id) michael@0: gViewController.popState(); michael@0: } michael@0: }; michael@0: michael@0: michael@0: var gUpdatesView = { michael@0: node: null, michael@0: _listBox: null, michael@0: _emptyNotice: null, michael@0: _sorters: null, michael@0: _updateSelected: null, michael@0: _categoryItem: null, michael@0: michael@0: initialize: function gUpdatesView_initialize() { michael@0: this.node = document.getElementById("updates-view"); michael@0: this._listBox = document.getElementById("updates-list"); michael@0: this._emptyNotice = document.getElementById("updates-list-empty"); michael@0: this._sorters = document.getElementById("updates-sorters"); michael@0: this._sorters.handler = this; michael@0: michael@0: this._categoryItem = gCategories.get("addons://updates/available"); michael@0: michael@0: this._updateSelected = document.getElementById("update-selected-btn"); michael@0: this._updateSelected.addEventListener("command", function updateSelected_onCommand() { michael@0: gUpdatesView.installSelected(); michael@0: }, false); michael@0: michael@0: this.updateAvailableCount(true); michael@0: michael@0: AddonManager.addAddonListener(this); michael@0: AddonManager.addInstallListener(this); michael@0: }, michael@0: michael@0: shutdown: function gUpdatesView_shutdown() { michael@0: AddonManager.removeAddonListener(this); michael@0: AddonManager.removeInstallListener(this); michael@0: }, michael@0: michael@0: show: function gUpdatesView_show(aType, aRequest) { michael@0: document.getElementById("empty-availableUpdates-msg").hidden = aType != "available"; michael@0: document.getElementById("empty-recentUpdates-msg").hidden = aType != "recent"; michael@0: this.showEmptyNotice(false); michael@0: michael@0: while (this._listBox.itemCount > 0) michael@0: this._listBox.removeItemAt(0); michael@0: michael@0: this.node.setAttribute("updatetype", aType); michael@0: if (aType == "recent") michael@0: this._showRecentUpdates(aRequest); michael@0: else michael@0: this._showAvailableUpdates(false, aRequest); michael@0: }, michael@0: michael@0: hide: function gUpdatesView_hide() { michael@0: this._updateSelected.hidden = true; michael@0: this._categoryItem.disabled = this._categoryItem.badgeCount == 0; michael@0: doPendingUninstalls(this._listBox); michael@0: }, michael@0: michael@0: _showRecentUpdates: function gUpdatesView_showRecentUpdates(aRequest) { michael@0: var self = this; michael@0: AddonManager.getAllAddons(function showRecentUpdates_getAllAddons(aAddonsList) { michael@0: if (gViewController && aRequest != gViewController.currentViewRequest) michael@0: return; michael@0: michael@0: var elements = []; michael@0: let threshold = Date.now() - UPDATES_RECENT_TIMESPAN; michael@0: for (let addon of aAddonsList) { michael@0: if (!addon.updateDate || addon.updateDate.getTime() < threshold) michael@0: continue; michael@0: michael@0: elements.push(createItem(addon)); michael@0: } michael@0: michael@0: self.showEmptyNotice(elements.length == 0); michael@0: if (elements.length > 0) { michael@0: sortElements(elements, [self._sorters.sortBy], self._sorters.ascending); michael@0: for (let element of elements) michael@0: self._listBox.appendChild(element); michael@0: } michael@0: michael@0: gViewController.notifyViewChanged(); michael@0: }); michael@0: }, michael@0: michael@0: _showAvailableUpdates: function gUpdatesView_showAvailableUpdates(aIsRefresh, aRequest) { michael@0: /* Disable the Update Selected button so it can't get clicked michael@0: before everything is initialized asynchronously. michael@0: It will get re-enabled by maybeDisableUpdateSelected(). */ michael@0: this._updateSelected.disabled = true; michael@0: michael@0: var self = this; michael@0: AddonManager.getAllInstalls(function showAvailableUpdates_getAllInstalls(aInstallsList) { michael@0: if (!aIsRefresh && gViewController && aRequest && michael@0: aRequest != gViewController.currentViewRequest) michael@0: return; michael@0: michael@0: if (aIsRefresh) { michael@0: self.showEmptyNotice(false); michael@0: self._updateSelected.hidden = true; michael@0: michael@0: while (self._listBox.itemCount > 0) michael@0: self._listBox.removeItemAt(0); michael@0: } michael@0: michael@0: var elements = []; michael@0: michael@0: for (let install of aInstallsList) { michael@0: if (!self.isManualUpdate(install)) michael@0: continue; michael@0: michael@0: let item = createItem(install.existingAddon); michael@0: item.setAttribute("upgrade", true); michael@0: item.addEventListener("IncludeUpdateChanged", function item_onIncludeUpdateChanged() { michael@0: self.maybeDisableUpdateSelected(); michael@0: }, false); michael@0: elements.push(item); michael@0: } michael@0: michael@0: self.showEmptyNotice(elements.length == 0); michael@0: if (elements.length > 0) { michael@0: self._updateSelected.hidden = false; michael@0: sortElements(elements, [self._sorters.sortBy], self._sorters.ascending); michael@0: for (let element of elements) michael@0: self._listBox.appendChild(element); michael@0: } michael@0: michael@0: // ensure badge count is in sync michael@0: self._categoryItem.badgeCount = self._listBox.itemCount; michael@0: michael@0: gViewController.notifyViewChanged(); michael@0: }); michael@0: }, michael@0: michael@0: showEmptyNotice: function gUpdatesView_showEmptyNotice(aShow) { michael@0: this._emptyNotice.hidden = !aShow; michael@0: }, michael@0: michael@0: isManualUpdate: function gUpdatesView_isManualUpdate(aInstall, aOnlyAvailable) { michael@0: var isManual = aInstall.existingAddon && michael@0: !AddonManager.shouldAutoUpdate(aInstall.existingAddon); michael@0: if (isManual && aOnlyAvailable) michael@0: return isInState(aInstall, "available"); michael@0: return isManual; michael@0: }, michael@0: michael@0: maybeRefresh: function gUpdatesView_maybeRefresh() { michael@0: if (gViewController.currentViewId == "addons://updates/available") michael@0: this._showAvailableUpdates(true); michael@0: this.updateAvailableCount(); michael@0: }, michael@0: michael@0: updateAvailableCount: function gUpdatesView_updateAvailableCount(aInitializing) { michael@0: if (aInitializing) michael@0: gPendingInitializations++; michael@0: var self = this; michael@0: AddonManager.getAllInstalls(function updateAvailableCount_getAllInstalls(aInstallsList) { michael@0: var count = aInstallsList.filter(function installListFilter(aInstall) { michael@0: return self.isManualUpdate(aInstall, true); michael@0: }).length; michael@0: self._categoryItem.disabled = gViewController.currentViewId != "addons://updates/available" && michael@0: count == 0; michael@0: self._categoryItem.badgeCount = count; michael@0: if (aInitializing) michael@0: notifyInitialized(); michael@0: }); michael@0: }, michael@0: michael@0: maybeDisableUpdateSelected: function gUpdatesView_maybeDisableUpdateSelected() { michael@0: for (let item of this._listBox.childNodes) { michael@0: if (item.includeUpdate) { michael@0: this._updateSelected.disabled = false; michael@0: return; michael@0: } michael@0: } michael@0: this._updateSelected.disabled = true; michael@0: }, michael@0: michael@0: installSelected: function gUpdatesView_installSelected() { michael@0: for (let item of this._listBox.childNodes) { michael@0: if (item.includeUpdate) michael@0: item.upgrade(); michael@0: } michael@0: michael@0: this._updateSelected.disabled = true; michael@0: }, michael@0: michael@0: getSelectedAddon: function gUpdatesView_getSelectedAddon() { michael@0: var item = this._listBox.selectedItem; michael@0: if (item) michael@0: return item.mAddon; michael@0: return null; michael@0: }, michael@0: michael@0: getListItemForID: function gUpdatesView_getListItemForID(aId) { michael@0: var listitem = this._listBox.firstChild; michael@0: while (listitem) { michael@0: if (listitem.mAddon.id == aId) michael@0: return listitem; michael@0: listitem = listitem.nextSibling; michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: onSortChanged: function gUpdatesView_onSortChanged(aSortBy, aAscending) { michael@0: sortList(this._listBox, aSortBy, aAscending); michael@0: }, michael@0: michael@0: onNewInstall: function gUpdatesView_onNewInstall(aInstall) { michael@0: if (!this.isManualUpdate(aInstall)) michael@0: return; michael@0: this.maybeRefresh(); michael@0: }, michael@0: michael@0: onInstallStarted: function gUpdatesView_onInstallStarted(aInstall) { michael@0: this.updateAvailableCount(); michael@0: }, michael@0: michael@0: onInstallCancelled: function gUpdatesView_onInstallCancelled(aInstall) { michael@0: if (!this.isManualUpdate(aInstall)) michael@0: return; michael@0: this.maybeRefresh(); michael@0: }, michael@0: michael@0: onPropertyChanged: function gUpdatesView_onPropertyChanged(aAddon, aProperties) { michael@0: if (aProperties.indexOf("applyBackgroundUpdates") != -1) michael@0: this.updateAvailableCount(); michael@0: } michael@0: }; michael@0: michael@0: function debuggingPrefChanged() { michael@0: gViewController.updateState(); michael@0: gViewController.updateCommands(); michael@0: gViewController.notifyViewChanged(); michael@0: } michael@0: michael@0: var gDragDrop = { michael@0: onDragOver: function gDragDrop_onDragOver(aEvent) { michael@0: var types = aEvent.dataTransfer.types; michael@0: if (types.contains("text/uri-list") || michael@0: types.contains("text/x-moz-url") || michael@0: types.contains("application/x-moz-file")) michael@0: aEvent.preventDefault(); michael@0: }, michael@0: michael@0: onDrop: function gDragDrop_onDrop(aEvent) { michael@0: var dataTransfer = aEvent.dataTransfer; michael@0: var urls = []; michael@0: michael@0: // Convert every dropped item into a url michael@0: for (var i = 0; i < dataTransfer.mozItemCount; i++) { michael@0: var url = dataTransfer.mozGetDataAt("text/uri-list", i); michael@0: if (url) { michael@0: urls.push(url); michael@0: continue; michael@0: } michael@0: michael@0: url = dataTransfer.mozGetDataAt("text/x-moz-url", i); michael@0: if (url) { michael@0: urls.push(url.split("\n")[0]); michael@0: continue; michael@0: } michael@0: michael@0: var file = dataTransfer.mozGetDataAt("application/x-moz-file", i); michael@0: if (file) { michael@0: urls.push(Services.io.newFileURI(file).spec); michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: var pos = 0; michael@0: var installs = []; michael@0: michael@0: function buildNextInstall() { michael@0: if (pos == urls.length) { michael@0: if (installs.length > 0) { michael@0: // Display the normal install confirmation for the installs michael@0: AddonManager.installAddonsFromWebpage("application/x-xpinstall", michael@0: window, null, installs); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: AddonManager.getInstallForURL(urls[pos++], function onDrop_getInstallForURL(aInstall) { michael@0: installs.push(aInstall); michael@0: buildNextInstall(); michael@0: }, "application/x-xpinstall"); michael@0: } michael@0: michael@0: buildNextInstall(); michael@0: michael@0: aEvent.preventDefault(); michael@0: } michael@0: };