From 323f5e8b90f98f853f2b38ef79486adcf5758f5b Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Sun, 21 Mar 2010 15:36:39 -0700 Subject: [PATCH] Bug 553169: Remove the old extension manager code --- .../mozapps/extensions/nsExtensionManager.js | 8370 ----------------- .../extensions/nsIExtensionManager.idl | 758 -- 2 files changed, 9128 deletions(-) delete mode 100644 toolkit/mozapps/extensions/nsExtensionManager.js delete mode 100644 toolkit/mozapps/extensions/nsIExtensionManager.idl diff --git a/toolkit/mozapps/extensions/nsExtensionManager.js b/toolkit/mozapps/extensions/nsExtensionManager.js deleted file mode 100644 index 7a972ed4995..00000000000 --- a/toolkit/mozapps/extensions/nsExtensionManager.js +++ /dev/null @@ -1,8370 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* -# ***** BEGIN LICENSE BLOCK ***** -# Version: MPL 1.1/GPL 2.0/LGPL 2.1 -# -# The contents of this file are subject to the Mozilla Public License Version -# 1.1 (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# http://www.mozilla.org/MPL/ -# -# Software distributed under the License is distributed on an "AS IS" basis, -# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -# for the specific language governing rights and limitations under the -# License. -# -# The Original Code is the Extension Manager. -# -# The Initial Developer of the Original Code is Ben Goodger. -# Portions created by the Initial Developer are Copyright (C) 2004 -# the Initial Developer. All Rights Reserved. -# -# Contributor(s): -# Ben Goodger (Google Inc.) -# Benjamin Smedberg -# Jens Bannmann -# Robert Strong -# Dave Townsend -# Daniel Veditz -# Alexander J. Vincent -# -# Alternatively, the contents of this file may be used under the terms of -# either the GNU General Public License Version 2 or later (the "GPL"), or -# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -# in which case the provisions of the GPL or the LGPL are applicable instead -# of those above. If you wish to allow use of your version of this file only -# under the terms of either the GPL or the LGPL, and not to allow others to -# use your version of this file under the terms of the MPL, indicate your -# decision by deleting the provisions above and replace them with the notice -# and other provisions required by the GPL or the LGPL. If you do not delete -# the provisions above, a recipient may use your version of this file under -# the terms of any one of the MPL, the GPL or the LGPL. -# -# ***** END LICENSE BLOCK ***** -*/ - -// -// TODO: -// - better logging -// - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm"); -Components.utils.import("resource://gre/modules/FileUtils.jsm"); - -const PREF_EM_CHECK_COMPATIBILITY = "extensions.checkCompatibility"; -const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; -const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; -const PREF_EM_ENABLED_ITEMS = "extensions.enabledItems"; -const PREF_UPDATE_COUNT = "extensions.update.count"; -const PREF_UPDATE_DEFAULT_URL = "extensions.update.url"; -const PREF_EM_NEW_ADDONS_LIST = "extensions.newAddons"; -const PREF_EM_DISABLED_ADDONS_LIST = "extensions.disabledAddons"; -const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; -const PREF_EM_IGNOREMTIMECHANGES = "extensions.ignoreMTimeChanges"; -const PREF_EM_DISABLEDOBSOLETE = "extensions.disabledObsolete"; -const PREF_EM_EXTENSION_FORMAT = "extensions.%UUID%."; -const PREF_EM_ITEM_UPDATE_ENABLED = "extensions.%UUID%.update.enabled"; -const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; -const PREF_EM_ITEM_UPDATE_URL = "extensions.%UUID%.update.url"; -const PREF_EM_DSS_ENABLED = "extensions.dss.enabled"; -const PREF_DSS_SWITCHPENDING = "extensions.dss.switchPending"; -const PREF_DSS_SKIN_TO_SELECT = "extensions.lastSelectedSkin"; -const PREF_LWTHEME_TO_SELECT = "extensions.lwThemeToSelect"; -const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin"; -const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled"; -const PREF_EM_UPDATE_INTERVAL = "extensions.update.interval"; -const PREF_UPDATE_NOTIFYUSER = "extensions.update.notifyUser"; -const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; -const PREF_SELECTED_LOCALE = "general.useragent.locale"; - -const DIR_EXTENSIONS = "extensions"; -const DIR_CHROME = "chrome"; -const DIR_STAGE = "staged-xpis"; -const FILE_EXTENSIONS = "extensions.rdf"; -const FILE_EXTENSION_MANIFEST = "extensions.ini"; -const FILE_EXTENSIONS_STARTUP_CACHE = "extensions.cache"; -const FILE_EXTENSIONS_LOG = "extensions.log"; -const FILE_INSTALL_MANIFEST = "install.rdf"; -const FILE_CHROME_MANIFEST = "chrome.manifest"; - -const UNKNOWN_XPCOM_ABI = "unknownABI"; - -const TOOLKIT_ID = "toolkit@mozilla.org" - -const KEY_PROFILEDIR = "ProfD"; -const KEY_PROFILEDS = "ProfDS"; -const KEY_APPDIR = "XCurProcD"; -const KEY_TEMPDIR = "TmpD"; - -const EM_ACTION_REQUESTED_TOPIC = "em-action-requested"; -const EM_ITEM_INSTALLED = "item-installed"; -const EM_ITEM_UPGRADED = "item-upgraded"; -const EM_ITEM_UNINSTALLED = "item-uninstalled"; -const EM_ITEM_ENABLED = "item-enabled"; -const EM_ITEM_DISABLED = "item-disabled"; -const EM_ITEM_CANCEL = "item-cancel-action"; - -const OP_NONE = ""; -const OP_NEEDS_INSTALL = "needs-install"; -const OP_NEEDS_UPGRADE = "needs-upgrade"; -const OP_NEEDS_UNINSTALL = "needs-uninstall"; -const OP_NEEDS_ENABLE = "needs-enable"; -const OP_NEEDS_DISABLE = "needs-disable"; - -const KEY_APP_PROFILE = "app-profile"; -const KEY_APP_GLOBAL = "app-global"; -const KEY_APP_SYSTEM_LOCAL = "app-system-local"; -const KEY_APP_SYSTEM_SHARE = "app-system-share"; -const KEY_APP_SYSTEM_USER = "app-system-user"; - -const CATEGORY_INSTALL_LOCATIONS = "extension-install-locations"; -const CATEGORY_UPDATE_PARAMS = "extension-update-params"; - -const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#"; -const PREFIX_ITEM_URI = "urn:mozilla:item:"; -const PREFIX_EXTENSION = "urn:mozilla:extension:"; -const PREFIX_THEME = "urn:mozilla:theme:"; -const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest"; -const RDFURI_ITEM_ROOT = "urn:mozilla:item:root" -const RDFURI_DEFAULT_THEME = "urn:mozilla:item:{972ce4c6-7e08-4474-a285-3208198ce6fd}"; -const XMLURI_PARSE_ERROR = "http://www.mozilla.org/newlayout/xml/parsererror.xml" - -const URI_GENERIC_ICON_XPINSTALL = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png"; -const URI_GENERIC_ICON_THEME = "chrome://mozapps/skin/extensions/themeGeneric.png"; -const URI_XPINSTALL_CONFIRM_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul"; -const URI_EXTENSIONS_PROPERTIES = "chrome://mozapps/locale/extensions/extensions.properties"; -const URI_BRAND_PROPERTIES = "chrome://branding/locale/brand.properties"; -const URI_DOWNLOADS_PROPERTIES = "chrome://mozapps/locale/downloads/downloads.properties"; -const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul"; -const URI_EXTENSION_LIST_DIALOG = "chrome://mozapps/content/extensions/list.xul"; - -const URI_EXTENSION_MANAGER = "chrome://mozapps/content/extensions/extensions.xul"; -const FEATURES_EXTENSION_MANAGER = "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable"; -const FEATURES_EXTENSION_UPDATES = "chrome,centerscreen,extra-chrome,dialog,resizable,modal"; - -/** - * Constants that internal code can use to indicate the reason for an add-on - * update check. external code uses other constants in nsIExtensionManager.idl. - */ -const MAX_PUBLIC_UPDATE_WHEN = 15; -const UPDATE_WHEN_PERIODIC_UPDATE = 16; -const UPDATE_WHEN_ADDON_INSTALLED = 17; - -/** - * Bitmask of the different types of update check. - */ -const UPDATE_TYPE_COMPATIBILITY = 32; -const UPDATE_TYPE_NEWVERSION = 64; - -const INSTALLERROR_SUCCESS = 0; -const INSTALLERROR_INVALID_VERSION = -1; -const INSTALLERROR_INVALID_GUID = -2; -const INSTALLERROR_INCOMPATIBLE_VERSION = -3; -const INSTALLERROR_PHONING_HOME = -4; -const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5; -const INSTALLERROR_BLOCKLISTED = -6; -const INSTALLERROR_INSECURE_UPDATE = -7; -const INSTALLERROR_INVALID_MANIFEST = -8; -const INSTALLERROR_RESTRICTED = -9; -const INSTALLERROR_SOFTBLOCKED = -10; - -const REQ_VERSION = 2; - -var gApp = null; -var gPref = null; -var gRDF = null; -var gOS = null; -var gCheckCompatibilityPref; -var gEmSingleton = null; -var gBlocklist = null; -var gXPCOMABI = null; -var gOSTarget = null; -var gConsole = null; -var gInstallManifestRoot = null; -var gVersionChecker = null; -var gLoggingEnabled = null; -var gCheckCompatibility = true; -var gCheckUpdateSecurity = true; -var gLocale = "en-US"; -var gFirstRun = false; -var gAllowFlush = true; -var gDSNeedsFlush = false; -var gManifestNeedsFlush = false; -var gDefaultTheme = "classic/1.0"; - -/** - * Valid GUIDs fit this pattern. - */ -var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i; - -var gBranchVersion = /^([^\.]+\.[0-9]+[a-z]*).*/gi; - -// shared code for suppressing bad cert dialogs -XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() { - let temp = { }; - Components.utils.import("resource://gre/modules/CertUtils.jsm", temp); - return temp; -}); - -/** - * Creates a Version Checker object. - * @returns A handle to the global Version Checker service. - */ -function getVersionChecker() { - if (!gVersionChecker) { - gVersionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"]. - getService(Ci.nsIVersionComparator); - } - return gVersionChecker; -} - -var BundleManager = { - /** - * Creates and returns a String Bundle at the specified URI - * @param bundleURI - * The URI of the bundle to load - * @returns A nsIStringBundle which was retrieved. - */ - getBundle: function BundleManager_getBundle(bundleURI) { - var sbs = Cc["@mozilla.org/intl/stringbundle;1"]. - getService(Ci.nsIStringBundleService); - return sbs.createBundle(bundleURI); - }, - - _appName: "", - - /** - * The Application's display name. - */ - get appName() { - if (!this._appName) { - var brandBundle = this.getBundle(URI_BRAND_PROPERTIES) - this._appName = brandBundle.GetStringFromName("brandShortName"); - } - return this._appName; - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// -// Utility Functions -// -function EM_NS(property) { - return PREFIX_NS_EM + property; -} - -function EM_R(property) { - return gRDF.GetResource(EM_NS(property)); -} - -function EM_L(literal) { - return gRDF.GetLiteral(literal); -} - -function EM_I(integer) { - return gRDF.GetIntLiteral(integer); -} - -function EM_D(integer) { - return gRDF.GetDateLiteral(integer); -} - -/** - * Gets a preference value, handling the case where there is no default. - * @param func - * The name of the preference function to call, on nsIPrefBranch - * @param preference - * The name of the preference - * @param defaultValue - * The default value to return in the event the preference has - * no setting - * @returns The value of the preference, or undefined if there was no - * user or default value. - */ -function getPref(func, preference, defaultValue) { - try { - return gPref[func](preference); - } - catch (e) { - } - return defaultValue; -} - -/** - * Initializes a RDF Container at a URI in a datasource. - * @param datasource - * The datasource the container is in - * @param root - * The RDF Resource which is the root of the container. - * @returns The nsIRDFContainer, initialized at the root. - */ -function getContainer(datasource, root) { - var ctr = Cc["@mozilla.org/rdf/container;1"]. - createInstance(Ci.nsIRDFContainer); - ctr.Init(datasource, root); - return ctr; -} - -/** - * Gets a RDF Resource for item with the given ID - * @param id - * The GUID of the item to construct a RDF resource to the - * active item for - * @returns The RDF Resource to the Active item. - */ -function getResourceForID(id) { - return gRDF.GetResource(PREFIX_ITEM_URI + id); -} - -/** - * Construct a nsIUpdateItem with the supplied metadata - * ... - */ -function makeItem(id, version, locationKey, minVersion, maxVersion, name, - updateURL, updateHash, iconURL, updateRDF, updateKey, type, - targetAppID) { - var item = new UpdateItem(); - item.init(id, version, locationKey, minVersion, maxVersion, name, - updateURL, updateHash, iconURL, updateRDF, updateKey, type, - targetAppID); - return item; -} - -/** - * Gets the descriptor of a directory as a relative path to common base - * directories (profile, user home, app install dir, etc). - * - * @param itemLocation - * The nsILocalFile representing the item's directory. - * @param installLocation the nsIInstallLocation for this item - */ -function getDescriptorFromFile(itemLocation, installLocation) { - var baseDir = installLocation.location; - - if (baseDir && baseDir.contains(itemLocation, true)) { - return "rel%" + itemLocation.getRelativeDescriptor(baseDir); - } - - return "abs%" + itemLocation.persistentDescriptor; -} - -function getAbsoluteDescriptor(itemLocation) { - return itemLocation.persistentDescriptor; -} - -/** - * Initializes a Local File object based on a descriptor - * provided by "getDescriptorFromFile". - * - * @param descriptor - * The descriptor that locates the directory - * @param installLocation - * The nsIInstallLocation object for this item. - * @returns The nsILocalFile object representing the location of the item - */ -function getFileFromDescriptor(descriptor, installLocation) { - var location = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - - var m = descriptor.match(/^(abs|rel)\%(.*)$/); - if (!m) - throw Cr.NS_ERROR_INVALID_ARG; - - if (m[1] == "rel") { - location.setRelativeDescriptor(installLocation.location, m[2]); - } - else { - location.persistentDescriptor = m[2]; - } - - return location; -} - -/** - * Determines if a file is an item package - either a XPI or a JAR file. - * @param file - * The file to check - * @returns true if the file is an item package, false otherwise. - */ -function fileIsItemPackage(file) { - var fileURL = getURIFromFile(file); - if (fileURL instanceof Ci.nsIURL) - var extension = fileURL.fileExtension.toLowerCase(); - return extension == "xpi" || extension == "jar"; -} - -/** - * Deletes a directory and its children. First it tries nsIFile::Remove(true). - * If that fails it will fall back to recursing, setting the appropriate - * permissions, and deleting the current entry. This is needed for when we have - * rights to delete a directory but there are entries that have a read-only - * attribute (e.g. a copy restore from a read-only CD, etc.) - * @param dir - * A nsIFile for the directory to be deleted - */ -function removeDirRecursive(dir) { - try { - dir.remove(true); - return; - } - catch (e) { - } - - var dirEntries = dir.directoryEntries; - while (dirEntries.hasMoreElements()) { - var entry = dirEntries.getNext().QueryInterface(Ci.nsIFile); - - if (entry.isDirectory()) { - removeDirRecursive(entry); - } - else { - entry.permissions = FileUtils.PERMS_FILE; - entry.remove(false); - } - } - dir.permissions = FileUtils.PERMS_DIRECTORY; - dir.remove(true); -} - -/** - * Logs a string to the error console and the text console if logging is - * enabled. - * @param string - * The log message. - */ -function LOG(string) { - if (gLoggingEnabled) { - dump("*** EM_LOG *** " + string + "\n"); - if (gConsole) - gConsole.logStringMessage(string); - } -} - -/** - * Logs a warning to the error console and if logging is enabled to the text - * console. - * @param string - * The warning message. - */ -function WARN(string) { - if (gLoggingEnabled) - dump("*** EM_WARN *** " + string + "\n"); - if (gConsole) { - var message = Cc["@mozilla.org/scripterror;1"]. - createInstance(Ci.nsIScriptError); - message.init(string, null, null, 0, 0, Ci.nsIScriptError.warningFlag, - "component javascript"); - gConsole.logMessage(message); - } -} - -/** - * Logs an error to the error console and to a permanent log file. - * @param string - * The error message. - */ -function ERROR(string) { - if (gLoggingEnabled) - dump("*** EM_ERROR *** " + string + "\n"); - if (gConsole) { - var message = Cc["@mozilla.org/scripterror;1"]. - createInstance(Ci.nsIScriptError); - message.init(string, null, null, 0, 0, Ci.nsIScriptError.errorFlag, - "component javascript"); - gConsole.logMessage(message); - } - try { - var tstamp = new Date(); - var logfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_LOG]); - var stream = Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(Ci.nsIFileOutputStream); - stream.init(logfile, 0x02 | 0x08 | 0x10, 0666, 0); // write, create, append - var writer = Cc["@mozilla.org/intl/converter-output-stream;1"]. - createInstance(Ci.nsIConverterOutputStream); - writer.init(stream, "UTF-8", 0, 0x0000); - string = tstamp.toLocaleFormat("%Y-%m-%d %H:%M:%S - ") + string; - writer.writeString(string + "\n"); - writer.close(); - } - catch (e) { } -} - -/** - * Randomize the specified file name. Used to force RDF to bypass the cache - * when loading certain types of files. - * @param fileName - * A file name to randomize, e.g. install.rdf - * @returns A randomized file name, e.g. install-xyz.rdf - */ -function getRandomFileName(fileName) { - var extensionDelimiter = fileName.lastIndexOf("."); - var prefix = fileName.substr(0, extensionDelimiter); - var suffix = fileName.substr(extensionDelimiter); - - var characters = "abcdefghijklmnopqrstuvwxyz0123456789"; - var nameString = prefix + "-"; - for (var i = 0; i < 3; ++i) { - var index = Math.round((Math.random()) * characters.length); - nameString += characters.charAt(index); - } - return nameString + "." + suffix; -} - -/** - * Get the RDF URI prefix of a nsIUpdateItem type. This function should be used - * ONLY to support Firefox 1.0 Update RDF files! Item URIs in the datasource - * are NOT prefixed. - * @param type - * The nsIUpdateItem type to find a RDF URI prefix for - * @returns The RDF URI prefix. - */ -function getItemPrefix(type) { - if (type & Ci.nsIUpdateItem.TYPE_EXTENSION) - return PREFIX_EXTENSION; - else if (type & Ci.nsIUpdateItem.TYPE_THEME) - return PREFIX_THEME; - return PREFIX_ITEM_URI; -} - -/** - * Trims a prefix from a string. - * @param string - * The source string - * @param prefix - * The prefix to remove. - * @returns The suffix (string - prefix) - */ -function stripPrefix(string, prefix) { - return string.substr(prefix.length); -} - -/** - * Gets a File URL spec for a nsIFile - * @param file - * The file to get a file URL spec to - * @returns The file URL spec to the file - */ -function getURLSpecFromFile(file) { - var ioServ = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - var fph = ioServ.getProtocolHandler("file") - .QueryInterface(Ci.nsIFileProtocolHandler); - return fph.getURLSpecFromFile(file); -} - -/** - * Constructs a URI to a spec. - * @param spec - * The spec to construct a URI to - * @returns The nsIURI constructed. - */ -function newURI(spec) { - var ioServ = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - return ioServ.newURI(spec, null, null); -} - -/** - * Constructs a File URI to a nsIFile - * @param file - * The file to construct a File URI to - * @returns The file URI to the file - */ -function getURIFromFile(file) { - var ioServ = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - return ioServ.newFileURI(file); -} - -/** - * @returns Whether or not we are currently running in safe mode. - */ -function inSafeMode() { - return gApp.inSafeMode; -} - -/** - * Extract the string value from a RDF Literal or Resource - * @param literalOrResource - * RDF String Literal or Resource - * @returns String value of the literal or resource, or undefined if the object - * supplied is not a RDF string literal or resource. - */ -function stringData(literalOrResource) { - if (literalOrResource instanceof Ci.nsIRDFLiteral) - return literalOrResource.Value; - if (literalOrResource instanceof Ci.nsIRDFResource) - return literalOrResource.Value; - return undefined; -} - -/** - * Extract the integer value of a RDF Literal - * @param literal - * nsIRDFInt literal - * @return integer value of the literal - */ -function intData(literal) { - if (literal instanceof Ci.nsIRDFInt) - return literal.Value; - return undefined; -} - -/** - * Gets a property from an install manifest. - * @param installManifest - * An Install Manifest datasource to read from - * @param property - * The name of a proprety to read (sans EM_NS) - * @returns The literal value of the property, or undefined if the property has - * no value. - */ -function getManifestProperty(installManifest, property) { - var target = installManifest.GetTarget(gInstallManifestRoot, - gRDF.GetResource(EM_NS(property)), true); - var val = stringData(target); - return val === undefined ? intData(target) : val; -} - -/** - * Given an Install Manifest Datasource, retrieves the type of item the manifest - * describes. - * @param installManifest - * The Install Manifest Datasource. - * @return The nsIUpdateItem type of the item described by the manifest - * returns TYPE_EXTENSION if attempts to determine the type fail. - */ -function getAddonTypeFromInstallManifest(installManifest) { - var target = installManifest.GetTarget(gInstallManifestRoot, - gRDF.GetResource(EM_NS("type")), true); - if (target) { - var type = stringData(target); - return type === undefined ? intData(target) : parseInt(type); - } - - // Firefox 1.0 and earlier did not support addon-type annotation on the - // Install Manifest, so we fall back to a theme-only property to - // differentiate. - if (getManifestProperty(installManifest, "internalName") !== undefined) - return Ci.nsIUpdateItem.TYPE_THEME; - - // If no type is provided, default to "Extension" - return Ci.nsIUpdateItem.TYPE_EXTENSION; -} - -/** - * Shows a message about an incompatible Extension/Theme. - * @param installData - * An Install Data object from |getInstallData| - */ -function showIncompatibleError(installData) { - var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - var params = [extensionStrings.GetStringFromName("type-" + installData.type)]; - var title = extensionStrings.formatStringFromName("incompatibleTitle", - params, params.length); - params = [installData.name, installData.version, BundleManager.appName, - gApp.version]; - var message = extensionStrings.formatStringFromName("incompatibleMessage", - params, params.length); - var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"]. - getService(Ci.nsIPromptService); - ps.alert(null, title, message); -} - -/** - * Shows a message. - * @param titleKey - * String key of the title string in the Extensions localization file. - * @param messageKey - * String key of the message string in the Extensions localization file. - * @param messageParams - * Array of strings to be substituted into |messageKey|. Can be null. - */ -function showMessage(titleKey, titleParams, messageKey, messageParams) { - var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - if (titleParams && titleParams.length > 0) { - var title = extensionStrings.formatStringFromName(titleKey, titleParams, - titleParams.length); - } - else - title = extensionStrings.GetStringFromName(titleKey); - - if (messageParams && messageParams.length > 0) { - var message = extensionStrings.formatStringFromName(messageKey, messageParams, - messageParams.length); - } - else - message = extensionStrings.GetStringFromName(messageKey); - var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"]. - getService(Ci.nsIPromptService); - ps.alert(null, title, message); -} - -/** - * Shows a dialog for a blocklisted item. For soft blocked items this will - * return true if the item should still be installed - * @param item - * The nsIUpdateItem that is blocklisted - * @param softblocked - * True if this item is only soft blocked and may still be installed. - */ -function showBlocklistMessage(item, softblocked) { - var params = Cc["@mozilla.org/embedcomp/dialogparam;1"]. - createInstance(Ci.nsIDialogParamBlock); - params.SetInt(0, softblocked ? 1 : 0); - params.SetInt(1, 0); - params.SetNumberStrings(1); - params.SetString(0, item.name + " " + item.version); - - var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. - getService(Ci.nsIWindowMediator); - var win = wm.getMostRecentWindow("Extension:Manager"); - var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher); - ww.openWindow(win, URI_EXTENSION_LIST_DIALOG, "", - "chrome,centerscreen,modal,dialog,titlebar", params); - - return params.GetInt(1) == 0 ? false : true; -} - -/** - * Gets a zip reader for the file specified. - * @param zipFile - * A ZIP archive to open with a nsIZipReader. - * @return A nsIZipReader for the file specified. - */ -function getZipReaderForFile(zipFile) { - try { - var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(Ci.nsIZipReader); - zipReader.open(zipFile); - } - catch (e) { - zipReader.close(); - throw e; - } - return zipReader; -} - -/** - * Verifies that a zip file's contents are all signed by the same principal. - * Directory entries and anything in the META-INF directory are not checked. - * @param zip - * A nsIZipReader to check - * @param principal - * The nsIPrincipal to compare against - * @return true if all the contents were signed by the principal, false - * otherwise. - */ -function verifyZipSigning(zip, principal) { - var count = 0; - var entries = zip.findEntries(null); - while (entries.hasMore()) { - var entry = entries.getNext(); - // Nothing in META-INF is in the manifest. - if (entry.substr(0, 9) == "META-INF/") - continue; - // Directory entries aren't in the manifest. - if (entry.substr(-1) == "/") - continue; - count++; - var entryPrincipal = zip.getCertificatePrincipal(entry); - if (!entryPrincipal || !principal.equals(entryPrincipal)) - return false; - } - return zip.manifestEntriesCount == count; -} - -/** - * Extract a RDF file from a ZIP archive to a random location in the system - * temp directory. - * @param zipFile - * A ZIP archive to read from - * @param fileName - * The name of the file to read from the zip. - * @param suppressErrors - * Whether or not to report errors. - * @return The file created in the temp directory. - */ -function extractRDFFileToTempDir(zipFile, fileName, suppressErrors) { - var file = FileUtils.getFile(KEY_TEMPDIR, [getRandomFileName(fileName)]); - try { - var zipReader = getZipReaderForFile(zipFile); - zipReader.extract(fileName, file); - zipReader.close(); - } - catch (e) { - if (!suppressErrors) { - showMessage("missingFileTitle", [], "missingFileMessage", - [BundleManager.appName, fileName]); - throw e; - } - } - return file; -} - -/** - * Gets an Install Manifest datasource from a file. - * @param file - * The nsIFile that contains the Install Manifest RDF - * @returns The Install Manifest datasource - */ -function getInstallManifest(file) { - var uri = getURIFromFile(file); - try { - var fis = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - fis.init(file, -1, -1, false); - var bis = Cc["@mozilla.org/network/buffered-input-stream;1"]. - createInstance(Ci.nsIBufferedInputStream); - bis.init(fis, 4096); - - var rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"]. - createInstance(Ci.nsIRDFXMLParser) - var ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]. - createInstance(Ci.nsIRDFDataSource); - var listener = rdfParser.parseAsync(ds, uri); - var channel = Cc["@mozilla.org/network/input-stream-channel;1"]. - createInstance(Ci.nsIInputStreamChannel); - channel.setURI(uri); - channel.contentStream = bis; - channel.QueryInterface(Ci.nsIChannel); - channel.contentType = "text/xml"; - - listener.onStartRequest(channel, null); - try { - var pos = 0; - var count = bis.available(); - while (count > 0) { - listener.onDataAvailable(channel, null, bis, pos, count); - pos += count; - count = bis.available(); - } - listener.onStopRequest(channel, null, Components.results.NS_OK); - bis.close(); - fis.close(); - - var arcs = ds.ArcLabelsOut(gInstallManifestRoot); - if (arcs.hasMoreElements()) - return ds; - } - catch (e) { - listener.onStopRequest(channel, null, e.result); - bis.close(); - fis.close(); - } - } - catch (e) { } - - var url = uri.QueryInterface(Ci.nsIURL); - showMessage("malformedTitle", [], "malformedMessage", - [BundleManager.appName, url.fileName]); - return null; -} - -/** - * Selects the closest matching localized resource in the given RDF resource - * @param aDataSource The datasource to look in - * @param aResource The root resource containing the localized sections - * @returns The nsIRDFResource of the best em:localized section or null - * if no valid match was found - */ -function findClosestLocalizedResource(aDataSource, aResource) { - var localizedProp = EM_R("localized"); - var localeProp = EM_R("locale"); - - // Holds the best matching localized resource - var bestmatch = null; - // The number of locale parts it matched with - var bestmatchcount = 0; - // The number of locale parts in the match - var bestpartcount = 0; - - var locales = [gLocale.toLowerCase()]; - /* If the current locale is English then it will find a match if there is - a valid match for en-US so no point searching that locale too. */ - if (locales[0].substring(0, 3) != "en-") - locales.push("en-us"); - - for each (var locale in locales) { - var lparts = locale.split("-"); - var localizations = aDataSource.GetTargets(aResource, localizedProp, true); - while (localizations.hasMoreElements()) { - var localized = localizations.getNext().QueryInterface(Ci.nsIRDFNode); - var list = aDataSource.GetTargets(localized, localeProp, true); - while (list.hasMoreElements()) { - var found = stringData(list.getNext().QueryInterface(Ci.nsIRDFNode)); - if (!found) - continue; - - found = found.toLowerCase(); - - // Exact match is returned immediately - if (locale == found) - return localized; - - var fparts = found.split("-"); - /* If we have found a possible match and this one isn't any longer - then we dont need to check further. */ - if (bestmatch && fparts.length < bestmatchcount) - continue; - - // Count the number of parts that match - var maxmatchcount = Math.min(fparts.length, lparts.length); - var matchcount = 0; - while (matchcount < maxmatchcount && - fparts[matchcount] == lparts[matchcount]) - matchcount++; - - /* If we matched more than the last best match or matched the same and - this locale is less specific than the last best match. */ - if (matchcount > bestmatchcount || - (matchcount == bestmatchcount && fparts.length < bestpartcount)) { - bestmatch = localized; - bestmatchcount = matchcount; - bestpartcount = fparts.length; - } - } - } - // If we found a valid match for this locale return it - if (bestmatch) - return bestmatch; - } - return null; -} - -/** - * An enumeration of items in a JS array. - * @constructor - */ -function ArrayEnumerator(aItems) { - if (aItems) { - for (var i = 0; i < aItems.length; ++i) { - if (!aItems[i]) - aItems.splice(i--, 1); - } - this._contents = aItems; - } else { - this._contents = []; - } -} - -ArrayEnumerator.prototype = { - _index: 0, - - hasMoreElements: function ArrayEnumerator_hasMoreElements() { - return this._index < this._contents.length; - }, - - getNext: function ArrayEnumerator_getNext() { - return this._contents[this._index++]; - } -}; - -/** - * An enumeration of files in a JS array. - * @param files - * The files to enumerate - * @constructor - */ -function FileEnumerator(files) { - if (files) { - for (var i = 0; i < files.length; ++i) { - if (!files[i]) - files.splice(i--, 1); - } - this._contents = files; - } else { - this._contents = []; - } -} - -FileEnumerator.prototype = { - _index: 0, - - /** - * Gets the next file in the sequence. - */ - get nextFile() { - if (this._index < this._contents.length) - return this._contents[this._index++]; - return null; - }, - - /** - * Stop enumerating. Nothing to do here. - */ - close: function FileEnumerator_close() { - } -}; - -/** - * An object which identifies an Install Location for items, where the location - * relationship is each item living in a directory named with its GUID under - * the directory used when constructing this object. - * - * e.g. \{GUID1} - * \{GUID2} - * \{GUID3} - * ... - * - * @param name - * The string identifier of this Install Location. - * @param location - * The directory that contains the items. - * @constructor - */ -function DirectoryInstallLocation(name, location, restricted, priority, independent) { - this._name = name; - if (location.exists()) { - if (!location.isDirectory()) - throw new Error("location must be a directoy!"); - } - else { - try { - location.create(Ci.nsILocalFile.DIRECTORY_TYPE, 0775); - } - catch (e) { - LOG("DirectoryInstallLocation: failed to create location " + - " directory = " + location.path + ", exception = " + e + "\n"); - } - } - - this._location = location; - this._locationToIDMap = {}; - this._restricted = restricted; - this._priority = priority; - this._independent = independent; -} -DirectoryInstallLocation.prototype = { - _name : "", - _location : null, - _locationToIDMap: null, - _restricted : false, - _priority : 0, - _independent : false, - _canAccess : null, - - /** - * See nsIExtensionManager.idl - */ - get name() { - return this._name; - }, - - /** - * Reads a directory linked to in a file. - * @param file - * The file containing the directory path - * @returns A nsILocalFile object representing the linked directory. - */ - _readDirectoryFromFile: function DirInstallLocation__readDirectoryFromFile(file) { - var fis = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - fis.init(file, -1, -1, false); - var line = { value: "" }; - if (fis instanceof Ci.nsILineInputStream) - fis.readLine(line); - fis.close(); - if (line.value) { - var linkedDirectory = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - try { - linkedDirectory.initWithPath(line.value); - } - catch (e) { - linkedDirectory.setRelativeDescriptor(file.parent, line.value); - } - - return linkedDirectory; - } - return null; - }, - - /** - * See nsIExtensionManager.idl - */ - get itemLocations() { - var locations = []; - if (!this._location.exists()) - return new FileEnumerator(locations); - - try { - var entries = this._location.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - while (true) { - var entry = entries.nextFile; - if (!entry) - break; - entry instanceof Ci.nsILocalFile; - if (!entry.isDirectory() && gIDTest.test(entry.leafName)) { - var linkedDirectory = this._readDirectoryFromFile(entry); - if (linkedDirectory && linkedDirectory.exists() && - linkedDirectory.isDirectory()) { - locations.push(linkedDirectory); - this._locationToIDMap[linkedDirectory.persistentDescriptor] = entry.leafName; - } - } - else - locations.push(entry); - } - entries.close(); - } - catch (e) { - } - return new FileEnumerator(locations); - }, - - /** - * Retrieves the GUID for an item at the specified location. - * @param file - * The location where an item might live. - * @returns The ID for an item that might live at the location specified. - * - * N.B. This function makes no promises about whether or not this path is - * actually maintained by this Install Location. - */ - getIDForLocation: function DirInstallLocation_getIDForLocation(file) { - var section = file.leafName; - var filePD = file.persistentDescriptor; - if (filePD in this._locationToIDMap) - section = this._locationToIDMap[filePD]; - - if (gIDTest.test(section)) - return RegExp.$1; - return undefined; - }, - - /** - * See nsIExtensionManager.idl - */ - get location() { - return this._location.clone(); - }, - - /** - * See nsIExtensionManager.idl - */ - get restricted() { - return this._restricted; - }, - - /** - * See nsIExtensionManager.idl - */ - get canAccess() { - if (this._canAccess != null) - return this._canAccess; - - if (!this.location.exists()) { - this._canAccess = false; - return false; - } - - var testFile = this.location; - testFile.append("Access Privileges Test"); - try { - testFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE, - FileUtils.PERMS_DIRECTORY); - testFile.remove(false); - this._canAccess = true; - } - catch (e) { - this._canAccess = false; - } - return this._canAccess; - }, - - /** - * See nsIExtensionManager.idl - */ - get priority() { - return this._priority; - }, - - /** - * See nsIExtensionManager.idl - */ - getItemLocation: function DirInstallLocation_getItemLocation(id) { - var itemLocation = this.location; - itemLocation.append(id); - if (itemLocation.exists() && !itemLocation.isDirectory()) - return this._readDirectoryFromFile(itemLocation); - if (!itemLocation.exists() && this.canAccess) - itemLocation.create(Ci.nsILocalFile.DIRECTORY_TYPE, - FileUtils.PERMS_DIRECTORY); - return itemLocation; - }, - - /** - * See nsIExtensionManager.idl - */ - itemIsManagedIndependently: function DirInstallLocation_itemIsManagedIndependently(id) { - if (this._independent) - return true; - var itemLocation = this.location; - itemLocation.append(id); - return itemLocation.exists() && !itemLocation.isDirectory(); - }, - - /** - * See nsIExtensionManager.idl - */ - getItemFile: function DirInstallLocation_getItemFile(id, filePath) { - var itemLocation = this.getItemLocation(id); - var parts = filePath.split("/"); - for (var i = 0; i < parts.length; ++i) - itemLocation.append(parts[i]); - return itemLocation; - }, - - /** - * Stages the specified file for later. - * @param file - * The file to stage - * @param id - * The GUID of the item the file represents - */ - stageFile: function DirInstallLocation_stageFile(file, id) { - var stagedFile = this.location; - stagedFile.append(DIR_STAGE); - stagedFile.append(id); - stagedFile.append(file.leafName); - - // When an incompatible update is successful the file is already staged - if (stagedFile.equals(file)) - return stagedFile; - - if (stagedFile.exists()) - stagedFile.remove(false); - - file.copyTo(stagedFile.parent, stagedFile.leafName); - - // If the file has incorrect permissions set, correct them now. - if (!stagedFile.isWritable()) - stagedFile.permissions = FileUtils.PERMS_FILE; - - return stagedFile; - }, - - /** - * Returns the most recently staged package (e.g. the last XPI or JAR in a - * directory) for an item and removes items that do not qualify. - * @param id - * The ID of the staged package - * @returns an nsIFile if the package exists otherwise null. - */ - getStageFile: function DirInstallLocation_getStageFile(id) { - var stageFile = null; - var stageDir = this.location; - stageDir.append(DIR_STAGE); - stageDir.append(id); - if (!stageDir.exists() || !stageDir.isDirectory()) - return null; - try { - var entries = stageDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - while (entries.hasMoreElements()) { - var file = entries.nextFile; - if (!(file instanceof Ci.nsILocalFile)) - continue; - if (file.isDirectory()) - removeDirRecursive(file); - else if (fileIsItemPackage(file)) { - if (stageFile) - stageFile.remove(false); - stageFile = file; - } - else - file.remove(false); - } - } - catch (e) { - } - if (entries instanceof Ci.nsIDirectoryEnumerator) - entries.close(); - return stageFile; - }, - - /** - * Removes a file from the stage. This cleans up the stage if there is nothing - * else left after the remove operation. - * @param file - * The file to remove. - */ - removeFile: function DirInstallLocation_removeFile(file) { - if (file.exists()) - file.remove(false); - var parent = file.parent; - var entries = parent.directoryEntries; - try { - // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after - // it has been removed will cause a crash on Mac OS X - bug 292823 - while (parent && !parent.equals(this.location) && - !entries.hasMoreElements()) { - parent.remove(false); - parent = parent.parent; - entries = parent.directoryEntries; - } - if (entries instanceof Ci.nsIDirectoryEnumerator) - entries.close(); - } - catch (e) { - ERROR("DirectoryInstallLocation::removeFile: failed to remove staged " + - " directory = " + parent.path + ", exception = " + e + "\n"); - } - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIInstallLocation]) -}; - -#ifdef XP_WIN - -const nsIWindowsRegKey = Ci.nsIWindowsRegKey; - -/** - * An object that identifies the location of installed items based on entries - * in the Windows registry. For each application a subkey is defined that - * contains a set of values, where the name of each value is a GUID and the - * contents of the value is a filesystem path identifying a directory - * containing an installed item. - * - * @param name - * The string identifier of this Install Location. - * @param rootKey - * The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey). - * @param restricted - * Indicates that the location may be restricted (e.g., this is - * usually true of a system level install location). - * @param priority - * The priority of this install location. - * @constructor - */ -function WinRegInstallLocation(name, rootKey, restricted, priority) { - this._name = name; - this._rootKey = rootKey; - this._restricted = restricted; - this._priority = priority; - this._IDToDirMap = {}; - this._DirToIDMap = {}; - - // Reading the registry may throw an exception, and that's ok. In error - // cases, we just leave ourselves in the empty state. - try { - var path = this._appKeyPath + "\\Extensions"; - var key = Cc["@mozilla.org/windows-registry-key;1"]. - createInstance(nsIWindowsRegKey); - key.open(this._rootKey, path, nsIWindowsRegKey.ACCESS_READ); - this._readAddons(key); - } catch (e) { - if (key) - key.close(); - } -} -WinRegInstallLocation.prototype = { - _name : "", - _rootKey : null, - _restricted : false, - _priority : 0, - _IDToDirMap : null, // mapping from ID to directory object - _DirToIDMap : null, // mapping from directory path to ID - - /** - * Retrieves the path of this Application's data key in the registry. - */ - get _appKeyPath() { - var appVendor = gApp.vendor; - var appName = gApp.name; - -#ifdef MOZ_THUNDERBIRD - // XXX Thunderbird doesn't specify a vendor string!! - if (appVendor == "") - appVendor = "Mozilla"; -#endif - - // XULRunner-based apps may intentionally not specify a vendor: - if (appVendor != "") - appVendor += "\\"; - - return "SOFTWARE\\" + appVendor + appName; - }, - - /** - * Read the registry and build a mapping between GUID and directory for each - * installed item. - * @param key - * The key that contains the GUID->path pairs - */ - _readAddons: function RegInstallLocation__readAddons(key) { - var count = key.valueCount; - for (var i = 0; i < count; ++i) { - var id = key.getValueName(i); - - var dir = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - dir.initWithPath(key.readStringValue(id)); - - if (dir.exists() && dir.isDirectory()) { - this._IDToDirMap[id] = dir; - this._DirToIDMap[dir.path] = id; - } - } - }, - - get name() { - return this._name; - }, - - get itemLocations() { - var locations = []; - for (var id in this._IDToDirMap) { - locations.push(this._IDToDirMap[id]); - } - return new FileEnumerator(locations); - }, - - get location() { - return null; - }, - - get restricted() { - return this._restricted; - }, - - // you should never be able to write to this location - get canAccess() { - return false; - }, - - get priority() { - return this._priority; - }, - - getItemLocation: function RegInstallLocation_getItemLocation(id) { - if (!(id in this._IDToDirMap)) - return null; - return this._IDToDirMap[id].clone(); - }, - - getIDForLocation: function RegInstallLocation_getIDForLocation(dir) { - return this._DirToIDMap[dir.path]; - }, - - getItemFile: function RegInstallLocation_getItemFile(id, filePath) { - var itemLocation = this.getItemLocation(id); - if (!itemLocation) - return null; - var parts = filePath.split("/"); - for (var i = 0; i < parts.length; ++i) - itemLocation.append(parts[i]); - return itemLocation; - }, - - itemIsManagedIndependently: function RegInstallLocation_itemIsManagedIndependently(id) { - return true; - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIInstallLocation]) -}; - -#endif - -/** - * Safely attempt to install or uninstall a given item ID in an install - * location. Using aggressive success-safety checks, this function will attempt - * to move an existing location for an item aside and then allow installation - * into the appropriate folder. If any operation fails the installation will - * abort and roll back from the moved-aside old version. - * @param itemID - * The GUID of the item to perform the operation on. - * @param installLocation - * The Install Location where the item is installed. - * @param file - * An xpi file to install to the location or null to just uninstall - */ -function safeInstallOperation(itemID, installLocation, file) { - var movedFiles = []; - - /** - * Reverts a deep move by moving backed up files back to their original - * location. - */ - function rollbackMove() - { - for (var i = 0; i < movedFiles.length; ++i) { - var oldFile = movedFiles[i].oldFile; - var newFile = movedFiles[i].newFile; - try { - newFile.moveTo(oldFile.parent, newFile.leafName); - } - catch (e) { - ERROR("safeInstallOperation: failed to roll back files after an install " + - "operation failed. Failed to roll back: " + newFile.path + " to: " + - oldFile.path + " ... aborting installation."); - throw e; - } - } - } - - /** - * Moves a file to a new folder. - * @param file - * The file to move - * @param destination - * The target folder - */ - function moveFile(file, destination) { - try { - var oldFile = file.clone(); - file.moveTo(destination, file.leafName); - movedFiles.push({ oldFile: oldFile, newFile: file }); - } - catch (e) { - ERROR("safeInstallOperation: failed to back up file: " + file.path + " to: " + - destination.path + " ... rolling back file moves and aborting " + - "installation."); - rollbackMove(); - throw e; - } - } - - /** - * Moves a directory to a new location. If any part of the move fails, - * files already moved will be rolled back. - * @param sourceDir - * The directory to move - * @param targetDir - * The destination directory - * @param currentDir - * The current directory (a subdirectory of |sourceDir| or - * |sourceDir| itself) we are moving files from. - */ - function moveDirectory(sourceDir, targetDir, currentDir) { - var entries = currentDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator); - while (true) { - var entry = entries.nextFile; - if (!entry) - break; - if (entry.isDirectory()) - moveDirectory(sourceDir, targetDir, entry); - else if (entry instanceof Ci.nsILocalFile) { - var rd = entry.getRelativeDescriptor(sourceDir); - var destination = targetDir.clone().QueryInterface(Ci.nsILocalFile); - destination.setRelativeDescriptor(targetDir, rd); - moveFile(entry, destination.parent); - } - } - entries.close(); - } - - /** - * Removes the temporary backup directory where we stored files. - * @param directory - * The backup directory to remove - */ - function cleanUpTrash(directory) { - try { - // Us-generated. Safe. - if (directory && directory.exists()) - removeDirRecursive(directory); - } - catch (e) { - ERROR("safeInstallOperation: failed to clean up the temporary backup of the " + - "older version: " + itemLocationTrash.path); - // This is a non-fatal error. Annoying, but non-fatal. - } - } - - if (!installLocation.itemIsManagedIndependently(itemID)) { - var itemLocation = installLocation.getItemLocation(itemID); - if (itemLocation.exists()) { - var trashDirName = itemID + "-trash"; - var itemLocationTrash = itemLocation.parent.clone(); - itemLocationTrash.append(trashDirName); - if (itemLocationTrash.exists()) { - // We can remove recursively here since this is a folder we created, not - // one the user specified. If this fails, it'll throw, and the caller - // should stop installation. - try { - removeDirRecursive(itemLocationTrash); - } - catch (e) { - ERROR("safeFileOperation: failed to remove existing trash directory " + - itemLocationTrash.path + " ... aborting installation."); - throw e; - } - } - - // Move the directory that contains the existing version of the item aside, - // into {GUID}-trash. This will throw if there's a failure and the install - // will abort. - moveDirectory(itemLocation, itemLocationTrash, itemLocation); - - // Clean up the original location, if necessary. Again, this is a path we - // generated, so it is safe to recursively delete. - try { - removeDirRecursive(itemLocation); - } - catch (e) { - ERROR("safeInstallOperation: failed to clean up item location after its contents " + - "were properly backed up. Failed to clean up: " + itemLocation.path + - " ... rolling back file moves and aborting installation."); - rollbackMove(); - cleanUpTrash(itemLocationTrash); - throw e; - } - } - } - else if (installLocation.name == KEY_APP_PROFILE || - installLocation.name == KEY_APP_GLOBAL || - installLocation.name == KEY_APP_SYSTEM_USER) { - // Check for a pointer file and move it aside if it exists - var pointerFile = installLocation.location.clone(); - pointerFile.append(itemID); - if (pointerFile.exists() && !pointerFile.isDirectory()) { - var trashFileName = itemID + "-trash"; - var itemLocationTrash = installLocation.location.clone(); - itemLocationTrash.append(trashFileName); - if (itemLocationTrash.exists()) { - // We can remove recursively here since this is a folder we created, not - // one the user specified. If this fails, it'll throw, and the caller - // should stop installation. - try { - removeDirRecursive(itemLocationTrash); - } - catch (e) { - ERROR("safeFileOperation: failed to remove existing trash directory " + - itemLocationTrash.path + " ... aborting installation."); - throw e; - } - } - itemLocationTrash.create(Ci.nsILocalFile.DIRECTORY_TYPE, - FileUtils.PERMS_DIRECTORY); - // Move the pointer file to the trash. - moveFile(pointerFile, itemLocationTrash); - } - } - - if (file) { - // Extract the xpi's files into the new directory - try { - var zipReader = getZipReaderForFile(file); - - // create directories first - var entries = zipReader.findEntries("*/"); - while (entries.hasMore()) { - var entryName = entries.getNext(); - var target = installLocation.getItemFile(itemID, entryName); - if (!target.exists()) { - try { - target.create(Ci.nsILocalFile.DIRECTORY_TYPE, - FileUtils.PERMS_DIRECTORY); - } - catch (e) { - ERROR("extractFiles: failed to create target directory for extraction " + - "file = " + target.path + ", exception = " + e + "\n"); - } - } - } - - entries = zipReader.findEntries(null); - while (entries.hasMore()) { - var entryName = entries.getNext(); - target = installLocation.getItemFile(itemID, entryName); - if (target.exists()) - continue; - - zipReader.extract(entryName, target); - LOG("Extracted file " + entryName + " with permissions " + target.permissions.toString(8)); - target.permissions |= FileUtils.PERMS_FILE; - } - } - catch (e) { - // This means the install operation failed. Remove everything and roll back. - ERROR("safeInstallOperation: file extraction failed, " + - "rolling back file moves and aborting installation."); - try { - // Us-generated. Safe. - removeDirRecursive(itemLocation); - } - catch (e) { - ERROR("safeInstallOperation: failed to remove the folder we failed to install " + - "an item into: " + itemLocation.path + " -- There is not much to suggest " + - "here... maybe restart and try again?"); - cleanUpTrash(itemLocationTrash); - throw e; - } - rollbackMove(); - cleanUpTrash(itemLocationTrash); - throw e; - } - finally { - if (zipReader) - zipReader.close(); - } - } - - // Now, and only now - after everything else has succeeded (against all odds!) - // remove the {GUID}-trash directory where we stashed the old version of the - // item. - cleanUpTrash(itemLocationTrash); -} - -/** - * Manages the list of pending operations. - */ -var PendingOperations = { - _ops: { }, - - /** - * Adds an entry to the Pending Operations List - * @param opType - * The type of Operation to be performed - * @param entry - * A JS Object representing the item to be operated on: - * "locationKey" The name of the Install Location where the item - * is installed. - * "id" The GUID of the item. - */ - addItem: function PendingOperations_addItem(opType, entry) { - if (opType == OP_NONE) - this.clearOpsForItem(entry.id); - else { - if (!(opType in this._ops)) - this._ops[opType] = { }; - this._ops[opType][entry.id] = entry.locationKey; - } - }, - - /** - * Removes a Pending Operation from the list - * @param opType - * The type of Operation being removed - * @param id - * The GUID of the item to remove the entry for - */ - clearItem: function PendingOperations_clearItem(opType, id) { - if (opType in this._ops && id in this._ops[opType]) - delete this._ops[opType][id]; - }, - - /** - * Removes all Pending Operation for an item - * @param id - * The ID of the item to remove the entries for - */ - clearOpsForItem: function PendingOperations_clearOpsForItem(id) { - for (var opType in this._ops) { - if (id in this._ops[opType]) - delete this._ops[opType][id]; - } - }, - - /** - * Remove all Pending Operations of a certain type - * @param opType - * The type of Operation to remove all entries for - */ - clearItems: function PendingOperations_clearItems(opType) { - if (opType in this._ops) - delete this._ops[opType]; - }, - - /** - * Get an array of operations of a certain type - * @param opType - * The type of Operation to return a list of - */ - getOperations: function PendingOperations_getOperations(opType) { - if (!(opType in this._ops)) - return []; - var ops = []; - for (var id in this._ops[opType]) - ops.push( {id: id, locationKey: this._ops[opType][id] } ); - return ops; - }, - - /** - * The total number of Pending Operations, for all types. - */ - get size() { - var size = 0; - for (var opType in this._ops) { - for (var id in this._ops[opType]) - ++size; - } - return size; - } -}; - -/** - * Manages registered Install Locations - */ -var InstallLocations = { - _locations: { }, - - /** - * A nsISimpleEnumerator of all available Install Locations. - */ - get enumeration() { - var installLocations = []; - for (var key in this._locations) - installLocations.push(InstallLocations.get(key)); - return new ArrayEnumerator(installLocations); - }, - - /** - * Gets a named Install Location - * @param name - * The name of the Install Location to get - */ - get: function InstallLocations_get(name) { - return name in this._locations ? this._locations[name] : null; - }, - - /** - * Registers an Install Location - * @param installLocation - * The Install Location to register - */ - put: function InstallLocations_put(installLocation) { - this._locations[installLocation.name] = installLocation; - } -}; - -/** - * Manages the Startup Cache. The Startup Cache is a representation - * of the contents of extensions.cache, a list of all - * items the Extension System knows about, whether or not they - * are active or visible. - */ -var StartupCache = { - /** - * Location Name -> GUID hash of entries from the Startup Cache file - * Each entry has the following properties: - * "descriptor" The location on disk of the item - * "mtime" The time the location was last modified - * "op" Any pending operations on this item. - * "location" The Install Location name where the item is installed. - */ - entries: { }, - - /** - * Puts an entry into the Startup Cache - * @param installLocation - * The Install Location where the item is installed - * @param id - * The GUID of the item - * @param op - * The name of the operation to be performed - * @param shouldCreate - * Whether or not we should create a new entry for this item - * in the cache if one does not already exist. - */ - put: function StartupCache_put(installLocation, id, op, shouldCreate) { - var itemLocation = installLocation.getItemLocation(id); - - var descriptor = null; - var mtime = null; - if (itemLocation) { - itemLocation.QueryInterface(Ci.nsILocalFile); - descriptor = getDescriptorFromFile(itemLocation, installLocation); - if (itemLocation.exists() && itemLocation.isDirectory()) - mtime = Math.floor(itemLocation.lastModifiedTime / 1000); - } - - this._putRaw(installLocation.name, id, descriptor, mtime, op, shouldCreate); - }, - - /** - * Private helper function for putting an entry into the Startup Cache - * without relying on the presence of its associated nsIInstallLocation - * instance. - * - * @param key - * The install location name. - * @param id - * The ID of the item. - * @param descriptor - * Value returned from absoluteDescriptor. May be null, in which - * case the descriptor field is not updated. - * @param mtime - * The last modified time of the item. May be null, in which case the - * descriptor field is not updated. - * @param op - * The OP code to store with the entry. - * @param shouldCreate - * Boolean value indicating whether to create or delete the entry. - */ - _putRaw: function StartupCache__putRaw(key, id, descriptor, mtime, op, shouldCreate) { - if (!(key in this.entries)) - this.entries[key] = { }; - if (!(id in this.entries[key])) - this.entries[key][id] = { }; - if (shouldCreate) { - if (!this.entries[key][id]) - this.entries[key][id] = { }; - - var entry = this.entries[key][id]; - - if (descriptor) - entry.descriptor = descriptor; - if (mtime) - entry.mtime = mtime; - entry.op = op; - entry.location = key; - } - else - this.entries[key][id] = null; - }, - - /** - * Clears an entry from the Startup Cache - * @param installLocation - * The Install Location where item is installed - * @param id - * The GUID of the item. - */ - clearEntry: function StartupCache_clearEntry(installLocation, id) { - var key = installLocation.name; - if (key in this.entries && id in this.entries[key]) - this.entries[key][id] = null; - }, - - /** - * Get all the startup cache entries for a particular ID. - * @param id - * The GUID of the item to locate. - * @returns An array of Startup Cache entries for the specified ID. - */ - findEntries: function StartupCache_findEntries(id) { - var entries = []; - for (var key in this.entries) { - if (id in this.entries[key]) - entries.push(this.entries[key][id]); - } - return entries; - }, - - /** - * Read the Item-Change manifest file into a hash of properties. - * The Item-Change manifest currently holds a list of paths, with the last - * mtime for each path, and the GUID of the item at that path. - */ - read: function StartupCache_read() { - var itemChangeManifest = FileUtils.getFile(KEY_PROFILEDIR, - [FILE_EXTENSIONS_STARTUP_CACHE]); - if (!itemChangeManifest.exists()) { - // There is no change manifest for some reason, either we're in an initial - // state or something went wrong with one of the other files and the - // change manifest was removed. Return an empty dataset and rebuild. - gFirstRun = true; - return; - } - var fis = Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(Ci.nsIFileInputStream); - fis.init(itemChangeManifest, -1, -1, false); - if (fis instanceof Ci.nsILineInputStream) { - var line = { value: "" }; - var more = false; - do { - more = fis.readLine(line); - if (line.value) { - // The Item-Change manifest is formatted like so: - // (pd = descriptor) - // location-key\tguid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op - // location-key\tguid-of-item\tpd-to-extension2\tmtime-of-pd\tpending-op - // ... - // We hash on location-key first, because we don't want to have to - // spin up the main extensions datasource on every start to determine - // the Install Location for an item. - // We hash on guid second, because we want a way to quickly determine - // item GUID during a check loop that runs on every startup. - var parts = line.value.split("\t"); - // Silently drop any entries in unknown install locations - if (!InstallLocations.get(parts[0])) - continue; - var op = parts[4]; - this._putRaw(parts[0], parts[1], parts[2], parts[3], op, true); - if (op) - PendingOperations.addItem(op, { locationKey: parts[0], id: parts[1] }); - } - } - while (more); - } - fis.close(); - }, - - /** - * Writes the Startup Cache to disk - */ - write: function StartupCache_write() { - var extensionsCacheFile = FileUtils.getFile(KEY_PROFILEDIR, - [FILE_EXTENSIONS_STARTUP_CACHE]); - var fos = FileUtils.openSafeFileOutputStream(extensionsCacheFile); - for (var locationKey in this.entries) { - for (var id in this.entries[locationKey]) { - var entry = this.entries[locationKey][id]; - if (entry) { - try { - var itemLocation = getFileFromDescriptor(entry.descriptor, InstallLocations.get(locationKey)); - - // Update our knowledge of this item's last-modified-time. - // XXXdarin: this may cause us to miss changes in some cases. - var itemMTime = 0; - if (itemLocation.exists() && itemLocation.isDirectory()) - itemMTime = Math.floor(itemLocation.lastModifiedTime / 1000); - - // Each line in the startup cache manifest is in this form: - // location-key\tid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op - var line = locationKey + "\t" + id + "\t" + entry.descriptor + "\t" + - itemMTime + "\t" + entry.op + "\r\n"; - fos.write(line, line.length); - } - catch (e) {} - } - } - } - FileUtils.closeSafeFileOutputStream(fos); - } -}; - -/** - * Installs, manages and tracks compatibility for Extensions and Themes - * @constructor - */ -function ExtensionManager() { - gApp = Cc["@mozilla.org/xre/app-info;1"]. - getService(Ci.nsIXULAppInfo).QueryInterface(Ci.nsIXULRuntime); - gOSTarget = gApp.OS; - try { - gXPCOMABI = gApp.XPCOMABI; - } catch (ex) { - // Provide a default for gXPCOMABI. It won't be compared to an - // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI - // as targetPlatform), but it will be displayed in error messages and - // transmitted to update URLs. - gXPCOMABI = UNKNOWN_XPCOM_ABI; - } - gPref = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefBranch2). - QueryInterface(Ci.nsIPrefService); - var defaults = gPref.getDefaultBranch(""); - try { - gDefaultTheme = defaults.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN); - } catch(e) {} - - gOS = Cc["@mozilla.org/observer-service;1"]. - getService(Ci.nsIObserverService); - gOS.addObserver(this, "xpcom-shutdown", false); - gOS.addObserver(this, "lightweight-theme-preview-requested", false); - gOS.addObserver(this, "lightweight-theme-change-requested", false); - - gConsole = Cc["@mozilla.org/consoleservice;1"]. - getService(Ci.nsIConsoleService); - - gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. - getService(Ci.nsIRDFService); - gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT); - - // Register Global Install Location - var appGlobalExtensions = FileUtils.getDir(KEY_APPDIR, [DIR_EXTENSIONS], - false); - var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL; - var globalLocation = new DirectoryInstallLocation(KEY_APP_GLOBAL, - appGlobalExtensions, true, - priority, false); - InstallLocations.put(globalLocation); - - // Register App-Profile Install Location - var appProfileExtensions = FileUtils.getDir(KEY_PROFILEDS, [DIR_EXTENSIONS], - false); - var priority = Ci.nsIInstallLocation.PRIORITY_APP_PROFILE; - var profileLocation = new DirectoryInstallLocation(KEY_APP_PROFILE, - appProfileExtensions, false, - priority, false); - InstallLocations.put(profileLocation); - - // Register per-user Install Location - try { - var appSystemUExtensions = FileUtils.getDir("XREUSysExt", [gApp.ID], false); - } - catch(e) { } - - if (appSystemUExtensions) { - var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_USER; - var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_USER, - appSystemUExtensions, false, - priority, true); - - InstallLocations.put(systemLocation); - } - - // Register App-System-Shared Install Location - try { - var appSystemSExtensions = FileUtils.getDir("XRESysSExtPD", [gApp.ID], false); - } - catch (e) { } - - if (appSystemSExtensions) { - var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 10; - var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_SHARE, - appSystemSExtensions, true, - priority, true); - InstallLocations.put(systemLocation); - } - - // Register App-System-Local Install Location - try { - var appSystemLExtensions = FileUtils.getDir("XRESysLExtPD", [gApp.ID], false); - } - catch (e) { } - - if (appSystemLExtensions) { - var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 20; - var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL, - appSystemLExtensions, true, - priority, true); - InstallLocations.put(systemLocation); - } - -#ifdef XP_WIN - // Register HKEY_LOCAL_MACHINE Install Location - InstallLocations.put( - new WinRegInstallLocation("winreg-app-global", - nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, - true, - Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 10)); - - // Register HKEY_CURRENT_USER Install Location - InstallLocations.put( - new WinRegInstallLocation("winreg-app-user", - nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, - false, - Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_USER + 10)); -#endif - - // Register Additional Install Locations - var categoryManager = Cc["@mozilla.org/categorymanager;1"]. - getService(Ci.nsICategoryManager); - var locations = categoryManager.enumerateCategory(CATEGORY_INSTALL_LOCATIONS); - while (locations.hasMoreElements()) { - var entry = locations.getNext().QueryInterface(Ci.nsISupportsCString).data; - var contractID = categoryManager.getCategoryEntry(CATEGORY_INSTALL_LOCATIONS, entry); - var location = Cc[contractID].getService(Ci.nsIInstallLocation); - InstallLocations.put(location); - } -} - -ExtensionManager.prototype = { - /** - * See nsIObserver.idl - */ - observe: function EM_observe(subject, topic, data) { - switch (topic) { - case "profile-after-change": - this._profileSelected(); - break; - case "quit-application-requested": - this._confirmCancelDownloadsOnQuit(subject); - break; - case "offline-requested": - this._confirmCancelDownloadsOnOffline(subject); - break; - case "lightweight-theme-preview-requested": - if (gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) { - let cancel = subject.QueryInterface(Ci.nsISupportsPRBool); - cancel.data = true; - } - break; - case "lightweight-theme-change-requested": - let theme = JSON.parse(data); - if (!theme) - return; - - if (gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) { - if (getPref("getBoolPref", PREF_EM_DSS_ENABLED, false)) { - gPref.clearUserPref(PREF_GENERAL_SKINS_SELECTEDSKIN); - return; - } - - let cancel = subject.QueryInterface(Ci.nsISupportsPRBool); - cancel.data = true; - gPref.setBoolPref(PREF_DSS_SWITCHPENDING, true); - gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, gDefaultTheme); - gPref.setCharPref(PREF_LWTHEME_TO_SELECT, theme.id); - - // Open the UI so the user can see that a restart is necessary - var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. - getService(Ci.nsIWindowMediator); - var win = wm.getMostRecentWindow("Extension:Manager"); - - if (win) { - win.showView("themes"); - return; - } - - var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher); - var param = Cc["@mozilla.org/supports-array;1"]. - createInstance(Ci.nsISupportsArray); - var arg = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - arg.data = "themes"; - param.AppendElement(arg); - ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_MANAGER, param); - return; - } - else { - // Cancel any pending theme change and allow the lightweight theme - // change to go ahead - if (gPref.prefHasUserValue(PREF_DSS_SWITCHPENDING)) - gPref.clearUserPref(PREF_DSS_SWITCHPENDING); - if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT)) - gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT); - } - break; - case "xpcom-shutdown": - this._shutdown(); - break; - case "nsPref:changed": - if (data == PREF_EM_LOGGING_ENABLED) - this._loggingToggled(); - else if (data == gCheckCompatibilityPref || - data == PREF_EM_CHECK_UPDATE_SECURITY) - this._updateAppDisabledState(); - else if ((data == PREF_MATCH_OS_LOCALE) || (data == PREF_SELECTED_LOCALE)) - this._updateLocale(); - break; - } - }, - - /** - * Refresh the logging enabled global from preferences when the user changes - * the preference settting. - */ - _loggingToggled: function EM__loggingToggled() { - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - }, - - /** - * Retrieves the current locale - */ - _updateLocale: function EM__updateLocale() { - try { - if (gPref.getBoolPref(PREF_MATCH_OS_LOCALE)) { - var localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"]. - getService(Ci.nsILocaleService); - gLocale = localeSvc.getLocaleComponentForUserAgent(); - return; - } - } - catch (ex) { - } - gLocale = gPref.getCharPref(PREF_SELECTED_LOCALE); - }, - - /** - * When a preference is toggled that affects whether an item is usable or not - * we must app-enable or app-disable the item based on the new settings. - */ - _updateAppDisabledState: function EM__updateAppDisabledState() { - gCheckCompatibility = getPref("getBoolPref", gCheckCompatibilityPref, true); - gCheckUpdateSecurity = getPref("getBoolPref", PREF_EM_CHECK_UPDATE_SECURITY, true); - var ds = this.datasource; - - // Enumerate all items - var ctr = getContainer(ds, ds._itemRoot); - var elements = ctr.GetElements(); - while (elements.hasMoreElements()) { - var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource); - - // App disable or enable items as necessary - // _appEnableItem and _appDisableItem will do nothing if the item is already - // in the right state. - var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI); - if (this._isUsableItem(id)) - this._appEnableItem(id); - else - this._appDisableItem(id); - } - }, - - /** - * Initialize the system after a profile has been selected. - */ - _profileSelected: function EM__profileSelected() { - // Tell the Chrome Registry which Skin to select - try { - if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) { - var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT); - gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect); - gPref.clearUserPref(PREF_DSS_SWITCHPENDING); - gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT); - - // If we've changed to a non-default theme make sure there is no - // lightweight theme selected - if (toSelect != gDefaultTheme) { - if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) - gPref.clearUserPref(PREF_LWTHEME_TO_SELECT); - LightweightThemeManager.currentTheme = null; - } - } - - if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) { - var id = gPref.getCharPref(PREF_LWTHEME_TO_SELECT); - if (id) - LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(id); - else - LightweightThemeManager.currentTheme = null; - gPref.clearUserPref(PREF_LWTHEME_TO_SELECT); - } - } - catch (e) { - } - - var version = gApp.version.replace(gBranchVersion, "$1"); - gCheckCompatibilityPref = PREF_EM_CHECK_COMPATIBILITY + "." + version; - - gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false); - gCheckCompatibility = getPref("getBoolPref", gCheckCompatibilityPref, true); - gCheckUpdateSecurity = getPref("getBoolPref", PREF_EM_CHECK_UPDATE_SECURITY, true); - - if ("nsICrashReporter" in Ci && gApp instanceof Ci.nsICrashReporter) { - // Annotate the crash report with relevant add-on information. - try { - gApp.annotateCrashReport("Add-ons", gPref.getCharPref(PREF_EM_ENABLED_ITEMS)); - } catch (e) { } - try { - gApp.annotateCrashReport("Theme", gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN)); - } catch (e) { } - try { - gApp.annotateCrashReport("EMCheckCompatibility", gCheckCompatibility); - } catch (e) { } - } - - gPref.addObserver("extensions.", this, false); - gPref.addObserver(PREF_MATCH_OS_LOCALE, this, false); - gPref.addObserver(PREF_SELECTED_LOCALE, this, false); - this._updateLocale(); - }, - - /** - * Notify user that there are new addons updates - */ - _showUpdatesWindow: function EM__showUpdatesWindow() { - if (!getPref("getBoolPref", PREF_UPDATE_NOTIFYUSER, false)) - return; - - var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher); - var param = Cc["@mozilla.org/supports-array;1"]. - createInstance(Ci.nsISupportsArray); - var arg = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - arg.data = "updates-only"; - param.AppendElement(arg); - ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_UPDATES, param); - }, - - /** - * Clean up on application shutdown to avoid leaks. - */ - _shutdown: function EM__shutdown() { - if (!gAllowFlush) { - // Something went wrong and there are potentially flushes pending. - ERROR("Reached _shutdown and without clearing any pending flushes"); - try { - gAllowFlush = true; - if (gManifestNeedsFlush) { - gManifestNeedsFlush = false; - this._updateManifests(false); - } - if (gDSNeedsFlush) { - gDSNeedsFlush = false; - this.datasource.Flush(); - } - } - catch (e) { - ERROR("Error flushing caches: " + e); - } - } - - gOS.removeObserver(this, "xpcom-shutdown"); - gOS.removeObserver(this, "lightweight-theme-preview-requested"); - gOS.removeObserver(this, "lightweight-theme-change-requested"); - - // Release strongly held services. - gOS = null; - if (this._ds) { - gRDF.UnregisterDataSource(this._ptr); - this._ptr = null; - this._ds.shutdown(); - this._ds = null; - } - gRDF = null; - if (gPref) { - gPref.removeObserver("extensions.", this); - gPref.removeObserver(PREF_MATCH_OS_LOCALE, this); - gPref.removeObserver(PREF_SELECTED_LOCALE, this); - } - gPref = null; - gConsole = null; - gVersionChecker = null; - gInstallManifestRoot = null; - gApp = null; - }, - - /** - * Check for presence of critical Extension system files. If any is missing, - * delete the others and signal that the system needs to rebuild them all - * from scratch. - * @returns true if any critical file is missing and the system needs to - * be rebuilt, false otherwise. - */ - _ensureDatasetIntegrity: function EM__ensureDatasetIntegrity() { - var profD = FileUtils.getDir(KEY_PROFILEDIR, [], false); - var extensionsDS = profD.clone(); - extensionsDS.append(FILE_EXTENSIONS); - var extensionsINI = profD.clone(); - extensionsINI.append(FILE_EXTENSION_MANIFEST); - var extensionsCache = profD; - extensionsCache.append(FILE_EXTENSIONS_STARTUP_CACHE); - - var dsExists = extensionsDS.exists(); - var iniExists = extensionsINI.exists(); - var cacheExists = extensionsCache.exists(); - - if (dsExists && iniExists && cacheExists) - return [false, !iniExists]; - - // If any of the files are missing, remove the .ini file - if (iniExists) - extensionsINI.remove(false); - - // If the extensions datasource is missing remove the .cache file if it exists - if (!dsExists && cacheExists) - extensionsCache.remove(false); - - return [true, !iniExists]; - }, - - /** - * See nsIExtensionManager.idl - */ - start: function EM_start() { - var isDirty, forceAutoReg; - - // Check for missing manifests - e.g. missing extensions.ini, missing - // extensions.cache, extensions.rdf etc. If any of these files - // is missing then we are in some kind of weird or initial state and need - // to force a regeneration. - [isDirty, forceAutoReg] = this._ensureDatasetIntegrity(); - - // Block attempts to flush for the entire startup - gAllowFlush = false; - - // Configure any items that are being installed, uninstalled or upgraded - // by being added, removed or modified by another process. We must do this - // on every startup since there is no way we can tell if this has happened - // or not! - if (this._checkForFileChanges()) - isDirty = true; - - this._showUpdatesWindow(); - - if (PendingOperations.size != 0) - isDirty = true; - - var needsRestart = false; - // Extension Changes - if (isDirty) { - needsRestart = this._finishOperations(); - - if (forceAutoReg) { - this._extensionListChanged = true; - needsRestart = true; - } - } - - // Resume flushing and perform a flush for anything that was deferred - try { - gAllowFlush = true; - if (gManifestNeedsFlush) { - gManifestNeedsFlush = false; - this._updateManifests(false); - } - if (gDSNeedsFlush) { - gDSNeedsFlush = false; - this.datasource.Flush(); - } - } - catch (e) { - ERROR("Error flushing caches: " + e); - } - - // Reset the first run flag. - gFirstRun = false; - - return needsRestart; - }, - - /** - * Notified when a timer fires - * @param timer - * The timer that fired - */ - notify: function EM_notify(timer) { - if (!getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true)) - return; - - var items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY); - - var updater = new ExtensionItemUpdater(this); - updater.checkForUpdates(items, items.length, - Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION, - new BackgroundUpdateCheckListener(this.datasource), - UPDATE_WHEN_PERIODIC_UPDATE); - - LightweightThemeManager.updateCurrentTheme(); - }, - - /** - * Check to see if a file is a XPI/JAR file that the user dropped into this - * Install Location. (i.e. a XPI that is not a staged XPI from an install - * transaction that is currently in operation). - * @param file - * The XPI/JAR file to configure - * @param location - * The Install Location where this file was found. - * @returns A nsIUpdateItem representing the dropped XPI if this file was a - * XPI/JAR that needs installation, null otherwise. - */ - _getItemForDroppedFile: function EM__getItemForDroppedFile(file, location) { - if (fileIsItemPackage(file)) { - // We know nothing about this item, it is not something we've - // staged in preparation for finalization, so assume it's something - // the user dropped in. - LOG("A Item Package appeared at: " + file.path + " that we know " + - "nothing about, assuming it was dropped in by the user and " + - "configuring for installation now. Location Key: " + location.name); - - var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true); - if (!installManifestFile.exists()) - return null; - var installManifest = getInstallManifest(installManifestFile); - installManifestFile.remove(false); - var ds = this.datasource; - var installData = this._getInstallData(installManifest); - var targetAppInfo = ds.getTargetApplicationInfo(installData.id, installManifest); - return makeItem(installData.id, - installData.version, - location.name, - targetAppInfo ? targetAppInfo.minVersion : "", - targetAppInfo ? targetAppInfo.maxVersion : "", - getManifestProperty(installManifest, "name"), - "", /* XPI Update URL */ - "", /* XPI Update Hash */ - getManifestProperty(installManifest, "iconURL"), - getManifestProperty(installManifest, "updateURL"), - getManifestProperty(installManifest, "updateKey"), - installData.type, - targetAppInfo ? targetAppInfo.appID : gApp.ID); - } - return null; - }, - - /** - * Configure an item that was installed or upgraded by another process - * so that |_finishOperations| can properly complete processing and - * registration. - * As this is the only point at which we can reliably know the Install - * Location of this item, we use this as an opportunity to: - * 1. Check that this item is compatible with this Firefox version. - * 2. If it is, configure the item by using the supplied callback. - * We do not do any special handling in the case that the item is - * not compatible with this version other than to simply not register - * it and log that fact - there is no "phone home" check for updates. - * It may or may not make sense to do this, but for now we'll just - * not register. - * @param id - * The GUID of the item to validate and configure. - * @param location - * The Install Location where this item is installed. - * @param callback - * The callback that configures the item for installation upon - * successful validation. - */ - installItem: function EM_installItem(id, location, callback) { - // As this is the only pint at which we reliably know the installation - var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST); - if (installRDF.exists()) { - LOG("Item Installed/Upgraded at Install Location: " + location.name + - " Item ID: " + id + ", attempting to register..."); - var installManifest = getInstallManifest(installRDF); - var installData = this._getInstallData(installManifest); - if (installData.error == INSTALLERROR_SUCCESS) { - LOG("... success, item is compatible"); - callback(installManifest, installData.id, location, installData.type); - } - else if (installData.error == INSTALLERROR_INCOMPATIBLE_VERSION) { - LOG("... success, item installed but is not compatible"); - callback(installManifest, installData.id, location, installData.type); - this._appDisableItem(id); - } - else if (installData.error == INSTALLERROR_INSECURE_UPDATE) { - LOG("... success, item installed but does not provide updates securely"); - callback(installManifest, installData.id, location, installData.type); - this._appDisableItem(id); - } - else if (installData.error == INSTALLERROR_BLOCKLISTED) { - LOG("... success, item installed but is blocklisted"); - callback(installManifest, installData.id, location, installData.type); - this._appDisableItem(id); - } - else if (installData.error == INSTALLERROR_SOFTBLOCKED) { - LOG("... success, item installed but is soft blocked, item will be disabled"); - callback(installManifest, installData.id, location, installData.type); - this.disableItem(id); - } - else { - /** - * Turns an error code into a message for logging - * @param error - * an Install Error code - * @returns A string message to be logged. - */ - function translateErrorMessage(error) { - switch (error) { - case INSTALLERROR_INVALID_GUID: - return "Invalid GUID"; - case INSTALLERROR_INVALID_VERSION: - return "Invalid Version"; - case INSTALLERROR_INCOMPATIBLE_PLATFORM: - return "Incompatible Platform"; - } - } - LOG("... failure, item is not compatible, error: " + - translateErrorMessage(installData.error)); - - // Add the item to the Startup Cache anyway, so we don't re-detect it - // every time the app starts. - StartupCache.put(location, id, OP_NONE, true); - } - } - }, - - /** - * Check for changes to items that were made independently of the Extension - * Manager, e.g. items were added or removed from a Install Location or items - * in an Install Location changed. - */ - _checkForFileChanges: function EM__checkForFileChanges() { - var em = this; - - /** - * Determines if an item can be used based on whether or not the install - * location of the "item" has an equal or higher priority than the install - * location where another version may live. - * @param id - * The GUID of the item being installed. - * @param location - * The location where an item is to be installed. - * @returns true if the item can be installed at that location, false - * otherwise. - */ - function canUse(id, location) { - for (var locationKey in StartupCache.entries) { - if (locationKey != location.name && - id in StartupCache.entries[locationKey]) { - if (StartupCache.entries[locationKey][id]) { - var oldInstallLocation = InstallLocations.get(locationKey); - if (oldInstallLocation.priority <= location.priority) - return false; - } - } - } - return true; - } - - /** - * Gets a Dialog Param Block loaded with a set of strings to initialize the - * XPInstall Confirmation Dialog. - * @param strings - * An array of strings - * @returns A nsIDialogParamBlock loaded with the strings and dialog state. - */ - function getParamBlock(strings) { - var dpb = Cc["@mozilla.org/embedcomp/dialogparam;1"]. - createInstance(Ci.nsIDialogParamBlock); - // OK and Cancel Buttons - dpb.SetInt(0, 2); - // Number of Strings - dpb.SetInt(1, strings.length); - dpb.SetNumberStrings(strings.length); - // Add Strings - for (var i = 0; i < strings.length; ++i) - dpb.SetString(i, strings[i]); - - var supportsString = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - supportsString.data = bundle.GetStringFromName("droppedInWarning"); - var objs = Cc["@mozilla.org/array;1"]. - createInstance(Ci.nsIMutableArray); - objs.appendElement(supportsString, false); - dpb.objects = objs; - return dpb; - } - - /** - * Installs a set of files which were dropped into an install location by - * the user, only after user confirmation. - * @param droppedInFiles - * An array of JS objects with the following properties: - * "file" The nsILocalFile where the XPI lives - * "location" The Install Location where the XPI was found. - * @param xpinstallStrings - * An array of strings used to initialize the XPInstall Confirm - * dialog. - */ - function installDroppedInFiles(droppedInFiles, xpinstallStrings) { - if (droppedInFiles.length == 0) - return; - - var dpb = getParamBlock(xpinstallStrings); - var ifptr = Cc["@mozilla.org/supports-interface-pointer;1"]. - createInstance(Ci.nsISupportsInterfacePointer); - ifptr.data = dpb; - ifptr.dataIID = Ci.nsIDialogParamBlock; - var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher); - ww.openWindow(null, URI_XPINSTALL_CONFIRM_DIALOG, - "", "chrome,centerscreen,modal,dialog,titlebar", ifptr); - if (!dpb.GetInt(0)) { - // User said OK - install items - for (var i = 0; i < droppedInFiles.length; ++i) { - em.installItemFromFile(droppedInFiles[i].file, - droppedInFiles[i].location.name); - // We are responsible for cleaning up this file - droppedInFiles[i].file.remove(false); - } - } - else { - for (i = 0; i < droppedInFiles.length; ++i) { - // We are responsible for cleaning up this file - droppedInFiles[i].file.remove(false); - } - } - } - - var isDirty = false; - var ignoreMTimeChanges = getPref("getBoolPref", PREF_EM_IGNOREMTIMECHANGES, - false); - StartupCache.read(); - - // Array of objects with 'location' and 'id' properties to maybe install. - var newItems = []; - - var droppedInFiles = []; - var xpinstallStrings = []; - - // Enumerate over the install locations from low to high priority. The - // enumeration returned is pre-sorted. - var installLocations = this.installLocations; - while (installLocations.hasMoreElements()) { - var location = installLocations.getNext().QueryInterface(Ci.nsIInstallLocation); - - // Hash the set of items actually held by the Install Location. - var actualItems = { }; - var entries = location.itemLocations; - while (true) { - var entry = entries.nextFile; - if (!entry) - break; - - // Is this location a valid item? It must be a directory, and contain - // an install.rdf manifest: - if (entry.isDirectory()) { - var installRDF = entry.clone(); - installRDF.append(FILE_INSTALL_MANIFEST); - - var id = location.getIDForLocation(entry); - if (!id || (!installRDF.exists() && - !location.itemIsManagedIndependently(id))) - continue; - - actualItems[id] = entry; - } - else { - // Check to see if this file is a XPI/JAR dropped into this dir - // by the user, installing it if necessary. We do this here rather - // than separately in |_finishOperations| because I don't want to - // walk these lists multiple times on every startup. - var item = this._getItemForDroppedFile(entry, location); - if (item) { - var prettyName = ""; - try { - var zipReader = getZipReaderForFile(entry); - var principal = zipReader.getCertificatePrincipal(null); - if (principal && principal.hasCertificate) { - if (verifyZipSigning(zipReader, principal)) { - var x509 = principal.certificate; - if (x509 instanceof Ci.nsIX509Cert && x509.commonName.length > 0) - prettyName = x509.commonName; - else - prettyName = principal.prettyName; - } - else { - // The xpi isn't correctly signed, don't offer to install. - LOG("Ignoring " + entry.path + " as it is not correctly signed."); - zipReader.close(); - entry.remove(true); - continue; - } - } - } - catch (e) { } - if (zipReader) - zipReader.close(); - droppedInFiles.push({ file: entry, location: location }); - xpinstallStrings = xpinstallStrings.concat([item.name, - getURLSpecFromFile(entry), - item.iconURL, - prettyName]); - isDirty = true; - } - } - } - - if (location.name in StartupCache.entries) { - // Look for items that have been uninstalled by removing their directory. - for (var id in StartupCache.entries[location.name]) { - if (!StartupCache.entries[location.name] || - !StartupCache.entries[location.name][id]) - continue; - - // Force _finishOperations to run if we have enabled or disabled items. - // XXXdarin this should be unnecessary now that we check - // PendingOperations.size in start() - if (StartupCache.entries[location.name][id].op == OP_NEEDS_ENABLE || - StartupCache.entries[location.name][id].op == OP_NEEDS_DISABLE) - isDirty = true; - - if (!(id in actualItems) && - StartupCache.entries[location.name][id].op != OP_NEEDS_UNINSTALL && - StartupCache.entries[location.name][id].op != OP_NEEDS_INSTALL && - StartupCache.entries[location.name][id].op != OP_NEEDS_UPGRADE) { - // We have an entry for this id in the Extensions database, for this - // install location, but it no longer exists in the Install Location. - // We can infer from this that the item has been removed, so uninstall - // it properly. - if (canUse(id, location)) { - LOG("Item Uninstalled via file removal from: " + StartupCache.entries[location.name][id].descriptor + - " Item ID: " + id + " Location Key: " + location.name + ", uninstalling item."); - - // Load the Extensions Datasource and force this item into the visible - // items list if it is not already. This allows us to handle the case - // where there is an entry for an item in the Startup Cache but not - // in the extensions.rdf file - in that case the item will not be in - // the visible list and calls to |getInstallLocation| will mysteriously - // fail. - this.datasource.updateVisibleList(id, location.name, false); - this.uninstallItem(id); - isDirty = true; - } - } - else if (!ignoreMTimeChanges) { - // Look for items whose mtime has changed, and as such we can assume - // they have been "upgraded". - var lf = { path: StartupCache.entries[location.name][id].descriptor }; - try { - lf = getFileFromDescriptor(StartupCache.entries[location.name][id].descriptor, location); - } - catch (e) { } - - if (lf.exists && lf.exists()) { - var actualMTime = Math.floor(lf.lastModifiedTime / 1000); - if (actualMTime != StartupCache.entries[location.name][id].mtime) { - LOG("Item Location path changed: " + lf.path + " Item ID: " + - id + " Location Key: " + location.name + ", attempting to upgrade item..."); - if (canUse(id, location)) { - this.installItem(id, location, - function(installManifest, id, location, type) { - em._upgradeItem(installManifest, id, location, - type); - }); - isDirty = true; - } - } - } - else { - isDirty = true; - LOG("Install Location returned a missing or malformed item path! " + - "Item Path: " + lf.path + ", Location Key: " + location.name + - " Item ID: " + id); - if (canUse(id, location)) { - // Load the Extensions Datasource and force this item into the visible - // items list if it is not already. This allows us to handle the case - // where there is an entry for an item in the Startup Cache but not - // in the extensions.rdf file - in that case the item will not be in - // the visible list and calls to |getInstallLocation| will mysteriously - // fail. - this.datasource.updateVisibleList(id, location.name, false); - this.uninstallItem(id); - } - } - } - } - } - - // Look for items that have been installed by appearing in the location. - for (var id in actualItems) { - if (!(location.name in StartupCache.entries) || - !(id in StartupCache.entries[location.name]) || - !StartupCache.entries[location.name][id]) { - // Remember that we've seen this item - StartupCache.put(location, id, OP_NONE, true); - // Push it on the stack of items to maybe install later - newItems.push({location: location, id: id}); - } - } - } - - // Process any newly discovered items. We do this here instead of in the - // previous loop so that we can be sure that we have a fully populated - // StartupCache. - for (var i = 0; i < newItems.length; ++i) { - var id = newItems[i].id; - var location = newItems[i].location; - if (canUse(id, location)) { - LOG("Item Installed via directory addition to Install Location: " + - location.name + " Item ID: " + id + ", attempting to register..."); - this.installItem(id, location, - function(installManifest, id, location, type) { - em._configureForthcomingItem(installManifest, id, location, - type); - }); - // Disable add-ons on install when the InstallDisabled file exists. - // This is so Talkback will be disabled on a subset of installs. - var installDisabled = location.getItemFile(id, "InstallDisabled"); - if (installDisabled.exists()) - em.disableItem(id); - isDirty = true; - } - } - - // Ask the user if they want to install the dropped items, for security - // purposes. - installDroppedInFiles(droppedInFiles, xpinstallStrings); - - return isDirty; - }, - - _checkForUncoveredItem: function EM__checkForUncoveredItem(id) { - var ds = this.datasource; - var oldLocation = this.getInstallLocation(id); - var newLocations = []; - for (var locationKey in StartupCache.entries) { - var location = InstallLocations.get(locationKey); - if (id in StartupCache.entries[locationKey] && - location.priority > oldLocation.priority) - newLocations.push(location); - } - newLocations.sort(function(a, b) { return b.priority - a.priority; }); - if (newLocations.length > 0) { - for (var i = 0; i < newLocations.length; ++i) { - // Check to see that the item at the location exists - var installRDF = newLocations[i].getItemFile(id, FILE_INSTALL_MANIFEST); - if (installRDF.exists()) { - // Update the visible item cache so that |_finalizeUpgrade| is properly - // called from |_finishOperations| - var name = newLocations[i].name; - ds.updateVisibleList(id, name, true); - PendingOperations.addItem(OP_NEEDS_UPGRADE, - { locationKey: name, id: id }); - PendingOperations.addItem(OP_NEEDS_INSTALL, - { locationKey: name, id: id }); - break; - } - else { - // If no item exists at the location specified, remove this item - // from the visible items list and check again. - StartupCache.clearEntry(newLocations[i], id); - ds.updateVisibleList(id, null, true); - } - } - } - else - ds.updateVisibleList(id, null, true); - }, - - /** - * Finish up pending operations - perform upgrades, installs, enables/disables, - * uninstalls etc. - * @returns true if actions were performed that require a restart, false - * otherwise. - */ - _finishOperations: function EM__finishOperations() { - try { - // Stuff has changed, load the Extensions datasource in all its RDFey - // glory. - var ds = this.datasource; - var updatedTargetAppInfos = []; - - var needsRestart = false; - var upgrades = []; - var newAddons = []; - var addons = getPref("getCharPref", PREF_EM_NEW_ADDONS_LIST, ""); - if (addons != "") - newAddons = addons.split(","); - do { - // Enable and disable during startup so items that are changed in the - // ui can be reset to a no-op. - // Look for extensions that need to be enabled. - var items = PendingOperations.getOperations(OP_NEEDS_ENABLE); - for (var i = items.length - 1; i >= 0; --i) { - var id = items[i].id; - var installLocation = this.getInstallLocation(id); - StartupCache.put(installLocation, id, OP_NONE, true); - PendingOperations.clearItem(OP_NEEDS_ENABLE, id); - needsRestart = true; - } - PendingOperations.clearItems(OP_NEEDS_ENABLE); - - // Look for extensions that need to be disabled. - items = PendingOperations.getOperations(OP_NEEDS_DISABLE); - for (i = items.length - 1; i >= 0; --i) { - id = items[i].id; - installLocation = this.getInstallLocation(id); - StartupCache.put(installLocation, id, OP_NONE, true); - PendingOperations.clearItem(OP_NEEDS_DISABLE, id); - needsRestart = true; - } - PendingOperations.clearItems(OP_NEEDS_DISABLE); - - // Look for extensions that need to be upgraded. The process here is to - // uninstall the old version of the extension first, then install the - // new version in its place. - items = PendingOperations.getOperations(OP_NEEDS_UPGRADE); - for (i = items.length - 1; i >= 0; --i) { - id = items[i].id; - var newLocation = InstallLocations.get(items[i].locationKey); - // check if there is updated app compatibility info - var newTargetAppInfo = ds.getUpdatedTargetAppInfo(id); - if (newTargetAppInfo) - updatedTargetAppInfos.push(newTargetAppInfo); - this._finalizeUpgrade(id, newLocation); - upgrades.push(id); - } - PendingOperations.clearItems(OP_NEEDS_UPGRADE); - - // Install items - items = PendingOperations.getOperations(OP_NEEDS_INSTALL); - for (i = items.length - 1; i >= 0; --i) { - needsRestart = true; - id = items[i].id; - // check if there is updated app compatibility info - newTargetAppInfo = ds.getUpdatedTargetAppInfo(id); - if (newTargetAppInfo) - updatedTargetAppInfos.push(newTargetAppInfo); - this._finalizeInstall(id, null); - if (upgrades.indexOf(id) < 0 && newAddons.indexOf(id) < 0) - newAddons.push(id); - } - PendingOperations.clearItems(OP_NEEDS_INSTALL); - - // Look for extensions that need to be removed. This MUST be done after - // the install operations since extensions to be installed may have to be - // uninstalled if there are errors during the installation process! - items = PendingOperations.getOperations(OP_NEEDS_UNINSTALL); - for (i = items.length - 1; i >= 0; --i) { - id = items[i].id; - this._finalizeUninstall(id); - this._checkForUncoveredItem(id); - needsRestart = true; - var pos = newAddons.indexOf(id); - if (pos >= 0) - newAddons.splice(pos, 1); - } - PendingOperations.clearItems(OP_NEEDS_UNINSTALL); - - // When there have been operations and all operations have completed. - if (PendingOperations.size == 0) { - // If there is updated app compatibility info update the datasource. - for (i = 0; i < updatedTargetAppInfos.length; ++i) - ds.setTargetApplicationInfo(updatedTargetAppInfos[i].id, - updatedTargetAppInfos[i].targetAppID, - updatedTargetAppInfos[i].minVersion, - updatedTargetAppInfos[i].maxVersion, - null); - - // Enumerate all items - var ctr = getContainer(ds, ds._itemRoot); - var elements = ctr.GetElements(); - while (elements.hasMoreElements()) { - var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource); - - // Ensure appDisabled is in the correct state. - id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI); - if (this._isUsableItem(id)) - ds.setItemProperty(id, EM_R("appDisabled"), null); - else - ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true")); - - // userDisabled is set based on its value being OP_NEEDS_ENABLE or - // OP_NEEDS_DISABLE. This allows us to have an item to be enabled - // by the app and disabled by the user during a single restart. - var value = stringData(ds.GetTarget(itemResource, EM_R("userDisabled"), true)); - if (value == OP_NEEDS_ENABLE) - ds.setItemProperty(id, EM_R("userDisabled"), null); - else if (value == OP_NEEDS_DISABLE) - ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true")); - } - } - } - while (PendingOperations.size > 0); - - // If no additional restart is required, it implies that there are - // no new components that need registering so we can inform the app - // not to do any extra startup checking next time round. - this._updateManifests(needsRestart); - - // Remember the list of add-ons that were installed this time around - // unless this was a new profile. - if (!gFirstRun && newAddons.length > 0) - gPref.setCharPref(PREF_EM_NEW_ADDONS_LIST, newAddons.join(",")); - } - catch (e) { - ERROR("ExtensionManager:_finishOperations - failure, catching exception - lineno: " + - e.lineNumber + " - file: " + e.fileName + " - " + e); - } - return needsRestart; - }, - - /** - * Checks to see if there are items that are incompatible with this version - * of the application, disables them to prevent incompatibility problems and - * invokes the Update Wizard to look for newer versions. - * @returns true if there were incompatible items installed and disabled, and - * the application must now be restarted to reinitialize XPCOM, - * false otherwise. - */ - checkForMismatches: function EM_checkForMismatches() { - // Check to see if the version of the application that is being started - // now is the same one that was started last time. - var currAppVersion = gApp.version; - var lastAppVersion = getPref("getCharPref", PREF_EM_LAST_APP_VERSION, ""); - if (currAppVersion == lastAppVersion) - return false; - // With a new profile lastAppVersion doesn't exist yet. - if (!lastAppVersion) { - gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion); - return false; - } - - // Block attempts to flush for the entire startup - gAllowFlush = false; - - // Make the extensions datasource consistent if it isn't already. - var isDirty; - [isDirty,] = this._ensureDatasetIntegrity(); - - if (this._checkForFileChanges()) - isDirty = true; - - if (PendingOperations.size != 0) - isDirty = true; - - var ds = this.datasource; - var inactiveItemIDs = []; - var appEnabledItemIDs = []; - var ctr = getContainer(ds, ds._itemRoot); - var elements = ctr.GetElements(); - while (elements.hasMoreElements()) { - var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource); - var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI); - var appDisabled = ds.getItemProperty(id, "appDisabled"); - var userDisabled = ds.getItemProperty(id, "userDisabled") - if (appDisabled == "true" || appDisabled == OP_NEEDS_DISABLE || - userDisabled == "true" || userDisabled == OP_NEEDS_DISABLE) - inactiveItemIDs.push(id); - if (appDisabled != "true" && appDisabled != OP_NEEDS_DISABLE) - appEnabledItemIDs.push(id); - } - - if (isDirty) - this._finishOperations(); - - // During app upgrade cleanup invalid entries in the extensions datasource. - ds.beginUpdateBatch(); - var allResources = ds.GetAllResources(); - while (allResources.hasMoreElements()) { - var res = allResources.getNext().QueryInterface(Ci.nsIRDFResource); - if (ds.GetTarget(res, EM_R("downloadURL"), true) || - (!ds.GetTarget(res, EM_R("installLocation"), true) && - stringData(ds.GetTarget(res, EM_R("appDisabled"), true)) == "true")) - ds.removeDownload(res.Value); - } - ds.endUpdateBatch(); - - var badItems = []; - var disabledAddons = []; - var allAppManaged = true; - elements = ctr.GetElements(); - while (elements.hasMoreElements()) { - var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource); - var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI); - var location = this.getInstallLocation(id); - if (!location) { - // Item was in an unknown install location - badItems.push(id); - continue; - } - - if (ds.getItemProperty(id, "appManaged") == "true") { - // Force an update of the metadata for appManaged extensions since the - // last modified time is not updated for directories on FAT / FAT32 - // filesystems when software update applies a new version of the app. - if (location.name == KEY_APP_GLOBAL) { - var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST); - if (installRDF.exists()) { - var metadataDS = getInstallManifest(installRDF); - ds.addItemMetadata(id, metadataDS, location); - ds.updateProperty(id, "compatible"); - } - } - } - else if (allAppManaged) - allAppManaged = false; - - var properties = { - availableUpdateURL: null, - availableUpdateVersion: null - }; - - if (ds.getItemProperty(id, "providesUpdatesSecurely") == "false") { - /* It's possible the previous version did not understand updateKeys so - * check if we can import one for this addon from its manifest. */ - installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST); - if (installRDF.exists()) { - metadataDS = getInstallManifest(installRDF); - var literal = metadataDS.GetTarget(gInstallManifestRoot, EM_R("updateKey"), true); - if (literal && literal instanceof Ci.nsIRDFLiteral) - ds.setItemProperty(id, EM_R("updateKey"), literal); - } - } - - // appDisabled is determined by an item being compatible, using secure - // updates, satisfying its dependencies, and not being blocklisted - if (this._isUsableItem(id)) { - if (ds.getItemProperty(id, "appDisabled")) - properties.appDisabled = null; - } - else { - if (!ds.getItemProperty(id, "appDisabled")) - properties.appDisabled = EM_L("true"); - // If this item used to be app enabled then the upgrade has made it incompatible - if (appEnabledItemIDs.indexOf(id) >= 0) - disabledAddons.push(id); - } - - ds.setItemProperties(id, properties); - } - - // Must clean up outside of the loop. Modifying the container while - // iterating its contents is bad. - for (var i = 0; i < badItems.length; i++) { - id = badItems[i]; - LOG("Item " + id + " was installed in an unknown location, removing."); - var disabled = ds.getItemProperty(id, "userDisabled") == "true"; - // Clean up the datasource - ds.removeCorruptItem(id); - // Check for any unhidden items. - var entries = StartupCache.findEntries(id); - if (entries.length > 0) { - var newLocation = InstallLocations.get(entries[0].location); - for (var j = 1; j < entries.length; j++) { - location = InstallLocations.get(entries[j].location); - if (newLocation.priority < location.priority) - newLocation = location; - } - LOG("Activating item " + id + " in " + newLocation.name); - var em = this; - this.installItem(id, newLocation, - function(installManifest, id, location, type) { - em._configureForthcomingItem(installManifest, id, location, - type); - }); - if (disabled) - em.disableItem(id); - } - } - - // Update the manifests to reflect the items that were disabled / enabled. - this._updateManifests(true); - - // Determine if we should check for compatibility updates when upgrading if - // we have add-ons that aren't managed by the application. - if (!allAppManaged && !gFirstRun) { - // Should we show a UI or just pass the list via a pref? - if (getPref("getBoolPref", PREF_EM_SHOW_MISMATCH_UI, true)) { - this._showMismatchWindow(inactiveItemIDs); - } - else { - // Remember the list of add-ons that were disabled this time around - gPref.setCharPref(PREF_EM_DISABLED_ADDONS_LIST, disabledAddons.join(",")); - } - } else if (gPref.prefHasUserValue(PREF_EM_DISABLED_ADDONS_LIST)) { - // Clear the disabled addons list if necessary - gPref.clearUserPref(PREF_EM_DISABLED_ADDONS_LIST); - } - - // Finish any pending upgrades from the compatibility update to avoid an - // additional restart. - if (PendingOperations.size != 0) - this._finishOperations(); - - // Update the last app version so we don't do this again with this version. - gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion); - - // Prevent extension update dialog from showing - gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false); - - // Re-enable flushing and flush anything that was deferred - try { - gAllowFlush = true; - if (gManifestNeedsFlush) { - gManifestNeedsFlush = false; - this._updateManifests(false); - } - if (gDSNeedsFlush) { - gDSNeedsFlush = false; - this.datasource.Flush(); - } - } - catch (e) { - ERROR("Error flushing caches: " + e); - } - - return true; - }, - - /** - * Shows the "Compatibility Updates" UI - * @param items - * an array of item IDs that were not enabled in the previous version - * of the application. - */ - _showMismatchWindow: function EM__showMismatchWindow(items) { - var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. - getService(Ci.nsIWindowMediator); - var wizard = wm.getMostRecentWindow("Update:Wizard"); - if (wizard) - wizard.focus(); - else { - var variant = Cc["@mozilla.org/variant;1"]. - createInstance(Ci.nsIWritableVariant); - variant.setFromVariant(items); - var features = "chrome,centerscreen,dialog,titlebar,modal"; - // This *must* be modal so as not to break startup! This code is invoked before - // the main event loop is initiated (via checkForMismatches). - var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher); - ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant); - } - }, - - /** - * Write the Extensions List and the Startup Cache - * @param needsRestart - * true if the application needs to restart again, false otherwise. - */ - _updateManifests: function EM__updateManifests(needsRestart) { - // During startup we block flushing until the startup operations are all - // complete to reduce file accesses that can trigger bug 431065 - if (gAllowFlush) { - // Write the Startup Cache (All Items, visible or not) - StartupCache.write(); - // Write the Extensions Locations Manifest (Visible, enabled items) - this._updateExtensionsManifest(); - } - else { - gManifestNeedsFlush = true; - } - - // Notify nsAppRunner to update the compatibility manifest on next startup - this._extensionListChanged = needsRestart; - }, - - /** - * Get a list of items that are currently "active" (turned on) of a specific - * type - * @param type - * The nsIUpdateItem type to return a list of items of - * @returns An array of active items of the specified type. - */ - _getActiveItems: function EM__getActiveItems(type) { - var allItems = this.getItemList(type); - var activeItems = []; - var ds = this.datasource; - for (var i = 0; i < allItems.length; ++i) { - var item = allItems[i]; - - var installLocation = this.getInstallLocation(item.id); - // An entry with an invalid install location is not active. - if (!installLocation) - continue; - // An item entry is valid only if it is not disabled, not about to - // be disabled, and not about to be uninstalled. - if (installLocation.name in StartupCache.entries && - item.id in StartupCache.entries[installLocation.name] && - StartupCache.entries[installLocation.name][item.id]) { - var op = StartupCache.entries[installLocation.name][item.id].op; - if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE || - op == OP_NEEDS_UNINSTALL || op == OP_NEEDS_DISABLE) - continue; - } - // Suppress items that have been disabled by the user or the app. - if (ds.getItemProperty(item.id, "isDisabled") != "true") - activeItems.push({ id: item.id, version: item.version, - location: installLocation }); - } - - return activeItems; - }, - - /** - * Write the Extensions List - */ - _updateExtensionsManifest: function EM__updateExtensionsManifest() { - // When an operation is performed that requires a component re-registration - // (extension enabled/disabled, installed, uninstalled), we must write the - // set of paths where extensions live so that the startup system can determine - // where additional components, preferences, chrome manifests etc live. - // - // To do this we obtain a list of active extensions and themes and write - // these to the extensions.ini file in the profile directory. - var validExtensions = this._getActiveItems(Ci.nsIUpdateItem.TYPE_ANY - - Ci.nsIUpdateItem.TYPE_THEME); - var validThemes = this._getActiveItems(Ci.nsIUpdateItem.TYPE_THEME); - - var extensionsLocationsFile = FileUtils.getFile(KEY_PROFILEDIR, - [FILE_EXTENSION_MANIFEST]); - var fos = FileUtils.openSafeFileOutputStream(extensionsLocationsFile); - - var enabledItems = []; - var extensionSectionHeader = "[ExtensionDirs]\r\n"; - fos.write(extensionSectionHeader, extensionSectionHeader.length); - for (var i = 0; i < validExtensions.length; ++i) { - var e = validExtensions[i]; - var itemLocation = e.location.getItemLocation(e.id).QueryInterface(Ci.nsILocalFile); - var descriptor = getAbsoluteDescriptor(itemLocation); - var line = "Extension" + i + "=" + descriptor + "\r\n"; - fos.write(line, line.length); - enabledItems.push(e.id + ":" + e.version); - } - - var themeSectionHeader = "[ThemeDirs]\r\n"; - fos.write(themeSectionHeader, themeSectionHeader.length); - for (i = 0; i < validThemes.length; ++i) { - var e = validThemes[i]; - var itemLocation = e.location.getItemLocation(e.id).QueryInterface(Ci.nsILocalFile); - var descriptor = getAbsoluteDescriptor(itemLocation); - var line = "Extension" + i + "=" + descriptor + "\r\n"; - fos.write(line, line.length); - enabledItems.push(e.id + ":" + e.version); - } - - FileUtils.closeSafeFileOutputStream(fos); - - // Cache the enabled list for annotating the crash report subsequently - gPref.setCharPref(PREF_EM_ENABLED_ITEMS, enabledItems.join(",")); - }, - - /** - * Say whether or not the Extension List has changed (and thus whether or not - * the system will have to restart the next time it is started). - * @param val - * true if the Extension List has changed, false otherwise. - * @returns |val| - */ - set _extensionListChanged(val) { - // When an extension has an operation perform on it (e.g. install, upgrade, - // disable, etc.) we are responsible for writing this information to - // compatibility.ini, and nsAppRunner is responsible for checking this on - // restart. At some point it may make sense to be able to cancel a - // registration but for now we only create the file. - if (val) { - let XRE = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); - XRE.invalidateCachesOnRestart(); - } - return val; - }, - - /** - * Gathers data about an item specified by the supplied Install Manifest - * and determines whether or not it can be installed as-is. It makes this - * determination by validating the item's GUID, Version, and determining - * if it is compatible with this application. - * @param installManifest - * A nsIRDFDataSource representing the Install Manifest of the - * item to be installed. - * @return A JS Object with the following properties: - * "id" The GUID of the Item being installed. - * "version" The Version string of the Item being installed. - * "name" The Name of the Item being installed. - * "type" The nsIUpdateItem type of the Item being installed. - * "targetApps" An array of TargetApplication Info Objects - * with "id", "minVersion" and "maxVersion" properties, - * representing applications targeted by this item. - * "error" The result code: - * INSTALLERROR_SUCCESS - * no error, item can be installed - * INSTALLERROR_INVALID_GUID - * error, GUID is not well-formed - * INSTALLERROR_INVALID_VERSION - * error, Version is not well-formed - * INSTALLERROR_INCOMPATIBLE_VERSION - * error, item is not compatible with this version - * of the application. - * INSTALLERROR_INCOMPATIBLE_PLATFORM - * error, item is not compatible with the operating - * system or ABI the application was built for. - * INSTALLERROR_INSECURE_UPDATE - * error, item has no secure method of providing updates - * INSTALLERROR_BLOCKLISTED - * error, item is blocklisted - */ - _getInstallData: function EM__getInstallData(installManifest) { - var installData = { id : "", - version : "", - name : "", - type : 0, - error : INSTALLERROR_SUCCESS, - targetApps : [], - updateURL : "", - updateKey : "", - currentApp : null }; - - // Fetch properties from the Install Manifest - installData.id = getManifestProperty(installManifest, "id"); - installData.version = getManifestProperty(installManifest, "version"); - installData.name = getManifestProperty(installManifest, "name"); - installData.type = getAddonTypeFromInstallManifest(installManifest); - installData.updateURL= getManifestProperty(installManifest, "updateURL"); - installData.updateKey= getManifestProperty(installManifest, "updateKey"); - - /** - * Reads a property off a Target Application resource - * @param resource - * The RDF Resource for a Target Application - * @param property - * The property (less EM_NS) to read - * @returns The string literal value of the property. - */ - function readTAProperty(resource, property) { - return stringData(installManifest.GetTarget(resource, EM_R(property), true)); - } - - var targetApps = installManifest.GetTargets(gInstallManifestRoot, - EM_R("targetApplication"), - true); - while (targetApps.hasMoreElements()) { - var targetApp = targetApps.getNext(); - if (targetApp instanceof Ci.nsIRDFResource) { - try { - var data = { id : readTAProperty(targetApp, "id"), - minVersion: readTAProperty(targetApp, "minVersion"), - maxVersion: readTAProperty(targetApp, "maxVersion") }; - installData.targetApps.push(data); - if ((data.id == gApp.ID) || - (data.id == TOOLKIT_ID) && !installData.currentApp) - installData.currentApp = data; - } - catch (e) { - continue; - } - } - } - - // If the item specifies one or more target platforms, make sure our OS/ABI - // combination is in the list - otherwise, refuse to install the item. - var targetPlatforms = null; - try { - targetPlatforms = installManifest.GetTargets(gInstallManifestRoot, - EM_R("targetPlatform"), - true); - } catch(e) { - // No targetPlatform nodes, continue. - } - if (targetPlatforms != null && targetPlatforms.hasMoreElements()) { - var foundMatchingOS = false; - var foundMatchingOSAndABI = false; - var requireABICompatibility = false; - while (targetPlatforms.hasMoreElements()) { - var targetPlatform = stringData(targetPlatforms.getNext()); - var os = targetPlatform.split("_")[0]; - var index = targetPlatform.indexOf("_"); - var abi = index != -1 ? targetPlatform.substr(index + 1) : null; - if (os == gOSTarget) { - foundMatchingOS = true; - // The presence of any ABI part after our OS means ABI is important. - if (abi != null) { - requireABICompatibility = true; - // If we don't know our ABI, we can't be compatible - if (abi == gXPCOMABI && abi != UNKNOWN_XPCOM_ABI) { - foundMatchingOSAndABI = true; - break; - } - } - } - } - if (!foundMatchingOS || (requireABICompatibility && !foundMatchingOSAndABI)) { - installData.error = INSTALLERROR_INCOMPATIBLE_PLATFORM; - return installData; - } - } - - // Validate the Item ID - if (!gIDTest.test(installData.id)) { - installData.error = INSTALLERROR_INVALID_GUID; - return installData; - } - - // Check that the add-on provides a secure update method. - if (gCheckUpdateSecurity && - installData.updateURL && - installData.updateURL.substring(0, 6) != "https:" && - !installData.updateKey) { - installData.error = INSTALLERROR_INSECURE_UPDATE; - return installData; - } - - // Check that the item is compatible with the application. - if (!this.datasource.isCompatible(installManifest, gInstallManifestRoot, false)) { - installData.error = INSTALLERROR_INCOMPATIBLE_VERSION; - return installData; - } - - // Check if the item is blocklisted. - if (!gBlocklist) - gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"]. - getService(Ci.nsIBlocklistService); - var state = gBlocklist.getAddonBlocklistState(installData.id, installData.version); - if (state == Ci.nsIBlocklistService.STATE_BLOCKED) - installData.error = INSTALLERROR_BLOCKLISTED; - else if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - installData.error = INSTALLERROR_SOFTBLOCKED; - - return installData; - }, - - /** - * Installs an item from a XPI/JAR file. - * This is the main entry point into the Install system from outside code - * (e.g. XPInstall). - * @param aXPIFile - * The file to install from. - * @param aInstallLocationKey - * The name of the Install Location where this item should be - * installed. - */ - installItemFromFile: function EM_installItemFromFile(xpiFile, installLocationKey) { - this.installItemFromFileInternal(xpiFile, installLocationKey, null); - - // If there are no compatibility checks running and no downloads in - // progress then the install operations are complete. - if (this._compatibilityCheckCount == 0 && this._transactions.length == 0) - this._callInstallListeners("onInstallsCompleted"); - }, - - /** - * Installs an item from a XPI/JAR file. - * @param aXPIFile - * The file to install from. - * @param aInstallLocationKey - * The name of the Install Location where this item should be - * installed. - * @param aInstallManifest - * An updated Install Manifest from the Version Update check. - * Can be null when invoked from callers other than the Version - * Update check. - * @returns The install result code. If this is INSTALLERROR_PHONING_HOME - * then a remote update check has been started to attempt to resolve - * compatibility problems. - */ - installItemFromFileInternal: function EM_installItemFromFileInternal(aXPIFile, - aInstallLocationKey, - aInstallManifest) { - var em = this; - /** - * Gets the Install Location for an Item. - * @param itemID - * The GUID of the item to find an Install Location for. - * @return An object implementing nsIInstallLocation which represents the - * location where the specified item should be installed. - * This can be: - * 1. an object that corresponds to the location key supplied to - * |installItemFromFileInternal|, - * 2. the default install location (the App Profile Extensions Folder) - * if no location key was supplied, or the location key supplied - * was not in the set of registered locations - * 3. null, if the location selected by 1 or 2 above does not support - * installs from XPI/JAR files, or that location is not writable - * with the current access privileges. - */ - function getInstallLocation(itemID) { - // Here I use "upgrade" to mean "install a different version of an item". - var installLocation = em.getInstallLocation(itemID); - if (!installLocation) { - // This is not an "upgrade", since we don't have any location data for the - // extension ID specified - that is, it's not in our database. - - // Caller supplied a key to a registered location, use that location - // for the installation - installLocation = InstallLocations.get(aInstallLocationKey); - if (installLocation) { - // If the specified location does not have a common metadata location - // (e.g. extensions have no common root, or other location specified - // by the location implementation) - e.g. for a Registry Key enumeration - // location - we cannot install or upgrade using a XPI file, probably - // because these application types will be handling upgrading themselves. - // Just bail. - if (!installLocation.location) { - LOG("Install Location \"" + installLocation.name + "\" does not support " + - "installation of items from XPI/JAR files. You must manage " + - "installation and update of these items yourself."); - installLocation = null; - } - } - else { - // In the absence of a preferred install location, just default to - // the App-Profile - installLocation = InstallLocations.get(KEY_APP_PROFILE); - } - } - else { - // This is an "upgrade", but not through the Update System, because the - // Update code will not let an extension with an incompatible target - // app version range through to this point. This is an "upgrade" in the - // sense that the user found a different version of an installed extension - // and installed it through the web interface, so we have metadata. - - // If the location is different, return the preferred location rather than - // the location of the currently installed version, because we may be in - // the situation where an item is being installed into the global app - // dir when there's a version in the profile dir. - if (installLocation.name != aInstallLocationKey) - installLocation = InstallLocations.get(aInstallLocationKey); - } - if (!installLocation.canAccess) { - LOG("Install Location\"" + installLocation.name + "\" cannot be written " + - "to with your access privileges. Installation will not proceed."); - installLocation = null; - } - return installLocation; - } - - /** - * Stages a XPI file in the default item location specified by other - * applications when they registered with XulRunner if the item's - * install manifest specified compatibility with them. - */ - function stageXPIForOtherApps(xpiFile, installData) { - for (var i = 0; i < installData.targetApps.length; ++i) { - var targetApp = installData.targetApps[i]; - if (targetApp.id != gApp.ID && targetApp.id != TOOLKIT_ID) { - /* XXXben uncomment when this works! - var settingsThingy = Cc[]. - getService(Ci.nsIXULRunnerSettingsThingy); - try { - var appPrefix = "SOFTWARE\\Mozilla\\XULRunner\\Applications\\"; - var branch = settingsThingy.getBranch(appPrefix + targetApp.id); - var path = branch.getProperty("ExtensionsLocation"); - var destination = Cc["@mozilla.org/file/local;1"]. - createInstance(Ci.nsILocalFile); - destination.initWithPath(path); - xpiFile.copyTo(file, xpiFile.leafName); - } - catch (e) { - } - */ - } - } - } - - /** - * Extracts and then starts the install for extensions / themes contained - * within a xpi. - */ - function installMultiXPI(xpiFile, installData) { - var fileURL = getURIFromFile(xpiFile).QueryInterface(Ci.nsIURL); - if (fileURL.fileExtension.toLowerCase() != "xpi") { - LOG("Invalid File Extension: Item: \"" + fileURL.fileName + "\" has an " + - "invalid file extension. Only xpi file extensions are allowed for " + - "multiple item packages."); - var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - showMessage("invalidFileExtTitle", [], - "invalidFileExtMessage", [installData.name, - fileURL.fileExtension, - bundle.GetStringFromName("type-" + installData.type)]); - return; - } - - try { - var zipReader = getZipReaderForFile(xpiFile); - } - catch (e) { - LOG("installMultiXPI: failed to open xpi file: " + xpiFile.path); - throw e; - } - - var searchForEntries = ["*.xpi", "*.jar"]; - var files = []; - for (var i = 0; i < searchForEntries.length; ++i) { - var entries = zipReader.findEntries(searchForEntries[i]); - while (entries.hasMore()) { - var entryName = entries.getNext(); - var target = FileUtils.getFile(KEY_TEMPDIR, [entryName]); - try { - target.createUnique(Ci.nsILocalFile.NORMAL_FILE_TYPE, - FileUtils.PERMS_FILE); - } - catch (e) { - LOG("installMultiXPI: failed to create target file for extraction " + - " file = " + target.path + ", exception = " + e + "\n"); - } - zipReader.extract(entryName, target); - files.push(target); - } - } - zipReader.close(); - - if (files.length == 0) { - LOG("Multiple Item Package: Item: \"" + fileURL.fileName + "\" does " + - "not contain a valid package to install."); - var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - showMessage("missingPackageFilesTitle", - [bundle.GetStringFromName("type-" + installData.type)], - "missingPackageFilesMessage", [installData.name, - bundle.GetStringFromName("type-" + installData.type)]); - return; - } - - for (i = 0; i < files.length; ++i) { - em.installItemFromFileInternal(files[i], aInstallLocationKey, null); - files[i].remove(false); - } - } - - /** - * An observer for the Extension Update System. - * @constructor - */ - function IncompatibleObserver() {} - IncompatibleObserver.prototype = { - _xpi: null, - _installManifest: null, - - /** - * Ask the Extension Update System if there are any version updates for - * this item that will allow it to be compatible with this version of - * the Application. - * @param item - * An nsIUpdateItem representing the item being installed. - * @param installManifest - * The Install Manifest datasource for the item. - * @param xpiFile - * The staged source XPI file that contains the item. Cleaned - * up by this process. - * @param installRDF - * The install.rdf file that was extracted from the xpi. - */ - checkForUpdates: function IncObs_checkForUpdates(item, installManifest, xpiFile) { - this._xpi = xpiFile; - this._installManifest = installManifest; - - em._callInstallListeners("onCompatibilityCheckStarted", item); - em._compatibilityCheckCount++; - var updater = new ExtensionItemUpdater(em); - updater.checkForUpdates([item], 1, - Ci.nsIExtensionManager.UPDATE_CHECK_COMPATIBILITY, - this, UPDATE_WHEN_ADDON_INSTALLED); - }, - - /** - * See nsIExtensionManager.idl - */ - onUpdateStarted: function IncObs_onUpdateStarted() { - LOG("Phone Home Listener: Update Started"); - }, - - /** - * See nsIExtensionManager.idl - */ - onUpdateEnded: function IncObs_onUpdateEnded() { - LOG("Phone Home Listener: Update Ended"); - }, - - /** - * See nsIExtensionManager.idl - */ - onAddonUpdateStarted: function IncObs_onAddonUpdateStarted(addon) { - if (!addon) - throw Cr.NS_ERROR_INVALID_ARG; - - LOG("Phone Home Listener: Update For " + addon.id + " started"); - em.datasource.addIncompatibleUpdateItem(addon.name, this._xpi.path, - addon.type, addon.version); - }, - - /** - * See nsIExtensionManager.idl - */ - onAddonUpdateEnded: function IncObs_onAddonUpdateEnded(addon, status) { - if (!addon) - throw Cr.NS_ERROR_INVALID_ARG; - - LOG("Phone Home Listener: Update For " + addon.id + " ended, status = " + status); - em.datasource.removeDownload(this._xpi.path); - LOG("Version Check Phone Home Completed"); - - em._callInstallListeners("onCompatibilityCheckEnded", addon, status); - - // Only compatibility updates (e.g. STATUS_VERSIONINFO) are currently - // supported - if (status == Ci.nsIAddonUpdateCheckListener.STATUS_VERSIONINFO) { - em.datasource.setTargetApplicationInfo(addon.id, - addon.targetAppID, - addon.minAppVersion, - addon.maxAppVersion, - this._installManifest); - - // Try and install again, but use the updated compatibility DB. - // This will send out an apropriate onInstallEnded notification for us. - var status = em.installItemFromFileInternal(this._xpi, - aInstallLocationKey, - this._installManifest); - - // The install may still have failed at this point due to the blocklist - if (status == INSTALLERROR_SUCCESS) { - // Add the updated compatibility info to the datasource if done - if (StartupCache.entries[aInstallLocationKey][addon.id].op == OP_NONE) { - em.datasource.setTargetApplicationInfo(addon.id, - addon.targetAppID, - addon.minAppVersion, - addon.maxAppVersion, - null); - } - else { // needs a restart - // Add updatedMinVersion and updatedMaxVersion so it can be used - // to update the datasource during the installation or upgrade. - em.datasource.setUpdatedTargetAppInfo(addon.id, - addon.targetAppID, - addon.minAppVersion, - addon.maxAppVersion, - null); - } - } - } - else { - em.datasource.removeDownload(this._xpi.path); - showIncompatibleError(installData); - LOG("Add-on " + addon.id + " is incompatible with " + - BundleManager.appName + " " + gApp.version + ", Toolkit " + - gApp.platformVersion + ". Remote compatibility check did not " + - "resolve this."); - - em._callInstallListeners("onInstallEnded", addon, INSTALLERROR_INCOMPATIBLE_VERSION); - - // We are responsible for cleaning up this file! - InstallLocations.get(aInstallLocationKey).removeFile(this._xpi); - } - - em._compatibilityCheckCount--; - // If there are no more compatibility checks running and no downloads in - // progress then the install operations are complete. - if (em._compatibilityCheckCount == 0 && em._transactions.length == 0) - em._callInstallListeners("onInstallsCompleted"); - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonUpdateCheckListener]) - } - - var shouldPhoneHomeIfNecessary = false; - if (!aInstallManifest) { - // If we were not called with an Install Manifest, we were called from - // some other path than the Phone Home system, so we do want to phone - // home if the version is incompatible. As this is the first point in the - // install process we must notify observers here. - - var addon = makeItem(getURIFromFile(aXPIFile).spec, "", - aInstallLocationKey, "", "", "", - getURIFromFile(aXPIFile).spec, - "", "", "", "", 0, gApp.ID); - this._callInstallListeners("onInstallStarted", addon); - - shouldPhoneHomeIfNecessary = true; - var installManifest = null; - var installManifestFile = extractRDFFileToTempDir(aXPIFile, - FILE_INSTALL_MANIFEST, - true); - if (installManifestFile.exists()) { - installManifest = getInstallManifest(installManifestFile); - installManifestFile.remove(false); - } - if (!installManifest) { - LOG("The Install Manifest supplied by this item is not well-formed. " + - "Installation will not proceed."); - this._callInstallListeners("onInstallEnded", addon, INSTALLERROR_INVALID_MANIFEST); - return INSTALLERROR_INVALID_MANIFEST; - } - } - else - installManifest = aInstallManifest; - - var installData = this._getInstallData(installManifest); - // Recreate the add-on item with the full detail from the install manifest - addon = makeItem(installData.id, installData.version, - aInstallLocationKey, - installData.currentApp ? installData.currentApp.minVersion : "", - installData.currentApp ? installData.currentApp.maxVersion : "", - installData.name, - getURIFromFile(aXPIFile).spec, - "", /* XPI Update Hash */ - "", /* Icon URL */ - installData.updateURL || "", - installData.updateKey || "", - installData.type, - installData.currentApp ? installData.currentApp.id : ""); - - switch (installData.error) { - case INSTALLERROR_INCOMPATIBLE_VERSION: - // Since the caller cleans up |aXPIFile|, and we're not yet sure whether or - // not we need it (we may need it if a remote version bump that makes it - // compatible is discovered by the call home) - so we must stage it for - // later ourselves. - if (shouldPhoneHomeIfNecessary && installData.currentApp) { - var installLocation = getInstallLocation(installData.id, aInstallLocationKey); - if (!installLocation) - return INSTALLERROR_INCOMPATIBLE_VERSION; - var stagedFile = installLocation.stageFile(aXPIFile, installData.id); - (new IncompatibleObserver(this)).checkForUpdates(addon, installManifest, - stagedFile); - // Return early to prevent deletion of the install manifest file. - return INSTALLERROR_PHONING_HOME; - } - else { - // XXXben Look up XULRunnerSettingsThingy to see if there is a registered - // app that can handle this item, if so just stage and don't show - // this error! - showIncompatibleError(installData); - LOG("Add-on " + installData.id + " is incompatible with " + - BundleManager.appName + " " + gApp.version + ", Toolkit " + - gApp.platformVersion + ". Remote compatibility check was not performed."); - } - break; - case INSTALLERROR_SOFTBLOCKED: - if (!showBlocklistMessage(installData, true)) - break; - installData.error = INSTALLERROR_SUCCESS; - // Fall through to continue the install - case INSTALLERROR_SUCCESS: - // Installation of multiple extensions / themes contained within a single xpi. - if (installData.type == Ci.nsIUpdateItem.TYPE_MULTI_XPI) { - installMultiXPI(aXPIFile, installData); - break; - } - - // Stage the extension's XPI so it can be extracted at the next restart. - var installLocation = getInstallLocation(installData.id, aInstallLocationKey); - if (!installLocation) { - // No cleanup of any of the staged XPI files should be required here, - // because this should only ever fail on the first recurse through - // this function, BEFORE staging takes place... technically speaking - // a location could become readonly during the phone home process, - // but that's an edge case I don't care about. - this._callInstallListeners("onInstallEnded", addon, INSTALLERROR_RESTRICTED); - return INSTALLERROR_RESTRICTED; - } - - // Stage a copy of the XPI/JAR file for our own evil purposes... - stagedFile = installLocation.stageFile(aXPIFile, installData.id); - - var restartRequired = this.installRequiresRestart(installData.id, - installData.type); - // Determine which configuration function to use based on whether or not - // there is data about this item in our datasource already - if there is - // we want to upgrade, otherwise we install fresh. - var ds = this.datasource; - if (installData.id in ds.visibleItems && ds.visibleItems[installData.id]) { - // We enter this function if any data corresponding to an existing GUID - // is found, regardless of its Install Location. We need to check before - // "upgrading" an item that Install Location of the new item is of equal - // or higher priority than the old item, to make sure the datasource only - // ever tracks metadata for active items. - var oldInstallLocation = this.getInstallLocation(installData.id); - if (oldInstallLocation.priority >= installLocation.priority) { - this._upgradeItem(installManifest, installData.id, installLocation, - installData.type); - if (!restartRequired) { - this._finalizeUpgrade(installData.id, installLocation); - this._finalizeInstall(installData.id, stagedFile); - } - } - } - else { - this._configureForthcomingItem(installManifest, installData.id, - installLocation, installData.type); - if (!restartRequired) { - this._finalizeInstall(installData.id, stagedFile); - if (installData.type == Ci.nsIUpdateItem.TYPE_THEME) { - var internalName = this.datasource.getItemProperty(installData.id, "internalName"); - if (gPref.getBoolPref(PREF_EM_DSS_ENABLED)) { - gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, internalName); - } - else { - gPref.setBoolPref(PREF_DSS_SWITCHPENDING, true); - gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, internalName); - } - } - } - } - this._updateManifests(restartRequired); - break; - case INSTALLERROR_INVALID_GUID: - LOG("Invalid GUID: Item has GUID: \"" + installData.id + "\"" + - " which is not well-formed."); - var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - showMessage("incompatibleTitle", - [bundle.GetStringFromName("type-" + installData.type)], - "invalidGUIDMessage", [installData.name, installData.id]); - break; - case INSTALLERROR_INVALID_VERSION: - LOG("Invalid Version: Item: \"" + installData.id + "\" has version " + - installData.version + " which is not well-formed."); - var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - showMessage("incompatibleTitle", - [bundle.GetStringFromName("type-" + installData.type)], - "invalidVersionMessage", [installData.name, installData.version]); - break; - case INSTALLERROR_INCOMPATIBLE_PLATFORM: - const osABI = gOSTarget + "_" + gXPCOMABI; - LOG("Incompatible Platform: Item: \"" + installData.id + "\" is not " + - "compatible with '" + osABI + "'."); - var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - showMessage("incompatibleTitle", - [bundle.GetStringFromName("type-" + installData.type)], - "incompatiblePlatformMessage", - [installData.name, BundleManager.appName, osABI]); - break; - case INSTALLERROR_BLOCKLISTED: - LOG("Blocklisted Item: Item: \"" + installData.id + "\" version " + - installData.version + " was not installed."); - showBlocklistMessage(installData, false); - break; - case INSTALLERROR_INSECURE_UPDATE: - LOG("No secure updates: Item: \"" + installData.id + "\" version " + - installData.version + " was not installed."); - var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - showMessage("incompatibleTitle", - [bundle.GetStringFromName("type-" + installData.type)], - "insecureUpdateMessage", [installData.name]); - break; - default: - break; - } - - // Check to see if this item supports other applications and in that case - // stage the the XPI file in the location specified by those applications. - stageXPIForOtherApps(aXPIFile, installData); - - // The install of this item is complete, notify observers - this._callInstallListeners("onInstallEnded", addon, installData.error); - return installData.error; - }, - - /** - * Whether or not this type's installation/uninstallation requires - * the application to be restarted. - * @param id - * The GUID of the item - * @param type - * The nsIUpdateItem type of the item - * @returns true if installation of an item of this type requires a - * restart. - */ - installRequiresRestart: function EM_installRequiresRestart(id, type) { - switch (type) { - case Ci.nsIUpdateItem.TYPE_THEME: - var internalName = this.datasource.getItemProperty(id, "internalName"); - var needsRestart = false; - if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT)) - needsRestart = internalName == gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT); - if (!needsRestart && - gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) - needsRestart = internalName == gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN); - return needsRestart; - } - return ((type & Ci.nsIUpdateItem.TYPE_ADDON) > 0); - }, - - /** - * Perform initial configuration on an item that has just or will be - * installed. This inserts the item into the appropriate container in the - * datasource, so that the application UI shows the item even if it will - * not actually be installed until the next restart. - * @param installManifest - * The Install Manifest datasource that describes this item. - * @param id - * The GUID of this item. - * @param installLocation - * The Install Location where this item is installed. - * @param type - * The nsIUpdateItem type of this item. - */ - _configureForthcomingItem: function EM__configureForthcomingItem(installManifest, - id, - installLocation, - type) { - var ds = this.datasource; - ds.updateVisibleList(id, installLocation.name, false); - - var name = null; - var localized = findClosestLocalizedResource(installManifest, gInstallManifestRoot); - if (localized) - name = installManifest.GetTarget(localized, EM_R("name"), true); - else - name = EM_L(getManifestProperty(installManifest, "name")); - - var props = { name : name, - version : EM_L(getManifestProperty(installManifest, "version")), - newVersion : EM_L(getManifestProperty(installManifest, "version")), - installLocation : EM_L(installLocation.name), - type : EM_I(type), - availableUpdateURL : null, - availableUpdateHash : null, - availableUpdateVersion: null, - availableUpdateInfo : null }; - ds.setItemProperties(id, props); - ds.updateProperty(id, "availableUpdateURL"); - - this._setOp(id, OP_NEEDS_INSTALL); - - // Insert it into the child list NOW rather than later because: - // - extensions installed using the command line need to be a member - // of a container during the install phase for the code to be able - // to identify profile vs. global - // - extensions installed through the UI should show some kind of - // feedback to indicate their presence is forthcoming (i.e. they - // will be available after a restart). - ds.insertItemIntoContainer(id); - - this._notifyAction(id, EM_ITEM_INSTALLED); - }, - - /** - * Perform configuration on an item that has just or will be upgraded. - * @param installManifest - * The Install Manifest datasource that describes this item. - * @param itemID - * The GUID of this item. - * @param installLocation - * The Install Location where this item is installed. - * @param type - * The nsIUpdateItem type of this item. - */ - _upgradeItem: function EM__upgradeItem(installManifest, id, installLocation, type) { - // Don't change any props that would need to be reset if the install fails. - // They will be reset as appropriate by the upgrade/install process. - var ds = this.datasource; - ds.updateVisibleList(id, installLocation.name, false); - var props = { installLocation : EM_L(installLocation.name), - type : EM_I(type), - newVersion : EM_L(getManifestProperty(installManifest, "version")), - availableUpdateURL : null, - availableUpdateHash : null, - availableUpdateVersion : null, - availableUpdateInfo : null }; - ds.setItemProperties(id, props); - ds.updateProperty(id, "availableUpdateURL"); - - this._setOp(id, OP_NEEDS_UPGRADE); - this._notifyAction(id, EM_ITEM_UPGRADED); - }, - - /** - * Completes an Extension's installation. - * @param id - * The GUID of the Extension to install. - * @param file - * The XPI/JAR file to install from. If this is null, we try to - * determine the stage file location from the ID. - */ - _finalizeInstall: function EM__finalizeInstall(id, file) { - var ds = this.datasource; - var type = ds.getItemProperty(id, "type"); - if (id == 0 || id == -1) { - ds.removeCorruptItem(id); - return; - } - var installLocation = this.getInstallLocation(id); - if (!installLocation) { - // If the install location is null, that means we've reached the finalize - // state without the item ever having metadata added for it, which implies - // bogus data in the Startup Cache. Clear the entries and don't do anything - // else. - var entries = StartupCache.findEntries(id); - for (var i = 0; i < entries.length; ++i) { - var location = InstallLocations.get(entries[i].location); - StartupCache.clearEntry(location, id); - PendingOperations.clearItem(OP_NEEDS_INSTALL, id); - } - return; - } - var itemLocation = installLocation.getItemLocation(id); - - if (!file && "stageFile" in installLocation) - file = installLocation.getStageFile(id); - - // If there is a staged file then we must extract it to the correct place, - // otherwise we are dealing with a dropped-in directory. - if (file && file.exists()) - safeInstallOperation(id, installLocation, file); - - var metadataFile = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST); - if (metadataFile && metadataFile.exists()) { - var metadataDS = getInstallManifest(metadataFile); - if (metadataDS) { - // Add metadata for the item to the extensions datasource - this.datasource.addItemMetadata(id, metadataDS, installLocation); - } - } - else { - LOG("_finalizeInstall: install manifest for extension " + id + " at " + - metadataFile.path + " could not be loaded. Add-on is not usable."); - } - - // If the file was staged, we must clean it up ourselves, otherwise the - // EM caller is responsible for doing so (e.g. XPInstall) - if (file) - installLocation.removeFile(file); - - // Clear the op flag from the Startup Cache and Pending Operations sets - StartupCache.put(installLocation, id, OP_NONE, true); - PendingOperations.clearItem(OP_NEEDS_INSTALL, id); - }, - - /** - * Removes an item's metadata in preparation for an upgrade-install. - * @param id - * The GUID of the item to uninstall. - * @param installLocation - * The nsIInstallLocation of the item - */ - _finalizeUpgrade: function EM__finalizeUpgrade(id, installLocation) { - // Retrieve the item properties *BEFORE* we clean the resource! - var ds = this.datasource; - - var stagedFile = null; - if ("getStageFile" in installLocation) - stagedFile = installLocation.getStageFile(id); - - if (stagedFile) - var installRDF = extractRDFFileToTempDir(stagedFile, FILE_INSTALL_MANIFEST, true); - else - installRDF = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST); - if (installRDF && installRDF.exists()) { - var installManifest = getInstallManifest(installRDF); - if (installManifest) { - var type = getAddonTypeFromInstallManifest(installManifest); - var userDisabled = ds.getItemProperty(id, "userDisabled") == "true"; - - // Clean the item resource - ds.removeItemMetadata(id); - // Now set up the properties on the item to mimic an item in its - // "initial state" for installation. - this._configureForthcomingItem(installManifest, id, installLocation, - type); - if (userDisabled) - ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true")); - } - if (stagedFile) - installRDF.remove(false); - } - // Clear the op flag from the Pending Operations set. Do NOT clear op flag in - // the startup cache since this may have been reset to OP_NEEDS_INSTALL by - // |_configureForthcomingItem|. - PendingOperations.clearItem(OP_NEEDS_UPGRADE, id); - }, - - /** - * Completes an item's uninstallation. - * @param id - * The GUID of the item to uninstall. - */ - _finalizeUninstall: function EM__finalizeUninstall(id) { - var ds = this.datasource; - - var installLocation = this.getInstallLocation(id); - if (!installLocation.itemIsManagedIndependently(id)) { - try { - // Passing null for the file to install will just cause the directory - // removed. - safeInstallOperation(id, installLocation, null); - } - catch (e) { - ERROR("_finalizeUninstall: failed to remove directory for item: " + id + - " at Install Location: " + installLocation.name + ", rolling back uninstall"); - var manifest = installLocation.getItemFile(id, "FILE_INSTALL_MANIFEST"); - // If there is no manifest then either the rollback failed, or there was - // no manifest in the first place. Either way this item is now invalid - // and we shouldn't try to re-install it. - if (manifest && manifest.exists()) { - // Removal of the files failed, reset the uninstalled flag and rewrite - // the install manifests so this item's components are registered. - // Clear the op flag from the Startup Cache - StartupCache.put(installLocation, id, OP_NONE, true); - var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type")) - this._updateManifests(restartRequired); - return; - } - } - } - else if (installLocation.name == KEY_APP_PROFILE || - installLocation.name == KEY_APP_GLOBAL || - installLocation.name == KEY_APP_SYSTEM_USER) { - // Check for a pointer file and remove it if it exists - var pointerFile = installLocation.location.clone(); - pointerFile.append(id); - if (pointerFile.exists() && !pointerFile.isDirectory()) - pointerFile.remove(false); - } - - // Clean the item resource - ds.removeItemMetadata(id); - - // Do this LAST since inferences are made about an item based on - // what container it's in. - ds.removeItemFromContainer(id); - - // Clear the op flag from the Startup Cache and the Pending Operations set. - StartupCache.clearEntry(installLocation, id); - PendingOperations.clearItem(OP_NEEDS_UNINSTALL, id); - }, - - /** - * Uninstalls an item. If the uninstallation cannot be performed immediately - * it is scheduled for the next restart. - * @param id - * The GUID of the item to uninstall. - */ - uninstallItem: function EM_uninstallItem(id) { - var ds = this.datasource; - ds.updateDownloadState(PREFIX_ITEM_URI + id, null); - if (!ds.isDownloadItem(id)) { - var opType = ds.getItemProperty(id, "opType"); - var installLocation = this.getInstallLocation(id); - // Removes any staged xpis for this item. - if (opType == OP_NEEDS_UPGRADE || opType == OP_NEEDS_INSTALL) { - var stageFile = installLocation.getStageFile(id); - if (stageFile) - installLocation.removeFile(stageFile); - } - // Addons with an opType of OP_NEEDS_INSTALL only have a staged xpi file - // and are removed immediately since the uninstall can't be canceled. - if (opType == OP_NEEDS_INSTALL) { - ds.removeItemMetadata(id); - ds.removeItemFromContainer(id); - ds.updateVisibleList(id, null, true); - StartupCache.clearEntry(installLocation, id); - this._updateManifests(false); - } - else { - if (opType == OP_NEEDS_UPGRADE) - ds.setItemProperty(id, EM_R("newVersion"), null); - this._setOp(id, OP_NEEDS_UNINSTALL); - var type = ds.getItemProperty(id, "type"); - var restartRequired = this.installRequiresRestart(id, type); - if (!restartRequired) { - this._finalizeUninstall(id); - this._updateManifests(restartRequired); - } - } - } - else { - // Bad download entry - uri is url, e.g. "http://www.foo.com/test.xpi" - // ... just remove it from the list. - ds.removeCorruptDLItem(id); - } - - this._notifyAction(id, EM_ITEM_UNINSTALLED); - }, - - /* See nsIExtensionManager.idl */ - cancelInstallItem: function EM_cancelInstallItem(id) { - var ds = this.datasource; - var opType = ds.getItemProperty(id, "opType"); - if (opType != OP_NEEDS_UPGRADE && opType != OP_NEEDS_INSTALL) - return; - - ds.updateDownloadState(PREFIX_ITEM_URI + id, null); - var installLocation = this.getInstallLocation(id); - // Removes any staged xpis for this item. - var stageFile = installLocation.getStageFile(id); - if (stageFile) - installLocation.removeFile(stageFile); - // Addons with an opType of OP_NEEDS_INSTALL only have a staged xpi file - // and just need to be removed completely from the ds. - if (opType == OP_NEEDS_INSTALL) { - ds.removeItemMetadata(id); - ds.removeItemFromContainer(id); - ds.updateVisibleList(id, null, true); - StartupCache.clearEntry(installLocation, id); - this._updateManifests(false); - this._notifyAction(id, EM_ITEM_CANCEL); - } - else { - // Clear upgrade information and reset any request to enable/disable. - ds.setItemProperty(id, EM_R("newVersion"), null); - var appDisabled = ds.getItemProperty(id, "appDisabled"); - var userDisabled = ds.getItemProperty(id, "userDisabled"); - if (appDisabled == "true" || appDisabled == OP_NONE && userDisabled == OP_NONE) { - this._setOp(id, OP_NONE); - this._notifyAction(id, EM_ITEM_CANCEL); - } - else if (appDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NEEDS_DISABLE) { - this._setOp(id, OP_NEEDS_DISABLE); - this._notifyAction(id, EM_ITEM_DISABLED); - } - else if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE) { - this._setOp(id, OP_NEEDS_ENABLE); - this._notifyAction(id, EM_ITEM_ENABLED); - } - else { - this._setOp(id, OP_NONE); - this._notifyAction(id, EM_ITEM_CANCEL); - } - } - }, - - /** - * Cancels a pending uninstall of an item - * @param id - * The ID of the item. - */ - cancelUninstallItem: function EM_cancelUninstallItem(id) { - var ds = this.datasource; - var appDisabled = ds.getItemProperty(id, "appDisabled"); - var userDisabled = ds.getItemProperty(id, "userDisabled"); - if (appDisabled == "true" || appDisabled == OP_NONE && userDisabled == OP_NONE) { - this._setOp(id, OP_NONE); - this._notifyAction(id, EM_ITEM_CANCEL); - } - else if (appDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NEEDS_DISABLE) { - this._setOp(id, OP_NEEDS_DISABLE); - this._notifyAction(id, EM_ITEM_DISABLED); - } - else if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE) { - this._setOp(id, OP_NEEDS_ENABLE); - this._notifyAction(id, EM_ITEM_ENABLED); - } - else { - this._setOp(id, OP_NONE); - this._notifyAction(id, EM_ITEM_CANCEL); - } - }, - - /** - * Sets the pending operation for a visible item. - * @param id - * The GUID of the item - * @param op - * The name of the operation to be performed - */ - _setOp: function EM__setOp(id, op) { - var location = this.getInstallLocation(id); - StartupCache.put(location, id, op, true); - PendingOperations.addItem(op, { locationKey: location.name, id: id }); - var ds = this.datasource; - if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE) - ds.updateDownloadState(PREFIX_ITEM_URI + id, "success"); - - ds.updateProperty(id, "opType"); - ds.updateProperty(id, "updateable"); - ds.updateProperty(id, "satisfiesDependencies"); - var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type")) - this._updateDependentItemsForID(id); - this._updateManifests(restartRequired); - }, - - /** - * Note on appDisabled and userDisabled property arcs. - * The appDisabled and userDisabled RDF property arcs are used to store - * the pending operation for app disabling and user disabling for an item as - * well as the user and app disabled status after the pending operation has - * been completed upon restart. When the appDisabled value changes the value - * of userDisabled is reset to prevent the state of widgets and status - * messages from being in an incorrect state. - */ - - /** - * Enables an item for the application (e.g. the item satisfies all - * requirements like app compatibility for it to be enabled). The appDisabled - * property arc will be removed if the item will be app disabled on next - * restart to cancel the app disabled operation for the item otherwise the - * property value will be set to OP_NEEDS_ENABLE. The item's pending - * operations are then evaluated in order to set the operation to perform - * and notify the observers if the operation has been changed. - * See "Note on appDisabled and userDisabled property arcs" above. - * @param id - * The ID of the item to be enabled by the application. - */ - _appEnableItem: function EM__appEnableItem(id) { - var ds = this.datasource; - var appDisabled = ds.getItemProperty(id, "appDisabled"); - if (appDisabled == OP_NONE || appDisabled == OP_NEEDS_ENABLE) - return; - - var opType = ds.getItemProperty(id, "opType"); - var userDisabled = ds.getItemProperty(id, "userDisabled"); - // reset user disabled if it has a pending operation to prevent the ui - // state from getting confused as to an item's current state. - if (userDisabled == OP_NEEDS_DISABLE) - ds.setItemProperty(id, EM_R("userDisabled"), null); - else if (userDisabled == OP_NEEDS_ENABLE) - ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true")); - - if (appDisabled == OP_NEEDS_DISABLE) - ds.setItemProperty(id, EM_R("appDisabled"), null); - else if (appDisabled == "true") - ds.setItemProperty(id, EM_R("appDisabled"), EM_L(OP_NEEDS_ENABLE)); - - // Don't set a new operation when there is a pending uninstall operation. - if (opType == OP_NEEDS_UNINSTALL) { - this._updateDependentItemsForID(id); - return; - } - - var operation, action; - // if this item is already enabled or user disabled don't set a pending - // operation - instead immediately enable it and reset the operation type - // if needed. - if (appDisabled == OP_NEEDS_DISABLE || appDisabled == OP_NONE || - userDisabled == "true") { - if (opType != OP_NONE) { - operation = OP_NONE; - action = EM_ITEM_CANCEL; - } - } - else { - if (opType != OP_NEEDS_ENABLE) { - operation = OP_NEEDS_ENABLE; - action = EM_ITEM_ENABLED; - } - } - - if (action) { - this._setOp(id, operation); - this._notifyAction(id, action); - } - else { - ds.updateProperty(id, "satisfiesDependencies"); - this._updateDependentItemsForID(id); - } - }, - - /** - * Disables an item for the application (e.g. the item doesn't satisfy all - * requirements like app compatibility for it to be enabled). The appDisabled - * property arc will be set to true if the item will be app enabled on next - * restart to cancel the app enabled operation for the item otherwise the - * property value will be set to OP_NEEDS_DISABLE. The item's pending - * operations are then evaluated in order to set the operation to perform - * and notify the observers if the operation has been changed. - * See "Note on appDisabled and userDisabled property arcs" above. - * @param id - * The ID of the item to be disabled by the application. - */ - _appDisableItem: function EM__appDisableItem(id) { - var ds = this.datasource; - var appDisabled = ds.getItemProperty(id, "appDisabled"); - if (appDisabled == "true" || appDisabled == OP_NEEDS_DISABLE) - return; - - var opType = ds.getItemProperty(id, "opType"); - var userDisabled = ds.getItemProperty(id, "userDisabled"); - - // reset user disabled if it has a pending operation to prevent the ui - // state from getting confused as to an item's current state. - if (userDisabled == OP_NEEDS_DISABLE) - ds.setItemProperty(id, EM_R("userDisabled"), null); - else if (userDisabled == OP_NEEDS_ENABLE) - ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true")); - - if (appDisabled == OP_NEEDS_ENABLE || userDisabled == OP_NEEDS_ENABLE || - ds.getItemProperty(id, "userDisabled") == "true") - ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true")); - else if (appDisabled == OP_NONE) - ds.setItemProperty(id, EM_R("appDisabled"), EM_L(OP_NEEDS_DISABLE)); - - // Don't set a new operation when there is a pending uninstall operation. - if (opType == OP_NEEDS_UNINSTALL) { - this._updateDependentItemsForID(id); - return; - } - - var operation, action; - // if this item is already disabled don't set a pending operation - instead - // immediately disable it and reset the operation type if needed. - if (appDisabled == OP_NEEDS_ENABLE || appDisabled == "true" || - userDisabled == OP_NEEDS_ENABLE || userDisabled == "true") { - if (opType != OP_NONE) { - operation = OP_NONE; - action = EM_ITEM_CANCEL; - } - } - else { - if (opType != OP_NEEDS_DISABLE) { - operation = OP_NEEDS_DISABLE; - action = EM_ITEM_DISABLED; - } - } - - if (action) { - this._setOp(id, operation); - this._notifyAction(id, action); - } - else { - ds.updateProperty(id, "satisfiesDependencies"); - this._updateDependentItemsForID(id); - } - }, - - /** - * Sets an item to be enabled by the user. If the item is already enabled this - * clears the needs-enable operation for the next restart. - * See "Note on appDisabled and userDisabled property arcs" above. - * @param id - * The ID of the item to be enabled by the user. - */ - enableItem: function EM_enableItem(id) { - var ds = this.datasource; - var opType = ds.getItemProperty(id, "opType"); - var appDisabled = ds.getItemProperty(id, "appDisabled"); - var userDisabled = ds.getItemProperty(id, "userDisabled"); - - var operation, action; - // if this item is already enabled don't set a pending operation - instead - // immediately enable it and reset the operation type if needed. - if (appDisabled == OP_NONE && - userDisabled == OP_NEEDS_DISABLE || userDisabled == OP_NONE) { - if (userDisabled == OP_NEEDS_DISABLE) - ds.setItemProperty(id, EM_R("userDisabled"), null); - if (opType != OP_NONE) { - operation = OP_NONE; - action = EM_ITEM_CANCEL; - } - } - else { - if (userDisabled == "true") - ds.setItemProperty(id, EM_R("userDisabled"), EM_L(OP_NEEDS_ENABLE)); - if (opType != OP_NEEDS_ENABLE) { - operation = OP_NEEDS_ENABLE; - action = EM_ITEM_ENABLED; - } - } - - if (action) { - this._setOp(id, operation); - this._notifyAction(id, action); - } - else { - ds.updateProperty(id, "satisfiesDependencies"); - this._updateDependentItemsForID(id); - } - }, - - /** - * Sets an item to be disabled by the user. If the item is already disabled - * this clears the needs-disable operation for the next restart. - * See "Note on appDisabled and userDisabled property arcs" above. - * @param id - * The ID of the item to be disabled by the user. - */ - disableItem: function EM_disableItem(id) { - var ds = this.datasource; - var opType = ds.getItemProperty(id, "opType"); - var appDisabled = ds.getItemProperty(id, "appDisabled"); - var userDisabled = ds.getItemProperty(id, "userDisabled"); - - var operation, action; - // if this item is already disabled don't set a pending operation - instead - // immediately disable it and reset the operation type if needed. - if (userDisabled == OP_NEEDS_ENABLE || userDisabled == "true" || - appDisabled == OP_NEEDS_ENABLE) { - if (userDisabled != "true") - ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true")); - if (opType != OP_NONE) { - operation = OP_NONE; - action = EM_ITEM_CANCEL; - } - } - else { - if (userDisabled == OP_NONE) - ds.setItemProperty(id, EM_R("userDisabled"), EM_L(OP_NEEDS_DISABLE)); - if (opType != OP_NEEDS_DISABLE) { - operation = OP_NEEDS_DISABLE; - action = EM_ITEM_DISABLED; - } - } - - if (action) { - this._setOp(id, operation); - this._notifyAction(id, action); - } - else { - ds.updateProperty(id, "satisfiesDependencies"); - this._updateDependentItemsForID(id); - } - }, - - /** - * Determines whether an item should be disabled by the application. - * @param id - * The ID of the item to check - */ - _isUsableItem: function EM__isUsableItem(id) { - var ds = this.datasource; - /* If the item is compatible and if it isn't blocklisted and has all - * dependencies satisfied then proceed to the security check */ - if (ds.isCompatible(ds, getResourceForID(id), false) && - ds.getItemProperty(id, "blocklisted") == "false" && - ds.getItemProperty(id, "satisfiesDependencies") == "true") { - - // appManaged items aren't updated so no need to check update security. - if (ds.getItemProperty(id, "appManaged") == "true") - return true; - - /* If we are not ignoring update security then check that the item has - * a secure update mechanism */ - return (!gCheckUpdateSecurity || - ds.getItemProperty(id, "providesUpdatesSecurely") == "true"); - } - return false; - }, - - /** - * Sets an item's dependent items disabled state for the app based on whether - * its dependencies are met and the item is compatible. - * @param id - * The ID of the item whose dependent items will be checked - */ - _updateDependentItemsForID: function EM__updateDependentItemsForID(id) { - var ds = this.datasource; - var dependentItems = this.getDependentItemListForID(id, true); - for (var i = 0; i < dependentItems.length; ++i) { - var dependentID = dependentItems[i].id; - ds.updateProperty(dependentID, "satisfiesDependencies"); - if (this._isUsableItem(dependentID)) - this._appEnableItem(dependentID); - else - this._appDisableItem(dependentID); - } - }, - - /** - * Notify observers of a change to an item that has been requested by the - * user. - */ - _notifyAction: function EM__notifyAction(id, reason) { - gOS.notifyObservers(this.datasource.getItemForID(id), - EM_ACTION_REQUESTED_TOPIC, reason); - }, - - /** - * See nsIExtensionManager.idl - */ - update: function EM_update(items, itemCount, updateCheckType, listener, - updateType, appVersion, platformVersion) { - - // Callers through the API are only allowed to use update types declared - // in nsIExtensionManager.idl - if (updateType > MAX_PUBLIC_UPDATE_WHEN) - throw Cr.NS_ERROR_ILLEGAL_VALUE; - - for (var i = 0; i < itemCount; ++i) { - var currItem = items[i]; - if (!currItem) - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - - if (items.length == 0) - items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY); - - var updater = new ExtensionItemUpdater(this); - updater.checkForUpdates(items, items.length, updateCheckType, listener, - updateType, appVersion, platformVersion); - }, - - /** - * See nsIExtensionManager.idl - */ - updateAndGetNewBlocklistedItems: function EM_updateAndGetNewBlocklistedItems(itemCount) { - if (!gBlocklist) - gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"]. - getService(Ci.nsIBlocklistService); - - var list = []; - var ds = this.datasource; - var items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY); - for (var i = 0; i < items.length; ++i) { - var id = items[i].id; - - // Get whether the add-on is currently disabled or set to be disabled. - var appDisabled = (ds.getItemProperty(id, "appDisabled") == "true" || - ds.getItemProperty(id, "appDisabled") == OP_NEEDS_DISABLE); - var userDisabled = (ds.getItemProperty(id, "userDisabled") == "true" || - ds.getItemProperty(id, "userDisabled") == OP_NEEDS_DISABLE); - var usable = this._isUsableItem(id); - var state = gBlocklist.getAddonBlocklistState(items[i].id, items[i].version); - - // We only return items that are now blocked or to be warned about and aren't - // already disabled for some reason. - if (!appDisabled && !userDisabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) - list.push(items[i]); - - // Update the appDisabled status based on the new blocked state - if (usable) - this._appEnableItem(id); - else - this._appDisableItem(id); - - // If the item was appDisabled and is now usable then it is something - // that is no longer hard blocked. If it is still to be warned about then - // just user disable it. - if (appDisabled && usable && !userDisabled && - state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - this.disableItem(id); - - ds.updateProperty(id, "blocklisted"); - ds.updateProperty(id, "blocklistedsoft"); - } - - if (itemCount) - itemCount.value = list.length; - return list; - }, - - /** - * @returns An enumeration of all registered Install Locations. - */ - get installLocations () { - return InstallLocations.enumeration; - }, - - /** - * Gets the Install Location where a visible Item is stored. - * @param id - * The GUID of the item to locate an Install Location for. - * @returns The Install Location object where the item is stored. - */ - getInstallLocation: function EM_getInstallLocation(id) { - var key = this.datasource.visibleItems[id]; - return key ? InstallLocations.get(this.datasource.visibleItems[id]) : null; - }, - - /** - * Gets a nsIUpdateItem for the item with the specified id. - * @param id - * The GUID of the item to construct a nsIUpdateItem for. - * @returns The nsIUpdateItem representing the item. - */ - getItemForID: function EM_getItemForID(id) { - return this.datasource.getItemForID(id); - }, - - /** - * Retrieves a list of installed nsIUpdateItems of items that are dependent - * on another item. - * @param id - * The ID of the item that other items depend on. - * @param includeDisabled - * Whether to include disabled items in the set returned. - * @param countRef - * The XPCJS reference to the number of items returned. - * @returns An array of installed nsIUpdateItems that depend on the item - * specified by the id parameter. - */ - getDependentItemListForID: function EM_getDependentItemListForID(id, - includeDisabled, - countRef) { - return this.datasource.getDependentItemListForID(id, includeDisabled, countRef); - }, - - /* See nsIExtensionManager.idl */ - getItemList: function EM_getItemList(type, countRef) { - return this.datasource.getItemList(type, countRef); - }, - - /* See nsIExtensionManager.idl */ - getIncompatibleItemList: function EM_getIncompatibleItemList(appVersion, - platformVersion, - type, - includeDisabled, - countRef) { - var items = this.datasource.getIncompatibleItemList(appVersion ? appVersion : undefined, - platformVersion ? platformVersion : undefined, - type, includeDisabled); - if (countRef) - countRef.value = items.length; - return items; - }, - - ///////////////////////////////////////////////////////////////////////////// - // Downloads - _transactions: [], - _downloadCount: 0, - _compatibilityCheckCount: 0, - - /** - * Ask the user if they really want to quit the application, since this will - * cancel one or more Extension/Theme downloads. - * @param subject - * A nsISupportsPRBool which this function sets to false if the user - * wishes to cancel all active downloads and quit the application, - * false otherwise. - */ - _confirmCancelDownloadsOnQuit: function EM__confirmCancelDownloadsOnQuit(subject) { - // If user has already dismissed quit request, then do nothing - if ((subject instanceof Ci.nsISupportsPRBool) && subject.data) - return; - - if (this._downloadCount > 0) { - // The observers will be notified again after this so set the download - // count to 0 to prevent this dialog from being displayed again. - this._downloadCount = 0; - var result; -#ifndef XP_MACOSX - result = this._confirmCancelDownloads(this._downloadCount, - "quitCancelDownloadsAlertTitle", - "quitCancelDownloadsAlertMsgMultiple", - "quitCancelDownloadsAlertMsg", - "dontQuitButtonWin"); -#else - result = this._confirmCancelDownloads(this._downloadCount, - "quitCancelDownloadsAlertTitle", - "quitCancelDownloadsAlertMsgMacMultiple", - "quitCancelDownloadsAlertMsgMac", - "dontQuitButtonMac"); -#endif - if (subject instanceof Ci.nsISupportsPRBool) - subject.data = result; - } - }, - - /** - * Ask the user if they really want to go offline, since this will cancel - * one or more Extension/Theme downloads. - * @param subject - * A nsISupportsPRBool which this function sets to false if the user - * wishes to cancel all active downloads and go offline, false - * otherwise. - */ - _confirmCancelDownloadsOnOffline: function EM__confirmCancelDownloadsOnOffline(subject) { - if (this._downloadCount > 0) { - result = this._confirmCancelDownloads(this._downloadCount, - "offlineCancelDownloadsAlertTitle", - "offlineCancelDownloadsAlertMsgMultiple", - "offlineCancelDownloadsAlertMsg", - "dontGoOfflineButton"); - if (subject instanceof Ci.nsISupportsPRBool) - subject.data = result; - } - }, - - /** - * Ask the user whether or not they wish to cancel the Extension/Theme - * downloads which are currently under way. - * @param count - * The number of active downloads. - * @param title - * The key of the title for the message box to be displayed - * @param cancelMessageMultiple - * The key of the message to be displayed in the message box - * when there are > 1 active downloads. - * @param cancelMessageSingle - * The key of the message to be displayed in the message box - * when there is just one active download. - * @param dontCancelButton - * The key of the label to be displayed on the "Don't Cancel - * Downloads" button. - */ - _confirmCancelDownloads: function EM__confirmCancelDownloads(count, title, - cancelMessageMultiple, - cancelMessageSingle, - dontCancelButton) { - var bundle = BundleManager.getBundle(URI_DOWNLOADS_PROPERTIES); - var title = bundle.GetStringFromName(title); - var message, quitButton; - if (count > 1) { - message = bundle.formatStringFromName(cancelMessageMultiple, [count], 1); - quitButton = bundle.formatStringFromName("cancelDownloadsOKTextMultiple", [count], 1); - } - else { - message = bundle.GetStringFromName(cancelMessageSingle); - quitButton = bundle.GetStringFromName("cancelDownloadsOKText"); - } - var dontQuitButton = bundle.GetStringFromName(dontCancelButton); - - var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. - getService(Ci.nsIWindowMediator); - var win = wm.getMostRecentWindow("Extension:Manager"); - const nsIPromptService = Ci.nsIPromptService; - var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"]. - getService(nsIPromptService); - var flags = (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_0) + - (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_1); - var rv = ps.confirmEx(win, title, message, flags, quitButton, dontQuitButton, null, null, { }); - return rv == 1; - }, - - /* See nsIExtensionManager.idl */ - addDownloads: function EM_addDownloads(items, itemCount, manager) { - if (itemCount == 0) - throw Cr.NS_ERROR_ILLEGAL_VALUE; - - for (i = 0; i < itemCount; ++i) { - var currItem = items[i]; - if (!currItem) - throw Cr.NS_ERROR_ILLEGAL_VALUE; - } - - var ds = this.datasource; - // Add observers only if they aren't already added for an active download - if (this._downloadCount == 0) { - gOS.addObserver(this, "offline-requested", false); - gOS.addObserver(this, "quit-application-requested", false); - } - this._downloadCount += itemCount; - - var urls = []; - var hashes = []; - var txnID = Math.round(Math.random() * 100); - var txn = new ItemDownloadTransaction(this, txnID); - for (var i = 0; i < itemCount; ++i) { - var currItem = items[i]; - - txn.addDownload(currItem); - urls.push(currItem.xpiURL); - hashes.push(currItem.xpiHash ? currItem.xpiHash : null); - // if this is an update remove the update metadata to prevent it from - // being updated during an install. - if (!manager) { - var id = currItem.id - var props = { - availableUpdateURL: null, - availableUpdateHash: null, - availableUpdateVersion: null, - availableUpdateInfo: null - }; - var updateVersion = ds.getItemProperty(id, "availableUpdateVersion"); - var updateURL = ds.getItemProperty(id, "availableUpdateURL"); - if (updateVersion && (updateURL == currItem.xpiURL)) - props.newVersion = EM_L(updateVersion); - ds.setItemProperties(id, props); - ds.updateProperty(id, "availableUpdateURL"); - ds.updateProperty(id, "updateable"); - } - var id = !manager ? PREFIX_ITEM_URI + currItem.id : currItem.xpiURL; - ds.updateDownloadState(id, "waiting"); - } - this._transactions.push(txn); - - if (manager) { - // XPIManager initiated -- let it know we're ready - manager.observe(txn, "xpinstall-progress", "open"); - } - else { - // Initiate an install from chrome - var xpimgr = Cc["@mozilla.org/xpinstall/install-manager;1"]. - createInstance(Ci.nsIXPInstallManager); - xpimgr.initManagerWithHashes(urls, hashes, urls.length, txn); - } - }, - - /** - * Download Operation State has changed from one to another. - * - * The nsIXPIProgressDialog implementation in the download transaction object - * forwards notifications through these methods which we then pass on to any - * front end objects implementing nsIExtensionDownloadListener that - * are listening. We maintain the master state of download operations HERE, - * not in the front end, because if the user closes the extension or theme - * managers during the downloads we need to maintain state and not terminate - * the download/install process. - * - * @param transaction - * The ItemDownloadTransaction object receiving the download - * notifications from XPInstall. - * @param addon - * An object representing nsIUpdateItem for the addon being updated - * @param state - * The state we are entering - * @param value - * ??? - */ - onStateChange: function EM_onStateChange(transaction, addon, state, value) { - var ds = this.datasource; - var id = addon.id != addon.xpiURL ? PREFIX_ITEM_URI + addon.id : addon.xpiURL; - const nsIXPIProgressDialog = Ci.nsIXPIProgressDialog; - switch (state) { - case nsIXPIProgressDialog.DOWNLOAD_START: - ds.updateDownloadState(id, "downloading"); - this._callInstallListeners("onDownloadStarted", addon); - break; - case nsIXPIProgressDialog.DOWNLOAD_DONE: - this._callInstallListeners("onDownloadEnded", addon); - break; - case nsIXPIProgressDialog.INSTALL_START: - ds.updateDownloadState(id, "finishing"); - ds.updateDownloadProgress(id, null); - break; - case nsIXPIProgressDialog.INSTALL_DONE: - --this._downloadCount; - // From nsInstall.h - // SUCCESS = 0 - // USER_CANCELLED = -210 - if (value != 0 && value != -210 && id != addon.xpiURL) { - ds.updateDownloadState(id, "failure"); - ds.updateDownloadProgress(id, null); - } - transaction.removeDownload(addon.xpiURL); - // A successful install will be passing notifications via installItemFromFile - if (value != 0) - this._callInstallListeners("onInstallEnded", addon, value); - break; - case nsIXPIProgressDialog.DIALOG_CLOSE: - for (var i = 0; i < this._transactions.length; ++i) { - if (this._transactions[i].id == transaction.id) { - this._transactions.splice(i, 1); - // Remove the observers when all transactions have completed. - if (this._transactions.length == 0) { - gOS.removeObserver(this, "offline-requested"); - gOS.removeObserver(this, "quit-application-requested"); - - // If there are no compatibility checks running then the install - // operations are complete. - if (this._compatibilityCheckCount == 0) - this._callInstallListeners("onInstallsCompleted"); - } - break; - } - } - // Remove any remaining downloads from this transaction - transaction.removeAllDownloads(); - break; - } - }, - - onProgress: function EM_onProgress(addon, value, maxValue) { - this._callInstallListeners("onDownloadProgress", addon, value, maxValue); - - var id = addon.id != addon.xpiURL ? PREFIX_ITEM_URI + addon.id : addon.xpiURL; - var progress = Math.round((value / maxValue) * 100); - this.datasource.updateDownloadProgress(id, progress); - }, - - _installListeners: [], - addInstallListener: function EM_addInstallListener(listener) { - for (var i = 0; i < this._installListeners.length; ++i) { - if (this._installListeners[i] == listener) - return i; - } - this._installListeners.push(listener); - return this._installListeners.length - 1; - }, - - removeInstallListenerAt: function EM_removeInstallListenerAt(index) { - if (index < 0 || index >= this._installListeners.length) - throw Cr.NS_ERROR_INVALID_ARG; - - this._installListeners[index] = null; - while (this._installListeners[this._installListeners.length - 1] === null) - this._installListeners.splice(this._installListeners.length - 1, 1); - }, - - _callInstallListeners: function EM__callInstallListeners(method) { - for (var i = 0; i < this._installListeners.length; ++i) { - try { - if (this._installListeners[i]) - this._installListeners[i][method].apply(this._installListeners[i], - Array.slice(arguments, 1)); - } - catch (e) { - LOG("Failure in install listener's " + method + ": " + e); - } - } - }, - - /** - * The Extensions RDF Datasource - */ - _ds: null, - _ptr: null, - - /** - * Loads the Extensions Datasource. This should not be called unless: - * - a piece of Extensions UI is being shown, or - * - on startup and there has been a change to an Install Location - * ... it should NOT be called on every startup! - */ - _ensureDS: function EM__ensureDS() { - if (!this._ds) { - this._ds = new ExtensionsDataSource(this); - if (this._ds) { - this._ds.loadExtensions(); - this._ptr = getContainer(this._ds, this._ds._itemRoot).DataSource; - gRDF.RegisterDataSource(this._ptr, true); - } - } - }, - - /** - * See nsIExtensionManager.idl - */ - get datasource() { - this._ensureDS(); - return this._ds; - }, - - // nsIClassInfo - flags: Ci.nsIClassInfo.SINGLETON, - implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT, - getHelperForLanguage: function(language) null, - getInterfaces: function(count) { - var interfaces = [Ci.nsIExtensionManager, Ci.nsIObserver]; - count.value = interfaces.length; - return interfaces; - }, - - classDescription: "Extension Manager", - contractID: "@mozilla.org/extensions/manager;1", - classID: Components.ID("{8A115FAA-7DCB-4e8f-979B-5F53472F51CF}"), - _xpcom_categories: [{ category: "profile-after-change" }, - { category: "update-timer", - value: "@mozilla.org/extensions/manager;1," + - "getService,addon-background-update-timer," + - PREF_EM_UPDATE_INTERVAL + ",86400" }], - _xpcom_factory: { - createInstance: function(outer, iid) { - if (outer != null) - throw Cr.NS_ERROR_NO_AGGREGATION; - - if (!gEmSingleton) - gEmSingleton = new ExtensionManager(); - return gEmSingleton.QueryInterface(iid); - } - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIExtensionManager, - Ci.nsITimerCallback, - Ci.nsIObserver, - Ci.nsIClassInfo]) -}; - -/** - * This object implements nsIXPIProgressDialog and represents a collection of - * XPI/JAR download and install operations. There is one - * ItemDownloadTransaction per back-end XPInstallManager object. We maintain - * a collection of separate transaction objects because it's possible to have - * multiple separate XPInstall download/install operations going on - * simultaneously, each with its own XPInstallManager instance. For instance - * you could start downloading two extensions and then download a theme. Each - * of these operations would open the appropriate FE and have to be able to - * track each operation independently. - * - * @constructor - * @param manager - * The extension manager creating this transaction - * @param id - * The integer identifier of this transaction - */ -function ItemDownloadTransaction(manager, id) { - this._manager = manager; - this._downloads = []; - this.id = id; -} -ItemDownloadTransaction.prototype = { - _manager : null, - _downloads : [], - id : -1, - - /** - * Add a download to this transaction - * @param addon - * An object implementing nsIUpdateItem for the item to be downloaded - */ - addDownload: function ItemDownloadTransaction_addDownload(addon) { - this._downloads.push({ addon: addon, waiting: true }); - this._manager.datasource.addDownload(addon); - }, - - /** - * Removes a download from this transaction - * @param url - * The URL to remove - */ - removeDownload: function ItemDownloadTransaction_removeDownload(url) { - this._manager.datasource.removeDownload(url); - }, - - /** - * Remove all downloads from this transaction - */ - removeAllDownloads: function ItemDownloadTransaction_removeAllDownloads() { - for (var i = 0; i < this._downloads.length; ++i) { - var addon = this._downloads[i].addon; - this.removeDownload(addon.xpiURL); - } - }, - - /** - * Determine if this transaction is handling the download of a url. - * @param url - * The URL to look for - * @returns true if this transaction is downloading the supplied url. - */ - containsURL: function ItemDownloadTransaction_containsURL(url) { - for (var i = 0; i < this._downloads.length; ++i) { - if (this._downloads[i].addon.xpiURL == url) - return true; - } - return false; - }, - - /** - * See nsIXPIProgressDialog.idl - */ - onStateChange: function ItemDownloadTransaction_onStateChange(index, state, value) { - this._manager.onStateChange(this, this._downloads[index].addon, - state, value); - }, - - /** - * See nsIXPIProgressDialog.idl - */ - onProgress: function ItemDownloadTransaction_onProgress(index, value, maxValue) { - this._manager.onProgress(this._downloads[index].addon, value, maxValue); - }, - - QueryInterface: XPCOMUtils.generateQI([Ci.nsIXPIProgressDialog]) -}; - -/** - * A listener object that watches the background update check and notifies the - * user of any updates found. - */ -function BackgroundUpdateCheckListener(datasource) { - this._emDS = datasource; -} -BackgroundUpdateCheckListener.prototype = { - _updateCount: 0, - _emDS: null, - - // nsIObserver implementation - observe: function BackgroundUpdateListener_observe(aSubject, aTopic, aData) { - if (aTopic != "alertclickcallback") - return; - - var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. - getService(Ci.nsIWindowMediator); - var win = wm.getMostRecentWindow("Extension:Manager"); - if (win) { - win.focus(); - win.showView("updates"); - // Don't show the update notification on next startup - gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false); - } - else { - var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. - getService(Ci.nsIWindowWatcher); - var param = Cc["@mozilla.org/supports-array;1"]. - createInstance(Ci.nsISupportsArray); - var arg = Cc["@mozilla.org/supports-string;1"]. - createInstance(Ci.nsISupportsString); - arg.data = "updates"; - param.AppendElement(arg); - ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_MANAGER, param); - } - }, - - // nsIAddonUpdateCheckListener implementation - onUpdateStarted: function BackgroundUpdateListener_onUpdateStarted() { - }, - - onUpdateEnded: function BackgroundUpdateListener_onUpdateEnded() { - if (this._updateCount > 0 && Cc["@mozilla.org/alerts-service;1"]) { - var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - var title = extensionStrings.GetStringFromName("updateNotificationTitle"); - var text; - if (this._updateCount > 1) - text = extensionStrings.formatStringFromName("multipleUpdateNotificationText", - [BundleManager.appName, this._updateCount], 2); - else - text = extensionStrings.formatStringFromName("updateNotificationText", - [BundleManager.appName], 1); - - try { - var notifier = Cc["@mozilla.org/alerts-service;1"]. - getService(Ci.nsIAlertsService); - notifier.showAlertNotification(URI_GENERIC_ICON_XPINSTALL, - title, text, true, "", this); - } - catch (e) { - LOG("Failed to retrieve alerts service, probably an unsupported " + - "platform - " + e); - } - } - }, - - onAddonUpdateStarted: function BackgroundUpdateListener_onAddonUpdateStarted(item) { - }, - - onAddonUpdateEnded: function BackgroundUpdateListener_onAddonUpdateEnded(item, status) { - if (status == Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE) { - var lastupdate = this._emDS.getItemProperty(item.id, "availableUpdateVersion"); - if (lastupdate != item.version) { - gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, true); - this._updateCount++; - } - } - } -}; - - -/** - * A listener object to the update check process that routes notifications to - * the right places and keeps the datasource up to date. - */ -function AddonUpdateCheckListener(listener, datasource) { - this._listener = listener; - this._ds = datasource; -} -AddonUpdateCheckListener.prototype = { - _listener: null, - _ds: null, - - onUpdateStarted: function AddonUpdateListener_onUpdateStarted() { - if (this._listener) - this._listener.onUpdateStarted(); - this._ds.onUpdateStarted(); - }, - - onUpdateEnded: function AddonUpdateListener_onUpdateEnded() { - if (this._listener) - this._listener.onUpdateEnded(); - this._ds.onUpdateEnded(); - }, - - onAddonUpdateStarted: function AddonUpdateListener_onAddonUpdateStarted(addon) { - if (this._listener) - this._listener.onAddonUpdateStarted(addon); - this._ds.onAddonUpdateStarted(addon); - }, - - onAddonUpdateEnded: function AddonUpdateListener_onAddonUpdateEnded(addon, status) { - if (this._listener) - this._listener.onAddonUpdateEnded(addon, status); - this._ds.onAddonUpdateEnded(addon, status); - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// -// ExtensionItemUpdater -// -function ExtensionItemUpdater(aEM) -{ - this._emDS = aEM._ds; - this._em = aEM; - - getVersionChecker(); -} - -ExtensionItemUpdater.prototype = { - _emDS : null, - _em : null, - _updateCheckType : 0, - _updateType : 0, - _items : [], - _listener : null, - - /* ExtensionItemUpdater -# -# When we check for updates to an item, there are two pieces of information -# that are returned - 1) info about the newest available version, if any, -# and 2) info about the currently installed version. The latter is provided -# primarily to inform the client of changes to the application compatibility -# metadata for the current item. Depending on the situation, either 2 or -# 1&2 may be what is required. -# -# Callers: -# 1 - nsUpdateService.js, user event -# User clicked on the update icon to invoke an update check, -# user clicked on an Extension/Theme and clicked "Update". In this -# case we want to update compatibility metadata about the installed -# version, and look for newer versions to offer. -# 2 - nsUpdateService.js, background event -# Timer fired, background update is being performed. In this case -# we also want to check for compatibility metadata and newer -# versions that will make the add-on compatible. -# 3 - Mismatch -# User upgraded to a newer version of the app, update compatibility -# metadata and look for newer versions. -# 4 - Install Phone Home -# User installed an item that was deemed incompatible based only -# on the information provided in the item's install.rdf manifest, -# we look ONLY for compatibility updates in this case to determine -# whether or not the item can be installed. - */ - checkForUpdates: function ExtensionItemUpdater_checkForUpdates(aItems, - aItemCount, - aUpdateCheckType, - aListener, - aUpdateType, - aAppVersion, - aPlatformVersion) { - if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) { - this._listener = aListener; - this._appVersion = aAppVersion ? aAppVersion : gApp.version; - this._platformVersion = aPlatformVersion ? aPlatformVersion - : gApp.platformVersion; - } - else { - this._listener = new AddonUpdateCheckListener(aListener, this._emDS); - this._appVersion = gApp.version; - this._platformVersion = gApp.platformVersion; - } - - if (this._listener) - this._listener.onUpdateStarted(); - this._updateCheckType = aUpdateCheckType; - this._items = aItems; - this._responseCount = aItemCount; - - this._updateType = aUpdateType; - // All update check types look for compatibility currently - this._updateType |= UPDATE_TYPE_COMPATIBILITY; - // Only two types also look for new versions - if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION || - aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION) - this._updateType |= UPDATE_TYPE_NEWVERSION; - - // This is the number of extensions/themes/etc that we found updates for. - this._updateCount = 0; - - for (var i = 0; i < aItemCount; ++i) { - var e = this._items[i]; - if (this._listener) - this._listener.onAddonUpdateStarted(e); - (new RDFItemUpdater(this)).checkForUpdates(e, aUpdateCheckType); - } - - if (this._listener && aItemCount == 0) - this._listener.onUpdateEnded(); - }, - - ///////////////////////////////////////////////////////////////////////////// - // ExtensionItemUpdater - _applyVersionUpdates: function ExtensionItemUpdater__applyVersionUpdates(aLocalItem, - aRemoteItem) { - var targetAppInfo = this._emDS.getTargetApplicationInfo(aLocalItem.id, this._emDS); - // If targetAppInfo is null this is for a new install. If the local item's - // maxVersion does not equal the targetAppInfo maxVersion then this is for - // an upgrade. In both of these cases return true if the remotely specified - // maxVersion is greater than the local item's maxVersion. - if (!targetAppInfo || - gVersionChecker.compare(aLocalItem.maxAppVersion, targetAppInfo.maxVersion) != 0) { - if (gVersionChecker.compare(aLocalItem.maxAppVersion, aRemoteItem.maxAppVersion) < 0) - return true; - else - return false; - } - - if (gVersionChecker.compare(targetAppInfo.maxVersion, aRemoteItem.maxAppVersion) < 0) { - // Remotely specified maxVersion is newer than the maxVersion - // for the installed Extension. Apply that change to the datasources. - this._emDS.setTargetApplicationInfo(aLocalItem.id, - aRemoteItem.targetAppID, - aRemoteItem.minAppVersion, - aRemoteItem.maxAppVersion, - null); - - // If we got here through |checkForMismatches|, this extension has - // already been disabled, re-enable it. - var op = StartupCache.entries[aLocalItem.installLocationKey][aLocalItem.id].op; - if (op == OP_NEEDS_DISABLE || - this._emDS.getItemProperty(aLocalItem.id, "appDisabled") == "true") - this._em._appEnableItem(aLocalItem.id); - return true; - } - else if (this._updateCheckType == Ci.nsIExtensionManager.UPDATE_SYNC_COMPATIBILITY) - this._emDS.setTargetApplicationInfo(aLocalItem.id, - aRemoteItem.targetAppID, - aRemoteItem.minAppVersion, - aRemoteItem.maxAppVersion, - null); - return false; - }, - - /** - * Checks whether a discovered update is valid for install - * @param aLocalItem - * The already installed nsIUpdateItem that the update is for - * @param aRemoteItem - * The nsIUpdateItem we are trying to update to - * @param aUpdateCheckType - * The type of update check being performed - * - * @returns true if the item is compatible and is not blocklisted. - * false if the item is not compatible or is blocklisted. - */ - _isValidUpdate: function _isValidUpdate(aLocalItem, aRemoteItem, aUpdateCheckType) { - var appExtensionsVersion = (aRemoteItem.targetAppID != TOOLKIT_ID) ? - this._appVersion : - this._platformVersion; - - var min = aRemoteItem.minAppVersion; - var max = aRemoteItem.maxAppVersion; - // Check if the update will only run on a newer version of the application. - if (!min || gVersionChecker.compare(appExtensionsVersion, min) < 0) - return false; - - // Check if the update will only run on an older version of the application. - if (!max || gVersionChecker.compare(appExtensionsVersion, max) > 0) - return false; - - // Ignore the blocklist for compatibility only checks. - if (aUpdateCheckType != Ci.nsIExtensionManager.UPDATE_CHECK_COMPATIBILITY) { - if (!gBlocklist) - gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"]. - getService(Ci.nsIBlocklistService); - // Denies updates that are hard blocked, soft blocked items will be warned - // about during the install. - if (gBlocklist.isAddonBlocklisted(aLocalItem.id, aRemoteItem.version, - this._appVersion, this._platformVersion)) - return false; - } - - return true; - }, - - checkForDone: function ExtensionItemUpdater_checkForDone(item, status) { - if (this._listener) { - try { - this._listener.onAddonUpdateEnded(item, status); - } - catch (e) { - LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onAddonUpdateEnded: " + e); - } - } - if (--this._responseCount == 0 && this._listener) { - try { - this._listener.onUpdateEnded(); - } - catch (e) { - LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onUpdateEnded: " + e); - } - } - }, -}; - -/** - * Replaces %...% strings in an addon url (update and updateInfo) with - * appropriate values. - * @param aItem - * The nsIUpdateItem representing the item - * @param aAppVersion - * The application version to check for updates for - * @param aUpdateType - * The type of the update (see nsIExtensionManager) - * @param aURI - * The uri to escape - * @param aDS - * The extensions datasource - * - * @returns the appropriately escaped uri. - */ -function escapeAddonURI(aItem, aAppVersion, aUpdateType, aURI, aDS) -{ - var itemStatus = "userEnabled"; - if (aDS.getItemProperty(aItem.id, "userDisabled") == "true" || - aDS.getItemProperty(aItem.id, "userDisabled") == OP_NEEDS_ENABLE) - itemStatus = "userDisabled"; - else if (aDS.getItemProperty(aItem.id, "type") == Ci.nsIUpdateItem.TYPE_THEME) { - var currentSkin = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN); - if (aDS.getItemProperty(aItem.id, "internalName") != currentSkin) - itemStatus = "userDisabled"; - } - - if (aDS.getItemProperty(aItem.id, "compatible") == "false") - itemStatus += ",incompatible"; - if (aDS.getItemProperty(aItem.id, "blocklisted") == "true") - itemStatus += ",blocklisted"; - if (aDS.getItemProperty(aItem.id, "satisfiesDependencies") == "false") - itemStatus += ",needsDependencies"; - - aURI = aURI.replace(/%ITEM_ID%/g, aItem.id); - aURI = aURI.replace(/%ITEM_VERSION%/g, aItem.version); - aURI = aURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion); - aURI = aURI.replace(/%ITEM_STATUS%/g, itemStatus); - aURI = aURI.replace(/%APP_ID%/g, gApp.ID); - aURI = aURI.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : gApp.version); - aURI = aURI.replace(/%REQ_VERSION%/g, REQ_VERSION); - aURI = aURI.replace(/%APP_OS%/g, gOSTarget); - aURI = aURI.replace(/%APP_ABI%/g, gXPCOMABI); - aURI = aURI.replace(/%APP_LOCALE%/g, gLocale); - aURI = aURI.replace(/%CURRENT_APP_VERSION%/g, gApp.version); - if (aUpdateType) - aURI = aURI.replace(/%UPDATE_TYPE%/g, aUpdateType); - - // Replace custom parameters (names of custom parameters must have at - // least 3 characters to prevent lookups for something like %D0%C8) - var catMan = null; - aURI = aURI.replace(/%(\w{3,})%/g, function(match, param) { - if (!catMan) { - catMan = Cc["@mozilla.org/categorymanager;1"]. - getService(Ci.nsICategoryManager); - } - - try { - var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, param); - var paramHandler = Cc[contractID]. - getService(Ci.nsIPropertyBag2); - return paramHandler.getPropertyAsAString(param); - } - catch(e) { - return match; - } - }); - - // escape() does not properly encode + symbols in any embedded FVF strings. - return aURI.replace(/\+/g, "%2B"); -} - -function RDFItemUpdater(aUpdater) { - this._updater = aUpdater; -} - -RDFItemUpdater.prototype = { - _updater : null, - _updateCheckType : 0, - _item : null, - - checkForUpdates: function RDFItemUpdater_checkForUpdates(aItem, aUpdateCheckType) { - // A preference setting can disable updating for this item - try { - if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, aItem.id))) { - var status = Ci.nsIAddonUpdateCheckListener.STATUS_DISABLED; - this._updater.checkForDone(aItem, status); - return; - } - } - catch (e) { } - - // Items managed by the app are not checked for updates. - var emDS = this._updater._emDS; - if (emDS.getItemProperty(aItem.id, "appManaged") == "true") { - var status = Ci.nsIAddonUpdateCheckListener.STATUS_APP_MANAGED; - this._updater.checkForDone(aItem, status); - return; - } - - // Items that have a pending install, uninstall, or upgrade are not checked - // for updates. - var opType = emDS.getItemProperty(aItem.id, "opType"); - if (opType) { - var status = Ci.nsIAddonUpdateCheckListener.STATUS_PENDING_OP; - this._updater.checkForDone(aItem, status); - return; - } - - var installLocation = InstallLocations.get(emDS.getInstallLocationKey(aItem.id)); - // Don't check items for updates that are managed independently - if (installLocation && installLocation.itemIsManagedIndependently(aItem.id)) { - var status = Ci.nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED; - this._updater.checkForDone(aItem, status); - return; - } - - // Don't check items for updates if the location can't be written to except - // when performing a version only update. - if ((aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION || - aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) && - (!installLocation || !installLocation.canAccess)) { - var status = Ci.nsIAddonUpdateCheckListener.STATUS_READ_ONLY; - this._updater.checkForDone(aItem, status); - return; - } - - this._updateCheckType = aUpdateCheckType; - this._item = aItem; - - // Look for a custom update URI: 1) supplied by a pref, 2) supplied by the - // install manifest, 3) the default configuration - try { - var dsURI = gPref.getComplexValue(PREF_EM_ITEM_UPDATE_URL.replace(/%UUID%/, aItem.id), - Ci.nsIPrefLocalizedString).data; - } - catch (e) { } - if (!dsURI) - dsURI = aItem.updateRDF; - if (!dsURI) - dsURI = gPref.getCharPref(PREF_UPDATE_DEFAULT_URL); - - dsURI = escapeAddonURI(aItem, this._updater._appVersion, - this._updater._updateType, dsURI, emDS); - - // Verify that the URI provided is valid - try { - var uri = newURI(dsURI); - } - catch (e) { - WARN("RDFItemUpdater:checkForUpdates: There was an error loading the \r\n" + - " update datasource for: " + dsURI + ", item = " + aItem.id + ", error: " + e); - this._updater.checkForDone(aItem, - Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE); - return; - } - - LOG("RDFItemUpdater:checkForUpdates sending a request to server for: " + - uri.spec + ", item = " + aItem.objectSource); - - var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. - createInstance(Ci.nsIXMLHttpRequest); - request.open("GET", uri.spec, true); - request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(); - request.overrideMimeType("text/xml"); - request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; - - var self = this; - request.onerror = function(event) { self.onXMLError(event, aItem); }; - request.onload = function(event) { self.onXMLLoad(event, aItem); }; - request.send(null); - }, - - onXMLLoad: function RDFItemUpdater_onXMLLoad(aEvent, aItem) { - var request = aEvent.target; - try { - gCertUtils.checkCert(request.channel); - } - catch (e) { - // This may be overly restrictive in two cases: corporate installations - // with a corporate update server using an in-house CA cert (installed - // but not "built-in") and lone developers hosting their updates on a - // site with a self-signed cert (permanently accepted, otherwise the - // BadCertHandler would prevent getting this far). Update checks will - // fail in both these scenarios. - // How else can we protect the vast majority of updates served from AMO - // from the spoofing attack described in bug 340198 while allowing those - // other cases? A "hackme" pref? Domain-control certs are cheap, getting - // one should not be a barrier in either case. - LOG("RDFItemUpdater::onXMLLoad: " + e); - this._updater.checkForDone(aItem, - Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE); - return; - } - var responseXML = request.responseXML; - - // If the item does not have an update RDF and returns an error it is not - // treated as a failure since all items without an updateURL are checked - // for updates on AMO even if they are not hosted there. - if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR || - (request.status != 200 && request.status != 0)) { - this._updater.checkForDone(aItem, (aItem.updateRDF ? Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE : - Ci.nsIAddonUpdateCheckListener.STATUS_NONE)); - return; - } - - var rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"]. - createInstance(Ci.nsIRDFXMLParser) - var ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]. - createInstance(Ci.nsIRDFDataSource); - rdfParser.parseString(ds, request.channel.URI, request.responseText); - - this.onDatasourceLoaded(ds, aItem); - }, - - onXMLError: function RDFItemUpdater_onXMLError(aEvent, aItem) { - try { - var request = aEvent.target; - // the following may throw (e.g. a local file or timeout) - var status = request.status; - } - catch (e) { - request = aEvent.target.channel.QueryInterface(Ci.nsIRequest); - status = request.status; - } - // this can fail when a network connection is not present. - try { - var statusText = request.statusText; - } - catch (e) { - status = 0; - } - // When status is 0 we don't have a valid channel. - if (status == 0) - statusText = "nsIXMLHttpRequest channel unavailable"; - - WARN("RDFItemUpdater:onError: There was an error loading the \r\n" + - "the update datasource for item " + aItem.id + ", error: " + statusText); - this._updater.checkForDone(aItem, - Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE); - }, - - onDatasourceLoaded: function RDFItemUpdater_onDatasourceLoaded(aDatasource, aLocalItem) { - /* -# The extension update RDF file looks something like this: -# -# -# -# -# -# -# -# -# -# 5.0 -# http://www.mysite.com/myext-50.xpi -# -# -# -# 4.9 -# -# -# {ec8030f7-c20a-464f-9b0e-13a3a9e97384} -# 0.9 -# 1.0 -# http://www.mysite.com/myext-49.xpi -# -# -# -# -# If we get here because the following happened: -# 1) User was using Firefox 0.9 with ExtensionX 0.5 (minVersion 0.8, -# maxVersion 0.9 for Firefox) -# 2) User upgraded Firefox to 1.0 -# 3) |checkForMismatches| deems ExtensionX 0.5 incompatible with this -# new version of Firefox on the basis of its maxVersion -# 4) ** We reach this point ** -# -# If the version of ExtensionX (0.5) matches that provided by the -# server, then this is a cue that the author updated the rdf file -# or central repository to say "0.5 is ALSO compatible with Firefox 1.0, -# no changes are necessary." In this event, the local metadata for -# installed ExtensionX (0.5) is freshened with the new maxVersion, -# and we advance to the next item WITHOUT any download/install -# updates. - */ - if (!aDatasource.GetAllResources().hasMoreElements()) { - LOG("RDFItemUpdater:onDatasourceLoaded: Datasource empty.\r\n" + - "If you are an Extension developer and were expecting there to be\r\n" + - "updates, this could mean any number of things, since the RDF system\r\n" + - "doesn't give up much in the way of information when the load fails.\r\n" + - "\r\nTry checking that: \r\n" + - " 1. Your remote RDF file exists at the location.\r\n" + - " 2. Your RDF file is valid XML (starts with \r\n" + - " and loads in Firefox displaying pretty printed like other XML documents\r\n" + - " 3. Your server is sending the data in the correct MIME\r\n" + - " type (text/xml)"); - } - - // If we have an update key then the update manifest must be signed - if (aLocalItem.updateKey) { - var extensionRes = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id); - LOG(extensionRes.Value); - var signature = this._getPropertyFromResource(aDatasource, extensionRes, "signature", null); - if (signature) { - var serializer = new RDFSerializer(); - try { - var updateString = serializer.serializeResource(aDatasource, extensionRes); - var verifier = Cc["@mozilla.org/security/datasignatureverifier;1"]. - getService(Ci.nsIDataSignatureVerifier); - try { - if (!verifier.verifyData(updateString, signature, aLocalItem.updateKey)) { - WARN("RDFItemUpdater:onDatasourceLoaded: Update manifest for " + - aLocalItem.id + " failed signature check."); - this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE); - return; - } - } - catch (e) { - WARN("RDFItemUpdater:onDatasourceLoaded: Failed to verify signature for " + - aLocalItem.id + ". This indicates a malformed update key or signature."); - this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE); - return; - } - } - catch (e) { - WARN("RDFItemUpdater:onDatasourceLoaded: Failed to generate signature " + - "string for " + aLocalItem.id + ". Serializer threw " + e); - this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE); - return; - } - } - else { - WARN("RDFItemUpdater:onDatasourceLoaded: Update manifest for " + - aLocalItem.id + " did not contain a signature."); - this._updater.checkForDone(aLocalItem, Ci.nsIAddonUpdateCheckListener.STATUS_FAILURE); - return; - } - } - /* If there is no updateKey either the update was over SSL, or it is an old - * addon that we are allowing a grace update. */ - - // Parse the response RDF - var newerItem, sameItem; - - // Firefox 1.0PR+ update.rdf format - if (this._updateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION || - this._updateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) { - // Look for newer versions of this item, we only do this in "normal" - // mode... see comment by ExtensionItemUpdater_checkForUpdates - // about how we do this in all cases but Install Phone Home - which - // only needs to do a version check. - newerItem = this._parseV20UpdateInfo(aDatasource, aLocalItem, - this._updateCheckType); - - if (newerItem) { - ++this._updater._updateCount; - LOG("RDFItemUpdater:onDatasourceLoaded: Found a newer version of this item:\r\n" + - newerItem.objectSource); - } - } - - // Now look for updated version compatibility metadata for the currently - // installed version... - sameItem = this._parseV20UpdateInfo(aDatasource, aLocalItem, - Ci.nsIExtensionManager.UPDATE_CHECK_COMPATIBILITY); - - if (sameItem) { - // Install-time updates are not written to the DS because there is no - // entry yet, EM just uses the notifications to ascertain (by hand) - // whether or not there is a remote maxVersion tweak that makes the - // item being installed compatible. - if (!this._updater._applyVersionUpdates(aLocalItem, sameItem)) - sameItem = null; - else - LOG("RDFItemUpdater:onDatasourceLoaded: Found info about the installed\r\n" + - "version of this item: " + sameItem.objectSource); - } - var item = null, status = Ci.nsIAddonUpdateCheckListener.STATUS_NONE; - if ((this._updateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION || - this._updateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) - && newerItem) { - item = newerItem; - status = Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE; - } - else if (sameItem) { - item = sameItem; - status = Ci.nsIAddonUpdateCheckListener.STATUS_VERSIONINFO; - } - else { - item = aLocalItem; - status = Ci.nsIAddonUpdateCheckListener.STATUS_NO_UPDATE; - } - // Only one call of this._updater.checkForDone is needed for RDF - // responses, since there is only one response per item. - this._updater.checkForDone(item, status); - }, - - // Get a compulsory property from a resource. Reports an error if the - // property was not present. - _getPropertyFromResource: function RDFItemUpdater__getPropertyFromResource(aDataSource, - aSourceResource, - aProperty, - aLocalItem) { - var rv; - try { - var property = gRDF.GetResource(EM_NS(aProperty)); - rv = stringData(aDataSource.GetTarget(aSourceResource, property, true)); - if (rv === undefined) - throw Cr.NS_ERROR_FAILURE; - } - catch (e) { - // XXXben show console message "aProperty" not found on aSourceResource. - return null; - } - return rv; - }, - - /** - * Parses the Firefox 1.0RC1+ update manifest format looking for new versions - * of updated compatibility information about the given add-on. - * @param aDataSource - * The update manifest's datasource - * @param aLocalItem - * The nsIUpdateItem representing the add-on being checked for updates. - * @param aUpdateCheckType - * The type of update check being performed. See the constants in - * nsIExtensionManager - * @returns An nsIUpdateItem holding the update's information if a valid - * update is found or null if not. - */ - _parseV20UpdateInfo: function RDFItemUpdater__parseV20UpdateInfo(aDataSource, - aLocalItem, - aUpdateCheckType) { - var extensionRes = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id); - - var updatesArc = gRDF.GetResource(EM_NS("updates")); - var updates = aDataSource.GetTarget(extensionRes, updatesArc, true); - - try { - updates = updates.QueryInterface(Ci.nsIRDFResource); - } - catch (e) { - WARN("RDFItemUpdater:_parseV20UpdateInfo: No updates were found for:\r\n" + - aLocalItem.id + "\r\n" + - "If you are an Extension developer and were expecting there to be\r\n" + - "updates, this could mean any number of things, since the RDF system\r\n" + - "doesn't give up much in the way of information when the load fails.\r\n" + - "\r\nTry checking that: \r\n" + - " 1. Your RDF File is correct - e.g. check that there is a top level\r\n" + - " RDF Resource with a URI urn:mozilla:extension:{GUID}, and that\r\n" + - " the listed all have matching GUIDs."); - return null; - } - - // Track the newest update found - var updatedItem = null; - - var cu = Cc["@mozilla.org/rdf/container-utils;1"]. - getService(Ci.nsIRDFContainerUtils); - if (cu.IsContainer(aDataSource, updates)) { - var ctr = getContainer(aDataSource, updates); - - var versions = ctr.GetElements(); - while (versions.hasMoreElements()) { - // There are two different methodologies for collecting version - // information depending on whether or not we've been invoked in - // "version updates only" mode or "version+newest" mode. - var version = versions.getNext().QueryInterface(Ci.nsIRDFResource); - var foundItem = this._parseV20Update(aDataSource, version, aLocalItem, - updatedItem ? updatedItem.version : aLocalItem.version, - aUpdateCheckType); - if (foundItem) { - // When not checking for new versions we can bail out on the first - // result. - if (aUpdateCheckType) - return foundItem; - updatedItem = foundItem; - } - } - } - return updatedItem; - }, - - /** - * Parses a single version's update entry looking for the best matching - * targetApplication entry. - * @param aDataSource - * The update manifest's datasource - * @param aUpdateResource - * The nsIRDFResource of the update entry. - * @param aLocalItem - * The nsIUpdateItem representing the add-on being checked for updates. - * @param aNewestVersionFound - * When checking for new versions holds the newest version of this - * add-on that we know about. Otherwise holds the current version. - * @param aUpdateCheckType - * The type of update check being performed. See the constants in - * nsIExtensionManager - * @returns An nsIUpdateItem holding the update's information if a valid - * update is found or null if not. - */ - _parseV20Update: function RDFItemUpdater__parseV20Update(aDataSource, - aUpdateResource, - aLocalItem, - aNewestVersionFound, - aUpdateCheckType) { - var version = this._getPropertyFromResource(aDataSource, aUpdateResource, - "version", aLocalItem); - /* If we are looking for new versions then test whether this discovered - * version is greater than any previously found update. Otherwise check - * if this update is for the same version as we have installed. */ - var result = gVersionChecker.compare(version, aNewestVersionFound); - if ((aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION || - aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) ? result <= 0 : result != 0) - return null; - - var taArc = gRDF.GetResource(EM_NS("targetApplication")); - var targetApps = aDataSource.GetTargets(aUpdateResource, taArc, true); - - // Track the best update we have found so far - var newestUpdateItem = null; - while (targetApps.hasMoreElements()) { - var targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource); - var appID = this._getPropertyFromResource(aDataSource, targetApp, "id", aLocalItem); - if (appID != gApp.ID && appID != TOOLKIT_ID) - continue; - - var updateLink = this._getPropertyFromResource(aDataSource, targetApp, "updateLink", aLocalItem); - var updateHash = this._getPropertyFromResource(aDataSource, targetApp, "updateHash", aLocalItem); - if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION || - aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_NOTIFY_NEWVERSION) { - // New version information is useless without a link to get it from - if (!updateLink) - continue; - - /* If the update link is non-ssl and we do not have a hash or the hash - * is of an insecure nature then we must ignore this update. Bypass - * this if not checking update security. Currently we only consider - * the sha hashing algorithms as secure. */ - if (gCheckUpdateSecurity && updateLink.substring(0, 6) != "https:" && - (!updateHash || updateHash.substring(0, 3) != "sha")) { - WARN("RDFItemUpdater:_parseV20Update: Update for " + aLocalItem.id + - " at " + updateLink + " ignored because it is insecure. updateLink " + - " must be a https url or an updateHash must be specified."); - continue; - } - } - - var updatedItem = makeItem(aLocalItem.id, - version, - aLocalItem.installLocationKey, - this._getPropertyFromResource(aDataSource, targetApp, "minVersion", aLocalItem), - this._getPropertyFromResource(aDataSource, targetApp, "maxVersion", aLocalItem), - aLocalItem.name, - updateLink, - updateHash, - "", /* Icon URL */ - "", /* RDF Update URL */ - "", /* Update Key */ - aLocalItem.type, - appID); - - if (this._updater._isValidUpdate(aLocalItem, updatedItem, aUpdateCheckType)) { - if (aUpdateCheckType == Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION) { - var infourl = this._getPropertyFromResource(aDataSource, targetApp, - "updateInfoURL"); - if (infourl) - infourl = EM_L(infourl); - this._updater._emDS.setItemProperty(aLocalItem.id, - EM_R("availableUpdateInfo"), - infourl); - } - if (appID == gApp.ID) { - // App takes precedence over toolkit. If we found the app, bail out. - return updatedItem; - } - newestUpdateItem = updatedItem; - } - } - return newestUpdateItem; - } -}; - -/** - * A serialisation method for RDF data that produces an identical string - * provided that the RDF assertions match. - * The serialisation is not complete, only assertions stemming from a given - * resource are included, multiple references to the same resource are not - * permitted, and the RDF prolog and epilog are not included. - * RDF Blob and Date literals are not supported. - */ -function RDFSerializer() -{ - this.cUtils = Cc["@mozilla.org/rdf/container-utils;1"]. - getService(Ci.nsIRDFContainerUtils); - this.resources = []; -} - -RDFSerializer.prototype = { - INDENT: " ", // The indent used for pretty-printing - resources: null, // Array of the resources that have been found - - /** - * Escapes characters from a string that should not appear in XML. - * @param string The string to be escaped - * @returns a string with all characters invalid in XML character data - * converted to entity references. - */ - escapeEntities: function RDFSerializer_escapeEntities(string) - { - string = string.replace(/&/g, "&"); - string = string.replace(//g, ">"); - string = string.replace(/"/g, """); - return string; - }, - - /** - * Serializes all the elements of an RDF container. - * @param ds The datasource holding the data - * @param container The RDF container to output the child elements of - * @param indent The current level of indent for pretty-printing - * @returns a string containing the serialized elements. - */ - serializeContainerItems: function RDFSerializer_serializeContainerItems(ds, container, indent) - { - var result = ""; - var items = container.GetElements(); - while (items.hasMoreElements()) { - var item = items.getNext().QueryInterface(Ci.nsIRDFResource); - result += indent + "\n" - result += this.serializeResource(ds, item, indent + this.INDENT); - result += indent + "\n" - } - return result; - }, - - /** - * Serializes all em:* (see EM_NS) properties of an RDF resource except for - * the em:signature property. As this serialization is to be compared against - * the manifest signature it cannot contain the em:signature property itself. - * @param ds The datasource holding the data - * @param resource The RDF resource to output the properties of - * @param indent The current level of indent for pretty-printing - * @returns a string containing the serialized properties. - */ - serializeResourceProperties: function RDFSerializer_serializeResourceProperties(ds, resource, indent) - { - var result = ""; - var items = []; - var arcs = ds.ArcLabelsOut(resource); - while (arcs.hasMoreElements()) { - var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource); - if (arc.ValueUTF8.substring(0, PREFIX_NS_EM.length) != PREFIX_NS_EM) - continue; - var prop = arc.ValueUTF8.substring(PREFIX_NS_EM.length); - if (prop == "signature") - continue; - - var targets = ds.GetTargets(resource, arc, true); - while (targets.hasMoreElements()) { - var target = targets.getNext(); - if (target instanceof Ci.nsIRDFResource) { - var item = indent + "\n"; - item += this.serializeResource(ds, target, indent + this.INDENT); - item += indent + "\n"; - items.push(item); - } - else if (target instanceof Ci.nsIRDFLiteral) { - items.push(indent + "" + this.escapeEntities(target.Value) + "\n"); - } - else if (target instanceof Ci.nsIRDFInt) { - items.push(indent + "" + target.Value + "\n"); - } - else { - throw new Error("Cannot serialize unknown literal type"); - } - } - } - items.sort(); - result += items.join(""); - return result; - }, - - /** - * Recursively serializes an RDF resource and all resources it links to. - * This will only output EM_NS properties and will ignore any em:signature - * property. - * @param ds The datasource holding the data - * @param resource The RDF resource to serialize - * @param indent The current level of indent for pretty-printing. - * Leave undefined for no indent - * @returns a string containing the serialized resource. - * @throws if the RDF data contains multiple references to the same resource. - */ - serializeResource: function RDFSerializer_serializeResource(ds, resource, indent) - { - if (this.resources.indexOf(resource) != -1 ) { - // We cannot output multiple references to the same resource. - throw new Error("Cannot serialize multiple references to "+resource.Value); - } - if (indent === undefined) - indent = ""; - - this.resources.push(resource); - var container = null; - var type = "Description"; - if (this.cUtils.IsSeq(ds, resource)) { - type = "Seq"; - container = this.cUtils.MakeSeq(ds, resource); - } - else if (this.cUtils.IsAlt(ds, resource)) { - type = "Alt"; - container = this.cUtils.MakeAlt(ds, resource); - } - else if (this.cUtils.IsBag(ds, resource)) { - type = "Bag"; - container = this.cUtils.MakeBag(ds, resource); - } - - var result = indent + "\n"; - return result; - } -} - -/** - * A Datasource that holds Extensions. - * - Implements nsIRDFDataSource to drive UI - * - Uses a RDF/XML datasource for storage (this is undesirable) - * - * @constructor - */ -function ExtensionsDataSource(em) { - this._em = em; - - this._itemRoot = gRDF.GetResource(RDFURI_ITEM_ROOT); - this._defaultTheme = gRDF.GetResource(RDFURI_DEFAULT_THEME); -} -ExtensionsDataSource.prototype = { - _inner : null, - _em : null, - _itemRoot : null, - _defaultTheme : null, - - /** - * Called during application shutdown to clear any references held. - * The ExtensionsDataSource is unusable after calling this. - */ - shutdown: function EMDS_shutdown() { - this._inner = null; - this._em = null; - this._itemRoot = null; - this._defaultTheme = null; - }, - - /** - * Determines if an item's dependencies are satisfied. An item's dependencies - * are satisifed when all items specified in the item's em:requires arc are - * installed, enabled, and the version is compatible based on the em:requires - * minVersion and maxVersion. - * @param id - * The ID of the item - * @returns true if the item's dependencies are satisfied. - * false if the item's dependencies are not satisfied. - */ - satisfiesDependencies: function EMDS_satisfiesDependencies(id) { - var ds = this._inner; - var itemResource = getResourceForID(id); - var targets = ds.GetTargets(itemResource, EM_R("requires"), true); - if (!targets.hasMoreElements()) - return true; - - getVersionChecker(); - var idRes = EM_R("id"); - var minVersionRes = EM_R("minVersion"); - var maxVersionRes = EM_R("maxVersion"); - while (targets.hasMoreElements()) { - var target = targets.getNext().QueryInterface(Ci.nsIRDFResource); - var dependencyID = stringData(ds.GetTarget(target, idRes, true)); - var version = null; - version = this.getItemProperty(dependencyID, "version"); - if (version) { - var opType = this.getItemProperty(dependencyID, "opType"); - if (opType == OP_NEEDS_DISABLE || opType == OP_NEEDS_UNINSTALL) - return false; - - if (this.getItemProperty(dependencyID, "userDisabled") == "true" || - this.getItemProperty(dependencyID, "appDisabled") == "true" || - this.getItemProperty(dependencyID, "userDisabled") == OP_NEEDS_DISABLE || - this.getItemProperty(dependencyID, "appDisabled") == OP_NEEDS_DISABLE) - return false; - - var minVersion = stringData(ds.GetTarget(target, minVersionRes, true)); - var maxVersion = stringData(ds.GetTarget(target, maxVersionRes, true)); - var compatible = (gVersionChecker.compare(version, minVersion) >= 0 && - gVersionChecker.compare(version, maxVersion) <= 0); - if (!compatible) - return false; - } - else { - return false; - } - } - - return true; - }, - - /** - * Determine if an item is compatible - * @param datasource - * The datasource to inspect for compatibility - can be the main - * datasource or an Install Manifest. - * @param source - * The RDF Resource of the item to inspect for compatibility. - * @param alwaysCheckVersion - * Set to true to only obey the compatibility information in the - * datasource. When false the compatibility range may be ignored - * if compatibility checking is disabled. - * @param appVersion - * The version of the application we are checking for compatibility - * against. If this parameter is undefined, the version of the running - * application is used. - * @param platformVersion - * The version of the toolkit to check compatibility against - * @returns true if the item is compatible with this version of the - * application, false, otherwise. - */ - isCompatible: function EMDS_isCompatible(datasource, source, alwaysCheckVersion, - appVersion, platformVersion) { - // The Default Theme is always compatible. - if (source.EqualsNode(this._defaultTheme)) - return true; - - // Items pending install have no target application info in the datasource - if (datasource === this && - this._getItemProperty(source, "opType") == OP_NEEDS_INSTALL) - return true; - - var appID = gApp.ID; - if (appVersion === undefined) - appVersion = gApp.version; - if (platformVersion === undefined) - var platformVersion = gApp.platformVersion; - - var targets = datasource.GetTargets(source, EM_R("targetApplication"), true); - var idRes = EM_R("id"); - var minVersionRes = EM_R("minVersion"); - var maxVersionRes = EM_R("maxVersion"); - var versionChecker = getVersionChecker(); - var rv = false; - while (targets.hasMoreElements()) { - var targetApp = targets.getNext().QueryInterface(Ci.nsIRDFResource); - var id = stringData(datasource.GetTarget(targetApp, idRes, true)); - var minVersion = stringData(datasource.GetTarget(targetApp, minVersionRes, true)); - var maxVersion = stringData(datasource.GetTarget(targetApp, maxVersionRes, true)); - if (id == appID) { - if (!alwaysCheckVersion && !gCheckCompatibility) - return true; - rv = (versionChecker.compare(appVersion, minVersion) >= 0) && - (versionChecker.compare(appVersion, maxVersion) <= 0); - return rv; // App takes precedence over toolkit. - } - - if (id == TOOLKIT_ID) { - if (!alwaysCheckVersion && !gCheckCompatibility) - return true; - rv = (versionChecker.compare(platformVersion, minVersion) >= 0) && - (versionChecker.compare(platformVersion, maxVersion) <= 0); - // Keep looping, in case the app id is later. - } - } - return rv; - }, - - /** - * Gets a list of items that are incompatible with a specific application version. - * @param appVersion - * The Version of the application to check for incompatibility against. - * @param platformVersion - * The version of the toolkit to check compatibility against - * @param desiredType - * The nsIUpdateItem type of items to look for - * @param includeDisabled - * Whether or not disabled items should be included in the set returned - * @returns An array of nsIUpdateItems that are incompatible with the application - * ID/Version supplied. - */ - getIncompatibleItemList: function EMDS_getIncompatibleItemList(appVersion, - platformVersion, - desiredType, - includeDisabled) { - var items = []; - var ctr = getContainer(this._inner, this._itemRoot); - var elements = ctr.GetElements(); - while (elements.hasMoreElements()) { - var item = elements.getNext().QueryInterface(Ci.nsIRDFResource); - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var type = this.getItemProperty(id, "type"); - // Skip this item if we're not seeking disabled items - if (!includeDisabled && this.getItemProperty(id, "isDisabled") == "true") - continue; - - // If the id of this item matches one of the items potentially installed - // with and maintained by this application AND it is installed in the - // global install location (i.e. the place installed by the app installer) - // it is and can be managed by the update file - it's not an item that has - // been manually installed by the user into their profile dir, and as such - // it is always compatible with the next release of the application since - // we will continue to support it. - var locationKey = this.getItemProperty(id, "installLocation"); - var appManaged = this.getItemProperty(id, "appManaged") == "true"; - if (appManaged && locationKey == KEY_APP_GLOBAL) - continue; - - if (type != -1 && (type & desiredType) && - !this.isCompatible(this, item, true, appVersion, platformVersion)) - items.push(this.getItemForID(id)); - } - return items; - }, - - /** - * Gets a list of items of a specific type - * @param desiredType - * The nsIUpdateItem type of items to return - * @param countRef - * The XPCJS reference to the size of the returned array - * @returns An array of nsIUpdateItems, populated only with an item for |id| - * if |id| is non-null, otherwise all items matching the specified - * type. - */ - getItemList: function EMDS_getItemList(desiredType, countRef) { - var items = []; - var ctr = getContainer(this, this._itemRoot); - var elements = ctr.GetElements(); - while (elements.hasMoreElements()) { - var e = elements.getNext().QueryInterface(Ci.nsIRDFResource); - var eID = stripPrefix(e.Value, PREFIX_ITEM_URI); - var type = this.getItemProperty(eID, "type"); - if (type != -1 && type & desiredType) - items.push(this.getItemForID(eID)); - } - if (countRef) - countRef.value = items.length; - return items; - }, - - /** - * Retrieves a list of installed nsIUpdateItems of items that are dependent - * on another item. - * @param id - * The ID of the item that other items depend on. - * @param includeDisabled - * Whether to include disabled items in the set returned. - * @param countRef - * The XPCJS reference to the number of items returned. - * @returns An array of installed nsIUpdateItems that depend on the item - * specified by the id parameter. - */ - getDependentItemListForID: function EMDS_getDependentItemListForID(id, - includeDisabled, - countRef) { - var items = []; - var ds = this._inner; - var ctr = getContainer(this, this._itemRoot); - var elements = ctr.GetElements(); - while (elements.hasMoreElements()) { - var e = elements.getNext().QueryInterface(Ci.nsIRDFResource); - var dependentID = stripPrefix(e.Value, PREFIX_ITEM_URI); - var targets = ds.GetTargets(e, EM_R("requires"), true); - var idRes = EM_R("id"); - while (targets.hasMoreElements()) { - var target = targets.getNext().QueryInterface(Ci.nsIRDFResource); - var dependencyID = stringData(ds.GetTarget(target, idRes, true)); - if (dependencyID == id) { - if (!includeDisabled && this.getItemProperty(dependentID, "isDisabled") == "true") - continue; - items.push(this.getItemForID(dependentID)); - break; - } - } - } - if (countRef) - countRef.value = items.length; - return items; - }, - - /** - * Constructs an nsIUpdateItem for the given item ID - * @param id - * The GUID of the item to construct a nsIUpdateItem for - * @returns The nsIUpdateItem for the id. - */ - getItemForID: function EMDS_getItemForID(id) { - if (!this.visibleItems[id]) - return null; - - var r = getResourceForID(id); - if (!r) - return null; - - var targetAppInfo = this.getTargetApplicationInfo(id, this); - var updateHash = this.getItemProperty(id, "availableUpdateHash"); - return makeItem(id, - this.getItemProperty(id, "version"), - this.getItemProperty(id, "installLocation"), - targetAppInfo ? targetAppInfo.minVersion : "", - targetAppInfo ? targetAppInfo.maxVersion : "", - this.getItemProperty(id, "name"), - this.getItemProperty(id, "availableUpdateURL"), - updateHash ? updateHash : "", - this.getItemProperty(id, "iconURL"), - this.getItemProperty(id, "updateURL"), - this.getItemProperty(id, "updateKey"), - this.getItemProperty(id, "type"), - targetAppInfo ? targetAppInfo.appID : gApp.ID); - }, - - /** - * Gets the name of the Install Location where an item is installed. - * @param id - * The GUID of the item to locate an Install Location for - * @returns The string name of the Install Location where the item is - * installed. - */ - getInstallLocationKey: function EMDS_getInstallLocationKey(id) { - return this.getItemProperty(id, "installLocation"); - }, - - /** - * Sets an RDF property on an item in a datasource. Does not create - * multiple assertions - * @param datasource - * The target datasource where the property should be set - * @param source - * The RDF Resource to set the property on - * @param property - * The RDF Resource of the property to set - * @param newValue - * The RDF Node containing the new property value - */ - _setProperty: function EMDS__setProperty(datasource, source, property, newValue) { - var oldValue = datasource.GetTarget(source, property, true); - if (oldValue) { - if (newValue) - datasource.Change(source, property, oldValue, newValue); - else - datasource.Unassert(source, property, oldValue); - } - else if (newValue) - datasource.Assert(source, property, newValue, true); - }, - - /** - * Gets the updated target application info if it exists for an item from - * the Extensions datasource during an installation or upgrade. - * @param id - * The ID of the item to discover updated target application info for - * @returns A JS Object with the following properties: - * "id" The id of the item - * "minVersion" The updated minimum version of the target - * application that this item can run in - * "maxVersion" The updated maximum version of the target - * application that this item can run in - */ - getUpdatedTargetAppInfo: function EMDS_getUpdatedTargetAppInfo(id) { - // The default theme is always compatible so there is never update info. - if (getResourceForID(id).EqualsNode(this._defaultTheme)) - return null; - - var appID = gApp.ID; - var r = getResourceForID(id); - var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true); - if (!targetApps.hasMoreElements()) - targetApps = this._inner.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); - var outData = null; - while (targetApps.hasMoreElements()) { - var targetApp = targetApps.getNext(); - if (targetApp instanceof Ci.nsIRDFResource) { - try { - var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true)); - // Different target application? - if (foundAppID != appID && foundAppID != TOOLKIT_ID) - continue; - var updatedMinVersion = this._inner.GetTarget(targetApp, EM_R("updatedMinVersion"), true); - var updatedMaxVersion = this._inner.GetTarget(targetApp, EM_R("updatedMaxVersion"), true); - if (updatedMinVersion && updatedMaxVersion) - outData = { id : id, - targetAppID : foundAppID, - minVersion : stringData(updatedMinVersion), - maxVersion : stringData(updatedMaxVersion) }; - if (foundAppID == appID) - return outData; - } - catch (e) { - continue; - } - } - } - return outData; - }, - - /** - * Sets the updated target application info for an item in the Extensions - * datasource during an installation or upgrade. - * @param id - * The ID of the item to set updated target application info for - * @param targetAppID - * The target application ID used for checking compatibility for this item. - * @param updatedMinVersion - * The updated minimum version of the target application that this - * item can run in - * @param updatedMaxVersion - * The updated maximum version of the target application that this - * item can run in - * - * @note Add-ons can specify a targetApplication id of toolkit@mozilla.org in - * their install manifest for compatibility with all apps using a - * specific release of the toolkit. - */ - setUpdatedTargetAppInfo: function EMDS_setUpdatedTargetAppInfo(id, targetAppID, - updatedMinVersion, - updatedMaxVersion) { - // The default theme is always compatible so it is never updated. - if (getResourceForID(id).EqualsNode(this._defaultTheme)) - return; - - // Version/Dependency Info - var updatedMinVersionRes = EM_R("updatedMinVersion"); - var updatedMaxVersionRes = EM_R("updatedMaxVersion"); - - var appID = gApp.ID; - var r = getResourceForID(id); - var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true); - // add updatedMinVersion and updatedMaxVersion for an install else an upgrade - if (!targetApps.hasMoreElements()) { - var idRes = EM_R("id"); - var targetRes = getResourceForID(id); - var property = EM_R("targetApplication"); - var anon = gRDF.GetAnonymousResource(); - this._inner.Assert(anon, idRes, EM_L(appID), true); - this._inner.Assert(anon, updatedMinVersionRes, EM_L(updatedMinVersion), true); - this._inner.Assert(anon, updatedMaxVersionRes, EM_L(updatedMaxVersion), true); - this._inner.Assert(targetRes, property, anon, true); - } - else { - while (targetApps.hasMoreElements()) { - var targetApp = targetApps.getNext(); - if (targetApp instanceof Ci.nsIRDFResource) { - var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true)); - // Different target application? - if (foundAppID != targetAppID) - continue; - this._inner.Assert(targetApp, updatedMinVersionRes, EM_L(updatedMinVersion), true); - this._inner.Assert(targetApp, updatedMaxVersionRes, EM_L(updatedMaxVersion), true); - break; - } - } - } - this.Flush(); - }, - - /** - * Gets the target application info for an item from a datasource. - * @param id - * The GUID of the item to discover target application info for - * @param datasource - * The datasource to look up target application info in - * @returns A JS Object with the following properties: - * "appID" The target application ID used for checking - * compatibility for this item. - * "minVersion" The minimum version of the target application - * that this item can run in - * "maxVersion" The maximum version of the target application - * that this item can run in - * or null, if no target application data exists for the specified - * id in the supplied datasource. - */ - getTargetApplicationInfo: function EMDS_getTargetApplicationInfo(id, datasource) { - var appID = gApp.ID; - // The default theme is always compatible. - if (getResourceForID(id).EqualsNode(this._defaultTheme)) { - var ver = gApp.version; - return { appID: appID, minVersion: ver, maxVersion: ver }; - } - - var r = getResourceForID(id); - var targetApps = datasource.GetTargets(r, EM_R("targetApplication"), true); - if (!targetApps) - return null; - - if (!targetApps.hasMoreElements()) - targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); - var outData = null; - while (targetApps.hasMoreElements()) { - var targetApp = targetApps.getNext(); - if (targetApp instanceof Ci.nsIRDFResource) { - try { - var foundAppID = stringData(datasource.GetTarget(targetApp, EM_R("id"), true)); - // Different target application? - if (foundAppID != appID && foundAppID != TOOLKIT_ID) - continue; - - outData = { appID: foundAppID, - minVersion: stringData(datasource.GetTarget(targetApp, EM_R("minVersion"), true)), - maxVersion: stringData(datasource.GetTarget(targetApp, EM_R("maxVersion"), true)) }; - if (foundAppID == appID) - return outData; - } - catch (e) { - continue; - } - } - } - return outData; - }, - - /** - * Sets the target application info for an item in a datasource. - * @param id - * The GUID of the item to discover target application info for - * @param targetAppID - * The target application ID used for checking compatibility for this - * item. - * @param minVersion - * The minimum version of the target application that this item can - * run in - * @param maxVersion - * The maximum version of the target application that this item can - * run in - * @param datasource - * The datasource to look up target application info in - * - * @note Add-ons can specify a targetApplication id of toolkit@mozilla.org in - * their install manifest for compatibility with all apps using a - * specific release of the toolkit. - */ - setTargetApplicationInfo: function EMDS_setTargetApplicationInfo(id, targetAppID, - minVersion, - maxVersion, - datasource) { - var targetDataSource = datasource; - if (!targetDataSource) - targetDataSource = this._inner; - - var appID = gApp.ID; - var r = getResourceForID(id); - var targetApps = targetDataSource.GetTargets(r, EM_R("targetApplication"), true); - if (!targetApps.hasMoreElements()) - targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); - while (targetApps.hasMoreElements()) { - var targetApp = targetApps.getNext(); - if (targetApp instanceof Ci.nsIRDFResource) { - var foundAppID = stringData(targetDataSource.GetTarget(targetApp, EM_R("id"), true)); - // Different target application? - if (foundAppID != targetAppID) - continue; - - this._setProperty(targetDataSource, targetApp, EM_R("minVersion"), EM_L(minVersion)); - this._setProperty(targetDataSource, targetApp, EM_R("maxVersion"), EM_L(maxVersion)); - - // If we were setting these properties on the main datasource, flush - // it now. (Don't flush changes set on Install Manifests - they are - // fleeting). - if (!datasource) - this.Flush(); - - break; - } - } - }, - - /** - * Gets a property of an item - * @param id - * The GUID of the item - * @param property - * The name of the property (excluding EM_NS) - * @returns The literal value of the property, or undefined if there is no - * value. - */ - getItemProperty: function EMDS_getItemProperty(id, property) { - var item = getResourceForID(id); - if (!item) { - LOG("getItemProperty failing for lack of an item. This means getResourceForItem \ - failed to locate a resource for aItemID (item ID = " + id + ", property = " + property + ")"); - } - else - return this._getItemProperty(item, property); - return undefined; - }, - - /** - * Gets a property of an item resource - * @param itemResource - * The RDF Resource of the item - * @param property - * The name of the property (excluding EM_NS) - * @returns The literal value of the property, or undefined if there is no - * value. - */ - _getItemProperty: function EMDS__getItemProperty(itemResource, property) { - var target = this.GetTarget(itemResource, EM_R(property), true); - var value = stringData(target); - if (value === undefined) - value = intData(target); - return value === undefined ? "" : value; - }, - - /** - * Sets a property on an item. - * @param id - * The GUID of the item - * @param propertyArc - * The RDF Resource of the property arc - * @param propertyValue - * A nsIRDFLiteral value of the property to be set - */ - setItemProperty: function EMDS_setItemProperty(id, propertyArc, propertyValue) { - var item = getResourceForID(id); - this._setProperty(this._inner, item, propertyArc, propertyValue); - this.Flush(); - }, - - /** - * Sets one or more properties for an item. - * @param id - * The ID of the item - * @param properties - * A JS object which maps properties to values. - */ - setItemProperties: function EMDS_setItemProperties(id, properties) { - var item = getResourceForID(id); - for (var key in properties) - this._setProperty(this._inner, item, EM_R(key), properties[key]); - this.Flush(); - }, - - /** - * Inserts the RDF resource for an item into a container. - * @param id - * The GUID of the item - */ - insertItemIntoContainer: function EMDS_insertItemIntoContainer(id) { - // Get the target container and resource - var ctr = getContainer(this._inner, this._itemRoot); - var itemResource = getResourceForID(id); - // Don't bother adding the extension to the list if it's already there. - // (i.e. we're upgrading) - var oldIndex = ctr.IndexOf(itemResource); - if (oldIndex == -1) - ctr.AppendElement(itemResource); - this.Flush(); - }, - - /** - * Removes the RDF resource for an item from its container. - * @param id - * The GUID of the item - */ - removeItemFromContainer: function EMDS_removeItemFromContainer(id) { - var ctr = getContainer(this._inner, this._itemRoot); - var itemResource = getResourceForID(id); - ctr.RemoveElement(itemResource, true); - this.Flush(); - }, - - /** - * Removes a corrupt item entry from the extension list added due to buggy - * code in previous EM versions! - * @param id - * The GUID of the item - */ - removeCorruptItem: function EMDS_removeCorruptItem(id) { - this.removeItemMetadata(id); - this.removeItemFromContainer(id); - this.visibleItems[id] = null; - }, - - /** - * Removes a corrupt download entry from the list - * @param uri - * The RDF URI of the item. - * @returns The RDF Resource of the removed entry - */ - removeCorruptDLItem: function EMDS_removeCorruptDLItem(uri) { - var itemResource = gRDF.GetResource(uri); - var ctr = getContainer(this._inner, this._itemRoot); - if (ctr.IndexOf(itemResource) != -1) { - ctr.RemoveElement(itemResource, true); - this._cleanResource(itemResource); - this.Flush(); - } - return itemResource; - }, - - /** - * Copies localized properties from an install manifest to the datasource - * - * @param installManifest - * The Install Manifest datasource we are copying from - * @param source - * The source resource of the localized properties - * @param target - * The target resource to store the localized properties - */ - _addLocalizedMetadata: function EMDS__addLocalizedMetadata(installManifest, - sourceRes, targetRes) - { - var singleProps = ["name", "description", "creator", "homepageURL"]; - - for (var i = 0; i < singleProps.length; ++i) { - var property = EM_R(singleProps[i]); - var literal = installManifest.GetTarget(sourceRes, property, true); - // If literal is null, _setProperty will remove any existing. - this._setProperty(this._inner, targetRes, property, literal); - } - - // Assert properties with multiple values - var manyProps = ["developer", "translator", "contributor"]; - for (var i = 0; i < manyProps.length; ++i) { - var property = EM_R(manyProps[i]); - var literals = installManifest.GetTargets(sourceRes, property, true); - - var oldValues = this._inner.GetTargets(targetRes, property, true); - while (oldValues.hasMoreElements()) { - var oldValue = oldValues.getNext().QueryInterface(Ci.nsIRDFNode); - this._inner.Unassert(targetRes, property, oldValue); - } - while (literals.hasMoreElements()) { - var literal = literals.getNext().QueryInterface(Ci.nsIRDFNode); - this._inner.Assert(targetRes, property, literal, true); - } - } - - }, - - /** - * Copies metadata from an Install Manifest Datasource into the Extensions - * DataSource. - * @param id - * The GUID of the item - * @param installManifest - * The Install Manifest datasource we are copying from - * @param installLocation - * The Install Location of the item. - */ - addItemMetadata: function EMDS_addItemMetadata(id, installManifest, installLocation) { - var targetRes = getResourceForID(id); - // Remove any temporary assertions used for the install process - this._setProperty(this._inner, targetRes, EM_R("newVersion"), null); - // Copy the assertions over from the source datasource. - // Assert properties with single values - var singleProps = ["version", "updateURL", "updateService", "optionsURL", - "aboutURL", "iconURL", "internalName", "updateKey"]; - - // Items installed into restricted Install Locations can also be locked - // (can't be removed or disabled) - if (installLocation.restricted) - singleProps = singleProps.concat(["locked"]); - if (installLocation.name == KEY_APP_GLOBAL) - singleProps = singleProps.concat(["appManaged"]); - for (var i = 0; i < singleProps.length; ++i) { - var property = EM_R(singleProps[i]); - var literal = installManifest.GetTarget(gInstallManifestRoot, property, true); - // If literal is null, _setProperty will remove any existing. - this._setProperty(this._inner, targetRes, property, literal); - } - - var localizedProp = EM_R("localized"); - var localeProp = EM_R("locale"); - // Remove old localized properties - var oldValues = this._inner.GetTargets(targetRes, localizedProp, true); - while (oldValues.hasMoreElements()) { - var oldValue = oldValues.getNext().QueryInterface(Ci.nsIRDFNode); - this._cleanResource(oldValue); - this._inner.Unassert(targetRes, localizedProp, oldValue); - } - // Add each localized property - var localizations = installManifest.GetTargets(gInstallManifestRoot, localizedProp, true); - while (localizations.hasMoreElements()) { - var localization = localizations.getNext().QueryInterface(Ci.nsIRDFResource); - var anon = gRDF.GetAnonymousResource(); - var literals = installManifest.GetTargets(localization, localeProp, true); - while (literals.hasMoreElements()) { - var literal = literals.getNext().QueryInterface(Ci.nsIRDFNode); - this._inner.Assert(anon, localeProp, literal, true); - } - this._addLocalizedMetadata(installManifest, localization, anon); - this._inner.Assert(targetRes, localizedProp, anon, true); - } - // Add the fallback properties - this._addLocalizedMetadata(installManifest, gInstallManifestRoot, targetRes); - - // Version/Dependency Info - var versionProps = ["targetApplication", "requires"]; - var idRes = EM_R("id"); - var minVersionRes = EM_R("minVersion"); - var maxVersionRes = EM_R("maxVersion"); - for (var i = 0; i < versionProps.length; ++i) { - var property = EM_R(versionProps[i]); - var newVersionInfos = installManifest.GetTargets(gInstallManifestRoot, property, true); - - var oldVersionInfos = this._inner.GetTargets(targetRes, property, true); - while (oldVersionInfos.hasMoreElements()) { - var oldVersionInfo = oldVersionInfos.getNext().QueryInterface(Ci.nsIRDFResource); - this._cleanResource(oldVersionInfo); - this._inner.Unassert(targetRes, property, oldVersionInfo); - } - while (newVersionInfos.hasMoreElements()) { - var newVersionInfo = newVersionInfos.getNext().QueryInterface(Ci.nsIRDFResource); - var anon = gRDF.GetAnonymousResource(); - this._inner.Assert(anon, idRes, installManifest.GetTarget(newVersionInfo, idRes, true), true); - this._inner.Assert(anon, minVersionRes, installManifest.GetTarget(newVersionInfo, minVersionRes, true), true); - this._inner.Assert(anon, maxVersionRes, installManifest.GetTarget(newVersionInfo, maxVersionRes, true), true); - this._inner.Assert(targetRes, property, anon, true); - } - } - this.updateProperty(id, "opType"); - this.updateProperty(id, "updateable"); - this.Flush(); - }, - - /** - * Strips an item entry of all assertions. - * @param id - * The GUID of the item - */ - removeItemMetadata: function EMDS_removeItemMetadata(id) { - var item = getResourceForID(id); - var resources = ["targetApplication", "requires", "localized"]; - for (var i = 0; i < resources.length; ++i) { - var targetApps = this._inner.GetTargets(item, EM_R(resources[i]), true); - while (targetApps.hasMoreElements()) { - var targetApp = targetApps.getNext().QueryInterface(Ci.nsIRDFResource); - this._cleanResource(targetApp); - } - } - - this._cleanResource(item); - }, - - /** - * Strips a resource of all outbound assertions. We use methods like this - * since the RDFXMLDatasource will write out all assertions, even if they - * are not connected through our root. - * @param resource - * The resource to clean. - */ - _cleanResource: function EMDS__cleanResource(resource) { - // Remove outward arcs - var arcs = this._inner.ArcLabelsOut(resource); - while (arcs.hasMoreElements()) { - var arc = arcs.getNext().QueryInterface(Ci.nsIRDFResource); - var targets = this._inner.GetTargets(resource, arc, true); - while (targets.hasMoreElements()) { - var value = targets.getNext().QueryInterface(Ci.nsIRDFNode); - if (value) - this._inner.Unassert(resource, arc, value); - } - } - }, - - /** - * Notify views that this propery has changed (this is for properties that - * are implemented by this datasource rather than by the inner in-memory - * datasource and thus do not get free change handling). - * @param id - * The GUID of the item to update the property for. - * @param property - * The property (less EM_NS) to update. - */ - updateProperty: function EMDS_updateProperty(id, property) { - var item = getResourceForID(id); - this._updateProperty(item, property); - }, - - /** - * Notify views that this propery has changed (this is for properties that - * are implemented by this datasource rather than by the inner in-memory - * datasource and thus do not get free change handling). This allows updating - * properties for download items which don't have the em item prefix in there - ( resource value. In most instances updateProperty should be used. - * @param item - * The item to update the property for. - * @param property - * The property (less EM_NS) to update. - */ - _updateProperty: function EMDS__updateProperty(item, property) { - if (item) { - var propertyResource = EM_R(property); - var value = this.GetTarget(item, propertyResource, true); - for (var i = 0; i < this._observers.length; ++i) { - if (value) - this._observers[i].onChange(this, item, propertyResource, - EM_L(""), value); - else - this._observers[i].onUnassert(this, item, propertyResource, - EM_L("")); - } - } - }, - - /** - * Determines if an Item is an active download - * @param id - * The ID of the item. This will be a uri scheme without the - * em item prefix so getProperty shouldn't be used. - * @returns true if the item is an active download, false otherwise. - */ - isDownloadItem: function EMDS_isDownloadItem(id) { - var downloadURL = stringData(this.GetTarget(gRDF.GetResource(id), EM_R("downloadURL"), true)); - return downloadURL && downloadURL != ""; - }, - - /** - * Adds an entry representing an active download to the appropriate container - * @param addon - * An object implementing nsIUpdateItem for the addon being - * downloaded. - */ - addDownload: function EMDS_addDownload(addon) { - // Updates have already been added to the datasource so we just update the - // download state. - if (addon.id != addon.xpiURL) { - this.updateDownloadState(PREFIX_ITEM_URI + addon.id, "waiting"); - return; - } - var res = gRDF.GetResource(addon.xpiURL); - this._setProperty(this._inner, res, EM_R("name"), EM_L(addon.name)); - this._setProperty(this._inner, res, EM_R("version"), EM_L(addon.version)); - this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(addon.iconURL)); - this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(addon.xpiURL)); - this._setProperty(this._inner, res, EM_R("type"), EM_I(addon.type)); - - var ctr = getContainer(this._inner, this._itemRoot); - if (ctr.IndexOf(res) == -1) - ctr.AppendElement(res); - - this.updateDownloadState(addon.xpiURL, "waiting"); - this.Flush(); - }, - - /** - * Adds an entry representing an item that is incompatible and is being - * checked for a compatibility update. - * @param name - * The display name of the item being checked - * @param url - * The URL string of the xpi file that has been staged. - * @param type - * The nsIUpdateItem type of the item - * @param version - * The version of the item - */ - addIncompatibleUpdateItem: function EMDS_addIncompatibleUpdateItem(name, url, type, version) { - var iconURL = (type == Ci.nsIUpdateItem.TYPE_THEME) ? URI_GENERIC_ICON_THEME : - URI_GENERIC_ICON_XPINSTALL; - var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES); - var updateMsg = extensionsStrings.formatStringFromName("incompatibleUpdateMessage", - [BundleManager.appName, name], 2) - - var res = gRDF.GetResource(url); - this._setProperty(this._inner, res, EM_R("name"), EM_L(name)); - this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(iconURL)); - this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(url)); - this._setProperty(this._inner, res, EM_R("type"), EM_I(type)); - this._setProperty(this._inner, res, EM_R("version"), EM_L(version)); - this._setProperty(this._inner, res, EM_R("incompatibleUpdate"), EM_L("true")); - this._setProperty(this._inner, res, EM_R("description"), EM_L(updateMsg)); - - var ctr = getContainer(this._inner, this._itemRoot); - if (ctr.IndexOf(res) == -1) - ctr.AppendElement(res); - - this.updateDownloadState(url, "incompatibleUpdate"); - this.Flush(); - }, - - /** - * Removes an active download from the appropriate container - * @param url - * The URL string of the active download to be removed - */ - removeDownload: function EMDS_removeDownload(url) { - var res = gRDF.GetResource(url); - var ctr = getContainer(this._inner, this._itemRoot); - if (ctr.IndexOf(res) != -1) - ctr.RemoveElement(res, true); - this._cleanResource(res); - this.updateDownloadState(url, null); - this.Flush(); - }, - - /** - * A hash of RDF resource values (e.g. Add-on IDs or XPI URLs) that represent - * installation progress for a single browser session. - */ - _progressData: { }, - - /** - * Updates the install progress data for a given ID (e.g. Add-on IDs or - * XPI URLs). - * @param id - * The URL string of the active download to be removed - * @param state - * The current state in the installation process. If null the object - * is deleted from _progressData. - */ - updateDownloadState: function EMDS_updateDownloadState(id, state) { - if (!state) { - if (id in this._progressData) - delete this._progressData[id]; - return; - } - else { - if (!(id in this._progressData)) - this._progressData[id] = { }; - this._progressData[id].state = state; - } - var item = gRDF.GetResource(id); - this._updateProperty(item, "state"); - }, - - updateDownloadProgress: function EMDS_updateDownloadProgress(id, progress) { - if (!progress) { - if (!(id in this._progressData)) - return; - this._progressData[id].progress = null; - } - else { - if (!(id in this._progressData)) - this.updateDownloadState(id, "downloading"); - - if (this._progressData[id].progress == progress) - return; - - this._progressData[id].progress = progress; - } - var item = gRDF.GetResource(id); - this._updateProperty(item, "progress"); - }, - - /** - * A GUID->location-key hash of items that are visible to the application. - * These are items that show up in the Extension/Themes etc UI. If there is - * an instance of the same item installed in Install Locations of differing - * profiles, the item at the highest priority location will appear in this - * list. - */ - visibleItems: { }, - - /** - * Walk the list of installed items and determine what the visible list is, - * based on which items are visible at the highest priority locations. - */ - _buildVisibleItemList: function EMDS__buildVisibleItemList() { - var ctr = getContainer(this, this._itemRoot); - var items = ctr.GetElements(); - while (items.hasMoreElements()) { - var item = items.getNext().QueryInterface(Ci.nsIRDFResource); - // Resource URIs adopt the format: location-key,item-id - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - this.visibleItems[id] = this.getItemProperty(id, "installLocation"); - } - }, - - /** - * Updates an item's location in the visible item list. - * @param id - * The GUID of the item to update - * @param locationKey - * The name of the Install Location where the item is installed. - * @param forceReplace - * true if the new location should be used, regardless of its - * priority relationship to existing entries, false if the location - * should only be updated if its priority is lower than the existing - * value. - */ - updateVisibleList: function EMDS_updateVisibleList(id, locationKey, forceReplace) { - if (id in this.visibleItems && this.visibleItems[id]) { - var oldLocation = InstallLocations.get(this.visibleItems[id]); - var newLocation = InstallLocations.get(locationKey); - if (forceReplace || !oldLocation || newLocation.priority < oldLocation.priority) - this.visibleItems[id] = locationKey; - } - else - this.visibleItems[id] = locationKey; - }, - - /** - * Load the Extensions Datasource from disk. - */ - loadExtensions: function EMDS_loadExtensions() { - var extensionsFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]); - try { - this._inner = gRDF.GetDataSourceBlocking(getURLSpecFromFile(extensionsFile)); - } - catch (e) { - ERROR("Datasource::loadExtensions: removing corrupted extensions datasource " + - " file = " + extensionsFile.path + ", exception = " + e + "\n"); - extensionsFile.remove(false); - return; - } - - var cu = Cc["@mozilla.org/rdf/container-utils;1"]. - getService(Ci.nsIRDFContainerUtils); - cu.MakeSeq(this._inner, this._itemRoot); - - this._buildVisibleItemList(); - }, - - /** - * See nsIExtensionManager.idl - */ - onUpdateStarted: function EMDS_onUpdateStarted() { - LOG("Datasource: Update Started"); - }, - - /** - * See nsIExtensionManager.idl - */ - onUpdateEnded: function EMDS_onUpdateEnded() { - LOG("Datasource: Update Ended"); - }, - - /** - * See nsIExtensionManager.idl - */ - onAddonUpdateStarted: function EMDS_onAddonUpdateStarted(addon) { - if (!addon) - throw Cr.NS_ERROR_INVALID_ARG; - - LOG("Datasource: Addon Update Started: " + addon.id); - this.updateProperty(addon.id, "availableUpdateURL"); - }, - - /** - * See nsIExtensionManager.idl - */ - onAddonUpdateEnded: function EMDS_onAddonUpdateEnded(addon, status) { - if (!addon) - throw Cr.NS_ERROR_INVALID_ARG; - - LOG("Datasource: Addon Update Ended: " + addon.id + ", status: " + status); - var url = null, hash = null, version = null; - var updateAvailable = status == Ci.nsIAddonUpdateCheckListener.STATUS_UPDATE; - if (updateAvailable) { - url = EM_L(addon.xpiURL); - if (addon.xpiHash) - hash = EM_L(addon.xpiHash); - version = EM_L(addon.version); - } - this.setItemProperties(addon.id, { - availableUpdateURL: url, - availableUpdateHash: hash, - availableUpdateVersion: version - }); - this.updateProperty(addon.id, "availableUpdateURL"); - }, - - ///////////////////////////////////////////////////////////////////////////// - // nsIRDFDataSource - get URI() { - return "rdf:extensions"; - }, - - GetSource: function EMDS_GetSource(property, target, truthValue) { - return this._inner.GetSource(property, target, truthValue); - }, - - GetSources: function EMDS_GetSources(property, target, truthValue) { - return this._inner.GetSources(property, target, truthValue); - }, - - /** - * Gets an URL to an item's image file - * @param item - * The RDF Resource representing the item - * @param fileName - * The file to locate a URL for - * @returns An RDF Resource to the URL discovered, or the fallback - * if the discovery failed. - */ - _getImageURL: function EMDS__getImageURL(item, fileName) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var installLocation = this._em.getInstallLocation(id); - if (!installLocation) - return null; - var file = installLocation.getItemFile(id, fileName) - if (file && file.exists()) - return gRDF.GetResource(getURLSpecFromFile(file)); - - return null; - }, - - /** - * Get the em:iconURL property (icon url of the item) - */ - _rdfGet_iconURL: function EMDS__rdfGet_iconURL(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - - var installLocation = this._em.getInstallLocation(id); - if (!this.isDownloadItem(id) && !installLocation) - return null; - - // Try to pick an icon from the item's install folder - iconURL = this._getImageURL(item, "icon.png"); - if (iconURL) - return iconURL; - - var type = this.getItemProperty(id, "type"); - if (type == Ci.nsIUpdateItem.TYPE_THEME) - return gRDF.GetResource(URI_GENERIC_ICON_THEME); - - // Only look for an iconURL if the item is not disabled and safe mode isn't - // active - if (!inSafeMode() && this.getItemProperty(id, "isDisabled") != "true") { - var iconURL = stringData(this._inner.GetTarget(item, property, true)); - if (iconURL) { - try { - var uri = newURI(iconURL); - var scheme = uri.scheme; - // Only allow chrome URIs normally. When an item is being installed - // allow http(s) URIs. - if (scheme == "chrome" || (scheme == "http" || scheme == "https") && - this._inner.hasArcOut(item, EM_R("downloadURL"))) - return null; - } - catch (e) { - } - } - } - - return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL); - }, - - /** - * Get the em:previewImage property (preview image of the item) - */ - _rdfGet_previewImage: function EMDS__rdfGet_previewImage(item, property) { - var type = this.getItemProperty(stripPrefix(item.Value, PREFIX_ITEM_URI), "type"); - if (type == Ci.nsIUpdateItem.TYPE_THEME) - return this._getImageURL(item, "preview.png"); - return null; - }, - - /** - * If we're in safe mode, the item is disabled by the user or app, or the - * item is to not an extension then don't offer an options url. - */ - _rdfGet_optionsURL: function EMDS__rdfGet_optionsURL(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - if (inSafeMode() || this.getItemProperty(id, "isDisabled") == "true" || - this.getItemProperty(id, "type") != Ci.nsIUpdateItem.TYPE_EXTENSION) - return EM_L(""); - - return null; - }, - - /** - * If we're in safe mode, the item is disabled by the user or app, the item - * is not an extension, or the item is to be upgraded force the generic about - * dialog for the item. - */ - _rdfGet_aboutURL: function EMDS__rdfGet_aboutURL(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - if (inSafeMode() || this.getItemProperty(id, "isDisabled") == "true" || - this.getItemProperty(id, "type") != Ci.nsIUpdateItem.TYPE_EXTENSION || - this.getItemProperty(id, "opType") == OP_NEEDS_UPGRADE) - return EM_L(""); - - return null; - }, - - _rdfGet_installDate: function EMDS__rdfGet_installDate(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var key = this.getItemProperty(id, "installLocation"); - if (key && key in StartupCache.entries && id in StartupCache.entries[key] && - StartupCache.entries[key][id] && StartupCache.entries[key][id].mtime) - return EM_D(StartupCache.entries[key][id].mtime * 1000000); - return null; - }, - - /** - * Get the em:compatible property (whether or not this item is compatible) - */ - _rdfGet_compatible: function EMDS__rdfGet_compatible(item, property) { - var compatible = this.isCompatible(this, item, true); - return compatible ? EM_L("true") : EM_L("false"); - }, - - /** - * Get the providesUpdatesSecurely property (whether or not this item has a - * secure update mechanism) - */ - _rdfGet_providesUpdatesSecurely: function EMDS__rdfGet_providesUpdatesSecurely(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - if (this.getItemProperty(id, "updateKey") || - !this.getItemProperty(id, "updateURL") || - this.getItemProperty(id, "updateURL").substring(0, 6) == "https:") - return EM_L("true"); - return EM_L("false"); - }, - - /** - * Get the em:blocklisted property (whether or not this item is blocklisted) - */ - _rdfGet_blocklisted: function EMDS__rdfGet_blocklisted(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var version = this.getItemProperty(id, "version"); - if (!gBlocklist) - gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"]. - getService(Ci.nsIBlocklistService); - if (gBlocklist.getAddonBlocklistState(id, version) == Ci.nsIBlocklistService.STATE_BLOCKED) - return EM_L("true"); - - return EM_L("false"); - }, - - /** - * Get the em:blocklistedsoft property (whether or not this item is listed in the blocklist - * at a low severity) - */ - _rdfGet_blocklistedsoft: function EMDS__rdfGet_blocklistedsoft(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var version = this.getItemProperty(id, "version"); - if (!gBlocklist) - gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"]. - getService(Ci.nsIBlocklistService); - if (gBlocklist.getAddonBlocklistState(id, version) == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) - return EM_L("true"); - - return EM_L("false"); - }, - - /** - * Get the em:state property (represents the current phase of an install). - */ - _rdfGet_state: function EMDS__rdfGet_state(item, property) { - var id = item.Value; - if (id in this._progressData) - return EM_L(this._progressData[id].state); - return null; - }, - - /** - * Get the em:progress property from the _progressData js object. By storing - * progress which is updated repeastedly during a download we avoid - * repeastedly writing it to the rdf file. - */ - _rdfGet_progress: function EMDS__rdfGet_progress(item, property) { - var id = item.Value; - if (id in this._progressData) - return EM_I(this._progressData[id].progress); - return null; - }, - - /** - * Get the em:appManaged property. This prevents extensions from hiding - * extensions installed into locations other than the app-global location. - */ - _rdfGet_appManaged: function EMDS__rdfGet_appManaged(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var locationKey = this.getItemProperty(id, "installLocation"); - if (locationKey != KEY_APP_GLOBAL) - return EM_L("false"); - return null; - }, - - /** - * Get the em:locked property. This prevents extensions from locking - * extensions installed into locations other than restricted locations. - */ - _rdfGet_locked: function EMDS__rdfGet_locked(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var installLocation = InstallLocations.get(this.getInstallLocationKey(id)); - if (!installLocation || !installLocation.restricted) - return EM_L("false"); - return null; - }, - - /** - * Get the em:satisfiesDependencies property - literal string "false" for - * dependencies not satisfied (e.g. dependency disabled, incorrect version, - * not installed etc.), and literal string "true" for dependencies satisfied. - */ - _rdfGet_satisfiesDependencies: function EMDS__rdfGet_satisfiesDependencies(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - if (this.satisfiesDependencies(id)) - return EM_L("true"); - return EM_L("false"); - }, - - /** - * Get the em:opType property (controls widget state for the EM UI) - * from the Startup Cache (e.g. extensions.cache) - */ - _rdfGet_opType: function EMDS__rdfGet_opType(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var key = this.getItemProperty(id, "installLocation"); - if (key in StartupCache.entries && id in StartupCache.entries[key] && - StartupCache.entries[key][id] && StartupCache.entries[key][id].op != OP_NONE) - return EM_L(StartupCache.entries[key][id].op); - return null; - }, - - /** - * Gets a localizable property. Install Manifests are generally only in one - * language, however an item can customize by providing localized prefs in - * the form: - * - * extensions.{GUID}.[name|description|creator|homepageURL] - * - * to specify localized text for each of these properties. - */ - _getLocalizablePropertyValue: function EMDS__getLocalizablePropertyValue(item, property) { - // These are localizable properties that a language pack supplied by the - // Extension may override. - var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, - stripPrefix(item.Value, PREFIX_ITEM_URI)) + - stripPrefix(property.Value, PREFIX_NS_EM); - try { - var value = gPref.getComplexValue(prefName, - Ci.nsIPrefLocalizedString); - if (value.data) - return EM_L(value.data); - } - catch (e) { - } - - var localized = findClosestLocalizedResource(this._inner, item); - if (localized) { - var value = this._inner.GetTarget(localized, property, true); - return value ? value : EM_L(""); - } - return null; - }, - - /** - * Get the em:name property (name of the item) - */ - _rdfGet_name: function EMDS__rdfGet_name(item, property) { - return this._getLocalizablePropertyValue(item, property); - }, - - /** - * Get the em:description property (description of the item) - */ - _rdfGet_description: function EMDS__rdfGet_description(item, property) { - return this._getLocalizablePropertyValue(item, property); - }, - - /** - * Get the em:creator property (creator of the item) - */ - _rdfGet_creator: function EMDS__rdfGet_creator(item, property) { - return this._getLocalizablePropertyValue(item, property); - }, - - /** - * Get the em:homepageURL property (homepage URL of the item) - */ - _rdfGet_homepageURL: function EMDS__rdfGet_homepageURL(item, property) { - return this._getLocalizablePropertyValue(item, property); - }, - - _rdfGet_availableUpdateInfo: function EMDS__rdfGet_availableUpdateInfo(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var uri = stringData(this._inner.GetTarget(item, EM_R("availableUpdateInfo"), true)); - if (uri) { - uri = escapeAddonURI(this.getItemForID(id), null, null, uri, this); - return EM_L(uri); - } - return null; - }, - - /** - * Get the em:isDisabled property. This will be true if the item has a - * appDisabled or a userDisabled property that is true or OP_NEEDS_ENABLE. - */ - _rdfGet_isDisabled: function EMDS__rdfGet_isDisabled(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - if (this.getItemProperty(id, "userDisabled") == "true" || - this.getItemProperty(id, "appDisabled") == "true" || - this.getItemProperty(id, "userDisabled") == OP_NEEDS_ENABLE || - this.getItemProperty(id, "appDisabled") == OP_NEEDS_ENABLE) - return EM_L("true"); - return EM_L("false"); - }, - - _rdfGet_addonID: function EMDS__rdfGet_addonID(item, property) { - var id = this._inner.GetTarget(item, EM_R("downloadURL"), true) ? item.Value : - stripPrefix(item.Value, PREFIX_ITEM_URI); - return EM_L(id); - }, - - /** - * Get the em:updateable property - this specifies whether the item is - * allowed to be updated - */ - _rdfGet_updateable: function EMDS__rdfGet_updateable(item, property) { - var id = stripPrefix(item.Value, PREFIX_ITEM_URI); - var opType = this.getItemProperty(id, "opType"); - if (opType != OP_NONE || this.getItemProperty(id, "appManaged") == "true") - return EM_L("false"); - - if (getPref("getBoolPref", (PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, id), false)) == true) - return EM_L("false"); - - var installLocation = InstallLocations.get(this.getInstallLocationKey(id)); - if (!installLocation || !installLocation.canAccess) - return EM_L("false"); - - return EM_L("true"); - }, - - /** - * See nsIRDFDataSource.idl - */ - GetTarget: function EMDS_GetTarget(source, property, truthValue) { - if (!source) - return null; - - var target = null; - var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM); - if (getter in this) - target = this[getter](source, property); - - return target || this._inner.GetTarget(source, property, truthValue); - }, - - /** - * Gets an enumeration of values of a localizable property. Install Manifests - * are generally only in one language, however an item can customize by - * providing localized prefs in the form: - * - * extensions.{GUID}.[contributor].1 - * extensions.{GUID}.[contributor].2 - * extensions.{GUID}.[contributor].3 - * ... - * - * to specify localized text for each of these properties. - */ - _getLocalizablePropertyValues: function EMDS__getLocalizablePropertyValues(item, property) { - // These are localizable properties that a language pack supplied by the - // Extension may override. - var values = []; - var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, - stripPrefix(item.Value, PREFIX_ITEM_URI)) + - stripPrefix(property.Value, PREFIX_NS_EM); - var i = 0; - while (true) { - try { - var value = gPref.getComplexValue(prefName + "." + ++i, - Ci.nsIPrefLocalizedString); - if (value.data) - values.push(EM_L(value.data)); - } - catch (e) { - try { - var value = gPref.getComplexValue(prefName, - Ci.nsIPrefLocalizedString); - if (value.data) - values.push(EM_L(value.data)); - } - catch (e) { - } - break; - } - } - if (values.length > 0) - return values; - - var localized = findClosestLocalizedResource(this._inner, item); - if (localized) { - var targets = this._inner.GetTargets(localized, property, true); - while (targets.hasMoreElements()) - values.push(targets.getNext()); - return values; - } - return null; - }, - - /** - * Get the em:developer property (developers of the extension) - */ - _rdfGets_developer: function EMDS__rdfGets_developer(item, property) { - return this._getLocalizablePropertyValues(item, property); - }, - - /** - * Get the em:translator property (translators of the extension) - */ - _rdfGets_translator: function EMDS__rdfGets_translator(item, property) { - return this._getLocalizablePropertyValues(item, property); - }, - - /** - * Get the em:contributor property (contributors to the extension) - */ - _rdfGets_contributor: function EMDS__rdfGets_contributor(item, property) { - return this._getLocalizablePropertyValues(item, property); - }, - - /** - * See nsIRDFDataSource.idl - */ - GetTargets: function EMDS_GetTargets(source, property, truthValue) { - if (!source) - return null; - - var ary = null; - var propertyName = stripPrefix(property.Value, PREFIX_NS_EM); - var getter = "_rdfGets_" + propertyName; - if (getter in this) - ary = this[getter](source, property); - else { - // The template builder calls GetTargets when single value properties - // are used in a triple. - getter = "_rdfGet_" + propertyName; - if (getter in this) - ary = [ this[getter](source, property) ]; - } - - return ary ? new ArrayEnumerator(ary) - : this._inner.GetTargets(source, property, truthValue); - }, - - Assert: function EMDS_Assert(source, property, target, truthValue) { - this._inner.Assert(source, property, target, truthValue); - }, - - Unassert: function EMDS_Unassert(source, property, target) { - this._inner.Unassert(source, property, target); - }, - - Change: function EMDS_Change(source, property, oldTarget, newTarget) { - this._inner.Change(source, property, oldTarget, newTarget); - }, - - Move: function EMDS_Move(oldSource, newSource, property, target) { - this._inner.Move(oldSource, newSource, property, target); - }, - - HasAssertion: function EMDS_HasAssertion(source, property, target, truthValue) { - if (!source || !property || !target) - return false; - - var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM); - if (getter in this) - return this[getter](source, property) == target; - return this._inner.HasAssertion(source, property, target, truthValue); - }, - - _observers: [], - AddObserver: function EMDS_AddObserver(observer) { - for (var i = 0; i < this._observers.length; ++i) { - if (this._observers[i] == observer) - return; - } - this._observers.push(observer); - this._inner.AddObserver(observer); - }, - - RemoveObserver: function EMDS_RemoveObserver(observer) { - for (var i = 0; i < this._observers.length; ++i) { - if (this._observers[i] == observer) - this._observers.splice(i, 1); - } - this._inner.RemoveObserver(observer); - }, - - ArcLabelsIn: function EMDS_ArcLabelsIn(node) { - return this._inner.ArcLabelsIn(node); - }, - - ArcLabelsOut: function EMDS_ArcLabelsOut(source) { - return this._inner.ArcLabelsOut(source); - }, - - GetAllResources: function EMDS_GetAllResources() { - return this._inner.GetAllResources(); - }, - - IsCommandEnabled: function EMDS_IsCommandEnabled(sources, command, arguments) { - return this._inner.IsCommandEnabled(sources, command, arguments); - }, - - DoCommand: function EMDS_DoCommand(sources, command, arguments) { - this._inner.DoCommand(sources, command, arguments); - }, - - GetAllCmds: function EMDS_GetAllCmds(source) { - return this._inner.GetAllCmds(source); - }, - - hasArcIn: function EMDS_hasArcIn(node, arc) { - return this._inner.hasArcIn(node, arc); - }, - - hasArcOut: function EMDS_hasArcOut(source, arc) { - return this._inner.hasArcOut(source, arc); - }, - - beginUpdateBatch: function EMDS_beginUpdateBatch() { - return this._inner.beginUpdateBatch(); - }, - - endUpdateBatch: function EMDS_endUpdateBatch() { - return this._inner.endUpdateBatch(); - }, - - /** - * See nsIRDFRemoteDataSource.idl - */ - get loaded() { - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - }, - - Init: function EMDS_Init(uri) { - }, - - Refresh: function EMDS_Refresh(blocking) { - }, - - Flush: function EMDS_Flush() { - // For some operations we block repeated flushing until all operations - // are complete to reduce file accesses that can trigger bug 431065 - if (!gAllowFlush) { - gDSNeedsFlush = true; - return; - } - if (this._inner instanceof Ci.nsIRDFRemoteDataSource) - this._inner.Flush(); - }, - - FlushTo: function EMDS_FlushTo(uri) { - }, - - classDescription: "Extension Manager Data Source", - contractID: "@mozilla.org/rdf/datasource;1?name=extensions", - classID: Components.ID("{69BB8313-2D4F-45EC-97E0-D39DA58ECCE9}"), - _xpcom_factory: { - createInstance: function() Cc[ExtensionManager.prototype.contractID]. - getService(Ci.nsIExtensionManager).datasource - }, - QueryInterface: XPCOMUtils.generateQI([Ci.nsIRDFDataSource, - Ci.nsIRDFRemoteDataSource]) -}; - -function UpdateItem () {} -UpdateItem.prototype = { - /** - * See nsIUpdateService.idl - */ - init: function(id, version, installLocationKey, minAppVersion, maxAppVersion, - name, downloadURL, xpiHash, iconURL, updateURL, updateKey, type, - targetAppID) { - this._id = id; - this._version = version; - this._installLocationKey = installLocationKey; - this._minAppVersion = minAppVersion; - this._maxAppVersion = maxAppVersion; - this._name = name; - this._downloadURL = downloadURL; - this._xpiHash = xpiHash; - this._iconURL = iconURL; - this._updateURL = updateURL; - this._updateKey = updateKey; - this._type = type; - this._targetAppID = targetAppID; - }, - - /** - * See nsIUpdateService.idl - */ - get id() { return this._id; }, - get version() { return this._version; }, - get installLocationKey(){ return this._installLocationKey;}, - get minAppVersion() { return this._minAppVersion; }, - get maxAppVersion() { return this._maxAppVersion; }, - get name() { return this._name; }, - get xpiURL() { return this._downloadURL; }, - get xpiHash() { return this._xpiHash; }, - get iconURL() { return this._iconURL }, - get updateRDF() { return this._updateURL; }, - get updateKey() { return this._updateKey; }, - get type() { return this._type; }, - get targetAppID() { return this._targetAppID; }, - - /** - * See nsIUpdateService.idl - */ - get objectSource() { - return { id : this._id, - version : this._version, - installLocationKey : this._installLocationKey, - minAppVersion : this._minAppVersion, - maxAppVersion : this._maxAppVersion, - name : this._name, - xpiURL : this._downloadURL, - xpiHash : this._xpiHash, - iconURL : this._iconURL, - updateRDF : this._updateURL, - updateKey : this._updateKey, - type : this._type, - targetAppID : this._targetAppID - }.toSource(); - }, - - classDescription: "Update Item", - contractID: "@mozilla.org/updates/item;1", - classID: Components.ID("{F3294B1C-89F4-46F8-98A0-44E1EAE92518}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateItem]) -}; - -function NSGetModule(compMgr, fileSpec) - XPCOMUtils.generateModule([ExtensionManager, ExtensionsDataSource, UpdateItem]); - -#if 0 -/** - * Logs a message and stack trace to the console. - * @param string - * The string to write to the console. - */ -function STACK(string) { - dump("*** " + string + "\n"); - stackTrace(arguments.callee.caller.arguments, -1); -} - -function stackTraceFunctionFormat(aFunctionName) { - var classDelimiter = aFunctionName.indexOf("_"); - var className = aFunctionName.substr(0, classDelimiter); - if (!className) - className = ""; - var functionName = aFunctionName.substr(classDelimiter + 1, aFunctionName.length); - if (!functionName) - functionName = ""; - return className + "::" + functionName; -} - -function stackTraceArgumentsFormat(aArguments) { - arglist = ""; - for (var i = 0; i < aArguments.length; i++) { - arglist += aArguments[i]; - if (i < aArguments.length - 1) - arglist += ", "; - } - return arglist; -} - -function stackTrace(aArguments, aMaxCount) { - dump("=[STACKTRACE]=====================================================\n"); - dump("*** at: " + stackTraceFunctionFormat(aArguments.callee.name) + "(" + - stackTraceArgumentsFormat(aArguments) + ")\n"); - var temp = aArguments.callee.caller; - var count = 0; - while (temp) { - dump("*** " + stackTraceFunctionFormat(temp.name) + "(" + - stackTraceArgumentsFormat(temp.arguments) + ")\n"); - - temp = temp.arguments.callee.caller; - if (aMaxCount > 0 && ++count == aMaxCount) - break; - } - dump("==================================================================\n"); -} - -function dumpFile(file) { - dump("*** file = " + file.path + ", exists = " + file.exists() + "\n"); -} -#endif diff --git a/toolkit/mozapps/extensions/nsIExtensionManager.idl b/toolkit/mozapps/extensions/nsIExtensionManager.idl deleted file mode 100644 index a070e8f1125..00000000000 --- a/toolkit/mozapps/extensions/nsIExtensionManager.idl +++ /dev/null @@ -1,758 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is the Extension Manager. - * - * The Initial Developer of the Original Code is Ben Goodger. - * Portions created by the Initial Developer are Copyright (C) 2004 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Ben Goodger (Google Inc.) - * Robert Strong - * Alexander J. Vincent - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - - -#include "nsISupports.idl" - -interface nsIFile; -interface nsIRDFDataSource; -interface nsIUpdateItem; -interface nsIAddonInstallListener; -interface nsIAddonUpdateCheckListener; -interface nsISimpleEnumerator; -interface nsIDirectoryEnumerator; -interface nsIObserver; - -/** - * Interface representing a location where extensions, themes etc are - * installed. - */ -[scriptable, uuid(32a74707-ec7c-af19-f4d8-d0cd8cb6a948)] -interface nsIInstallLocation : nsISupports -{ - /** - * The string identifier of this Install Location - */ - readonly attribute AString name; - - /** - * An enumeration of nsIFiles for: - * - locations that contain items - * - potential dropped-in XPIs - * Note: This enumeration resolves Text Links to the directories they - * refer to. - */ - readonly attribute nsIDirectoryEnumerator itemLocations; - - /** - * The file system location where items live. Items can be dropped in - * at this location. Can be null for Install Locations that don't have - * a file system presence. - * Note: This is a clone of the actual location which the caller can - * modify freely. - */ - readonly attribute nsIFile location; - - /** - * Whether or not this Install Location is on an area of the file system - * that could be restricted on a restricted-access account, regardless - * of whether or not the location is restricted with the current user - * privileges. - */ - readonly attribute boolean restricted; - - /** - * Whether or not the user can write to the Install Location with the - * current access privileges. This is different from restricted because - * it's not whether or not the location *might* be restricted, it's - * whether or not it actually *is* restricted right now. - */ - readonly attribute boolean canAccess; - - /** - * Constants representing priority of some default Install Locations. - * XXXben - priority ranking of user-level items vs. global-level items - * here is debatable. Should app-system-global trump - * xre-system-user? - * You should not use the exact values here, you should offset from - * these values each time you create a new Install Location. Offsetting - * can be brittle but you should know what Install Locations are being - * defined for your own application. - */ - const unsigned long PRIORITY_APP_PROFILE = 0; - const unsigned long PRIORITY_APP_SYSTEM_USER = 10; - const unsigned long PRIORITY_XRE_SYSTEM_USER = 100; - const unsigned long PRIORITY_APP_SYSTEM_GLOBAL = 1000; - const unsigned long PRIORITY_XRE_SYSTEM_GLOBAL = 10000; - - /** - * The priority level of this Install Location in loading. - */ - readonly attribute long priority; - - /** - * Gets the directory that contains an item. - * @param id - * The GUID of the item. - * @returns The location of the item. - */ - nsIFile getItemLocation(in AString id); - - /** - * Retrieves the GUID for an item at the specified location. - * @param file - * The location where an item might live. - * @returns The ID for an item that might live at the location specified. - * - * N.B. This function makes no promises about whether or not this path is - * actually maintained by this Install Location. - */ - AString getIDForLocation(in nsIFile file); - - /** - * Gets a nsIFile object for a file within an item's directory structure. - * @param id - * The GUID of the item. - * @param path - * The path to the file beneath an Extension's directory - * @returns A file object at the requested location. The file does not - * necessarily have to exist. - */ - nsIFile getItemFile(in AString id, in AString path); - - /** - * Determines whether or not an item's resources are managed by the Extension - * System or by some other user or process. For example, items linked to by - * text links are managed by the user, and items linked to from Registry - * Install Locations are usually managed by other applications or installers. - * - * @param id - * The GUID of the item. - * @returns true if the item's resources are managed independently of the - * Extension System, false otherwise. - */ - boolean itemIsManagedIndependently(in AString id); - - /** - * Stages the specified file by copying it to some location from where it - * can be retrieved later to complete installation. - * - * @param file - * The file to stage - * @param id - * The GUID of the item the file represents - * @returns The staged file - */ - nsIFile stageFile(in nsIFile file, in AString id); - - /** - * Returns the most recently staged package (e.g. the last XPI or JAR in a - * directory) for an item and removes items that do not qualify. - * - * @param id - * The ID of the staged package - * @returns an nsIFile if the package exists otherwise null. - */ - nsIFile getStageFile(in AString id); - - /** - * Removes a file from the stage. This cleans up the stage if there is nothing - * else left after the remove operation. - * - * @param file - * The file to remove. - */ - void removeFile(in nsIFile file); -}; - -/** - * Interface representing a system for the installation and management of - * Extensions, Themes etc. - * - * XXXben - Some of this stuff should go into a management-ey interface, - * some into an app-startup-ey interface. - */ -[scriptable, uuid(494e8302-8689-4a8a-a08d-0da17c649c7d)] -interface nsIExtensionManager : nsISupports -{ - /** - * Constants representing types of update checks. - */ - const unsigned long UPDATE_CHECK_NEWVERSION = 0; - const unsigned long UPDATE_CHECK_COMPATIBILITY = 1; - const unsigned long UPDATE_SYNC_COMPATIBILITY = 2; - const unsigned long UPDATE_NOTIFY_NEWVERSION = 3; - - /** - * Constants that callers can use to indicate the reason for an add-on update - * check. Internal code uses other constants in nsExtensionManager.js.in. - * - * The update check is being performed at the request of the user. - */ - const unsigned long UPDATE_WHEN_USER_REQUESTED = 1; - /** - * The update check is being performed after a new version of the application - * has been detected. - */ - const unsigned long UPDATE_WHEN_NEW_APP_DETECTED = 2; - /** - * The update check is being performed after a new version of the application - * has been installed. - */ - const unsigned long UPDATE_WHEN_NEW_APP_INSTALLED = 3; - - /** - * Starts the Extension Manager, checking for item changes, additions and - * removals, and finishing pending operations. - * @returns true if the application has rewritten the extensions.ini file - * and needs to restart to register components/chrome etc, - * false otherwise - */ - boolean start(); - - /** - * Determines if there are incompatible items installed (and offers to - * upgrade them to newer versions if available, via a UI). - * @returns true if there were incompatible items that were disabled - * and the application needs to restart to re-register components, - * chrome etc, false otherwise. - */ - boolean checkForMismatches(); - - /** - * Gets the Install Location for an item - * @param id - * The GUID of the item - * @returns The Install Location where the item is installed or null if the - * location cannot be obtained (e.g. an invalid id). - */ - nsIInstallLocation getInstallLocation(in AString id); - - /** - * An enumeration of all registered Install Items - */ - readonly attribute nsISimpleEnumerator installLocations; - - /** - * Installs an item from a XPI/JAR file into the location specified. - * @param xpiFile - * The source file to install from. This function stages a copy - * of this file for persistence across potential application - * restarts, you are responsible for removing the file you pass - * in. - * @param installLocationKey - * The name identifier of an Install Location to install into. - */ - void installItemFromFile(in nsIFile xpiFile, in AString installLocationKey); - - /** - * Uninstalls an item - * @param id - * The GUID of the item. - */ - void uninstallItem(in AString id); - - /** - * Enables a disabled item - * @param id - * The GUID of the item. - */ - void enableItem(in AString id); - - /** - * Disables an enabled item - * @param id - * The GUID of the item. - */ - void disableItem(in AString id); - - /** - * Checks for updates to a list of items. - * @param items - * An array of nsIUpdateItems to check for updates for. - * @param itemCount - * The length of |items| - * @param updateCheckType - * UPDATE_CHECK_NEWVERSION if this check should find the newest - * versions available and write the results to the extensions - * datasource. - * UPDATE_CHECK_COMPATIBILITY if this check should only find newer - * target application compatibility information for the currently - * installed version and write the results to the extensions - * datasource. - * UPDATE_SYNC_COMPATIBILITY if this check should only find target - * application compatibility information for the currently - * installed version and synchronize the values to the extensions - * datasource. - * UPDATE_NOTIFY_NEWVERSION if this check should find the newest - * versions available without updating the extensions datasource. - * @param listener - * An nsIAddonUpdateCheckListener object which will be notified - * during the update check process. If not null, the object's - * onUpdateStarted() and onUpdateEnded() methods will be called even - * if there are no items to update. - * @param updateType (optional) - * The type of the update check. Should be one of the UPDATE_WHEN - * values. - * @param appVersion (optional) - * The version of the application to check for compatible updates. - * This param is only honored when updateCheckType is equal to - * UPDATE_NOTIFY_NEWVERSION and it defaults to the current version of - * the application when it is not specified. - * @param platformVersion (optional) - * The version of the toolkit to check for compatible updates. - * This param is only honored when updateCheckType is equal to - * UPDATE_NOTIFY_NEWVERSION and it defaults to the current version of - * the toolkit when it is not specified. - * - * @throws NS_ERROR_ILLEGAL_VALUE if any item is invalid. - */ - void update([array, size_is(itemCount)] in nsIUpdateItem items, - in unsigned long itemCount, - in unsigned long updateCheckType, - in nsIAddonUpdateCheckListener listener, - [optional] in unsigned long updateType, - [optional] in AString appVersion, - [optional] in AString platformVersion); - - /** - * Gets a nsIUpdateItem for the item with the specified id. - * @param id - * The GUID of the item to construct a nsIUpdateItem for. - * @returns The nsIUpdateItem representing the item or null if the item does - * not exist. - */ - nsIUpdateItem getItemForID(in AString id); - - /** - * Retrieves a list of visible nsIUpdateItems of items matching the - * specified type. - * @param type - * The type of item to return. - * @param countRef - * The XPCJS reference to the number of items returned. - * @returns An array of nsIUpdateItems matching the id/type filter. - * - * XXXben - it would be good if this function took an optional - * install location. - */ - void getItemList(in unsigned long type, - [optional] out unsigned long itemCount, - [retval, array, size_is(itemCount)] out nsIUpdateItem items); - - /** - * Retrieves a list of nsIUpdateItems of items that are incompatible - * with the supplied parameters. - * @param appVersion - * The version of the application to check compatibility against - * @param platformVersion - * The version of the toolkit to check compatibility against - * @param type - * The type of item to return - * @param includeDisabled - * true if disabled items should be included in the result set, - * false otherwise - * @param countRef - * The XPCJS reference to the number of items returned. - * @returns An array of incompatible nsIUpdateItems. - */ - void getIncompatibleItemList(in AString appVersion, - in AString platformVersion, - in unsigned long type, - in boolean includeDisabled, - [optional] out unsigned long itemCount, - [retval, array, size_is(itemCount)] out nsIUpdateItem items); - - /** - * The Extensions Datasource - * XXXben - the datasource should be registered with the RDF system, so it - * can be accessed via rdf:extensions, and not exposed through the API - * like this. - */ - readonly attribute nsIRDFDataSource datasource; - - /** - * Adds active download entries to the UI - * @param items - * A list of nsIUpdateItems to entries to add - * @param itemCount - * The length of |items| - * @param manager - * null when called from chrome - * the XPInstallManager when not called from chrome (e.g. web page) - * - * @throws NS_ERROR_ILLEGAL_VALUE if any item is invalid, or if itemCount == 0. - */ - void addDownloads([array, size_is(itemCount)] in nsIUpdateItem items, - in unsigned long itemCount, in nsIObserver manager); - - /** - * Removes an active download from the UI - * @param url - * The URL of the active download to remove - */ - void removeDownload(in AString url); - - /** - * Adds an install listener so the front end can listen to download - * and install progress. - * @param listener - * The listener to add - * @returns the index of the added listen in the listener list. - */ - long addInstallListener(in nsIAddonInstallListener listener); - - /** - * Removes an install progress listener. - * @param index - * The index of the listener to remove. - */ - void removeInstallListenerAt(in long index); - - /** - * Cancels a pending install or upgrade of an item. If the item does - * not have a pending install or upgrade then this will do nothing. - * @param id - * The ID of the item. - */ - void cancelInstallItem(in AString id); - - /** - * Cancels a pending uninstall of an item - * @param id - * The ID of the item. - */ - void cancelUninstallItem(in AString id); - - /** - * Retrieves a list of installed nsIUpdateItems of items that are dependent - * on another item. - * @param id - * The ID of the item that other items depend on. - * @param includeDisabled - * Whether to include disabled items in the set returned. - * @param countRef - * The XPCJS reference to the number of items returned. - * @returns An array of installed nsIUpdateItems that depend on the item - * specified by the id parameter. - */ - void getDependentItemListForID(in AString id, - in boolean includeDisabled, - [optional] out unsigned long itemCount, - [retval, array, size_is(itemCount)] out nsIUpdateItem items); - - /** - * Checks for changes to the blocklist using the local blocklist file. - * This will immediately application disable items that have been hard blocked - * and application enable items that are no longer hard blocked. It will also - * return items that are either soft or hard blocked and aren't already - * disabled or disabled pending a restart. - * - * This is likely to change or go away in the future and should not be used - * by anyone outside of the blocklist service. - * - * @returns An array of nsIUpdateItems that are blocklisted or the user should - * be warned about but are currently enabled. - */ - void updateAndGetNewBlocklistedItems([optional] out unsigned long itemCount, - [retval, array, size_is(itemCount)] out nsIUpdateItem items); -}; - -/** - * An item managed by the Extension System. Contains metadata that describes - * the item. - * XXXben work in progress, the name of this interface will change after the - * update system is complete, probably to nsIAddon - */ -[scriptable, uuid(f559f340-5160-420f-abc8-19b251708e7e)] -interface nsIUpdateItem : nsISupports -{ - /** - * The GUID of the item. - */ - readonly attribute AString id; - - /** - * The Version of the item, in FVF format. - */ - readonly attribute AString version; - - /** - * The minimum version of the application that this item works with, - * in FVF format. - */ - readonly attribute AString minAppVersion; - - /** - * The maximum version of the application that this item works with, - * in FVF format. - */ - readonly attribute AString maxAppVersion; - - /** - * The name of the Install Location where this item is installed. - */ - readonly attribute AString installLocationKey; - - /** - * The name of this item. - */ - readonly attribute AString name; - - /** - * The URL of the XPI where this item can be downloaded. - */ - readonly attribute AString xpiURL; - - /** - * The string Hash for the XPI file. Can be null and if supplied must be in - * the format of "type:hash" (see the types in nsICryptoHash and - * nsIXPInstallManager::initManagerWithHashes). - */ - readonly attribute AString xpiHash; - - /** - * The URL of the icon that can be shown for this item. - */ - readonly attribute AString iconURL; - - /** - * The URL of the update RDF file for this item. - */ - readonly attribute AString updateRDF; - - /** - * The public key to verify updates for this item. This must be the public - * part of the key that was used to sign update manifests for this add-on. - */ - readonly attribute AString updateKey; - - const unsigned long TYPE_APP = 0x01; - const unsigned long TYPE_EXTENSION = 0x02; - const unsigned long TYPE_THEME = 0x04; - const unsigned long TYPE_LOCALE = 0x08; - const unsigned long TYPE_MULTI_XPI = 0x20; - const unsigned long TYPE_ADDON = TYPE_EXTENSION + TYPE_THEME + TYPE_LOCALE; - const unsigned long TYPE_ANY = 0xff; - - /** - * The type of this item. - */ - readonly attribute long type; - - /** - * The target application ID used for checking compatibility for this item. - * - * @note Add-ons can specify a targetApplication id of toolkit@mozilla.org in - * their install manifest for compatibility with all apps using a - * specific release of the toolkit. - */ - readonly attribute AString targetAppID; - - /** - * Initializes this Item object. - */ - void init(in AString id, in AString version, - in AString installLocationKey, in AString minAppVersion, - in AString maxAppVersion, in AString name, - in AString downloadURL, in AString xpiHash, in AString iconURL, - in AString updateURL, in AString updateKey, in long type, - in AString targetAppID); - - /** - * Returns a JS Object source representing an nsIUpdateItem. - */ - readonly attribute AString objectSource; -}; - -/** - * Interface for handling download and install progress notifications for - * add-ons. - */ -[scriptable, uuid(2333b04c-f371-4dea-a2f1-d8fdb0c82866)] -interface nsIAddonInstallListener : nsISupports -{ - /** - * Called when an add-on download starts. This will be called for every add-on - * downloaded including those started by an add-on update. - * @param addon - * The add-on that is being downloaded - */ - void onDownloadStarted(in nsIUpdateItem addon); - - /** - * Progress occurred in the download operation. This will be called multiple - * times during the download of an add-on. - * @param addon - * The add-on that progress occurred for - * @param value - * The value of the current progress - * @param maxValue - * The maximum value |value| can reach - */ - void onDownloadProgress(in nsIUpdateItem addon, in unsigned long value, - in unsigned long maxValue); - - /** - * Called when an add-on download is complete. This will be called for every - * add-on downloaded. - * @param addon - * The add-on that finished downloading - */ - void onDownloadEnded(in nsIUpdateItem addon); - - /** - * Called when the extension manager starts to install an add-on either - * through a call to installItemFromFile or from a webpage triggered install. - * @param addon - * The add-on being installed - */ - void onInstallStarted(in nsIUpdateItem addon); - - /** - * Called when an add-on compatibility check starts. This will be called - * during an install if an add-on is incompatible according to its - * install.rdf. - * @param addon - * The add-on that the compatibility check will be for - */ - void onCompatibilityCheckStarted(in nsIUpdateItem addon); - - /** - * Called at the end of an add-on compatibility check. The status will be one - * of the results from nsIAddonUpdateCheckListener to indicate whether new - * information was found or not. - * @param addon - * The add-on that is having its compatibility checked - * @param status - * The status code from nsIAddonUpdateCheckListener for the update - * operation - */ - void onCompatibilityCheckEnded(in nsIUpdateItem addon, in long status); - - /** - * Called when an add-on install completes. The status will be 0 on success. - * any other value constitutes a failed install. - * @param addon - * The add-on that finished installing - * @param status - * The status of the installation from nsInstall.h - */ - void onInstallEnded(in nsIUpdateItem addon, in long status); - - /** - * Called when there are no more add-on downloads or installs in progress. - */ - void onInstallsCompleted(); -}; - -/** - * Interface for handling notifications during the addon update check process. - */ -[scriptable, uuid(c946119f-9e7c-41aa-a794-803148045350)] -interface nsIAddonUpdateCheckListener : nsISupports -{ - /** - * Addon update has begun - */ - void onUpdateStarted(); - - const unsigned long STATUS_NONE = 0; - const unsigned long STATUS_UPDATE = 1; - const unsigned long STATUS_VERSIONINFO = 2; - const unsigned long STATUS_DATA_FOUND = - STATUS_UPDATE + STATUS_VERSIONINFO; - const unsigned long STATUS_FAILURE = 4; - const unsigned long STATUS_NO_UPDATE = 8; - const unsigned long STATUS_DISABLED = 16; - const unsigned long STATUS_APP_MANAGED = 32; - const unsigned long STATUS_READ_ONLY = 64; - const unsigned long STATUS_PENDING_OP = 128; - const unsigned long STATUS_NOT_MANAGED = 256; - const unsigned long STATUS_DISALLOWED = - STATUS_DISABLED + STATUS_APP_MANAGED + STATUS_READ_ONLY + - STATUS_PENDING_OP + STATUS_NOT_MANAGED; - - /** - * Addon update has ended - */ - void onUpdateEnded(); - - /** - * Update for an individual addon has begun - * @param addon - * A nsIUpdateItem object representing the addon being updated - * - * @throws NS_ERROR_ILLEGAL_VALUE if the addon is invalid. - */ - void onAddonUpdateStarted(in nsIUpdateItem addon); - - /** - * Update for an individual addon has ended - * @param addon - * A nsIUpdateItem object representing the addon being updated - * @param status - * The success or failure code of the update operation - * - * @throws NS_ERROR_ILLEGAL_VALUE if the addon is invalid. - */ - void onAddonUpdateEnded(in nsIUpdateItem addon, in long status); -}; - -%{ C++ -/** - * Install Location Key for Application-Global Items - */ -#define NS_INSTALL_LOCATION_APPGLOBAL NS_LITERAL_STRING("app-global") - -/** - * Install Location Key for Application-Profile Items - */ -#define NS_INSTALL_LOCATION_APPPROFILE NS_LITERAL_STRING("app-profile") - -/** - * The category that contains a list of contract-ids to Install Location - * services. - */ -#define CATEGORY_INSTALL_LOCATIONS "extension-install-locations" - -/** - * The observer topic to listen to for actions performed on installed - * items. - */ -#define EM_ACTION_REQUESTED_TOPIC "em-action-requested" - -#define EM_ITEM_INSTALLED "item-installed" -#define EM_ITEM_UPGRADED "item-upgraded" -#define EM_ITEM_UNINSTALLED "item-uninstalled" -#define EM_ITEM_ENABLED "item-enabled" -#define EM_ITEM_DISABLED "item-disabled" -#define EM_ITEM_CANCEL "item-cancel-action" - -%}