toolkit/mozapps/extensions/internal/XPIProvider.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,7434 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +const Cc = Components.classes;
    1.11 +const Ci = Components.interfaces;
    1.12 +const Cr = Components.results;
    1.13 +const Cu = Components.utils;
    1.14 +
    1.15 +this.EXPORTED_SYMBOLS = ["XPIProvider"];
    1.16 +
    1.17 +Components.utils.import("resource://gre/modules/Services.jsm");
    1.18 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
    1.19 +Components.utils.import("resource://gre/modules/AddonManager.jsm");
    1.20 +
    1.21 +XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
    1.22 +                                  "resource://gre/modules/addons/AddonRepository.jsm");
    1.23 +XPCOMUtils.defineLazyModuleGetter(this, "ChromeManifestParser",
    1.24 +                                  "resource://gre/modules/ChromeManifestParser.jsm");
    1.25 +XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
    1.26 +                                  "resource://gre/modules/LightweightThemeManager.jsm");
    1.27 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    1.28 +                                  "resource://gre/modules/FileUtils.jsm");
    1.29 +XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
    1.30 +                                  "resource://gre/modules/ZipUtils.jsm");
    1.31 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    1.32 +                                  "resource://gre/modules/NetUtil.jsm");
    1.33 +XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
    1.34 +                                  "resource://gre/modules/PermissionsUtils.jsm");
    1.35 +XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    1.36 +                                  "resource://gre/modules/Promise.jsm");
    1.37 +XPCOMUtils.defineLazyModuleGetter(this, "Task",
    1.38 +                                  "resource://gre/modules/Task.jsm");
    1.39 +XPCOMUtils.defineLazyModuleGetter(this, "OS",
    1.40 +                                  "resource://gre/modules/osfile.jsm");
    1.41 +XPCOMUtils.defineLazyModuleGetter(this, "BrowserToolboxProcess",
    1.42 +                                  "resource:///modules/devtools/ToolboxProcess.jsm");
    1.43 +
    1.44 +XPCOMUtils.defineLazyServiceGetter(this,
    1.45 +                                   "ChromeRegistry",
    1.46 +                                   "@mozilla.org/chrome/chrome-registry;1",
    1.47 +                                   "nsIChromeRegistry");
    1.48 +XPCOMUtils.defineLazyServiceGetter(this,
    1.49 +                                   "ResProtocolHandler",
    1.50 +                                   "@mozilla.org/network/protocol;1?name=resource",
    1.51 +                                   "nsIResProtocolHandler");
    1.52 +
    1.53 +const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
    1.54 +                                       "initWithPath");
    1.55 +
    1.56 +const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
    1.57 +const PREF_INSTALL_CACHE              = "extensions.installCache";
    1.58 +const PREF_BOOTSTRAP_ADDONS           = "extensions.bootstrappedAddons";
    1.59 +const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
    1.60 +const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
    1.61 +const PREF_SELECTED_LOCALE            = "general.useragent.locale";
    1.62 +const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
    1.63 +const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
    1.64 +const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
    1.65 +const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
    1.66 +const PREF_EM_UPDATE_URL              = "extensions.update.url";
    1.67 +const PREF_EM_UPDATE_BACKGROUND_URL   = "extensions.update.background.url";
    1.68 +const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
    1.69 +const PREF_EM_EXTENSION_FORMAT        = "extensions.";
    1.70 +const PREF_EM_ENABLED_SCOPES          = "extensions.enabledScopes";
    1.71 +const PREF_EM_AUTO_DISABLED_SCOPES    = "extensions.autoDisableScopes";
    1.72 +const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
    1.73 +const PREF_XPI_ENABLED                = "xpinstall.enabled";
    1.74 +const PREF_XPI_WHITELIST_REQUIRED     = "xpinstall.whitelist.required";
    1.75 +const PREF_XPI_DIRECT_WHITELISTED     = "xpinstall.whitelist.directRequest";
    1.76 +const PREF_XPI_FILE_WHITELISTED       = "xpinstall.whitelist.fileRequest";
    1.77 +const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
    1.78 +const PREF_XPI_UNPACK                 = "extensions.alwaysUnpack";
    1.79 +const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
    1.80 +const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
    1.81 +const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
    1.82 +const PREF_SHOWN_SELECTION_UI         = "extensions.shownSelectionUI";
    1.83 +
    1.84 +const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
    1.85 +const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
    1.86 +
    1.87 +const PREF_CHECKCOMAT_THEMEOVERRIDE   = "extensions.checkCompatibility.temporaryThemeOverride_minAppVersion";
    1.88 +
    1.89 +const URI_EXTENSION_SELECT_DIALOG     = "chrome://mozapps/content/extensions/selectAddons.xul";
    1.90 +const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
    1.91 +const URI_EXTENSION_STRINGS           = "chrome://mozapps/locale/extensions/extensions.properties";
    1.92 +
    1.93 +const STRING_TYPE_NAME                = "type.%ID%.name";
    1.94 +
    1.95 +const DIR_EXTENSIONS                  = "extensions";
    1.96 +const DIR_STAGE                       = "staged";
    1.97 +const DIR_XPI_STAGE                   = "staged-xpis";
    1.98 +const DIR_TRASH                       = "trash";
    1.99 +
   1.100 +const FILE_DATABASE                   = "extensions.json";
   1.101 +const FILE_OLD_CACHE                  = "extensions.cache";
   1.102 +const FILE_INSTALL_MANIFEST           = "install.rdf";
   1.103 +const FILE_XPI_ADDONS_LIST            = "extensions.ini";
   1.104 +
   1.105 +const KEY_PROFILEDIR                  = "ProfD";
   1.106 +const KEY_APPDIR                      = "XCurProcD";
   1.107 +const KEY_TEMPDIR                     = "TmpD";
   1.108 +const KEY_APP_DISTRIBUTION            = "XREAppDist";
   1.109 +
   1.110 +const KEY_APP_PROFILE                 = "app-profile";
   1.111 +const KEY_APP_GLOBAL                  = "app-global";
   1.112 +const KEY_APP_SYSTEM_LOCAL            = "app-system-local";
   1.113 +const KEY_APP_SYSTEM_SHARE            = "app-system-share";
   1.114 +const KEY_APP_SYSTEM_USER             = "app-system-user";
   1.115 +
   1.116 +const NOTIFICATION_FLUSH_PERMISSIONS  = "flush-pending-permissions";
   1.117 +const XPI_PERMISSION                  = "install";
   1.118 +
   1.119 +const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
   1.120 +const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
   1.121 +
   1.122 +const TOOLKIT_ID                      = "toolkit@mozilla.org";
   1.123 +
   1.124 +// The value for this is in Makefile.in
   1.125 +#expand const DB_SCHEMA                       = __MOZ_EXTENSIONS_DB_SCHEMA__;
   1.126 +
   1.127 +// Properties that exist in the install manifest
   1.128 +const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
   1.129 +                            "updateKey", "optionsURL", "optionsType", "aboutURL",
   1.130 +                            "iconURL", "icon64URL"];
   1.131 +const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
   1.132 +const PROP_LOCALE_MULTI  = ["developers", "translators", "contributors"];
   1.133 +const PROP_TARGETAPP     = ["id", "minVersion", "maxVersion"];
   1.134 +
   1.135 +// Properties that should be migrated where possible from an old database. These
   1.136 +// shouldn't include properties that can be read directly from install.rdf files
   1.137 +// or calculated
   1.138 +const DB_MIGRATE_METADATA= ["installDate", "userDisabled", "softDisabled",
   1.139 +                            "sourceURI", "applyBackgroundUpdates",
   1.140 +                            "releaseNotesURI", "foreignInstall", "syncGUID"];
   1.141 +// Properties to cache and reload when an addon installation is pending
   1.142 +const PENDING_INSTALL_METADATA =
   1.143 +    ["syncGUID", "targetApplications", "userDisabled", "softDisabled",
   1.144 +     "existingAddonID", "sourceURI", "releaseNotesURI", "installDate",
   1.145 +     "updateDate", "applyBackgroundUpdates", "compatibilityOverrides"];
   1.146 +
   1.147 +// Note: When adding/changing/removing items here, remember to change the
   1.148 +// DB schema version to ensure changes are picked up ASAP.
   1.149 +const STATIC_BLOCKLIST_PATTERNS = [
   1.150 +  { creator: "Mozilla Corp.",
   1.151 +    level: Ci.nsIBlocklistService.STATE_BLOCKED,
   1.152 +    blockID: "i162" },
   1.153 +  { creator: "Mozilla.org",
   1.154 +    level: Ci.nsIBlocklistService.STATE_BLOCKED,
   1.155 +    blockID: "i162" }
   1.156 +];
   1.157 +
   1.158 +
   1.159 +const BOOTSTRAP_REASONS = {
   1.160 +  APP_STARTUP     : 1,
   1.161 +  APP_SHUTDOWN    : 2,
   1.162 +  ADDON_ENABLE    : 3,
   1.163 +  ADDON_DISABLE   : 4,
   1.164 +  ADDON_INSTALL   : 5,
   1.165 +  ADDON_UNINSTALL : 6,
   1.166 +  ADDON_UPGRADE   : 7,
   1.167 +  ADDON_DOWNGRADE : 8
   1.168 +};
   1.169 +
   1.170 +// Map new string type identifiers to old style nsIUpdateItem types
   1.171 +const TYPES = {
   1.172 +  extension: 2,
   1.173 +  theme: 4,
   1.174 +  locale: 8,
   1.175 +  multipackage: 32,
   1.176 +  dictionary: 64,
   1.177 +  experiment: 128,
   1.178 +};
   1.179 +
   1.180 +const RESTARTLESS_TYPES = new Set([
   1.181 +  "dictionary",
   1.182 +  "experiment",
   1.183 +  "locale",
   1.184 +]);
   1.185 +
   1.186 +// Keep track of where we are in startup for telemetry
   1.187 +// event happened during XPIDatabase.startup()
   1.188 +const XPI_STARTING = "XPIStarting";
   1.189 +// event happened after startup() but before the final-ui-startup event
   1.190 +const XPI_BEFORE_UI_STARTUP = "BeforeFinalUIStartup";
   1.191 +// event happened after final-ui-startup
   1.192 +const XPI_AFTER_UI_STARTUP = "AfterFinalUIStartup";
   1.193 +
   1.194 +const COMPATIBLE_BY_DEFAULT_TYPES = {
   1.195 +  extension: true,
   1.196 +  dictionary: true
   1.197 +};
   1.198 +
   1.199 +const MSG_JAR_FLUSH = "AddonJarFlush";
   1.200 +
   1.201 +var gGlobalScope = this;
   1.202 +
   1.203 +/**
   1.204 + * Valid IDs fit this pattern.
   1.205 + */
   1.206 +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;
   1.207 +
   1.208 +Cu.import("resource://gre/modules/Log.jsm");
   1.209 +const LOGGER_ID = "addons.xpi";
   1.210 +
   1.211 +// Create a new logger for use by all objects in this Addons XPI Provider module
   1.212 +// (Requires AddonManager.jsm)
   1.213 +let logger = Log.repository.getLogger(LOGGER_ID);
   1.214 +
   1.215 +const LAZY_OBJECTS = ["XPIDatabase"];
   1.216 +
   1.217 +var gLazyObjectsLoaded = false;
   1.218 +
   1.219 +function loadLazyObjects() {
   1.220 +  let scope = {};
   1.221 +  scope.AddonInternal = AddonInternal;
   1.222 +  scope.XPIProvider = XPIProvider;
   1.223 +  Services.scriptloader.loadSubScript("resource://gre/modules/addons/XPIProviderUtils.js",
   1.224 +                                      scope);
   1.225 +
   1.226 +  for (let name of LAZY_OBJECTS) {
   1.227 +    delete gGlobalScope[name];
   1.228 +    gGlobalScope[name] = scope[name];
   1.229 +  }
   1.230 +  gLazyObjectsLoaded = true;
   1.231 +  return scope;
   1.232 +}
   1.233 +
   1.234 +for (let name of LAZY_OBJECTS) {
   1.235 +  Object.defineProperty(gGlobalScope, name, {
   1.236 +    get: function lazyObjectGetter() {
   1.237 +      let objs = loadLazyObjects();
   1.238 +      return objs[name];
   1.239 +    },
   1.240 +    configurable: true
   1.241 +  });
   1.242 +}
   1.243 +
   1.244 +
   1.245 +function findMatchingStaticBlocklistItem(aAddon) {
   1.246 +  for (let item of STATIC_BLOCKLIST_PATTERNS) {
   1.247 +    if ("creator" in item && typeof item.creator == "string") {
   1.248 +      if ((aAddon.defaultLocale && aAddon.defaultLocale.creator == item.creator) ||
   1.249 +          (aAddon.selectedLocale && aAddon.selectedLocale.creator == item.creator)) {
   1.250 +        return item;
   1.251 +      }
   1.252 +    }
   1.253 +  }
   1.254 +  return null;
   1.255 +}
   1.256 +
   1.257 +
   1.258 +/**
   1.259 + * Sets permissions on a file
   1.260 + *
   1.261 + * @param  aFile
   1.262 + *         The file or directory to operate on.
   1.263 + * @param  aPermissions
   1.264 + *         The permisions to set
   1.265 + */
   1.266 +function setFilePermissions(aFile, aPermissions) {
   1.267 +  try {
   1.268 +    aFile.permissions = aPermissions;
   1.269 +  }
   1.270 +  catch (e) {
   1.271 +    logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
   1.272 +         aFile.path, e);
   1.273 +  }
   1.274 +}
   1.275 +
   1.276 +/**
   1.277 + * A safe way to install a file or the contents of a directory to a new
   1.278 + * directory. The file or directory is moved or copied recursively and if
   1.279 + * anything fails an attempt is made to rollback the entire operation. The
   1.280 + * operation may also be rolled back to its original state after it has
   1.281 + * completed by calling the rollback method.
   1.282 + *
   1.283 + * Operations can be chained. Calling move or copy multiple times will remember
   1.284 + * the whole set and if one fails all of the operations will be rolled back.
   1.285 + */
   1.286 +function SafeInstallOperation() {
   1.287 +  this._installedFiles = [];
   1.288 +  this._createdDirs = [];
   1.289 +}
   1.290 +
   1.291 +SafeInstallOperation.prototype = {
   1.292 +  _installedFiles: null,
   1.293 +  _createdDirs: null,
   1.294 +
   1.295 +  _installFile: function SIO_installFile(aFile, aTargetDirectory, aCopy) {
   1.296 +    let oldFile = aCopy ? null : aFile.clone();
   1.297 +    let newFile = aFile.clone();
   1.298 +    try {
   1.299 +      if (aCopy)
   1.300 +        newFile.copyTo(aTargetDirectory, null);
   1.301 +      else
   1.302 +        newFile.moveTo(aTargetDirectory, null);
   1.303 +    }
   1.304 +    catch (e) {
   1.305 +      logger.error("Failed to " + (aCopy ? "copy" : "move") + " file " + aFile.path +
   1.306 +            " to " + aTargetDirectory.path, e);
   1.307 +      throw e;
   1.308 +    }
   1.309 +    this._installedFiles.push({ oldFile: oldFile, newFile: newFile });
   1.310 +  },
   1.311 +
   1.312 +  _installDirectory: function SIO_installDirectory(aDirectory, aTargetDirectory, aCopy) {
   1.313 +    let newDir = aTargetDirectory.clone();
   1.314 +    newDir.append(aDirectory.leafName);
   1.315 +    try {
   1.316 +      newDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   1.317 +    }
   1.318 +    catch (e) {
   1.319 +      logger.error("Failed to create directory " + newDir.path, e);
   1.320 +      throw e;
   1.321 +    }
   1.322 +    this._createdDirs.push(newDir);
   1.323 +
   1.324 +    // Use a snapshot of the directory contents to avoid possible issues with
   1.325 +    // iterating over a directory while removing files from it (the YAFFS2
   1.326 +    // embedded filesystem has this issue, see bug 772238), and to remove
   1.327 +    // normal files before their resource forks on OSX (see bug 733436).
   1.328 +    let entries = getDirectoryEntries(aDirectory, true);
   1.329 +    entries.forEach(function(aEntry) {
   1.330 +      try {
   1.331 +        this._installDirEntry(aEntry, newDir, aCopy);
   1.332 +      }
   1.333 +      catch (e) {
   1.334 +        logger.error("Failed to " + (aCopy ? "copy" : "move") + " entry " +
   1.335 +              aEntry.path, e);
   1.336 +        throw e;
   1.337 +      }
   1.338 +    }, this);
   1.339 +
   1.340 +    // If this is only a copy operation then there is nothing else to do
   1.341 +    if (aCopy)
   1.342 +      return;
   1.343 +
   1.344 +    // The directory should be empty by this point. If it isn't this will throw
   1.345 +    // and all of the operations will be rolled back
   1.346 +    try {
   1.347 +      setFilePermissions(aDirectory, FileUtils.PERMS_DIRECTORY);
   1.348 +      aDirectory.remove(false);
   1.349 +    }
   1.350 +    catch (e) {
   1.351 +      logger.error("Failed to remove directory " + aDirectory.path, e);
   1.352 +      throw e;
   1.353 +    }
   1.354 +
   1.355 +    // Note we put the directory move in after all the file moves so the
   1.356 +    // directory is recreated before all the files are moved back
   1.357 +    this._installedFiles.push({ oldFile: aDirectory, newFile: newDir });
   1.358 +  },
   1.359 +
   1.360 +  _installDirEntry: function SIO_installDirEntry(aDirEntry, aTargetDirectory, aCopy) {
   1.361 +    let isDir = null;
   1.362 +
   1.363 +    try {
   1.364 +      isDir = aDirEntry.isDirectory();
   1.365 +    }
   1.366 +    catch (e) {
   1.367 +      // If the file has already gone away then don't worry about it, this can
   1.368 +      // happen on OSX where the resource fork is automatically moved with the
   1.369 +      // data fork for the file. See bug 733436.
   1.370 +      if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
   1.371 +        return;
   1.372 +
   1.373 +      logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
   1.374 +            " to " + aTargetDirectory.path);
   1.375 +      throw e;
   1.376 +    }
   1.377 +
   1.378 +    try {
   1.379 +      if (isDir)
   1.380 +        this._installDirectory(aDirEntry, aTargetDirectory, aCopy);
   1.381 +      else
   1.382 +        this._installFile(aDirEntry, aTargetDirectory, aCopy);
   1.383 +    }
   1.384 +    catch (e) {
   1.385 +      logger.error("Failure " + (aCopy ? "copying" : "moving") + " " + aDirEntry.path +
   1.386 +            " to " + aTargetDirectory.path);
   1.387 +      throw e;
   1.388 +    }
   1.389 +  },
   1.390 +
   1.391 +  /**
   1.392 +   * Moves a file or directory into a new directory. If an error occurs then all
   1.393 +   * files that have been moved will be moved back to their original location.
   1.394 +   *
   1.395 +   * @param  aFile
   1.396 +   *         The file or directory to be moved.
   1.397 +   * @param  aTargetDirectory
   1.398 +   *         The directory to move into, this is expected to be an empty
   1.399 +   *         directory.
   1.400 +   */
   1.401 +  move: function SIO_move(aFile, aTargetDirectory) {
   1.402 +    try {
   1.403 +      this._installDirEntry(aFile, aTargetDirectory, false);
   1.404 +    }
   1.405 +    catch (e) {
   1.406 +      this.rollback();
   1.407 +      throw e;
   1.408 +    }
   1.409 +  },
   1.410 +
   1.411 +  /**
   1.412 +   * Copies a file or directory into a new directory. If an error occurs then
   1.413 +   * all new files that have been created will be removed.
   1.414 +   *
   1.415 +   * @param  aFile
   1.416 +   *         The file or directory to be copied.
   1.417 +   * @param  aTargetDirectory
   1.418 +   *         The directory to copy into, this is expected to be an empty
   1.419 +   *         directory.
   1.420 +   */
   1.421 +  copy: function SIO_copy(aFile, aTargetDirectory) {
   1.422 +    try {
   1.423 +      this._installDirEntry(aFile, aTargetDirectory, true);
   1.424 +    }
   1.425 +    catch (e) {
   1.426 +      this.rollback();
   1.427 +      throw e;
   1.428 +    }
   1.429 +  },
   1.430 +
   1.431 +  /**
   1.432 +   * Rolls back all the moves that this operation performed. If an exception
   1.433 +   * occurs here then both old and new directories are left in an indeterminate
   1.434 +   * state
   1.435 +   */
   1.436 +  rollback: function SIO_rollback() {
   1.437 +    while (this._installedFiles.length > 0) {
   1.438 +      let move = this._installedFiles.pop();
   1.439 +      if (move.newFile.isDirectory()) {
   1.440 +        let oldDir = move.oldFile.parent.clone();
   1.441 +        oldDir.append(move.oldFile.leafName);
   1.442 +        oldDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   1.443 +      }
   1.444 +      else if (!move.oldFile) {
   1.445 +        // No old file means this was a copied file
   1.446 +        move.newFile.remove(true);
   1.447 +      }
   1.448 +      else {
   1.449 +        move.newFile.moveTo(move.oldFile.parent, null);
   1.450 +      }
   1.451 +    }
   1.452 +
   1.453 +    while (this._createdDirs.length > 0)
   1.454 +      recursiveRemove(this._createdDirs.pop());
   1.455 +  }
   1.456 +};
   1.457 +
   1.458 +/**
   1.459 + * Gets the currently selected locale for display.
   1.460 + * @return  the selected locale or "en-US" if none is selected
   1.461 + */
   1.462 +function getLocale() {
   1.463 +  if (Prefs.getBoolPref(PREF_MATCH_OS_LOCALE, false))
   1.464 +    return Services.locale.getLocaleComponentForUserAgent();
   1.465 +  let locale = Prefs.getComplexValue(PREF_SELECTED_LOCALE, Ci.nsIPrefLocalizedString);
   1.466 +  if (locale)
   1.467 +    return locale;
   1.468 +  return Prefs.getCharPref(PREF_SELECTED_LOCALE, "en-US");
   1.469 +}
   1.470 +
   1.471 +/**
   1.472 + * Selects the closest matching locale from a list of locales.
   1.473 + *
   1.474 + * @param  aLocales
   1.475 + *         An array of locales
   1.476 + * @return the best match for the currently selected locale
   1.477 + */
   1.478 +function findClosestLocale(aLocales) {
   1.479 +  let appLocale = getLocale();
   1.480 +
   1.481 +  // Holds the best matching localized resource
   1.482 +  var bestmatch = null;
   1.483 +  // The number of locale parts it matched with
   1.484 +  var bestmatchcount = 0;
   1.485 +  // The number of locale parts in the match
   1.486 +  var bestpartcount = 0;
   1.487 +
   1.488 +  var matchLocales = [appLocale.toLowerCase()];
   1.489 +  /* If the current locale is English then it will find a match if there is
   1.490 +     a valid match for en-US so no point searching that locale too. */
   1.491 +  if (matchLocales[0].substring(0, 3) != "en-")
   1.492 +    matchLocales.push("en-us");
   1.493 +
   1.494 +  for each (var locale in matchLocales) {
   1.495 +    var lparts = locale.split("-");
   1.496 +    for each (var localized in aLocales) {
   1.497 +      for each (let found in localized.locales) {
   1.498 +        found = found.toLowerCase();
   1.499 +        // Exact match is returned immediately
   1.500 +        if (locale == found)
   1.501 +          return localized;
   1.502 +
   1.503 +        var fparts = found.split("-");
   1.504 +        /* If we have found a possible match and this one isn't any longer
   1.505 +           then we dont need to check further. */
   1.506 +        if (bestmatch && fparts.length < bestmatchcount)
   1.507 +          continue;
   1.508 +
   1.509 +        // Count the number of parts that match
   1.510 +        var maxmatchcount = Math.min(fparts.length, lparts.length);
   1.511 +        var matchcount = 0;
   1.512 +        while (matchcount < maxmatchcount &&
   1.513 +               fparts[matchcount] == lparts[matchcount])
   1.514 +          matchcount++;
   1.515 +
   1.516 +        /* If we matched more than the last best match or matched the same and
   1.517 +           this locale is less specific than the last best match. */
   1.518 +        if (matchcount > bestmatchcount ||
   1.519 +           (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
   1.520 +          bestmatch = localized;
   1.521 +          bestmatchcount = matchcount;
   1.522 +          bestpartcount = fparts.length;
   1.523 +        }
   1.524 +      }
   1.525 +    }
   1.526 +    // If we found a valid match for this locale return it
   1.527 +    if (bestmatch)
   1.528 +      return bestmatch;
   1.529 +  }
   1.530 +  return null;
   1.531 +}
   1.532 +
   1.533 +/**
   1.534 + * Sets the userDisabled and softDisabled properties of an add-on based on what
   1.535 + * values those properties had for a previous instance of the add-on. The
   1.536 + * previous instance may be a previous install or in the case of an application
   1.537 + * version change the same add-on.
   1.538 + *
   1.539 + * NOTE: this may modify aNewAddon in place; callers should save the database if
   1.540 + * necessary
   1.541 + *
   1.542 + * @param  aOldAddon
   1.543 + *         The previous instance of the add-on
   1.544 + * @param  aNewAddon
   1.545 + *         The new instance of the add-on
   1.546 + * @param  aAppVersion
   1.547 + *         The optional application version to use when checking the blocklist
   1.548 + *         or undefined to use the current application
   1.549 + * @param  aPlatformVersion
   1.550 + *         The optional platform version to use when checking the blocklist or
   1.551 + *         undefined to use the current platform
   1.552 + */
   1.553 +function applyBlocklistChanges(aOldAddon, aNewAddon, aOldAppVersion,
   1.554 +                               aOldPlatformVersion) {
   1.555 +  // Copy the properties by default
   1.556 +  aNewAddon.userDisabled = aOldAddon.userDisabled;
   1.557 +  aNewAddon.softDisabled = aOldAddon.softDisabled;
   1.558 +
   1.559 +  let bs = Cc["@mozilla.org/extensions/blocklist;1"].
   1.560 +           getService(Ci.nsIBlocklistService);
   1.561 +
   1.562 +  let oldBlocklistState = bs.getAddonBlocklistState(createWrapper(aOldAddon),
   1.563 +                                                    aOldAppVersion,
   1.564 +                                                    aOldPlatformVersion);
   1.565 +  let newBlocklistState = bs.getAddonBlocklistState(createWrapper(aNewAddon));
   1.566 +
   1.567 +  // If the blocklist state hasn't changed then the properties don't need to
   1.568 +  // change
   1.569 +  if (newBlocklistState == oldBlocklistState)
   1.570 +    return;
   1.571 +
   1.572 +  if (newBlocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
   1.573 +    if (aNewAddon.type != "theme") {
   1.574 +      // The add-on has become softblocked, set softDisabled if it isn't already
   1.575 +      // userDisabled
   1.576 +      aNewAddon.softDisabled = !aNewAddon.userDisabled;
   1.577 +    }
   1.578 +    else {
   1.579 +      // Themes just get userDisabled to switch back to the default theme
   1.580 +      aNewAddon.userDisabled = true;
   1.581 +    }
   1.582 +  }
   1.583 +  else {
   1.584 +    // If the new add-on is not softblocked then it cannot be softDisabled
   1.585 +    aNewAddon.softDisabled = false;
   1.586 +  }
   1.587 +}
   1.588 +
   1.589 +/**
   1.590 + * Calculates whether an add-on should be appDisabled or not.
   1.591 + *
   1.592 + * @param  aAddon
   1.593 + *         The add-on to check
   1.594 + * @return true if the add-on should not be appDisabled
   1.595 + */
   1.596 +function isUsableAddon(aAddon) {
   1.597 +  // Hack to ensure the default theme is always usable
   1.598 +  if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin)
   1.599 +    return true;
   1.600 +
   1.601 +  if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
   1.602 +    return false;
   1.603 +
   1.604 +  if (AddonManager.checkUpdateSecurity && !aAddon.providesUpdatesSecurely)
   1.605 +    return false;
   1.606 +
   1.607 +  if (!aAddon.isPlatformCompatible)
   1.608 +    return false;
   1.609 +
   1.610 +  if (AddonManager.checkCompatibility) {
   1.611 +    if (!aAddon.isCompatible)
   1.612 +      return false;
   1.613 +  }
   1.614 +  else {
   1.615 +    let app = aAddon.matchingTargetApplication;
   1.616 +    if (!app)
   1.617 +      return false;
   1.618 +
   1.619 +    // XXX Temporary solution to let applications opt-in to make themes safer
   1.620 +    //     following significant UI changes even if checkCompatibility=false has
   1.621 +    //     been set, until we get bug 962001.
   1.622 +    if (aAddon.type == "theme" && app.id == Services.appinfo.ID) {
   1.623 +      try {
   1.624 +        let minCompatVersion = Services.prefs.getCharPref(PREF_CHECKCOMAT_THEMEOVERRIDE);
   1.625 +        if (minCompatVersion &&
   1.626 +            Services.vc.compare(minCompatVersion, app.maxVersion) > 0) {
   1.627 +          return false;
   1.628 +        }
   1.629 +      } catch (e) {}
   1.630 +    }
   1.631 +  }
   1.632 +
   1.633 +  return true;
   1.634 +}
   1.635 +
   1.636 +function isAddonDisabled(aAddon) {
   1.637 +  return aAddon.appDisabled || aAddon.softDisabled || aAddon.userDisabled;
   1.638 +}
   1.639 +
   1.640 +XPCOMUtils.defineLazyServiceGetter(this, "gRDF", "@mozilla.org/rdf/rdf-service;1",
   1.641 +                                   Ci.nsIRDFService);
   1.642 +
   1.643 +function EM_R(aProperty) {
   1.644 +  return gRDF.GetResource(PREFIX_NS_EM + aProperty);
   1.645 +}
   1.646 +
   1.647 +/**
   1.648 + * Converts an RDF literal, resource or integer into a string.
   1.649 + *
   1.650 + * @param  aLiteral
   1.651 + *         The RDF object to convert
   1.652 + * @return a string if the object could be converted or null
   1.653 + */
   1.654 +function getRDFValue(aLiteral) {
   1.655 +  if (aLiteral instanceof Ci.nsIRDFLiteral)
   1.656 +    return aLiteral.Value;
   1.657 +  if (aLiteral instanceof Ci.nsIRDFResource)
   1.658 +    return aLiteral.Value;
   1.659 +  if (aLiteral instanceof Ci.nsIRDFInt)
   1.660 +    return aLiteral.Value;
   1.661 +  return null;
   1.662 +}
   1.663 +
   1.664 +/**
   1.665 + * Gets an RDF property as a string
   1.666 + *
   1.667 + * @param  aDs
   1.668 + *         The RDF datasource to read the property from
   1.669 + * @param  aResource
   1.670 + *         The RDF resource to read the property from
   1.671 + * @param  aProperty
   1.672 + *         The property to read
   1.673 + * @return a string if the property existed or null
   1.674 + */
   1.675 +function getRDFProperty(aDs, aResource, aProperty) {
   1.676 +  return getRDFValue(aDs.GetTarget(aResource, EM_R(aProperty), true));
   1.677 +}
   1.678 +
   1.679 +/**
   1.680 + * Reads an AddonInternal object from an RDF stream.
   1.681 + *
   1.682 + * @param  aUri
   1.683 + *         The URI that the manifest is being read from
   1.684 + * @param  aStream
   1.685 + *         An open stream to read the RDF from
   1.686 + * @return an AddonInternal object
   1.687 + * @throws if the install manifest in the RDF stream is corrupt or could not
   1.688 + *         be read
   1.689 + */
   1.690 +function loadManifestFromRDF(aUri, aStream) {
   1.691 +  function getPropertyArray(aDs, aSource, aProperty) {
   1.692 +    let values = [];
   1.693 +    let targets = aDs.GetTargets(aSource, EM_R(aProperty), true);
   1.694 +    while (targets.hasMoreElements())
   1.695 +      values.push(getRDFValue(targets.getNext()));
   1.696 +
   1.697 +    return values;
   1.698 +  }
   1.699 +
   1.700 +  /**
   1.701 +   * Reads locale properties from either the main install manifest root or
   1.702 +   * an em:localized section in the install manifest.
   1.703 +   *
   1.704 +   * @param  aDs
   1.705 +   *         The nsIRDFDatasource to read from
   1.706 +   * @param  aSource
   1.707 +   *         The nsIRDFResource to read the properties from
   1.708 +   * @param  isDefault
   1.709 +   *         True if the locale is to be read from the main install manifest
   1.710 +   *         root
   1.711 +   * @param  aSeenLocales
   1.712 +   *         An array of locale names already seen for this install manifest.
   1.713 +   *         Any locale names seen as a part of this function will be added to
   1.714 +   *         this array
   1.715 +   * @return an object containing the locale properties
   1.716 +   */
   1.717 +  function readLocale(aDs, aSource, isDefault, aSeenLocales) {
   1.718 +    let locale = { };
   1.719 +    if (!isDefault) {
   1.720 +      locale.locales = [];
   1.721 +      let targets = ds.GetTargets(aSource, EM_R("locale"), true);
   1.722 +      while (targets.hasMoreElements()) {
   1.723 +        let localeName = getRDFValue(targets.getNext());
   1.724 +        if (!localeName) {
   1.725 +          logger.warn("Ignoring empty locale in localized properties");
   1.726 +          continue;
   1.727 +        }
   1.728 +        if (aSeenLocales.indexOf(localeName) != -1) {
   1.729 +          logger.warn("Ignoring duplicate locale in localized properties");
   1.730 +          continue;
   1.731 +        }
   1.732 +        aSeenLocales.push(localeName);
   1.733 +        locale.locales.push(localeName);
   1.734 +      }
   1.735 +
   1.736 +      if (locale.locales.length == 0) {
   1.737 +        logger.warn("Ignoring localized properties with no listed locales");
   1.738 +        return null;
   1.739 +      }
   1.740 +    }
   1.741 +
   1.742 +    PROP_LOCALE_SINGLE.forEach(function(aProp) {
   1.743 +      locale[aProp] = getRDFProperty(aDs, aSource, aProp);
   1.744 +    });
   1.745 +
   1.746 +    PROP_LOCALE_MULTI.forEach(function(aProp) {
   1.747 +      // Don't store empty arrays
   1.748 +      let props = getPropertyArray(aDs, aSource,
   1.749 +                                   aProp.substring(0, aProp.length - 1));
   1.750 +      if (props.length > 0)
   1.751 +        locale[aProp] = props;
   1.752 +    });
   1.753 +
   1.754 +    return locale;
   1.755 +  }
   1.756 +
   1.757 +  let rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
   1.758 +                  createInstance(Ci.nsIRDFXMLParser)
   1.759 +  let ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
   1.760 +           createInstance(Ci.nsIRDFDataSource);
   1.761 +  let listener = rdfParser.parseAsync(ds, aUri);
   1.762 +  let channel = Cc["@mozilla.org/network/input-stream-channel;1"].
   1.763 +                createInstance(Ci.nsIInputStreamChannel);
   1.764 +  channel.setURI(aUri);
   1.765 +  channel.contentStream = aStream;
   1.766 +  channel.QueryInterface(Ci.nsIChannel);
   1.767 +  channel.contentType = "text/xml";
   1.768 +
   1.769 +  listener.onStartRequest(channel, null);
   1.770 +
   1.771 +  try {
   1.772 +    let pos = 0;
   1.773 +    let count = aStream.available();
   1.774 +    while (count > 0) {
   1.775 +      listener.onDataAvailable(channel, null, aStream, pos, count);
   1.776 +      pos += count;
   1.777 +      count = aStream.available();
   1.778 +    }
   1.779 +    listener.onStopRequest(channel, null, Components.results.NS_OK);
   1.780 +  }
   1.781 +  catch (e) {
   1.782 +    listener.onStopRequest(channel, null, e.result);
   1.783 +    throw e;
   1.784 +  }
   1.785 +
   1.786 +  let root = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
   1.787 +  let addon = new AddonInternal();
   1.788 +  PROP_METADATA.forEach(function(aProp) {
   1.789 +    addon[aProp] = getRDFProperty(ds, root, aProp);
   1.790 +  });
   1.791 +  addon.unpack = getRDFProperty(ds, root, "unpack") == "true";
   1.792 +
   1.793 +  if (!addon.type) {
   1.794 +    addon.type = addon.internalName ? "theme" : "extension";
   1.795 +  }
   1.796 +  else {
   1.797 +    let type = addon.type;
   1.798 +    addon.type = null;
   1.799 +    for (let name in TYPES) {
   1.800 +      if (TYPES[name] == type) {
   1.801 +        addon.type = name;
   1.802 +        break;
   1.803 +      }
   1.804 +    }
   1.805 +  }
   1.806 +
   1.807 +  if (!(addon.type in TYPES))
   1.808 +    throw new Error("Install manifest specifies unknown type: " + addon.type);
   1.809 +
   1.810 +  if (addon.type != "multipackage") {
   1.811 +    if (!addon.id)
   1.812 +      throw new Error("No ID in install manifest");
   1.813 +    if (!gIDTest.test(addon.id))
   1.814 +      throw new Error("Illegal add-on ID " + addon.id);
   1.815 +    if (!addon.version)
   1.816 +      throw new Error("No version in install manifest");
   1.817 +  }
   1.818 +
   1.819 +  addon.strictCompatibility = !(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
   1.820 +                              getRDFProperty(ds, root, "strictCompatibility") == "true";
   1.821 +
   1.822 +  // Only read the bootstrap property for extensions.
   1.823 +  if (addon.type == "extension") {
   1.824 +    addon.bootstrap = getRDFProperty(ds, root, "bootstrap") == "true";
   1.825 +    if (addon.optionsType &&
   1.826 +        addon.optionsType != AddonManager.OPTIONS_TYPE_DIALOG &&
   1.827 +        addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE &&
   1.828 +        addon.optionsType != AddonManager.OPTIONS_TYPE_TAB &&
   1.829 +        addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_INFO) {
   1.830 +      throw new Error("Install manifest specifies unknown type: " + addon.optionsType);
   1.831 +    }
   1.832 +  }
   1.833 +  else {
   1.834 +    // Some add-on types are always restartless.
   1.835 +    if (RESTARTLESS_TYPES.has(addon.type)) {
   1.836 +      addon.bootstrap = true;
   1.837 +    }
   1.838 +
   1.839 +    // Only extensions are allowed to provide an optionsURL, optionsType or aboutURL. For
   1.840 +    // all other types they are silently ignored
   1.841 +    addon.optionsURL = null;
   1.842 +    addon.optionsType = null;
   1.843 +    addon.aboutURL = null;
   1.844 +
   1.845 +    if (addon.type == "theme") {
   1.846 +      if (!addon.internalName)
   1.847 +        throw new Error("Themes must include an internalName property");
   1.848 +      addon.skinnable = getRDFProperty(ds, root, "skinnable") == "true";
   1.849 +    }
   1.850 +  }
   1.851 +
   1.852 +  addon.defaultLocale = readLocale(ds, root, true);
   1.853 +
   1.854 +  let seenLocales = [];
   1.855 +  addon.locales = [];
   1.856 +  let targets = ds.GetTargets(root, EM_R("localized"), true);
   1.857 +  while (targets.hasMoreElements()) {
   1.858 +    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
   1.859 +    let locale = readLocale(ds, target, false, seenLocales);
   1.860 +    if (locale)
   1.861 +      addon.locales.push(locale);
   1.862 +  }
   1.863 +
   1.864 +  let seenApplications = [];
   1.865 +  addon.targetApplications = [];
   1.866 +  targets = ds.GetTargets(root, EM_R("targetApplication"), true);
   1.867 +  while (targets.hasMoreElements()) {
   1.868 +    let target = targets.getNext().QueryInterface(Ci.nsIRDFResource);
   1.869 +    let targetAppInfo = {};
   1.870 +    PROP_TARGETAPP.forEach(function(aProp) {
   1.871 +      targetAppInfo[aProp] = getRDFProperty(ds, target, aProp);
   1.872 +    });
   1.873 +    if (!targetAppInfo.id || !targetAppInfo.minVersion ||
   1.874 +        !targetAppInfo.maxVersion) {
   1.875 +      logger.warn("Ignoring invalid targetApplication entry in install manifest");
   1.876 +      continue;
   1.877 +    }
   1.878 +    if (seenApplications.indexOf(targetAppInfo.id) != -1) {
   1.879 +      logger.warn("Ignoring duplicate targetApplication entry for " + targetAppInfo.id +
   1.880 +           " in install manifest");
   1.881 +      continue;
   1.882 +    }
   1.883 +    seenApplications.push(targetAppInfo.id);
   1.884 +    addon.targetApplications.push(targetAppInfo);
   1.885 +  }
   1.886 +
   1.887 +  // Note that we don't need to check for duplicate targetPlatform entries since
   1.888 +  // the RDF service coalesces them for us.
   1.889 +  let targetPlatforms = getPropertyArray(ds, root, "targetPlatform");
   1.890 +  addon.targetPlatforms = [];
   1.891 +  targetPlatforms.forEach(function(aPlatform) {
   1.892 +    let platform = {
   1.893 +      os: null,
   1.894 +      abi: null
   1.895 +    };
   1.896 +
   1.897 +    let pos = aPlatform.indexOf("_");
   1.898 +    if (pos != -1) {
   1.899 +      platform.os = aPlatform.substring(0, pos);
   1.900 +      platform.abi = aPlatform.substring(pos + 1);
   1.901 +    }
   1.902 +    else {
   1.903 +      platform.os = aPlatform;
   1.904 +    }
   1.905 +
   1.906 +    addon.targetPlatforms.push(platform);
   1.907 +  });
   1.908 +
   1.909 +  // A theme's userDisabled value is true if the theme is not the selected skin
   1.910 +  // or if there is an active lightweight theme. We ignore whether softblocking
   1.911 +  // is in effect since it would change the active theme.
   1.912 +  if (addon.type == "theme") {
   1.913 +    addon.userDisabled = !!LightweightThemeManager.currentTheme ||
   1.914 +                         addon.internalName != XPIProvider.selectedSkin;
   1.915 +  }
   1.916 +  // Experiments are disabled by default. It is up to the Experiments Manager
   1.917 +  // to enable them (it drives installation).
   1.918 +  else if (addon.type == "experiment") {
   1.919 +    addon.userDisabled = true;
   1.920 +  }
   1.921 +  else {
   1.922 +    addon.userDisabled = false;
   1.923 +    addon.softDisabled = addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
   1.924 +  }
   1.925 +
   1.926 +  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
   1.927 +
   1.928 +  // Experiments are managed and updated through an external "experiments
   1.929 +  // manager." So disable some built-in mechanisms.
   1.930 +  if (addon.type == "experiment") {
   1.931 +    addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE;
   1.932 +    addon.updateURL = null;
   1.933 +    addon.updateKey = null;
   1.934 +
   1.935 +    addon.targetApplications = [];
   1.936 +    addon.targetPlatforms = [];
   1.937 +  }
   1.938 +
   1.939 +  // Load the storage service before NSS (nsIRandomGenerator),
   1.940 +  // to avoid a SQLite initialization error (bug 717904).
   1.941 +  let storage = Services.storage;
   1.942 +
   1.943 +  // Generate random GUID used for Sync.
   1.944 +  // This was lifted from util.js:makeGUID() from services-sync.
   1.945 +  let rng = Cc["@mozilla.org/security/random-generator;1"].
   1.946 +            createInstance(Ci.nsIRandomGenerator);
   1.947 +  let bytes = rng.generateRandomBytes(9);
   1.948 +  let byte_string = [String.fromCharCode(byte) for each (byte in bytes)]
   1.949 +                    .join("");
   1.950 +  // Base64 encode
   1.951 +  addon.syncGUID = btoa(byte_string).replace(/\+/g, '-')
   1.952 +                                    .replace(/\//g, '_');
   1.953 +
   1.954 +  return addon;
   1.955 +}
   1.956 +
   1.957 +/**
   1.958 + * Loads an AddonInternal object from an add-on extracted in a directory.
   1.959 + *
   1.960 + * @param  aDir
   1.961 + *         The nsIFile directory holding the add-on
   1.962 + * @return an AddonInternal object
   1.963 + * @throws if the directory does not contain a valid install manifest
   1.964 + */
   1.965 +function loadManifestFromDir(aDir) {
   1.966 +  function getFileSize(aFile) {
   1.967 +    if (aFile.isSymlink())
   1.968 +      return 0;
   1.969 +
   1.970 +    if (!aFile.isDirectory())
   1.971 +      return aFile.fileSize;
   1.972 +
   1.973 +    let size = 0;
   1.974 +    let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
   1.975 +    let entry;
   1.976 +    while ((entry = entries.nextFile))
   1.977 +      size += getFileSize(entry);
   1.978 +    entries.close();
   1.979 +    return size;
   1.980 +  }
   1.981 +
   1.982 +  let file = aDir.clone();
   1.983 +  file.append(FILE_INSTALL_MANIFEST);
   1.984 +  if (!file.exists() || !file.isFile())
   1.985 +    throw new Error("Directory " + aDir.path + " does not contain a valid " +
   1.986 +                    "install manifest");
   1.987 +
   1.988 +  let fis = Cc["@mozilla.org/network/file-input-stream;1"].
   1.989 +            createInstance(Ci.nsIFileInputStream);
   1.990 +  fis.init(file, -1, -1, false);
   1.991 +  let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
   1.992 +            createInstance(Ci.nsIBufferedInputStream);
   1.993 +  bis.init(fis, 4096);
   1.994 +
   1.995 +  try {
   1.996 +    let addon = loadManifestFromRDF(Services.io.newFileURI(file), bis);
   1.997 +    addon._sourceBundle = aDir.clone();
   1.998 +    addon.size = getFileSize(aDir);
   1.999 +
  1.1000 +    file = aDir.clone();
  1.1001 +    file.append("chrome.manifest");
  1.1002 +    let chromeManifest = ChromeManifestParser.parseSync(Services.io.newFileURI(file));
  1.1003 +    addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
  1.1004 +                                                             "binary-component");
  1.1005 +
  1.1006 +    addon.appDisabled = !isUsableAddon(addon);
  1.1007 +    return addon;
  1.1008 +  }
  1.1009 +  finally {
  1.1010 +    bis.close();
  1.1011 +    fis.close();
  1.1012 +  }
  1.1013 +}
  1.1014 +
  1.1015 +/**
  1.1016 + * Loads an AddonInternal object from an nsIZipReader for an add-on.
  1.1017 + *
  1.1018 + * @param  aZipReader
  1.1019 + *         An open nsIZipReader for the add-on's files
  1.1020 + * @return an AddonInternal object
  1.1021 + * @throws if the XPI file does not contain a valid install manifest
  1.1022 + */
  1.1023 +function loadManifestFromZipReader(aZipReader) {
  1.1024 +  let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
  1.1025 +  let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
  1.1026 +            createInstance(Ci.nsIBufferedInputStream);
  1.1027 +  bis.init(zis, 4096);
  1.1028 +
  1.1029 +  try {
  1.1030 +    let uri = buildJarURI(aZipReader.file, FILE_INSTALL_MANIFEST);
  1.1031 +    let addon = loadManifestFromRDF(uri, bis);
  1.1032 +    addon._sourceBundle = aZipReader.file;
  1.1033 +
  1.1034 +    addon.size = 0;
  1.1035 +    let entries = aZipReader.findEntries(null);
  1.1036 +    while (entries.hasMore())
  1.1037 +      addon.size += aZipReader.getEntry(entries.getNext()).realSize;
  1.1038 +
  1.1039 +    // Binary components can only be loaded from unpacked addons.
  1.1040 +    if (addon.unpack) {
  1.1041 +      uri = buildJarURI(aZipReader.file, "chrome.manifest");
  1.1042 +      let chromeManifest = ChromeManifestParser.parseSync(uri);
  1.1043 +      addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
  1.1044 +                                                               "binary-component");
  1.1045 +    } else {
  1.1046 +      addon.hasBinaryComponents = false;
  1.1047 +    }
  1.1048 +
  1.1049 +    addon.appDisabled = !isUsableAddon(addon);
  1.1050 +    return addon;
  1.1051 +  }
  1.1052 +  finally {
  1.1053 +    bis.close();
  1.1054 +    zis.close();
  1.1055 +  }
  1.1056 +}
  1.1057 +
  1.1058 +/**
  1.1059 + * Loads an AddonInternal object from an add-on in an XPI file.
  1.1060 + *
  1.1061 + * @param  aXPIFile
  1.1062 + *         An nsIFile pointing to the add-on's XPI file
  1.1063 + * @return an AddonInternal object
  1.1064 + * @throws if the XPI file does not contain a valid install manifest
  1.1065 + */
  1.1066 +function loadManifestFromZipFile(aXPIFile) {
  1.1067 +  let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
  1.1068 +                  createInstance(Ci.nsIZipReader);
  1.1069 +  try {
  1.1070 +    zipReader.open(aXPIFile);
  1.1071 +
  1.1072 +    return loadManifestFromZipReader(zipReader);
  1.1073 +  }
  1.1074 +  finally {
  1.1075 +    zipReader.close();
  1.1076 +  }
  1.1077 +}
  1.1078 +
  1.1079 +function loadManifestFromFile(aFile) {
  1.1080 +  if (aFile.isFile())
  1.1081 +    return loadManifestFromZipFile(aFile);
  1.1082 +  else
  1.1083 +    return loadManifestFromDir(aFile);
  1.1084 +}
  1.1085 +
  1.1086 +/**
  1.1087 + * Gets an nsIURI for a file within another file, either a directory or an XPI
  1.1088 + * file. If aFile is a directory then this will return a file: URI, if it is an
  1.1089 + * XPI file then it will return a jar: URI.
  1.1090 + *
  1.1091 + * @param  aFile
  1.1092 + *         The file containing the resources, must be either a directory or an
  1.1093 + *         XPI file
  1.1094 + * @param  aPath
  1.1095 + *         The path to find the resource at, "/" separated. If aPath is empty
  1.1096 + *         then the uri to the root of the contained files will be returned
  1.1097 + * @return an nsIURI pointing at the resource
  1.1098 + */
  1.1099 +function getURIForResourceInFile(aFile, aPath) {
  1.1100 +  if (aFile.isDirectory()) {
  1.1101 +    let resource = aFile.clone();
  1.1102 +    if (aPath) {
  1.1103 +      aPath.split("/").forEach(function(aPart) {
  1.1104 +        resource.append(aPart);
  1.1105 +      });
  1.1106 +    }
  1.1107 +    return NetUtil.newURI(resource);
  1.1108 +  }
  1.1109 +
  1.1110 +  return buildJarURI(aFile, aPath);
  1.1111 +}
  1.1112 +
  1.1113 +/**
  1.1114 + * Creates a jar: URI for a file inside a ZIP file.
  1.1115 + *
  1.1116 + * @param  aJarfile
  1.1117 + *         The ZIP file as an nsIFile
  1.1118 + * @param  aPath
  1.1119 + *         The path inside the ZIP file
  1.1120 + * @return an nsIURI for the file
  1.1121 + */
  1.1122 +function buildJarURI(aJarfile, aPath) {
  1.1123 +  let uri = Services.io.newFileURI(aJarfile);
  1.1124 +  uri = "jar:" + uri.spec + "!/" + aPath;
  1.1125 +  return NetUtil.newURI(uri);
  1.1126 +}
  1.1127 +
  1.1128 +/**
  1.1129 + * Sends local and remote notifications to flush a JAR file cache entry
  1.1130 + *
  1.1131 + * @param aJarFile
  1.1132 + *        The ZIP/XPI/JAR file as a nsIFile
  1.1133 + */
  1.1134 +function flushJarCache(aJarFile) {
  1.1135 +  Services.obs.notifyObservers(aJarFile, "flush-cache-entry", null);
  1.1136 +  Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageBroadcaster)
  1.1137 +    .broadcastAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
  1.1138 +}
  1.1139 +
  1.1140 +function flushStartupCache() {
  1.1141 +  // Init this, so it will get the notification.
  1.1142 +  Services.obs.notifyObservers(null, "startupcache-invalidate", null);
  1.1143 +}
  1.1144 +
  1.1145 +/**
  1.1146 + * Creates and returns a new unique temporary file. The caller should delete
  1.1147 + * the file when it is no longer needed.
  1.1148 + *
  1.1149 + * @return an nsIFile that points to a randomly named, initially empty file in
  1.1150 + *         the OS temporary files directory
  1.1151 + */
  1.1152 +function getTemporaryFile() {
  1.1153 +  let file = FileUtils.getDir(KEY_TEMPDIR, []);
  1.1154 +  let random = Math.random().toString(36).replace(/0./, '').substr(-3);
  1.1155 +  file.append("tmp-" + random + ".xpi");
  1.1156 +  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
  1.1157 +
  1.1158 +  return file;
  1.1159 +}
  1.1160 +
  1.1161 +/**
  1.1162 + * Verifies that a zip file's contents are all signed by the same principal.
  1.1163 + * Directory entries and anything in the META-INF directory are not checked.
  1.1164 + *
  1.1165 + * @param  aZip
  1.1166 + *         A nsIZipReader to check
  1.1167 + * @param  aPrincipal
  1.1168 + *         The nsIPrincipal to compare against
  1.1169 + * @return true if all the contents that should be signed were signed by the
  1.1170 + *         principal
  1.1171 + */
  1.1172 +function verifyZipSigning(aZip, aPrincipal) {
  1.1173 +  var count = 0;
  1.1174 +  var entries = aZip.findEntries(null);
  1.1175 +  while (entries.hasMore()) {
  1.1176 +    var entry = entries.getNext();
  1.1177 +    // Nothing in META-INF is in the manifest.
  1.1178 +    if (entry.substr(0, 9) == "META-INF/")
  1.1179 +      continue;
  1.1180 +    // Directory entries aren't in the manifest.
  1.1181 +    if (entry.substr(-1) == "/")
  1.1182 +      continue;
  1.1183 +    count++;
  1.1184 +    var entryPrincipal = aZip.getCertificatePrincipal(entry);
  1.1185 +    if (!entryPrincipal || !aPrincipal.equals(entryPrincipal))
  1.1186 +      return false;
  1.1187 +  }
  1.1188 +  return aZip.manifestEntriesCount == count;
  1.1189 +}
  1.1190 +
  1.1191 +/**
  1.1192 + * Replaces %...% strings in an addon url (update and updateInfo) with
  1.1193 + * appropriate values.
  1.1194 + *
  1.1195 + * @param  aAddon
  1.1196 + *         The AddonInternal representing the add-on
  1.1197 + * @param  aUri
  1.1198 + *         The uri to escape
  1.1199 + * @param  aUpdateType
  1.1200 + *         An optional number representing the type of update, only applicable
  1.1201 + *         when creating a url for retrieving an update manifest
  1.1202 + * @param  aAppVersion
  1.1203 + *         The optional application version to use for %APP_VERSION%
  1.1204 + * @return the appropriately escaped uri.
  1.1205 + */
  1.1206 +function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion)
  1.1207 +{
  1.1208 +  let uri = AddonManager.escapeAddonURI(aAddon, aUri, aAppVersion);
  1.1209 +
  1.1210 +  // If there is an updateType then replace the UPDATE_TYPE string
  1.1211 +  if (aUpdateType)
  1.1212 +    uri = uri.replace(/%UPDATE_TYPE%/g, aUpdateType);
  1.1213 +
  1.1214 +  // If this add-on has compatibility information for either the current
  1.1215 +  // application or toolkit then replace the ITEM_MAXAPPVERSION with the
  1.1216 +  // maxVersion
  1.1217 +  let app = aAddon.matchingTargetApplication;
  1.1218 +  if (app)
  1.1219 +    var maxVersion = app.maxVersion;
  1.1220 +  else
  1.1221 +    maxVersion = "";
  1.1222 +  uri = uri.replace(/%ITEM_MAXAPPVERSION%/g, maxVersion);
  1.1223 +
  1.1224 +  let compatMode = "normal";
  1.1225 +  if (!AddonManager.checkCompatibility)
  1.1226 +    compatMode = "ignore";
  1.1227 +  else if (AddonManager.strictCompatibility)
  1.1228 +    compatMode = "strict";
  1.1229 +  uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);
  1.1230 +
  1.1231 +  return uri;
  1.1232 +}
  1.1233 +
  1.1234 +function removeAsync(aFile) {
  1.1235 +  return Task.spawn(function () {
  1.1236 +    let info = null;
  1.1237 +    try {
  1.1238 +      info = yield OS.File.stat(aFile.path);
  1.1239 +      if (info.isDir)
  1.1240 +        yield OS.File.removeDir(aFile.path);
  1.1241 +      else
  1.1242 +        yield OS.File.remove(aFile.path);
  1.1243 +    }
  1.1244 +    catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
  1.1245 +      // The file has already gone away
  1.1246 +      return;
  1.1247 +    }
  1.1248 +  });
  1.1249 +}
  1.1250 +
  1.1251 +/**
  1.1252 + * Recursively removes a directory or file fixing permissions when necessary.
  1.1253 + *
  1.1254 + * @param  aFile
  1.1255 + *         The nsIFile to remove
  1.1256 + */
  1.1257 +function recursiveRemove(aFile) {
  1.1258 +  let isDir = null;
  1.1259 +
  1.1260 +  try {
  1.1261 +    isDir = aFile.isDirectory();
  1.1262 +  }
  1.1263 +  catch (e) {
  1.1264 +    // If the file has already gone away then don't worry about it, this can
  1.1265 +    // happen on OSX where the resource fork is automatically moved with the
  1.1266 +    // data fork for the file. See bug 733436.
  1.1267 +    if (e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)
  1.1268 +      return;
  1.1269 +    if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND)
  1.1270 +      return;
  1.1271 +
  1.1272 +    throw e;
  1.1273 +  }
  1.1274 +
  1.1275 +  setFilePermissions(aFile, isDir ? FileUtils.PERMS_DIRECTORY
  1.1276 +                                  : FileUtils.PERMS_FILE);
  1.1277 +
  1.1278 +  try {
  1.1279 +    aFile.remove(true);
  1.1280 +    return;
  1.1281 +  }
  1.1282 +  catch (e) {
  1.1283 +    if (!aFile.isDirectory()) {
  1.1284 +      logger.error("Failed to remove file " + aFile.path, e);
  1.1285 +      throw e;
  1.1286 +    }
  1.1287 +  }
  1.1288 +
  1.1289 +  // Use a snapshot of the directory contents to avoid possible issues with
  1.1290 +  // iterating over a directory while removing files from it (the YAFFS2
  1.1291 +  // embedded filesystem has this issue, see bug 772238), and to remove
  1.1292 +  // normal files before their resource forks on OSX (see bug 733436).
  1.1293 +  let entries = getDirectoryEntries(aFile, true);
  1.1294 +  entries.forEach(recursiveRemove);
  1.1295 +
  1.1296 +  try {
  1.1297 +    aFile.remove(true);
  1.1298 +  }
  1.1299 +  catch (e) {
  1.1300 +    logger.error("Failed to remove empty directory " + aFile.path, e);
  1.1301 +    throw e;
  1.1302 +  }
  1.1303 +}
  1.1304 +
  1.1305 +/**
  1.1306 + * Returns the timestamp and leaf file name of the most recently modified
  1.1307 + * entry in a directory,
  1.1308 + * or simply the file's own timestamp if it is not a directory.
  1.1309 + * Also returns the total number of items (directories and files) visited in the scan
  1.1310 + *
  1.1311 + * @param  aFile
  1.1312 + *         A non-null nsIFile object
  1.1313 + * @return [File Name, Epoch time, items visited], as described above.
  1.1314 + */
  1.1315 +function recursiveLastModifiedTime(aFile) {
  1.1316 +  try {
  1.1317 +    let modTime = aFile.lastModifiedTime;
  1.1318 +    let fileName = aFile.leafName;
  1.1319 +    if (aFile.isFile())
  1.1320 +      return [fileName, modTime, 1];
  1.1321 +
  1.1322 +    if (aFile.isDirectory()) {
  1.1323 +      let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  1.1324 +      let entry;
  1.1325 +      let totalItems = 1;
  1.1326 +      while ((entry = entries.nextFile)) {
  1.1327 +        let [subName, subTime, items] = recursiveLastModifiedTime(entry);
  1.1328 +        totalItems += items;
  1.1329 +        if (subTime > modTime) {
  1.1330 +          modTime = subTime;
  1.1331 +          fileName = subName;
  1.1332 +        }
  1.1333 +      }
  1.1334 +      entries.close();
  1.1335 +      return [fileName, modTime, totalItems];
  1.1336 +    }
  1.1337 +  }
  1.1338 +  catch (e) {
  1.1339 +    logger.warn("Problem getting last modified time for " + aFile.path, e);
  1.1340 +  }
  1.1341 +
  1.1342 +  // If the file is something else, just ignore it.
  1.1343 +  return ["", 0, 0];
  1.1344 +}
  1.1345 +
  1.1346 +/**
  1.1347 + * Gets a snapshot of directory entries.
  1.1348 + *
  1.1349 + * @param  aDir
  1.1350 + *         Directory to look at
  1.1351 + * @param  aSortEntries
  1.1352 + *         True to sort entries by filename
  1.1353 + * @return An array of nsIFile, or an empty array if aDir is not a readable directory
  1.1354 + */
  1.1355 +function getDirectoryEntries(aDir, aSortEntries) {
  1.1356 +  let dirEnum;
  1.1357 +  try {
  1.1358 +    dirEnum = aDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  1.1359 +    let entries = [];
  1.1360 +    while (dirEnum.hasMoreElements())
  1.1361 +      entries.push(dirEnum.nextFile);
  1.1362 +
  1.1363 +    if (aSortEntries) {
  1.1364 +      entries.sort(function sortDirEntries(a, b) {
  1.1365 +        return a.path > b.path ? -1 : 1;
  1.1366 +      });
  1.1367 +    }
  1.1368 +
  1.1369 +    return entries
  1.1370 +  }
  1.1371 +  catch (e) {
  1.1372 +    logger.warn("Can't iterate directory " + aDir.path, e);
  1.1373 +    return [];
  1.1374 +  }
  1.1375 +  finally {
  1.1376 +    if (dirEnum) {
  1.1377 +      dirEnum.close();
  1.1378 +    }
  1.1379 +  }
  1.1380 +}
  1.1381 +
  1.1382 +/**
  1.1383 + * A helpful wrapper around the prefs service that allows for default values
  1.1384 + * when requested values aren't set.
  1.1385 + */
  1.1386 +var Prefs = {
  1.1387 +  /**
  1.1388 +   * Gets a preference from the default branch ignoring user-set values.
  1.1389 +   *
  1.1390 +   * @param  aName
  1.1391 +   *         The name of the preference
  1.1392 +   * @param  aDefaultValue
  1.1393 +   *         A value to return if the preference does not exist
  1.1394 +   * @return the default value of the preference or aDefaultValue if there is
  1.1395 +   *         none
  1.1396 +   */
  1.1397 +  getDefaultCharPref: function Prefs_getDefaultCharPref(aName, aDefaultValue) {
  1.1398 +    try {
  1.1399 +      return Services.prefs.getDefaultBranch("").getCharPref(aName);
  1.1400 +    }
  1.1401 +    catch (e) {
  1.1402 +    }
  1.1403 +    return aDefaultValue;
  1.1404 +  },
  1.1405 +
  1.1406 +  /**
  1.1407 +   * Gets a string preference.
  1.1408 +   *
  1.1409 +   * @param  aName
  1.1410 +   *         The name of the preference
  1.1411 +   * @param  aDefaultValue
  1.1412 +   *         A value to return if the preference does not exist
  1.1413 +   * @return the value of the preference or aDefaultValue if there is none
  1.1414 +   */
  1.1415 +  getCharPref: function Prefs_getCharPref(aName, aDefaultValue) {
  1.1416 +    try {
  1.1417 +      return Services.prefs.getCharPref(aName);
  1.1418 +    }
  1.1419 +    catch (e) {
  1.1420 +    }
  1.1421 +    return aDefaultValue;
  1.1422 +  },
  1.1423 +
  1.1424 +  /**
  1.1425 +   * Gets a complex preference.
  1.1426 +   *
  1.1427 +   * @param  aName
  1.1428 +   *         The name of the preference
  1.1429 +   * @param  aType
  1.1430 +   *         The interface type of the preference
  1.1431 +   * @param  aDefaultValue
  1.1432 +   *         A value to return if the preference does not exist
  1.1433 +   * @return the value of the preference or aDefaultValue if there is none
  1.1434 +   */
  1.1435 +  getComplexValue: function Prefs_getComplexValue(aName, aType, aDefaultValue) {
  1.1436 +    try {
  1.1437 +      return Services.prefs.getComplexValue(aName, aType).data;
  1.1438 +    }
  1.1439 +    catch (e) {
  1.1440 +    }
  1.1441 +    return aDefaultValue;
  1.1442 +  },
  1.1443 +
  1.1444 +  /**
  1.1445 +   * Gets a boolean preference.
  1.1446 +   *
  1.1447 +   * @param  aName
  1.1448 +   *         The name of the preference
  1.1449 +   * @param  aDefaultValue
  1.1450 +   *         A value to return if the preference does not exist
  1.1451 +   * @return the value of the preference or aDefaultValue if there is none
  1.1452 +   */
  1.1453 +  getBoolPref: function Prefs_getBoolPref(aName, aDefaultValue) {
  1.1454 +    try {
  1.1455 +      return Services.prefs.getBoolPref(aName);
  1.1456 +    }
  1.1457 +    catch (e) {
  1.1458 +    }
  1.1459 +    return aDefaultValue;
  1.1460 +  },
  1.1461 +
  1.1462 +  /**
  1.1463 +   * Gets an integer preference.
  1.1464 +   *
  1.1465 +   * @param  aName
  1.1466 +   *         The name of the preference
  1.1467 +   * @param  defaultValue
  1.1468 +   *         A value to return if the preference does not exist
  1.1469 +   * @return the value of the preference or defaultValue if there is none
  1.1470 +   */
  1.1471 +  getIntPref: function Prefs_getIntPref(aName, defaultValue) {
  1.1472 +    try {
  1.1473 +      return Services.prefs.getIntPref(aName);
  1.1474 +    }
  1.1475 +    catch (e) {
  1.1476 +    }
  1.1477 +    return defaultValue;
  1.1478 +  },
  1.1479 +
  1.1480 +  /**
  1.1481 +   * Clears a preference if it has a user value
  1.1482 +   *
  1.1483 +   * @param  aName
  1.1484 +   *         The name of the preference
  1.1485 +   */
  1.1486 +  clearUserPref: function Prefs_clearUserPref(aName) {
  1.1487 +    if (Services.prefs.prefHasUserValue(aName))
  1.1488 +      Services.prefs.clearUserPref(aName);
  1.1489 +  }
  1.1490 +}
  1.1491 +
  1.1492 +// Helper function to compare JSON saved version of the directory state
  1.1493 +// with the new state returned by getInstallLocationStates()
  1.1494 +// Structure is: ordered array of {'name':?, 'addons': {addonID: {'descriptor':?, 'mtime':?} ...}}
  1.1495 +function directoryStateDiffers(aState, aCache)
  1.1496 +{
  1.1497 +  // check equality of an object full of addons; fortunately we can destroy the 'aOld' object
  1.1498 +  function addonsMismatch(aNew, aOld) {
  1.1499 +    for (let [id, val] of aNew) {
  1.1500 +      if (!id in aOld)
  1.1501 +        return true;
  1.1502 +      if (val.descriptor != aOld[id].descriptor ||
  1.1503 +          val.mtime != aOld[id].mtime)
  1.1504 +        return true;
  1.1505 +      delete aOld[id];
  1.1506 +    }
  1.1507 +    // make sure aOld doesn't have any extra entries
  1.1508 +    for (let id in aOld)
  1.1509 +      return true;
  1.1510 +    return false;
  1.1511 +  }
  1.1512 +
  1.1513 +  if (!aCache)
  1.1514 +    return true;
  1.1515 +  try {
  1.1516 +    let old = JSON.parse(aCache);
  1.1517 +    if (aState.length != old.length)
  1.1518 +      return true;
  1.1519 +    for (let i = 0; i < aState.length; i++) {
  1.1520 +      // conveniently, any missing fields would require a 'true' return, which is
  1.1521 +      // handled by our catch wrapper
  1.1522 +      if (aState[i].name != old[i].name)
  1.1523 +        return true;
  1.1524 +      if (addonsMismatch(aState[i].addons, old[i].addons))
  1.1525 +        return true;
  1.1526 +    }
  1.1527 +  }
  1.1528 +  catch (e) {
  1.1529 +    return true;
  1.1530 +  }
  1.1531 +  return false;
  1.1532 +}
  1.1533 +
  1.1534 +/**
  1.1535 + * Wraps a function in an exception handler to protect against exceptions inside callbacks
  1.1536 + * @param aFunction function(args...)
  1.1537 + * @return function(args...), a function that takes the same arguments as aFunction
  1.1538 + *         and returns the same result unless aFunction throws, in which case it logs
  1.1539 + *         a warning and returns undefined.
  1.1540 + */
  1.1541 +function makeSafe(aFunction) {
  1.1542 +  return function(...aArgs) {
  1.1543 +    try {
  1.1544 +      return aFunction(...aArgs);
  1.1545 +    }
  1.1546 +    catch(ex) {
  1.1547 +      logger.warn("XPIProvider callback failed", ex);
  1.1548 +    }
  1.1549 +    return undefined;
  1.1550 +  }
  1.1551 +}
  1.1552 +
  1.1553 +this.XPIProvider = {
  1.1554 +  // An array of known install locations
  1.1555 +  installLocations: null,
  1.1556 +  // A dictionary of known install locations by name
  1.1557 +  installLocationsByName: null,
  1.1558 +  // An array of currently active AddonInstalls
  1.1559 +  installs: null,
  1.1560 +  // The default skin for the application
  1.1561 +  defaultSkin: "classic/1.0",
  1.1562 +  // The current skin used by the application
  1.1563 +  currentSkin: null,
  1.1564 +  // The selected skin to be used by the application when it is restarted. This
  1.1565 +  // will be the same as currentSkin when it is the skin to be used when the
  1.1566 +  // application is restarted
  1.1567 +  selectedSkin: null,
  1.1568 +  // The value of the minCompatibleAppVersion preference
  1.1569 +  minCompatibleAppVersion: null,
  1.1570 +  // The value of the minCompatiblePlatformVersion preference
  1.1571 +  minCompatiblePlatformVersion: null,
  1.1572 +  // A dictionary of the file descriptors for bootstrappable add-ons by ID
  1.1573 +  bootstrappedAddons: {},
  1.1574 +  // A dictionary of JS scopes of loaded bootstrappable add-ons by ID
  1.1575 +  bootstrapScopes: {},
  1.1576 +  // True if the platform could have activated extensions
  1.1577 +  extensionsActive: false,
  1.1578 +  // File / directory state of installed add-ons
  1.1579 +  installStates: [],
  1.1580 +  // True if all of the add-ons found during startup were installed in the
  1.1581 +  // application install location
  1.1582 +  allAppGlobal: true,
  1.1583 +  // A string listing the enabled add-ons for annotating crash reports
  1.1584 +  enabledAddons: null,
  1.1585 +  // An array of add-on IDs of add-ons that were inactive during startup
  1.1586 +  inactiveAddonIDs: [],
  1.1587 +  // Keep track of startup phases for telemetry
  1.1588 +  runPhase: XPI_STARTING,
  1.1589 +  // Keep track of the newest file in each add-on, in case we want to
  1.1590 +  // report it to telemetry.
  1.1591 +  _mostRecentlyModifiedFile: {},
  1.1592 +  // Per-addon telemetry information
  1.1593 +  _telemetryDetails: {},
  1.1594 +  // Experiments are disabled by default. Track ones that are locally enabled.
  1.1595 +  _enabledExperiments: null,
  1.1596 +
  1.1597 +  /*
  1.1598 +   * Set a value in the telemetry hash for a given ID
  1.1599 +   */
  1.1600 +  setTelemetry: function XPI_setTelemetry(aId, aName, aValue) {
  1.1601 +    if (!this._telemetryDetails[aId])
  1.1602 +      this._telemetryDetails[aId] = {};
  1.1603 +    this._telemetryDetails[aId][aName] = aValue;
  1.1604 +  },
  1.1605 +
  1.1606 +  // Keep track of in-progress operations that support cancel()
  1.1607 +  _inProgress: new Set(),
  1.1608 +
  1.1609 +  doing: function XPI_doing(aCancellable) {
  1.1610 +    this._inProgress.add(aCancellable);
  1.1611 +  },
  1.1612 +
  1.1613 +  done: function XPI_done(aCancellable) {
  1.1614 +    return this._inProgress.delete(aCancellable);
  1.1615 +  },
  1.1616 +
  1.1617 +  cancelAll: function XPI_cancelAll() {
  1.1618 +    // Cancelling one may alter _inProgress, so restart the iterator after each
  1.1619 +    while (this._inProgress.size > 0) {
  1.1620 +      for (let c of this._inProgress) {
  1.1621 +        try {
  1.1622 +          c.cancel();
  1.1623 +        }
  1.1624 +        catch (e) {
  1.1625 +          logger.warn("Cancel failed", e);
  1.1626 +        }
  1.1627 +        this._inProgress.delete(c);
  1.1628 +      }
  1.1629 +    }
  1.1630 +  },
  1.1631 +
  1.1632 +  /**
  1.1633 +   * Adds or updates a URI mapping for an Addon.id.
  1.1634 +   *
  1.1635 +   * Mappings should not be removed at any point. This is so that the mappings
  1.1636 +   * will be still valid after an add-on gets disabled or uninstalled, as
  1.1637 +   * consumers may still have URIs of (leaked) resources they want to map.
  1.1638 +   */
  1.1639 +  _addURIMapping: function XPI__addURIMapping(aID, aFile) {
  1.1640 +    try {
  1.1641 +      // Always use our own mechanics instead of nsIIOService.newFileURI, so
  1.1642 +      // that we can be sure to map things as we want them mapped.
  1.1643 +      let uri = this._resolveURIToFile(getURIForResourceInFile(aFile, "."));
  1.1644 +      if (!uri) {
  1.1645 +        throw new Error("Cannot resolve");
  1.1646 +      }
  1.1647 +      this._ensureURIMappings();
  1.1648 +      this._uriMappings[aID] = uri.spec;
  1.1649 +    }
  1.1650 +    catch (ex) {
  1.1651 +      logger.warn("Failed to add URI mapping", ex);
  1.1652 +    }
  1.1653 +  },
  1.1654 +
  1.1655 +  /**
  1.1656 +   * Ensures that the URI to Addon mappings are available.
  1.1657 +   *
  1.1658 +   * The function will add mappings for all non-bootstrapped but enabled
  1.1659 +   * add-ons.
  1.1660 +   * Bootstrapped add-on mappings will be added directly when the bootstrap
  1.1661 +   * scope get loaded. (See XPIProvider._addURIMapping() and callers)
  1.1662 +   */
  1.1663 +  _ensureURIMappings: function XPI__ensureURIMappings() {
  1.1664 +    if (this._uriMappings) {
  1.1665 +      return;
  1.1666 +    }
  1.1667 +    // XXX Convert to Map(), once it gets stable with stable iterators
  1.1668 +    this._uriMappings = Object.create(null);
  1.1669 +
  1.1670 +    // XXX Convert to Set(), once it gets stable with stable iterators
  1.1671 +    let enabled = Object.create(null);
  1.1672 +    let enabledAddons = this.enabledAddons || "";
  1.1673 +    for (let a of enabledAddons.split(",")) {
  1.1674 +      a = decodeURIComponent(a.split(":")[0]);
  1.1675 +      enabled[a] = null;
  1.1676 +    }
  1.1677 +
  1.1678 +    let cache = JSON.parse(Prefs.getCharPref(PREF_INSTALL_CACHE, "[]"));
  1.1679 +    for (let loc of cache) {
  1.1680 +      for (let [id, val] in Iterator(loc.addons)) {
  1.1681 +        if (!(id in enabled)) {
  1.1682 +          continue;
  1.1683 +        }
  1.1684 +        let file = new nsIFile(val.descriptor);
  1.1685 +        let spec = Services.io.newFileURI(file).spec;
  1.1686 +        this._uriMappings[id] = spec;
  1.1687 +      }
  1.1688 +    }
  1.1689 +  },
  1.1690 +
  1.1691 +  /**
  1.1692 +   * Resolve a URI back to physical file.
  1.1693 +   *
  1.1694 +   * Of course, this works only for URIs pointing to local resources.
  1.1695 +   *
  1.1696 +   * @param  aURI
  1.1697 +   *         URI to resolve
  1.1698 +   * @return
  1.1699 +   *         resolved nsIFileURL
  1.1700 +   */
  1.1701 +  _resolveURIToFile: function XPI__resolveURIToFile(aURI) {
  1.1702 +    switch (aURI.scheme) {
  1.1703 +      case "jar":
  1.1704 +      case "file":
  1.1705 +        if (aURI instanceof Ci.nsIJARURI) {
  1.1706 +          return this._resolveURIToFile(aURI.JARFile);
  1.1707 +        }
  1.1708 +        return aURI;
  1.1709 +
  1.1710 +      case "chrome":
  1.1711 +        aURI = ChromeRegistry.convertChromeURL(aURI);
  1.1712 +        return this._resolveURIToFile(aURI);
  1.1713 +
  1.1714 +      case "resource":
  1.1715 +        aURI = Services.io.newURI(ResProtocolHandler.resolveURI(aURI), null,
  1.1716 +                                  null);
  1.1717 +        return this._resolveURIToFile(aURI);
  1.1718 +
  1.1719 +      case "view-source":
  1.1720 +        aURI = Services.io.newURI(aURI.path, null, null);
  1.1721 +        return this._resolveURIToFile(aURI);
  1.1722 +
  1.1723 +      case "about":
  1.1724 +        if (aURI.spec == "about:blank") {
  1.1725 +          // Do not attempt to map about:blank
  1.1726 +          return null;
  1.1727 +        }
  1.1728 +
  1.1729 +        let chan;
  1.1730 +        try {
  1.1731 +          chan = Services.io.newChannelFromURI(aURI);
  1.1732 +        }
  1.1733 +        catch (ex) {
  1.1734 +          return null;
  1.1735 +        }
  1.1736 +        // Avoid looping
  1.1737 +        if (chan.URI.equals(aURI)) {
  1.1738 +          return null;
  1.1739 +        }
  1.1740 +        // We want to clone the channel URI to avoid accidentially keeping
  1.1741 +        // unnecessary references to the channel or implementation details
  1.1742 +        // around.
  1.1743 +        return this._resolveURIToFile(chan.URI.clone());
  1.1744 +
  1.1745 +      default:
  1.1746 +        return null;
  1.1747 +    }
  1.1748 +  },
  1.1749 +
  1.1750 +  /**
  1.1751 +   * Starts the XPI provider initializes the install locations and prefs.
  1.1752 +   *
  1.1753 +   * @param  aAppChanged
  1.1754 +   *         A tri-state value. Undefined means the current profile was created
  1.1755 +   *         for this session, true means the profile already existed but was
  1.1756 +   *         last used with an application with a different version number,
  1.1757 +   *         false means that the profile was last used by this version of the
  1.1758 +   *         application.
  1.1759 +   * @param  aOldAppVersion
  1.1760 +   *         The version of the application last run with this profile or null
  1.1761 +   *         if it is a new profile or the version is unknown
  1.1762 +   * @param  aOldPlatformVersion
  1.1763 +   *         The version of the platform last run with this profile or null
  1.1764 +   *         if it is a new profile or the version is unknown
  1.1765 +   */
  1.1766 +  startup: function XPI_startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
  1.1767 +    function addDirectoryInstallLocation(aName, aKey, aPaths, aScope, aLocked) {
  1.1768 +      try {
  1.1769 +        var dir = FileUtils.getDir(aKey, aPaths);
  1.1770 +      }
  1.1771 +      catch (e) {
  1.1772 +        // Some directories aren't defined on some platforms, ignore them
  1.1773 +        logger.debug("Skipping unavailable install location " + aName);
  1.1774 +        return;
  1.1775 +      }
  1.1776 +
  1.1777 +      try {
  1.1778 +        var location = new DirectoryInstallLocation(aName, dir, aScope, aLocked);
  1.1779 +      }
  1.1780 +      catch (e) {
  1.1781 +        logger.warn("Failed to add directory install location " + aName, e);
  1.1782 +        return;
  1.1783 +      }
  1.1784 +
  1.1785 +      XPIProvider.installLocations.push(location);
  1.1786 +      XPIProvider.installLocationsByName[location.name] = location;
  1.1787 +    }
  1.1788 +
  1.1789 +    function addRegistryInstallLocation(aName, aRootkey, aScope) {
  1.1790 +      try {
  1.1791 +        var location = new WinRegInstallLocation(aName, aRootkey, aScope);
  1.1792 +      }
  1.1793 +      catch (e) {
  1.1794 +        logger.warn("Failed to add registry install location " + aName, e);
  1.1795 +        return;
  1.1796 +      }
  1.1797 +
  1.1798 +      XPIProvider.installLocations.push(location);
  1.1799 +      XPIProvider.installLocationsByName[location.name] = location;
  1.1800 +    }
  1.1801 +
  1.1802 +    try {
  1.1803 +      AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
  1.1804 +
  1.1805 +      logger.debug("startup");
  1.1806 +      this.runPhase = XPI_STARTING;
  1.1807 +      this.installs = [];
  1.1808 +      this.installLocations = [];
  1.1809 +      this.installLocationsByName = {};
  1.1810 +      // Hook for tests to detect when saving database at shutdown time fails
  1.1811 +      this._shutdownError = null;
  1.1812 +      // Clear this at startup for xpcshell test restarts
  1.1813 +      this._telemetryDetails = {};
  1.1814 +      // Clear the set of enabled experiments (experiments disabled by default).
  1.1815 +      this._enabledExperiments = new Set();
  1.1816 +      // Register our details structure with AddonManager
  1.1817 +      AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);
  1.1818 +
  1.1819 +      let hasRegistry = ("nsIWindowsRegKey" in Ci);
  1.1820 +
  1.1821 +      let enabledScopes = Prefs.getIntPref(PREF_EM_ENABLED_SCOPES,
  1.1822 +                                           AddonManager.SCOPE_ALL);
  1.1823 +
  1.1824 +      // These must be in order of priority for processFileChanges etc. to work
  1.1825 +      if (enabledScopes & AddonManager.SCOPE_SYSTEM) {
  1.1826 +        if (hasRegistry) {
  1.1827 +          addRegistryInstallLocation("winreg-app-global",
  1.1828 +                                     Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
  1.1829 +                                     AddonManager.SCOPE_SYSTEM);
  1.1830 +        }
  1.1831 +        addDirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL, "XRESysLExtPD",
  1.1832 +                                    [Services.appinfo.ID],
  1.1833 +                                    AddonManager.SCOPE_SYSTEM, true);
  1.1834 +        addDirectoryInstallLocation(KEY_APP_SYSTEM_SHARE, "XRESysSExtPD",
  1.1835 +                                    [Services.appinfo.ID],
  1.1836 +                                    AddonManager.SCOPE_SYSTEM, true);
  1.1837 +      }
  1.1838 +
  1.1839 +      if (enabledScopes & AddonManager.SCOPE_APPLICATION) {
  1.1840 +        addDirectoryInstallLocation(KEY_APP_GLOBAL, KEY_APPDIR,
  1.1841 +                                    [DIR_EXTENSIONS],
  1.1842 +                                    AddonManager.SCOPE_APPLICATION, true);
  1.1843 +      }
  1.1844 +
  1.1845 +      if (enabledScopes & AddonManager.SCOPE_USER) {
  1.1846 +        if (hasRegistry) {
  1.1847 +          addRegistryInstallLocation("winreg-app-user",
  1.1848 +                                     Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
  1.1849 +                                     AddonManager.SCOPE_USER);
  1.1850 +        }
  1.1851 +        addDirectoryInstallLocation(KEY_APP_SYSTEM_USER, "XREUSysExt",
  1.1852 +                                    [Services.appinfo.ID],
  1.1853 +                                    AddonManager.SCOPE_USER, true);
  1.1854 +      }
  1.1855 +
  1.1856 +      // The profile location is always enabled
  1.1857 +      addDirectoryInstallLocation(KEY_APP_PROFILE, KEY_PROFILEDIR,
  1.1858 +                                  [DIR_EXTENSIONS],
  1.1859 +                                  AddonManager.SCOPE_PROFILE, false);
  1.1860 +
  1.1861 +      this.defaultSkin = Prefs.getDefaultCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
  1.1862 +                                                  "classic/1.0");
  1.1863 +      this.currentSkin = Prefs.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
  1.1864 +                                           this.defaultSkin);
  1.1865 +      this.selectedSkin = this.currentSkin;
  1.1866 +      this.applyThemeChange();
  1.1867 +
  1.1868 +      this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
  1.1869 +                                                       null);
  1.1870 +      this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
  1.1871 +                                                            null);
  1.1872 +      this.enabledAddons = "";
  1.1873 +
  1.1874 +      Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
  1.1875 +      Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
  1.1876 +      Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
  1.1877 +
  1.1878 +      try {
  1.1879 +        BrowserToolboxProcess.on("connectionchange",
  1.1880 +                                 this.onDebugConnectionChange.bind(this));
  1.1881 +      }
  1.1882 +      catch (e) {
  1.1883 +        // BrowserToolboxProcess is not available in all applications
  1.1884 +      }
  1.1885 +
  1.1886 +      let flushCaches = this.checkForChanges(aAppChanged, aOldAppVersion,
  1.1887 +                                             aOldPlatformVersion);
  1.1888 +
  1.1889 +      // Changes to installed extensions may have changed which theme is selected
  1.1890 +      this.applyThemeChange();
  1.1891 +
  1.1892 +      // If the application has been upgraded and there are add-ons outside the
  1.1893 +      // application directory then we may need to synchronize compatibility
  1.1894 +      // information but only if the mismatch UI isn't disabled
  1.1895 +      if (aAppChanged && !this.allAppGlobal &&
  1.1896 +          Prefs.getBoolPref(PREF_EM_SHOW_MISMATCH_UI, true)) {
  1.1897 +        this.showUpgradeUI();
  1.1898 +        flushCaches = true;
  1.1899 +      }
  1.1900 +      else if (aAppChanged === undefined) {
  1.1901 +        // For new profiles we will never need to show the add-on selection UI
  1.1902 +        Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
  1.1903 +      }
  1.1904 +
  1.1905 +      if (flushCaches) {
  1.1906 +        flushStartupCache();
  1.1907 +
  1.1908 +        // UI displayed early in startup (like the compatibility UI) may have
  1.1909 +        // caused us to cache parts of the skin or locale in memory. These must
  1.1910 +        // be flushed to allow extension provided skins and locales to take full
  1.1911 +        // effect
  1.1912 +        Services.obs.notifyObservers(null, "chrome-flush-skin-caches", null);
  1.1913 +        Services.obs.notifyObservers(null, "chrome-flush-caches", null);
  1.1914 +      }
  1.1915 +
  1.1916 +      this.enabledAddons = Prefs.getCharPref(PREF_EM_ENABLED_ADDONS, "");
  1.1917 +
  1.1918 +      // Invalidate the URI mappings now that |enabledAddons| was updated.
  1.1919 +      // |_ensureMappings()| will re-create the mappings when needed.
  1.1920 +      delete this._uriMappings;
  1.1921 +
  1.1922 +      if ("nsICrashReporter" in Ci &&
  1.1923 +          Services.appinfo instanceof Ci.nsICrashReporter) {
  1.1924 +        // Annotate the crash report with relevant add-on information.
  1.1925 +        try {
  1.1926 +          Services.appinfo.annotateCrashReport("Theme", this.currentSkin);
  1.1927 +        } catch (e) { }
  1.1928 +        try {
  1.1929 +          Services.appinfo.annotateCrashReport("EMCheckCompatibility",
  1.1930 +                                               AddonManager.checkCompatibility);
  1.1931 +        } catch (e) { }
  1.1932 +        this.addAddonsToCrashReporter();
  1.1933 +      }
  1.1934 +
  1.1935 +      try {
  1.1936 +        AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
  1.1937 +        for (let id in this.bootstrappedAddons) {
  1.1938 +          try {
  1.1939 +            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  1.1940 +            file.persistentDescriptor = this.bootstrappedAddons[id].descriptor;
  1.1941 +            let reason = BOOTSTRAP_REASONS.APP_STARTUP;
  1.1942 +            // Eventually set INSTALLED reason when a bootstrap addon
  1.1943 +            // is dropped in profile folder and automatically installed
  1.1944 +            if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
  1.1945 +                            .indexOf(id) !== -1)
  1.1946 +              reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
  1.1947 +            this.callBootstrapMethod(id, this.bootstrappedAddons[id].version,
  1.1948 +                                     this.bootstrappedAddons[id].type, file,
  1.1949 +                                     "startup", reason);
  1.1950 +          }
  1.1951 +          catch (e) {
  1.1952 +            logger.error("Failed to load bootstrap addon " + id + " from " +
  1.1953 +                  this.bootstrappedAddons[id].descriptor, e);
  1.1954 +          }
  1.1955 +        }
  1.1956 +        AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
  1.1957 +      }
  1.1958 +      catch (e) {
  1.1959 +        logger.error("bootstrap startup failed", e);
  1.1960 +        AddonManagerPrivate.recordException("XPI-BOOTSTRAP", "startup failed", e);
  1.1961 +      }
  1.1962 +
  1.1963 +      // Let these shutdown a little earlier when they still have access to most
  1.1964 +      // of XPCOM
  1.1965 +      Services.obs.addObserver({
  1.1966 +        observe: function shutdownObserver(aSubject, aTopic, aData) {
  1.1967 +          for (let id in XPIProvider.bootstrappedAddons) {
  1.1968 +            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  1.1969 +            file.persistentDescriptor = XPIProvider.bootstrappedAddons[id].descriptor;
  1.1970 +            XPIProvider.callBootstrapMethod(id, XPIProvider.bootstrappedAddons[id].version,
  1.1971 +                                            XPIProvider.bootstrappedAddons[id].type, file, "shutdown",
  1.1972 +                                            BOOTSTRAP_REASONS.APP_SHUTDOWN);
  1.1973 +          }
  1.1974 +          Services.obs.removeObserver(this, "quit-application-granted");
  1.1975 +        }
  1.1976 +      }, "quit-application-granted", false);
  1.1977 +
  1.1978 +      // Detect final-ui-startup for telemetry reporting
  1.1979 +      Services.obs.addObserver({
  1.1980 +        observe: function uiStartupObserver(aSubject, aTopic, aData) {
  1.1981 +          AddonManagerPrivate.recordTimestamp("XPI_finalUIStartup");
  1.1982 +          XPIProvider.runPhase = XPI_AFTER_UI_STARTUP;
  1.1983 +          Services.obs.removeObserver(this, "final-ui-startup");
  1.1984 +        }
  1.1985 +      }, "final-ui-startup", false);
  1.1986 +
  1.1987 +      AddonManagerPrivate.recordTimestamp("XPI_startup_end");
  1.1988 +
  1.1989 +      this.extensionsActive = true;
  1.1990 +      this.runPhase = XPI_BEFORE_UI_STARTUP;
  1.1991 +    }
  1.1992 +    catch (e) {
  1.1993 +      logger.error("startup failed", e);
  1.1994 +      AddonManagerPrivate.recordException("XPI", "startup failed", e);
  1.1995 +    }
  1.1996 +  },
  1.1997 +
  1.1998 +  /**
  1.1999 +   * Shuts down the database and releases all references.
  1.2000 +   * Return: Promise{integer} resolves / rejects with the result of
  1.2001 +   *                          flushing the XPI Database if it was loaded,
  1.2002 +   *                          0 otherwise.
  1.2003 +   */
  1.2004 +  shutdown: function XPI_shutdown() {
  1.2005 +    logger.debug("shutdown");
  1.2006 +
  1.2007 +    // Stop anything we were doing asynchronously
  1.2008 +    this.cancelAll();
  1.2009 +
  1.2010 +    this.bootstrappedAddons = {};
  1.2011 +    this.bootstrapScopes = {};
  1.2012 +    this.enabledAddons = null;
  1.2013 +    this.allAppGlobal = true;
  1.2014 +
  1.2015 +    this.inactiveAddonIDs = [];
  1.2016 +
  1.2017 +    // If there are pending operations then we must update the list of active
  1.2018 +    // add-ons
  1.2019 +    if (Prefs.getBoolPref(PREF_PENDING_OPERATIONS, false)) {
  1.2020 +      XPIDatabase.updateActiveAddons();
  1.2021 +      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
  1.2022 +                                 !XPIDatabase.writeAddonsList());
  1.2023 +    }
  1.2024 +
  1.2025 +    this.installs = null;
  1.2026 +    this.installLocations = null;
  1.2027 +    this.installLocationsByName = null;
  1.2028 +
  1.2029 +    // This is needed to allow xpcshell tests to simulate a restart
  1.2030 +    this.extensionsActive = false;
  1.2031 +
  1.2032 +    // Remove URI mappings again
  1.2033 +    delete this._uriMappings;
  1.2034 +
  1.2035 +    if (gLazyObjectsLoaded) {
  1.2036 +      let done = XPIDatabase.shutdown();
  1.2037 +      done.then(
  1.2038 +        ret => {
  1.2039 +          logger.debug("Notifying XPI shutdown observers");
  1.2040 +          Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
  1.2041 +        },
  1.2042 +        err => {
  1.2043 +          logger.debug("Notifying XPI shutdown observers");
  1.2044 +          this._shutdownError = err;
  1.2045 +          Services.obs.notifyObservers(null, "xpi-provider-shutdown", err);
  1.2046 +        }
  1.2047 +      );
  1.2048 +      return done;
  1.2049 +    }
  1.2050 +    else {
  1.2051 +      logger.debug("Notifying XPI shutdown observers");
  1.2052 +      Services.obs.notifyObservers(null, "xpi-provider-shutdown", null);
  1.2053 +    }
  1.2054 +  },
  1.2055 +
  1.2056 +  /**
  1.2057 +   * Applies any pending theme change to the preferences.
  1.2058 +   */
  1.2059 +  applyThemeChange: function XPI_applyThemeChange() {
  1.2060 +    if (!Prefs.getBoolPref(PREF_DSS_SWITCHPENDING, false))
  1.2061 +      return;
  1.2062 +
  1.2063 +    // Tell the Chrome Registry which Skin to select
  1.2064 +    try {
  1.2065 +      this.selectedSkin = Prefs.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  1.2066 +      Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
  1.2067 +                                 this.selectedSkin);
  1.2068 +      Services.prefs.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  1.2069 +      logger.debug("Changed skin to " + this.selectedSkin);
  1.2070 +      this.currentSkin = this.selectedSkin;
  1.2071 +    }
  1.2072 +    catch (e) {
  1.2073 +      logger.error("Error applying theme change", e);
  1.2074 +    }
  1.2075 +    Services.prefs.clearUserPref(PREF_DSS_SWITCHPENDING);
  1.2076 +  },
  1.2077 +
  1.2078 +  /**
  1.2079 +   * Shows the "Compatibility Updates" UI
  1.2080 +   */
  1.2081 +  showUpgradeUI: function XPI_showUpgradeUI() {
  1.2082 +    // Flip a flag to indicate that we interrupted startup with an interactive prompt
  1.2083 +    Services.startup.interrupted = true;
  1.2084 +
  1.2085 +    if (!Prefs.getBoolPref(PREF_SHOWN_SELECTION_UI, false)) {
  1.2086 +      // This *must* be modal as it has to block startup.
  1.2087 +      var features = "chrome,centerscreen,dialog,titlebar,modal";
  1.2088 +      Services.ww.openWindow(null, URI_EXTENSION_SELECT_DIALOG, "", features, null);
  1.2089 +      Services.prefs.setBoolPref(PREF_SHOWN_SELECTION_UI, true);
  1.2090 +    }
  1.2091 +    else {
  1.2092 +      var variant = Cc["@mozilla.org/variant;1"].
  1.2093 +                    createInstance(Ci.nsIWritableVariant);
  1.2094 +      variant.setFromVariant(this.inactiveAddonIDs);
  1.2095 +
  1.2096 +      // This *must* be modal as it has to block startup.
  1.2097 +      var features = "chrome,centerscreen,dialog,titlebar,modal";
  1.2098 +      var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  1.2099 +               getService(Ci.nsIWindowWatcher);
  1.2100 +      ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
  1.2101 +    }
  1.2102 +
  1.2103 +    // Ensure any changes to the add-ons list are flushed to disk
  1.2104 +    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
  1.2105 +                               !XPIDatabase.writeAddonsList());
  1.2106 +  },
  1.2107 +
  1.2108 +  /**
  1.2109 +   * Persists changes to XPIProvider.bootstrappedAddons to its store (a pref).
  1.2110 +   */
  1.2111 +  persistBootstrappedAddons: function XPI_persistBootstrappedAddons() {
  1.2112 +    // Experiments are disabled upon app load, so don't persist references.
  1.2113 +    let filtered = {};
  1.2114 +    for (let id in this.bootstrappedAddons) {
  1.2115 +      let entry = this.bootstrappedAddons[id];
  1.2116 +      if (entry.type == "experiment") {
  1.2117 +        continue;
  1.2118 +      }
  1.2119 +
  1.2120 +      filtered[id] = entry;
  1.2121 +    }
  1.2122 +
  1.2123 +    Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
  1.2124 +                               JSON.stringify(filtered));
  1.2125 +  },
  1.2126 +
  1.2127 +  /**
  1.2128 +   * Adds a list of currently active add-ons to the next crash report.
  1.2129 +   */
  1.2130 +  addAddonsToCrashReporter: function XPI_addAddonsToCrashReporter() {
  1.2131 +    if (!("nsICrashReporter" in Ci) ||
  1.2132 +        !(Services.appinfo instanceof Ci.nsICrashReporter))
  1.2133 +      return;
  1.2134 +
  1.2135 +    // In safe mode no add-ons are loaded so we should not include them in the
  1.2136 +    // crash report
  1.2137 +    if (Services.appinfo.inSafeMode)
  1.2138 +      return;
  1.2139 +
  1.2140 +    let data = this.enabledAddons;
  1.2141 +    for (let id in this.bootstrappedAddons) {
  1.2142 +      data += (data ? "," : "") + encodeURIComponent(id) + ":" +
  1.2143 +              encodeURIComponent(this.bootstrappedAddons[id].version);
  1.2144 +    }
  1.2145 +
  1.2146 +    try {
  1.2147 +      Services.appinfo.annotateCrashReport("Add-ons", data);
  1.2148 +    }
  1.2149 +    catch (e) { }
  1.2150 +
  1.2151 +    Cu.import("resource://gre/modules/TelemetryPing.jsm", {}).TelemetryPing.setAddOns(data);
  1.2152 +  },
  1.2153 +
  1.2154 +  /**
  1.2155 +   * Gets the add-on states for an install location.
  1.2156 +   * This function may be expensive because of the recursiveLastModifiedTime call.
  1.2157 +   *
  1.2158 +   * @param  location
  1.2159 +   *         The install location to retrieve the add-on states for
  1.2160 +   * @return a dictionary mapping add-on IDs to objects with a descriptor
  1.2161 +   *         property which contains the add-ons dir/file descriptor and an
  1.2162 +   *         mtime property which contains the add-on's last modified time as
  1.2163 +   *         the number of milliseconds since the epoch.
  1.2164 +   */
  1.2165 +  getAddonStates: function XPI_getAddonStates(aLocation) {
  1.2166 +    let addonStates = {};
  1.2167 +    for (let file of aLocation.addonLocations) {
  1.2168 +      let scanStarted = Date.now();
  1.2169 +      let id = aLocation.getIDForLocation(file);
  1.2170 +      let unpacked = 0;
  1.2171 +      let [modFile, modTime, items] = recursiveLastModifiedTime(file);
  1.2172 +      addonStates[id] = {
  1.2173 +        descriptor: file.persistentDescriptor,
  1.2174 +        mtime: modTime
  1.2175 +      };
  1.2176 +      try {
  1.2177 +        // get the install.rdf update time, if any
  1.2178 +        file.append(FILE_INSTALL_MANIFEST);
  1.2179 +        let rdfTime = file.lastModifiedTime;
  1.2180 +        addonStates[id].rdfTime = rdfTime;
  1.2181 +        unpacked = 1;
  1.2182 +      }
  1.2183 +      catch (e) { }
  1.2184 +      this._mostRecentlyModifiedFile[id] = modFile;
  1.2185 +      this.setTelemetry(id, "unpacked", unpacked);
  1.2186 +      this.setTelemetry(id, "location", aLocation.name);
  1.2187 +      this.setTelemetry(id, "scan_MS", Date.now() - scanStarted);
  1.2188 +      this.setTelemetry(id, "scan_items", items);
  1.2189 +    }
  1.2190 +
  1.2191 +    return addonStates;
  1.2192 +  },
  1.2193 +
  1.2194 +  /**
  1.2195 +   * Gets an array of install location states which uniquely describes all
  1.2196 +   * installed add-ons with the add-on's InstallLocation name and last modified
  1.2197 +   * time. This function may be expensive because of the getAddonStates() call.
  1.2198 +   *
  1.2199 +   * @return an array of add-on states for each install location. Each state
  1.2200 +   *         is an object with a name property holding the location's name and
  1.2201 +   *         an addons property holding the add-on states for the location
  1.2202 +   */
  1.2203 +  getInstallLocationStates: function XPI_getInstallLocationStates() {
  1.2204 +    let states = [];
  1.2205 +    this.installLocations.forEach(function(aLocation) {
  1.2206 +      let addons = aLocation.addonLocations;
  1.2207 +      if (addons.length == 0)
  1.2208 +        return;
  1.2209 +
  1.2210 +      let locationState = {
  1.2211 +        name: aLocation.name,
  1.2212 +        addons: this.getAddonStates(aLocation)
  1.2213 +      };
  1.2214 +
  1.2215 +      states.push(locationState);
  1.2216 +    }, this);
  1.2217 +    return states;
  1.2218 +  },
  1.2219 +
  1.2220 +  /**
  1.2221 +   * Check the staging directories of install locations for any add-ons to be
  1.2222 +   * installed or add-ons to be uninstalled.
  1.2223 +   *
  1.2224 +   * @param  aManifests
  1.2225 +   *         A dictionary to add detected install manifests to for the purpose
  1.2226 +   *         of passing through updated compatibility information
  1.2227 +   * @return true if an add-on was installed or uninstalled
  1.2228 +   */
  1.2229 +  processPendingFileChanges: function XPI_processPendingFileChanges(aManifests) {
  1.2230 +    let changed = false;
  1.2231 +    this.installLocations.forEach(function(aLocation) {
  1.2232 +      aManifests[aLocation.name] = {};
  1.2233 +      // We can't install or uninstall anything in locked locations
  1.2234 +      if (aLocation.locked)
  1.2235 +        return;
  1.2236 +
  1.2237 +      let stagedXPIDir = aLocation.getXPIStagingDir();
  1.2238 +      let stagingDir = aLocation.getStagingDir();
  1.2239 +
  1.2240 +      if (stagedXPIDir.exists() && stagedXPIDir.isDirectory()) {
  1.2241 +        let entries = stagedXPIDir.directoryEntries
  1.2242 +                                  .QueryInterface(Ci.nsIDirectoryEnumerator);
  1.2243 +        while (entries.hasMoreElements()) {
  1.2244 +          let stageDirEntry = entries.nextFile;
  1.2245 +
  1.2246 +          if (!stageDirEntry.isDirectory()) {
  1.2247 +            logger.warn("Ignoring file in XPI staging directory: " + stageDirEntry.path);
  1.2248 +            continue;
  1.2249 +          }
  1.2250 +
  1.2251 +          // Find the last added XPI file in the directory
  1.2252 +          let stagedXPI = null;
  1.2253 +          var xpiEntries = stageDirEntry.directoryEntries
  1.2254 +                                        .QueryInterface(Ci.nsIDirectoryEnumerator);
  1.2255 +          while (xpiEntries.hasMoreElements()) {
  1.2256 +            let file = xpiEntries.nextFile;
  1.2257 +            if (file.isDirectory())
  1.2258 +              continue;
  1.2259 +
  1.2260 +            let extension = file.leafName;
  1.2261 +            extension = extension.substring(extension.length - 4);
  1.2262 +
  1.2263 +            if (extension != ".xpi" && extension != ".jar")
  1.2264 +              continue;
  1.2265 +
  1.2266 +            stagedXPI = file;
  1.2267 +          }
  1.2268 +          xpiEntries.close();
  1.2269 +
  1.2270 +          if (!stagedXPI)
  1.2271 +            continue;
  1.2272 +
  1.2273 +          let addon = null;
  1.2274 +          try {
  1.2275 +            addon = loadManifestFromZipFile(stagedXPI);
  1.2276 +          }
  1.2277 +          catch (e) {
  1.2278 +            logger.error("Unable to read add-on manifest from " + stagedXPI.path, e);
  1.2279 +            continue;
  1.2280 +          }
  1.2281 +
  1.2282 +          logger.debug("Migrating staged install of " + addon.id + " in " + aLocation.name);
  1.2283 +
  1.2284 +          if (addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
  1.2285 +            let targetDir = stagingDir.clone();
  1.2286 +            targetDir.append(addon.id);
  1.2287 +            try {
  1.2288 +              targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
  1.2289 +            }
  1.2290 +            catch (e) {
  1.2291 +              logger.error("Failed to create staging directory for add-on " + addon.id, e);
  1.2292 +              continue;
  1.2293 +            }
  1.2294 +
  1.2295 +            try {
  1.2296 +              ZipUtils.extractFiles(stagedXPI, targetDir);
  1.2297 +            }
  1.2298 +            catch (e) {
  1.2299 +              logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
  1.2300 +                    aLocation.name, e);
  1.2301 +            }
  1.2302 +          }
  1.2303 +          else {
  1.2304 +            try {
  1.2305 +              stagedXPI.moveTo(stagingDir, addon.id + ".xpi");
  1.2306 +            }
  1.2307 +            catch (e) {
  1.2308 +              logger.error("Failed to move staged XPI for add-on " + addon.id + " in " +
  1.2309 +                    aLocation.name, e);
  1.2310 +            }
  1.2311 +          }
  1.2312 +        }
  1.2313 +        entries.close();
  1.2314 +      }
  1.2315 +
  1.2316 +      if (stagedXPIDir.exists()) {
  1.2317 +        try {
  1.2318 +          recursiveRemove(stagedXPIDir);
  1.2319 +        }
  1.2320 +        catch (e) {
  1.2321 +          // Non-critical, just saves some perf on startup if we clean this up.
  1.2322 +          logger.debug("Error removing XPI staging dir " + stagedXPIDir.path, e);
  1.2323 +        }
  1.2324 +      }
  1.2325 +
  1.2326 +      try {
  1.2327 +        if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
  1.2328 +          return;
  1.2329 +      }
  1.2330 +      catch (e) {
  1.2331 +        logger.warn("Failed to find staging directory", e);
  1.2332 +        return;
  1.2333 +      }
  1.2334 +
  1.2335 +      let seenFiles = [];
  1.2336 +      // Use a snapshot of the directory contents to avoid possible issues with
  1.2337 +      // iterating over a directory while removing files from it (the YAFFS2
  1.2338 +      // embedded filesystem has this issue, see bug 772238), and to remove
  1.2339 +      // normal files before their resource forks on OSX (see bug 733436).
  1.2340 +      let stagingDirEntries = getDirectoryEntries(stagingDir, true);
  1.2341 +      for (let stageDirEntry of stagingDirEntries) {
  1.2342 +        let id = stageDirEntry.leafName;
  1.2343 +
  1.2344 +        let isDir;
  1.2345 +        try {
  1.2346 +          isDir = stageDirEntry.isDirectory();
  1.2347 +        }
  1.2348 +        catch (e if e.result == Cr.NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
  1.2349 +          // If the file has already gone away then don't worry about it, this
  1.2350 +          // can happen on OSX where the resource fork is automatically moved
  1.2351 +          // with the data fork for the file. See bug 733436.
  1.2352 +          continue;
  1.2353 +        }
  1.2354 +
  1.2355 +        if (!isDir) {
  1.2356 +          if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
  1.2357 +            id = id.substring(0, id.length - 4);
  1.2358 +          }
  1.2359 +          else {
  1.2360 +            if (id.substring(id.length - 5).toLowerCase() != ".json") {
  1.2361 +              logger.warn("Ignoring file: " + stageDirEntry.path);
  1.2362 +              seenFiles.push(stageDirEntry.leafName);
  1.2363 +            }
  1.2364 +            continue;
  1.2365 +          }
  1.2366 +        }
  1.2367 +
  1.2368 +        // Check that the directory's name is a valid ID.
  1.2369 +        if (!gIDTest.test(id)) {
  1.2370 +          logger.warn("Ignoring directory whose name is not a valid add-on ID: " +
  1.2371 +               stageDirEntry.path);
  1.2372 +          seenFiles.push(stageDirEntry.leafName);
  1.2373 +          continue;
  1.2374 +        }
  1.2375 +
  1.2376 +        changed = true;
  1.2377 +
  1.2378 +        if (isDir) {
  1.2379 +          // Check if the directory contains an install manifest.
  1.2380 +          let manifest = stageDirEntry.clone();
  1.2381 +          manifest.append(FILE_INSTALL_MANIFEST);
  1.2382 +
  1.2383 +          // If the install manifest doesn't exist uninstall this add-on in this
  1.2384 +          // install location.
  1.2385 +          if (!manifest.exists()) {
  1.2386 +            logger.debug("Processing uninstall of " + id + " in " + aLocation.name);
  1.2387 +            try {
  1.2388 +              aLocation.uninstallAddon(id);
  1.2389 +              seenFiles.push(stageDirEntry.leafName);
  1.2390 +            }
  1.2391 +            catch (e) {
  1.2392 +              logger.error("Failed to uninstall add-on " + id + " in " + aLocation.name, e);
  1.2393 +            }
  1.2394 +            // The file check later will spot the removal and cleanup the database
  1.2395 +            continue;
  1.2396 +          }
  1.2397 +        }
  1.2398 +
  1.2399 +        aManifests[aLocation.name][id] = null;
  1.2400 +        let existingAddonID = id;
  1.2401 +
  1.2402 +        let jsonfile = stagingDir.clone();
  1.2403 +        jsonfile.append(id + ".json");
  1.2404 +
  1.2405 +        try {
  1.2406 +          aManifests[aLocation.name][id] = loadManifestFromFile(stageDirEntry);
  1.2407 +        }
  1.2408 +        catch (e) {
  1.2409 +          logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e);
  1.2410 +          // This add-on can't be installed so just remove it now
  1.2411 +          seenFiles.push(stageDirEntry.leafName);
  1.2412 +          seenFiles.push(jsonfile.leafName);
  1.2413 +          continue;
  1.2414 +        }
  1.2415 +
  1.2416 +        // Check for a cached metadata for this add-on, it may contain updated
  1.2417 +        // compatibility information
  1.2418 +        if (jsonfile.exists()) {
  1.2419 +          logger.debug("Found updated metadata for " + id + " in " + aLocation.name);
  1.2420 +          let fis = Cc["@mozilla.org/network/file-input-stream;1"].
  1.2421 +                       createInstance(Ci.nsIFileInputStream);
  1.2422 +          let json = Cc["@mozilla.org/dom/json;1"].
  1.2423 +                     createInstance(Ci.nsIJSON);
  1.2424 +
  1.2425 +          try {
  1.2426 +            fis.init(jsonfile, -1, 0, 0);
  1.2427 +            let metadata = json.decodeFromStream(fis, jsonfile.fileSize);
  1.2428 +            aManifests[aLocation.name][id].importMetadata(metadata);
  1.2429 +          }
  1.2430 +          catch (e) {
  1.2431 +            // If some data can't be recovered from the cached metadata then it
  1.2432 +            // is unlikely to be a problem big enough to justify throwing away
  1.2433 +            // the install, just log and error and continue
  1.2434 +            logger.error("Unable to read metadata from " + jsonfile.path, e);
  1.2435 +          }
  1.2436 +          finally {
  1.2437 +            fis.close();
  1.2438 +          }
  1.2439 +        }
  1.2440 +        seenFiles.push(jsonfile.leafName);
  1.2441 +
  1.2442 +        existingAddonID = aManifests[aLocation.name][id].existingAddonID || id;
  1.2443 +
  1.2444 +        var oldBootstrap = null;
  1.2445 +        logger.debug("Processing install of " + id + " in " + aLocation.name);
  1.2446 +        if (existingAddonID in this.bootstrappedAddons) {
  1.2447 +          try {
  1.2448 +            var existingAddon = aLocation.getLocationForID(existingAddonID);
  1.2449 +            if (this.bootstrappedAddons[existingAddonID].descriptor ==
  1.2450 +                existingAddon.persistentDescriptor) {
  1.2451 +              oldBootstrap = this.bootstrappedAddons[existingAddonID];
  1.2452 +
  1.2453 +              // We'll be replacing a currently active bootstrapped add-on so
  1.2454 +              // call its uninstall method
  1.2455 +              let newVersion = aManifests[aLocation.name][id].version;
  1.2456 +              let oldVersion = oldBootstrap.version;
  1.2457 +              let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
  1.2458 +                                    BOOTSTRAP_REASONS.ADDON_UPGRADE :
  1.2459 +                                    BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
  1.2460 +
  1.2461 +              this.callBootstrapMethod(existingAddonID, oldBootstrap.version,
  1.2462 +                                       oldBootstrap.type, existingAddon, "uninstall", uninstallReason,
  1.2463 +                                       { newVersion: newVersion });
  1.2464 +              this.unloadBootstrapScope(existingAddonID);
  1.2465 +              flushStartupCache();
  1.2466 +            }
  1.2467 +          }
  1.2468 +          catch (e) {
  1.2469 +          }
  1.2470 +        }
  1.2471 +
  1.2472 +        try {
  1.2473 +          var addonInstallLocation = aLocation.installAddon(id, stageDirEntry,
  1.2474 +                                                            existingAddonID);
  1.2475 +          if (aManifests[aLocation.name][id])
  1.2476 +            aManifests[aLocation.name][id]._sourceBundle = addonInstallLocation;
  1.2477 +        }
  1.2478 +        catch (e) {
  1.2479 +          logger.error("Failed to install staged add-on " + id + " in " + aLocation.name,
  1.2480 +                e);
  1.2481 +          // Re-create the staged install
  1.2482 +          AddonInstall.createStagedInstall(aLocation, stageDirEntry,
  1.2483 +                                           aManifests[aLocation.name][id]);
  1.2484 +          // Make sure not to delete the cached manifest json file
  1.2485 +          seenFiles.pop();
  1.2486 +
  1.2487 +          delete aManifests[aLocation.name][id];
  1.2488 +
  1.2489 +          if (oldBootstrap) {
  1.2490 +            // Re-install the old add-on
  1.2491 +            this.callBootstrapMethod(existingAddonID, oldBootstrap.version,
  1.2492 +                                     oldBootstrap.type, existingAddon, "install",
  1.2493 +                                     BOOTSTRAP_REASONS.ADDON_INSTALL);
  1.2494 +          }
  1.2495 +          continue;
  1.2496 +        }
  1.2497 +      }
  1.2498 +
  1.2499 +      try {
  1.2500 +        aLocation.cleanStagingDir(seenFiles);
  1.2501 +      }
  1.2502 +      catch (e) {
  1.2503 +        // Non-critical, just saves some perf on startup if we clean this up.
  1.2504 +        logger.debug("Error cleaning staging dir " + stagingDir.path, e);
  1.2505 +      }
  1.2506 +    }, this);
  1.2507 +    return changed;
  1.2508 +  },
  1.2509 +
  1.2510 +  /**
  1.2511 +   * Installs any add-ons located in the extensions directory of the
  1.2512 +   * application's distribution specific directory into the profile unless a
  1.2513 +   * newer version already exists or the user has previously uninstalled the
  1.2514 +   * distributed add-on.
  1.2515 +   *
  1.2516 +   * @param  aManifests
  1.2517 +   *         A dictionary to add new install manifests to to save having to
  1.2518 +   *         reload them later
  1.2519 +   * @return true if any new add-ons were installed
  1.2520 +   */
  1.2521 +  installDistributionAddons: function XPI_installDistributionAddons(aManifests) {
  1.2522 +    let distroDir;
  1.2523 +    try {
  1.2524 +      distroDir = FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS]);
  1.2525 +    }
  1.2526 +    catch (e) {
  1.2527 +      return false;
  1.2528 +    }
  1.2529 +
  1.2530 +    if (!distroDir.exists())
  1.2531 +      return false;
  1.2532 +
  1.2533 +    if (!distroDir.isDirectory())
  1.2534 +      return false;
  1.2535 +
  1.2536 +    let changed = false;
  1.2537 +    let profileLocation = this.installLocationsByName[KEY_APP_PROFILE];
  1.2538 +
  1.2539 +    let entries = distroDir.directoryEntries
  1.2540 +                           .QueryInterface(Ci.nsIDirectoryEnumerator);
  1.2541 +    let entry;
  1.2542 +    while ((entry = entries.nextFile)) {
  1.2543 +
  1.2544 +      let id = entry.leafName;
  1.2545 +
  1.2546 +      if (entry.isFile()) {
  1.2547 +        if (id.substring(id.length - 4).toLowerCase() == ".xpi") {
  1.2548 +          id = id.substring(0, id.length - 4);
  1.2549 +        }
  1.2550 +        else {
  1.2551 +          logger.debug("Ignoring distribution add-on that isn't an XPI: " + entry.path);
  1.2552 +          continue;
  1.2553 +        }
  1.2554 +      }
  1.2555 +      else if (!entry.isDirectory()) {
  1.2556 +        logger.debug("Ignoring distribution add-on that isn't a file or directory: " +
  1.2557 +            entry.path);
  1.2558 +        continue;
  1.2559 +      }
  1.2560 +
  1.2561 +      if (!gIDTest.test(id)) {
  1.2562 +        logger.debug("Ignoring distribution add-on whose name is not a valid add-on ID: " +
  1.2563 +            entry.path);
  1.2564 +        continue;
  1.2565 +      }
  1.2566 +
  1.2567 +      let addon;
  1.2568 +      try {
  1.2569 +        addon = loadManifestFromFile(entry);
  1.2570 +      }
  1.2571 +      catch (e) {
  1.2572 +        logger.warn("File entry " + entry.path + " contains an invalid add-on", e);
  1.2573 +        continue;
  1.2574 +      }
  1.2575 +
  1.2576 +      if (addon.id != id) {
  1.2577 +        logger.warn("File entry " + entry.path + " contains an add-on with an " +
  1.2578 +             "incorrect ID")
  1.2579 +        continue;
  1.2580 +      }
  1.2581 +
  1.2582 +      let existingEntry = null;
  1.2583 +      try {
  1.2584 +        existingEntry = profileLocation.getLocationForID(id);
  1.2585 +      }
  1.2586 +      catch (e) {
  1.2587 +      }
  1.2588 +
  1.2589 +      if (existingEntry) {
  1.2590 +        let existingAddon;
  1.2591 +        try {
  1.2592 +          existingAddon = loadManifestFromFile(existingEntry);
  1.2593 +
  1.2594 +          if (Services.vc.compare(addon.version, existingAddon.version) <= 0)
  1.2595 +            continue;
  1.2596 +        }
  1.2597 +        catch (e) {
  1.2598 +          // Bad add-on in the profile so just proceed and install over the top
  1.2599 +          logger.warn("Profile contains an add-on with a bad or missing install " +
  1.2600 +               "manifest at " + existingEntry.path + ", overwriting", e);
  1.2601 +        }
  1.2602 +      }
  1.2603 +      else if (Prefs.getBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, false)) {
  1.2604 +        continue;
  1.2605 +      }
  1.2606 +
  1.2607 +      // Install the add-on
  1.2608 +      try {
  1.2609 +        profileLocation.installAddon(id, entry, null, true);
  1.2610 +        logger.debug("Installed distribution add-on " + id);
  1.2611 +
  1.2612 +        Services.prefs.setBoolPref(PREF_BRANCH_INSTALLED_ADDON + id, true)
  1.2613 +
  1.2614 +        // aManifests may contain a copy of a newly installed add-on's manifest
  1.2615 +        // and we'll have overwritten that so instead cache our install manifest
  1.2616 +        // which will later be put into the database in processFileChanges
  1.2617 +        if (!(KEY_APP_PROFILE in aManifests))
  1.2618 +          aManifests[KEY_APP_PROFILE] = {};
  1.2619 +        aManifests[KEY_APP_PROFILE][id] = addon;
  1.2620 +        changed = true;
  1.2621 +      }
  1.2622 +      catch (e) {
  1.2623 +        logger.error("Failed to install distribution add-on " + entry.path, e);
  1.2624 +      }
  1.2625 +    }
  1.2626 +
  1.2627 +    entries.close();
  1.2628 +
  1.2629 +    return changed;
  1.2630 +  },
  1.2631 +
  1.2632 +  /**
  1.2633 +   * Compares the add-ons that are currently installed to those that were
  1.2634 +   * known to be installed when the application last ran and applies any
  1.2635 +   * changes found to the database. Also sends "startupcache-invalidate" signal to
  1.2636 +   * observerservice if it detects that data may have changed.
  1.2637 +   *
  1.2638 +   * @param  aState
  1.2639 +   *         The array of current install location states
  1.2640 +   * @param  aManifests
  1.2641 +   *         A dictionary of cached AddonInstalls for add-ons that have been
  1.2642 +   *         installed
  1.2643 +   * @param  aUpdateCompatibility
  1.2644 +   *         true to update add-ons appDisabled property when the application
  1.2645 +   *         version has changed
  1.2646 +   * @param  aOldAppVersion
  1.2647 +   *         The version of the application last run with this profile or null
  1.2648 +   *         if it is a new profile or the version is unknown
  1.2649 +   * @param  aOldPlatformVersion
  1.2650 +   *         The version of the platform last run with this profile or null
  1.2651 +   *         if it is a new profile or the version is unknown
  1.2652 +   * @return a boolean indicating if a change requiring flushing the caches was
  1.2653 +   *         detected
  1.2654 +   */
  1.2655 +  processFileChanges: function XPI_processFileChanges(aState, aManifests,
  1.2656 +                                                      aUpdateCompatibility,
  1.2657 +                                                      aOldAppVersion,
  1.2658 +                                                      aOldPlatformVersion) {
  1.2659 +    let visibleAddons = {};
  1.2660 +    let oldBootstrappedAddons = this.bootstrappedAddons;
  1.2661 +    this.bootstrappedAddons = {};
  1.2662 +
  1.2663 +    /**
  1.2664 +     * Updates an add-on's metadata and determines if a restart of the
  1.2665 +     * application is necessary. This is called when either the add-on's
  1.2666 +     * install directory path or last modified time has changed.
  1.2667 +     *
  1.2668 +     * @param  aInstallLocation
  1.2669 +     *         The install location containing the add-on
  1.2670 +     * @param  aOldAddon
  1.2671 +     *         The AddonInternal as it appeared the last time the application
  1.2672 +     *         ran
  1.2673 +     * @param  aAddonState
  1.2674 +     *         The new state of the add-on
  1.2675 +     * @return a boolean indicating if flushing caches is required to complete
  1.2676 +     *         changing this add-on
  1.2677 +     */
  1.2678 +    function updateMetadata(aInstallLocation, aOldAddon, aAddonState) {
  1.2679 +      logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
  1.2680 +
  1.2681 +      // Check if there is an updated install manifest for this add-on
  1.2682 +      let newAddon = aManifests[aInstallLocation.name][aOldAddon.id];
  1.2683 +
  1.2684 +      try {
  1.2685 +        // If not load it
  1.2686 +        if (!newAddon) {
  1.2687 +          let file = aInstallLocation.getLocationForID(aOldAddon.id);
  1.2688 +          newAddon = loadManifestFromFile(file);
  1.2689 +          applyBlocklistChanges(aOldAddon, newAddon);
  1.2690 +
  1.2691 +          // Carry over any pendingUninstall state to add-ons modified directly
  1.2692 +          // in the profile. This is important when the attempt to remove the
  1.2693 +          // add-on in processPendingFileChanges failed and caused an mtime
  1.2694 +          // change to the add-ons files.
  1.2695 +          newAddon.pendingUninstall = aOldAddon.pendingUninstall;
  1.2696 +        }
  1.2697 +
  1.2698 +        // The ID in the manifest that was loaded must match the ID of the old
  1.2699 +        // add-on.
  1.2700 +        if (newAddon.id != aOldAddon.id)
  1.2701 +          throw new Error("Incorrect id in install manifest");
  1.2702 +      }
  1.2703 +      catch (e) {
  1.2704 +        logger.warn("Add-on is invalid", e);
  1.2705 +        XPIDatabase.removeAddonMetadata(aOldAddon);
  1.2706 +        if (!aInstallLocation.locked)
  1.2707 +          aInstallLocation.uninstallAddon(aOldAddon.id);
  1.2708 +        else
  1.2709 +          logger.warn("Could not uninstall invalid item from locked install location");
  1.2710 +        // If this was an active add-on then we must force a restart
  1.2711 +        if (aOldAddon.active)
  1.2712 +          return true;
  1.2713 +
  1.2714 +        return false;
  1.2715 +      }
  1.2716 +
  1.2717 +      // Set the additional properties on the new AddonInternal
  1.2718 +      newAddon._installLocation = aInstallLocation;
  1.2719 +      newAddon.updateDate = aAddonState.mtime;
  1.2720 +      newAddon.visible = !(newAddon.id in visibleAddons);
  1.2721 +
  1.2722 +      // Update the database
  1.2723 +      let newDBAddon = XPIDatabase.updateAddonMetadata(aOldAddon, newAddon,
  1.2724 +                                                       aAddonState.descriptor);
  1.2725 +      if (newDBAddon.visible) {
  1.2726 +        visibleAddons[newDBAddon.id] = newDBAddon;
  1.2727 +        // Remember add-ons that were changed during startup
  1.2728 +        AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
  1.2729 +                                             newDBAddon.id);
  1.2730 +
  1.2731 +        // If this was the active theme and it is now disabled then enable the
  1.2732 +        // default theme
  1.2733 +        if (aOldAddon.active && isAddonDisabled(newDBAddon))
  1.2734 +          XPIProvider.enableDefaultTheme();
  1.2735 +
  1.2736 +        // If the new add-on is bootstrapped and active then call its install method
  1.2737 +        if (newDBAddon.active && newDBAddon.bootstrap) {
  1.2738 +          // Startup cache must be flushed before calling the bootstrap script
  1.2739 +          flushStartupCache();
  1.2740 +
  1.2741 +          let installReason = Services.vc.compare(aOldAddon.version, newDBAddon.version) < 0 ?
  1.2742 +                              BOOTSTRAP_REASONS.ADDON_UPGRADE :
  1.2743 +                              BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
  1.2744 +
  1.2745 +          let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  1.2746 +          file.persistentDescriptor = aAddonState.descriptor;
  1.2747 +          XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version,
  1.2748 +                                          newDBAddon.type, file, "install",
  1.2749 +                                          installReason, { oldVersion: aOldAddon.version });
  1.2750 +          return false;
  1.2751 +        }
  1.2752 +
  1.2753 +        return true;
  1.2754 +      }
  1.2755 +
  1.2756 +      return false;
  1.2757 +    }
  1.2758 +
  1.2759 +    /**
  1.2760 +     * Updates an add-on's descriptor for when the add-on has moved in the
  1.2761 +     * filesystem but hasn't changed in any other way.
  1.2762 +     *
  1.2763 +     * @param  aInstallLocation
  1.2764 +     *         The install location containing the add-on
  1.2765 +     * @param  aOldAddon
  1.2766 +     *         The AddonInternal as it appeared the last time the application
  1.2767 +     *         ran
  1.2768 +     * @param  aAddonState
  1.2769 +     *         The new state of the add-on
  1.2770 +     * @return a boolean indicating if flushing caches is required to complete
  1.2771 +     *         changing this add-on
  1.2772 +     */
  1.2773 +    function updateDescriptor(aInstallLocation, aOldAddon, aAddonState) {
  1.2774 +      logger.debug("Add-on " + aOldAddon.id + " moved to " + aAddonState.descriptor);
  1.2775 +
  1.2776 +      aOldAddon.descriptor = aAddonState.descriptor;
  1.2777 +      aOldAddon.visible = !(aOldAddon.id in visibleAddons);
  1.2778 +      XPIDatabase.saveChanges();
  1.2779 +
  1.2780 +      if (aOldAddon.visible) {
  1.2781 +        visibleAddons[aOldAddon.id] = aOldAddon;
  1.2782 +
  1.2783 +        if (aOldAddon.bootstrap && aOldAddon.active) {
  1.2784 +          let bootstrap = oldBootstrappedAddons[aOldAddon.id];
  1.2785 +          bootstrap.descriptor = aAddonState.descriptor;
  1.2786 +          XPIProvider.bootstrappedAddons[aOldAddon.id] = bootstrap;
  1.2787 +        }
  1.2788 +
  1.2789 +        return true;
  1.2790 +      }
  1.2791 +
  1.2792 +      return false;
  1.2793 +    }
  1.2794 +
  1.2795 +    /**
  1.2796 +     * Called when no change has been detected for an add-on's metadata. The
  1.2797 +     * add-on may have become visible due to other add-ons being removed or
  1.2798 +     * the add-on may need to be updated when the application version has
  1.2799 +     * changed.
  1.2800 +     *
  1.2801 +     * @param  aInstallLocation
  1.2802 +     *         The install location containing the add-on
  1.2803 +     * @param  aOldAddon
  1.2804 +     *         The AddonInternal as it appeared the last time the application
  1.2805 +     *         ran
  1.2806 +     * @param  aAddonState
  1.2807 +     *         The new state of the add-on
  1.2808 +     * @return a boolean indicating if flushing caches is required to complete
  1.2809 +     *         changing this add-on
  1.2810 +     */
  1.2811 +    function updateVisibilityAndCompatibility(aInstallLocation, aOldAddon,
  1.2812 +                                              aAddonState) {
  1.2813 +      let changed = false;
  1.2814 +
  1.2815 +      // This add-ons metadata has not changed but it may have become visible
  1.2816 +      if (!(aOldAddon.id in visibleAddons)) {
  1.2817 +        visibleAddons[aOldAddon.id] = aOldAddon;
  1.2818 +
  1.2819 +        if (!aOldAddon.visible) {
  1.2820 +          // Remember add-ons that were changed during startup.
  1.2821 +          AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
  1.2822 +                                               aOldAddon.id);
  1.2823 +          XPIDatabase.makeAddonVisible(aOldAddon);
  1.2824 +
  1.2825 +          if (aOldAddon.bootstrap) {
  1.2826 +            // The add-on is bootstrappable so call its install script
  1.2827 +            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  1.2828 +            file.persistentDescriptor = aAddonState.descriptor;
  1.2829 +            XPIProvider.callBootstrapMethod(aOldAddon.id, aOldAddon.version, aOldAddon.type, file,
  1.2830 +                                            "install",
  1.2831 +                                            BOOTSTRAP_REASONS.ADDON_INSTALL);
  1.2832 +
  1.2833 +            // If it should be active then mark it as active otherwise unload
  1.2834 +            // its scope
  1.2835 +            if (!isAddonDisabled(aOldAddon)) {
  1.2836 +              XPIDatabase.updateAddonActive(aOldAddon, true);
  1.2837 +            }
  1.2838 +            else {
  1.2839 +              XPIProvider.unloadBootstrapScope(newAddon.id);
  1.2840 +            }
  1.2841 +          }
  1.2842 +          else {
  1.2843 +            // Otherwise a restart is necessary
  1.2844 +            changed = true;
  1.2845 +          }
  1.2846 +        }
  1.2847 +      }
  1.2848 +
  1.2849 +      // App version changed, we may need to update the appDisabled property.
  1.2850 +      if (aUpdateCompatibility) {
  1.2851 +        let wasDisabled = isAddonDisabled(aOldAddon);
  1.2852 +        let wasAppDisabled = aOldAddon.appDisabled;
  1.2853 +        let wasUserDisabled = aOldAddon.userDisabled;
  1.2854 +        let wasSoftDisabled = aOldAddon.softDisabled;
  1.2855 +
  1.2856 +        // This updates the addon's JSON cached data in place
  1.2857 +        applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
  1.2858 +                              aOldPlatformVersion);
  1.2859 +        aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
  1.2860 +
  1.2861 +        let isDisabled = isAddonDisabled(aOldAddon);
  1.2862 +
  1.2863 +        // If either property has changed update the database.
  1.2864 +        if (wasAppDisabled != aOldAddon.appDisabled ||
  1.2865 +            wasUserDisabled != aOldAddon.userDisabled ||
  1.2866 +            wasSoftDisabled != aOldAddon.softDisabled) {
  1.2867 +          logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " +
  1.2868 +              aOldAddon.appDisabled + ", userDisabled state to " +
  1.2869 +              aOldAddon.userDisabled + " and softDisabled state to " +
  1.2870 +              aOldAddon.softDisabled);
  1.2871 +          XPIDatabase.saveChanges();
  1.2872 +        }
  1.2873 +
  1.2874 +        // If this is a visible add-on and it has changed disabled state then we
  1.2875 +        // may need a restart or to update the bootstrap list.
  1.2876 +        if (aOldAddon.visible && wasDisabled != isDisabled) {
  1.2877 +          // Remember add-ons that became disabled or enabled by the application
  1.2878 +          // change
  1.2879 +          let change = isDisabled ? AddonManager.STARTUP_CHANGE_DISABLED
  1.2880 +                                  : AddonManager.STARTUP_CHANGE_ENABLED;
  1.2881 +          AddonManagerPrivate.addStartupChange(change, aOldAddon.id);
  1.2882 +          if (aOldAddon.bootstrap) {
  1.2883 +            // Update the add-ons active state
  1.2884 +            XPIDatabase.updateAddonActive(aOldAddon, !isDisabled);
  1.2885 +          }
  1.2886 +          else {
  1.2887 +            changed = true;
  1.2888 +          }
  1.2889 +        }
  1.2890 +      }
  1.2891 +
  1.2892 +      if (aOldAddon.visible && aOldAddon.active && aOldAddon.bootstrap) {
  1.2893 +        XPIProvider.bootstrappedAddons[aOldAddon.id] = {
  1.2894 +          version: aOldAddon.version,
  1.2895 +          type: aOldAddon.type,
  1.2896 +          descriptor: aAddonState.descriptor
  1.2897 +        };
  1.2898 +      }
  1.2899 +
  1.2900 +      return changed;
  1.2901 +    }
  1.2902 +
  1.2903 +    /**
  1.2904 +     * Called when an add-on has been removed.
  1.2905 +     *
  1.2906 +     * @param  aOldAddon
  1.2907 +     *         The AddonInternal as it appeared the last time the application
  1.2908 +     *         ran
  1.2909 +     * @return a boolean indicating if flushing caches is required to complete
  1.2910 +     *         changing this add-on
  1.2911 +     */
  1.2912 +    function removeMetadata(aOldAddon) {
  1.2913 +      // This add-on has disappeared
  1.2914 +      logger.debug("Add-on " + aOldAddon.id + " removed from " + aOldAddon.location);
  1.2915 +      XPIDatabase.removeAddonMetadata(aOldAddon);
  1.2916 +
  1.2917 +      // Remember add-ons that were uninstalled during startup
  1.2918 +      if (aOldAddon.visible) {
  1.2919 +        AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED,
  1.2920 +                                             aOldAddon.id);
  1.2921 +      }
  1.2922 +      else if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
  1.2923 +                           .indexOf(aOldAddon.id) != -1) {
  1.2924 +        AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
  1.2925 +                                             aOldAddon.id);
  1.2926 +      }
  1.2927 +
  1.2928 +      if (aOldAddon.active) {
  1.2929 +        // Enable the default theme if the previously active theme has been
  1.2930 +        // removed
  1.2931 +        if (aOldAddon.type == "theme")
  1.2932 +          XPIProvider.enableDefaultTheme();
  1.2933 +
  1.2934 +        return true;
  1.2935 +      }
  1.2936 +
  1.2937 +      return false;
  1.2938 +    }
  1.2939 +
  1.2940 +    /**
  1.2941 +     * Called to add the metadata for an add-on in one of the install locations
  1.2942 +     * to the database. This can be called in three different cases. Either an
  1.2943 +     * add-on has been dropped into the location from outside of Firefox, or
  1.2944 +     * an add-on has been installed through the application, or the database
  1.2945 +     * has been upgraded or become corrupt and add-on data has to be reloaded
  1.2946 +     * into it.
  1.2947 +     *
  1.2948 +     * @param  aInstallLocation
  1.2949 +     *         The install location containing the add-on
  1.2950 +     * @param  aId
  1.2951 +     *         The ID of the add-on
  1.2952 +     * @param  aAddonState
  1.2953 +     *         The new state of the add-on
  1.2954 +     * @param  aMigrateData
  1.2955 +     *         If during startup the database had to be upgraded this will
  1.2956 +     *         contain data that used to be held about this add-on
  1.2957 +     * @return a boolean indicating if flushing caches is required to complete
  1.2958 +     *         changing this add-on
  1.2959 +     */
  1.2960 +    function addMetadata(aInstallLocation, aId, aAddonState, aMigrateData) {
  1.2961 +      logger.debug("New add-on " + aId + " installed in " + aInstallLocation.name);
  1.2962 +
  1.2963 +      let newAddon = null;
  1.2964 +      let sameVersion = false;
  1.2965 +      // Check the updated manifests lists for the install location, If there
  1.2966 +      // is no manifest for the add-on ID then newAddon will be undefined
  1.2967 +      if (aInstallLocation.name in aManifests)
  1.2968 +        newAddon = aManifests[aInstallLocation.name][aId];
  1.2969 +
  1.2970 +      // If we had staged data for this add-on or we aren't recovering from a
  1.2971 +      // corrupt database and we don't have migration data for this add-on then
  1.2972 +      // this must be a new install.
  1.2973 +      let isNewInstall = (!!newAddon) || (!XPIDatabase.activeBundles && !aMigrateData);
  1.2974 +
  1.2975 +      // If it's a new install and we haven't yet loaded the manifest then it
  1.2976 +      // must be something dropped directly into the install location
  1.2977 +      let isDetectedInstall = isNewInstall && !newAddon;
  1.2978 +
  1.2979 +      // Load the manifest if necessary and sanity check the add-on ID
  1.2980 +      try {
  1.2981 +        if (!newAddon) {
  1.2982 +          // Load the manifest from the add-on.
  1.2983 +          let file = aInstallLocation.getLocationForID(aId);
  1.2984 +          newAddon = loadManifestFromFile(file);
  1.2985 +        }
  1.2986 +        // The add-on in the manifest should match the add-on ID.
  1.2987 +        if (newAddon.id != aId) {
  1.2988 +          throw new Error("Invalid addon ID: expected addon ID " + aId +
  1.2989 +                          ", found " + newAddon.id + " in manifest");
  1.2990 +        }
  1.2991 +      }
  1.2992 +      catch (e) {
  1.2993 +        logger.warn("Add-on is invalid", e);
  1.2994 +
  1.2995 +        // Remove the invalid add-on from the install location if the install
  1.2996 +        // location isn't locked, no restart will be necessary
  1.2997 +        if (!aInstallLocation.locked)
  1.2998 +          aInstallLocation.uninstallAddon(aId);
  1.2999 +        else
  1.3000 +          logger.warn("Could not uninstall invalid item from locked install location");
  1.3001 +        return false;
  1.3002 +      }
  1.3003 +
  1.3004 +      // Update the AddonInternal properties.
  1.3005 +      newAddon._installLocation = aInstallLocation;
  1.3006 +      newAddon.visible = !(newAddon.id in visibleAddons);
  1.3007 +      newAddon.installDate = aAddonState.mtime;
  1.3008 +      newAddon.updateDate = aAddonState.mtime;
  1.3009 +      newAddon.foreignInstall = isDetectedInstall;
  1.3010 +
  1.3011 +      if (aMigrateData) {
  1.3012 +        // If there is migration data then apply it.
  1.3013 +        logger.debug("Migrating data from old database");
  1.3014 +
  1.3015 +        DB_MIGRATE_METADATA.forEach(function(aProp) {
  1.3016 +          // A theme's disabled state is determined by the selected theme
  1.3017 +          // preference which is read in loadManifestFromRDF
  1.3018 +          if (aProp == "userDisabled" && newAddon.type == "theme")
  1.3019 +            return;
  1.3020 +
  1.3021 +          if (aProp in aMigrateData)
  1.3022 +            newAddon[aProp] = aMigrateData[aProp];
  1.3023 +        });
  1.3024 +
  1.3025 +        // Force all non-profile add-ons to be foreignInstalls since they can't
  1.3026 +        // have been installed through the API
  1.3027 +        newAddon.foreignInstall |= aInstallLocation.name != KEY_APP_PROFILE;
  1.3028 +
  1.3029 +        // Some properties should only be migrated if the add-on hasn't changed.
  1.3030 +        // The version property isn't a perfect check for this but covers the
  1.3031 +        // vast majority of cases.
  1.3032 +        if (aMigrateData.version == newAddon.version) {
  1.3033 +          logger.debug("Migrating compatibility info");
  1.3034 +          sameVersion = true;
  1.3035 +          if ("targetApplications" in aMigrateData)
  1.3036 +            newAddon.applyCompatibilityUpdate(aMigrateData, true);
  1.3037 +        }
  1.3038 +
  1.3039 +        // Since the DB schema has changed make sure softDisabled is correct
  1.3040 +        applyBlocklistChanges(newAddon, newAddon, aOldAppVersion,
  1.3041 +                              aOldPlatformVersion);
  1.3042 +      }
  1.3043 +
  1.3044 +      // The default theme is never a foreign install
  1.3045 +      if (newAddon.type == "theme" && newAddon.internalName == XPIProvider.defaultSkin)
  1.3046 +        newAddon.foreignInstall = false;
  1.3047 +
  1.3048 +      if (isDetectedInstall && newAddon.foreignInstall) {
  1.3049 +        // If the add-on is a foreign install and is in a scope where add-ons
  1.3050 +        // that were dropped in should default to disabled then disable it
  1.3051 +        let disablingScopes = Prefs.getIntPref(PREF_EM_AUTO_DISABLED_SCOPES, 0);
  1.3052 +        if (aInstallLocation.scope & disablingScopes)
  1.3053 +          newAddon.userDisabled = true;
  1.3054 +      }
  1.3055 +
  1.3056 +      // If we have a list of what add-ons should be marked as active then use
  1.3057 +      // it to guess at migration data.
  1.3058 +      if (!isNewInstall && XPIDatabase.activeBundles) {
  1.3059 +        // For themes we know which is active by the current skin setting
  1.3060 +        if (newAddon.type == "theme")
  1.3061 +          newAddon.active = newAddon.internalName == XPIProvider.currentSkin;
  1.3062 +        else
  1.3063 +          newAddon.active = XPIDatabase.activeBundles.indexOf(aAddonState.descriptor) != -1;
  1.3064 +
  1.3065 +        // If the add-on wasn't active and it isn't already disabled in some way
  1.3066 +        // then it was probably either softDisabled or userDisabled
  1.3067 +        if (!newAddon.active && newAddon.visible && !isAddonDisabled(newAddon)) {
  1.3068 +          // If the add-on is softblocked then assume it is softDisabled
  1.3069 +          if (newAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
  1.3070 +            newAddon.softDisabled = true;
  1.3071 +          else
  1.3072 +            newAddon.userDisabled = true;
  1.3073 +        }
  1.3074 +      }
  1.3075 +      else {
  1.3076 +        newAddon.active = (newAddon.visible && !isAddonDisabled(newAddon))
  1.3077 +      }
  1.3078 +
  1.3079 +      let newDBAddon = XPIDatabase.addAddonMetadata(newAddon, aAddonState.descriptor);
  1.3080 +
  1.3081 +      if (newDBAddon.visible) {
  1.3082 +        // Remember add-ons that were first detected during startup.
  1.3083 +        if (isDetectedInstall) {
  1.3084 +          // If a copy from a higher priority location was removed then this
  1.3085 +          // add-on has changed
  1.3086 +          if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_UNINSTALLED)
  1.3087 +                          .indexOf(newDBAddon.id) != -1) {
  1.3088 +            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
  1.3089 +                                                 newDBAddon.id);
  1.3090 +          }
  1.3091 +          else {
  1.3092 +            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_INSTALLED,
  1.3093 +                                                 newDBAddon.id);
  1.3094 +          }
  1.3095 +        }
  1.3096 +
  1.3097 +        // Note if any visible add-on is not in the application install location
  1.3098 +        if (newDBAddon._installLocation.name != KEY_APP_GLOBAL)
  1.3099 +          XPIProvider.allAppGlobal = false;
  1.3100 +
  1.3101 +        visibleAddons[newDBAddon.id] = newDBAddon;
  1.3102 +
  1.3103 +        let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
  1.3104 +        let extraParams = {};
  1.3105 +
  1.3106 +        // If we're hiding a bootstrapped add-on then call its uninstall method
  1.3107 +        if (newDBAddon.id in oldBootstrappedAddons) {
  1.3108 +          let oldBootstrap = oldBootstrappedAddons[newDBAddon.id];
  1.3109 +          extraParams.oldVersion = oldBootstrap.version;
  1.3110 +          XPIProvider.bootstrappedAddons[newDBAddon.id] = oldBootstrap;
  1.3111 +
  1.3112 +          // If the old version is the same as the new version, or we're
  1.3113 +          // recovering from a corrupt DB, don't call uninstall and install
  1.3114 +          // methods.
  1.3115 +          if (sameVersion || !isNewInstall)
  1.3116 +            return false;
  1.3117 +
  1.3118 +          installReason = Services.vc.compare(oldBootstrap.version, newDBAddon.version) < 0 ?
  1.3119 +                          BOOTSTRAP_REASONS.ADDON_UPGRADE :
  1.3120 +                          BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
  1.3121 +
  1.3122 +          let oldAddonFile = Cc["@mozilla.org/file/local;1"].
  1.3123 +                             createInstance(Ci.nsIFile);
  1.3124 +          oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
  1.3125 +
  1.3126 +          XPIProvider.callBootstrapMethod(newDBAddon.id, oldBootstrap.version,
  1.3127 +                                          oldBootstrap.type, oldAddonFile, "uninstall",
  1.3128 +                                          installReason, { newVersion: newDBAddon.version });
  1.3129 +          XPIProvider.unloadBootstrapScope(newDBAddon.id);
  1.3130 +
  1.3131 +          // If the new add-on is bootstrapped then we must flush the caches
  1.3132 +          // before calling the new bootstrap script
  1.3133 +          if (newDBAddon.bootstrap)
  1.3134 +            flushStartupCache();
  1.3135 +        }
  1.3136 +
  1.3137 +        if (!newDBAddon.bootstrap)
  1.3138 +          return true;
  1.3139 +
  1.3140 +        // Visible bootstrapped add-ons need to have their install method called
  1.3141 +        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  1.3142 +        file.persistentDescriptor = aAddonState.descriptor;
  1.3143 +        XPIProvider.callBootstrapMethod(newDBAddon.id, newDBAddon.version, newDBAddon.type, file,
  1.3144 +                                        "install", installReason, extraParams);
  1.3145 +        if (!newDBAddon.active)
  1.3146 +          XPIProvider.unloadBootstrapScope(newDBAddon.id);
  1.3147 +      }
  1.3148 +
  1.3149 +      return false;
  1.3150 +    }
  1.3151 +
  1.3152 +    let changed = false;
  1.3153 +    let knownLocations = XPIDatabase.getInstallLocations();
  1.3154 +
  1.3155 +    // The install locations are iterated in reverse order of priority so when
  1.3156 +    // there are multiple add-ons installed with the same ID the one that
  1.3157 +    // should be visible is the first one encountered.
  1.3158 +    for (let aSt of aState.reverse()) {
  1.3159 +
  1.3160 +      // We can't include the install location directly in the state as it has
  1.3161 +      // to be cached as JSON.
  1.3162 +      let installLocation = this.installLocationsByName[aSt.name];
  1.3163 +      let addonStates = aSt.addons;
  1.3164 +
  1.3165 +      // Check if the database knows about any add-ons in this install location.
  1.3166 +      if (knownLocations.has(installLocation.name)) {
  1.3167 +        knownLocations.delete(installLocation.name);
  1.3168 +        let addons = XPIDatabase.getAddonsInLocation(installLocation.name);
  1.3169 +        // Iterate through the add-ons installed the last time the application
  1.3170 +        // ran
  1.3171 +        for (let aOldAddon of addons) {
  1.3172 +          // If a version of this add-on has been installed in an higher
  1.3173 +          // priority install location then count it as changed
  1.3174 +          if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
  1.3175 +                          .indexOf(aOldAddon.id) != -1) {
  1.3176 +            AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
  1.3177 +                                                 aOldAddon.id);
  1.3178 +          }
  1.3179 +
  1.3180 +          // Check if the add-on is still installed
  1.3181 +          if (aOldAddon.id in addonStates) {
  1.3182 +            let addonState = addonStates[aOldAddon.id];
  1.3183 +            delete addonStates[aOldAddon.id];
  1.3184 +
  1.3185 +            // Remember add-ons that were inactive during startup
  1.3186 +            if (aOldAddon.visible && !aOldAddon.active)
  1.3187 +              XPIProvider.inactiveAddonIDs.push(aOldAddon.id);
  1.3188 +
  1.3189 +            // record a bit more per-addon telemetry
  1.3190 +            let loc = aOldAddon.defaultLocale;
  1.3191 +            if (loc) {
  1.3192 +              XPIProvider.setTelemetry(aOldAddon.id, "name", loc.name);
  1.3193 +              XPIProvider.setTelemetry(aOldAddon.id, "creator", loc.creator);
  1.3194 +            }
  1.3195 +
  1.3196 +            // Check if the add-on has been changed outside the XPI provider
  1.3197 +            if (aOldAddon.updateDate != addonState.mtime) {
  1.3198 +              // Did time change in the wrong direction?
  1.3199 +              if (addonState.mtime < aOldAddon.updateDate) {
  1.3200 +                this.setTelemetry(aOldAddon.id, "olderFile", {
  1.3201 +                  name: this._mostRecentlyModifiedFile[aOldAddon.id],
  1.3202 +                  mtime: addonState.mtime,
  1.3203 +                  oldtime: aOldAddon.updateDate
  1.3204 +                });
  1.3205 +              }
  1.3206 +              // Is the add-on unpacked?
  1.3207 +              else if (addonState.rdfTime) {
  1.3208 +                // Was the addon manifest "install.rdf" modified, or some other file?
  1.3209 +                if (addonState.rdfTime > aOldAddon.updateDate) {
  1.3210 +                  this.setTelemetry(aOldAddon.id, "modifiedInstallRDF", 1);
  1.3211 +                }
  1.3212 +                else {
  1.3213 +                  this.setTelemetry(aOldAddon.id, "modifiedFile",
  1.3214 +                                    this._mostRecentlyModifiedFile[aOldAddon.id]);
  1.3215 +                }
  1.3216 +              }
  1.3217 +              else {
  1.3218 +                this.setTelemetry(aOldAddon.id, "modifiedXPI", 1);
  1.3219 +              }
  1.3220 +            }
  1.3221 +
  1.3222 +            // The add-on has changed if the modification time has changed, or
  1.3223 +            // we have an updated manifest for it. Also reload the metadata for
  1.3224 +            // add-ons in the application directory when the application version
  1.3225 +            // has changed
  1.3226 +            if (aOldAddon.id in aManifests[installLocation.name] ||
  1.3227 +                aOldAddon.updateDate != addonState.mtime ||
  1.3228 +                (aUpdateCompatibility && installLocation.name == KEY_APP_GLOBAL)) {
  1.3229 +              changed = updateMetadata(installLocation, aOldAddon, addonState) ||
  1.3230 +                        changed;
  1.3231 +            }
  1.3232 +            else if (aOldAddon.descriptor != addonState.descriptor) {
  1.3233 +              changed = updateDescriptor(installLocation, aOldAddon, addonState) ||
  1.3234 +                        changed;
  1.3235 +            }
  1.3236 +            else {
  1.3237 +              changed = updateVisibilityAndCompatibility(installLocation,
  1.3238 +                                                         aOldAddon, addonState) ||
  1.3239 +                        changed;
  1.3240 +            }
  1.3241 +            if (aOldAddon.visible && aOldAddon._installLocation.name != KEY_APP_GLOBAL)
  1.3242 +              XPIProvider.allAppGlobal = false;
  1.3243 +          }
  1.3244 +          else {
  1.3245 +            changed = removeMetadata(aOldAddon) || changed;
  1.3246 +          }
  1.3247 +        }
  1.3248 +      }
  1.3249 +
  1.3250 +      // All the remaining add-ons in this install location must be new.
  1.3251 +
  1.3252 +      // Get the migration data for this install location.
  1.3253 +      let locMigrateData = {};
  1.3254 +      if (XPIDatabase.migrateData && installLocation.name in XPIDatabase.migrateData)
  1.3255 +        locMigrateData = XPIDatabase.migrateData[installLocation.name];
  1.3256 +      for (let id in addonStates) {
  1.3257 +        changed = addMetadata(installLocation, id, addonStates[id],
  1.3258 +                              (locMigrateData[id] || null)) || changed;
  1.3259 +      }
  1.3260 +    }
  1.3261 +
  1.3262 +    // The remaining locations that had add-ons installed in them no longer
  1.3263 +    // have any add-ons installed in them, or the locations no longer exist.
  1.3264 +    // The metadata for the add-ons that were in them must be removed from the
  1.3265 +    // database.
  1.3266 +    for (let location of knownLocations) {
  1.3267 +      let addons = XPIDatabase.getAddonsInLocation(location);
  1.3268 +      for (let aOldAddon of addons) {
  1.3269 +        changed = removeMetadata(aOldAddon) || changed;
  1.3270 +      }
  1.3271 +    }
  1.3272 +
  1.3273 +    // Cache the new install location states
  1.3274 +    this.installStates = this.getInstallLocationStates();
  1.3275 +    let cache = JSON.stringify(this.installStates);
  1.3276 +    Services.prefs.setCharPref(PREF_INSTALL_CACHE, cache);
  1.3277 +    this.persistBootstrappedAddons();
  1.3278 +
  1.3279 +    // Clear out any cached migration data.
  1.3280 +    XPIDatabase.migrateData = null;
  1.3281 +
  1.3282 +    return changed;
  1.3283 +  },
  1.3284 +
  1.3285 +  /**
  1.3286 +   * Imports the xpinstall permissions from preferences into the permissions
  1.3287 +   * manager for the user to change later.
  1.3288 +   */
  1.3289 +  importPermissions: function XPI_importPermissions() {
  1.3290 +    PermissionsUtils.importFromPrefs(PREF_XPI_PERMISSIONS_BRANCH,
  1.3291 +                                     XPI_PERMISSION);
  1.3292 +  },
  1.3293 +
  1.3294 +  /**
  1.3295 +   * Checks for any changes that have occurred since the last time the
  1.3296 +   * application was launched.
  1.3297 +   *
  1.3298 +   * @param  aAppChanged
  1.3299 +   *         A tri-state value. Undefined means the current profile was created
  1.3300 +   *         for this session, true means the profile already existed but was
  1.3301 +   *         last used with an application with a different version number,
  1.3302 +   *         false means that the profile was last used by this version of the
  1.3303 +   *         application.
  1.3304 +   * @param  aOldAppVersion
  1.3305 +   *         The version of the application last run with this profile or null
  1.3306 +   *         if it is a new profile or the version is unknown
  1.3307 +   * @param  aOldPlatformVersion
  1.3308 +   *         The version of the platform last run with this profile or null
  1.3309 +   *         if it is a new profile or the version is unknown
  1.3310 +   * @return true if a change requiring a restart was detected
  1.3311 +   */
  1.3312 +  checkForChanges: function XPI_checkForChanges(aAppChanged, aOldAppVersion,
  1.3313 +                                                aOldPlatformVersion) {
  1.3314 +    logger.debug("checkForChanges");
  1.3315 +
  1.3316 +    // Keep track of whether and why we need to open and update the database at
  1.3317 +    // startup time.
  1.3318 +    let updateReasons = [];
  1.3319 +    if (aAppChanged) {
  1.3320 +      updateReasons.push("appChanged");
  1.3321 +    }
  1.3322 +
  1.3323 +    // Load the list of bootstrapped add-ons first so processFileChanges can
  1.3324 +    // modify it
  1.3325 +    try {
  1.3326 +      this.bootstrappedAddons = JSON.parse(Prefs.getCharPref(PREF_BOOTSTRAP_ADDONS,
  1.3327 +                                           "{}"));
  1.3328 +    } catch (e) {
  1.3329 +      logger.warn("Error parsing enabled bootstrapped extensions cache", e);
  1.3330 +    }
  1.3331 +
  1.3332 +    // First install any new add-ons into the locations, if there are any
  1.3333 +    // changes then we must update the database with the information in the
  1.3334 +    // install locations
  1.3335 +    let manifests = {};
  1.3336 +    let updated = this.processPendingFileChanges(manifests);
  1.3337 +    if (updated) {
  1.3338 +      updateReasons.push("pendingFileChanges");
  1.3339 +    }
  1.3340 +
  1.3341 +    // This will be true if the previous session made changes that affect the
  1.3342 +    // active state of add-ons but didn't commit them properly (normally due
  1.3343 +    // to the application crashing)
  1.3344 +    let hasPendingChanges = Prefs.getBoolPref(PREF_PENDING_OPERATIONS);
  1.3345 +    if (hasPendingChanges) {
  1.3346 +      updateReasons.push("hasPendingChanges");
  1.3347 +    }
  1.3348 +
  1.3349 +    // If the application has changed then check for new distribution add-ons
  1.3350 +    if (aAppChanged !== false &&
  1.3351 +        Prefs.getBoolPref(PREF_INSTALL_DISTRO_ADDONS, true))
  1.3352 +    {
  1.3353 +      updated = this.installDistributionAddons(manifests);
  1.3354 +      if (updated) {
  1.3355 +        updateReasons.push("installDistributionAddons");
  1.3356 +      }
  1.3357 +    }
  1.3358 +
  1.3359 +    // Telemetry probe added around getInstallLocationStates() to check perf
  1.3360 +    let telemetryCaptureTime = Date.now();
  1.3361 +    this.installStates = this.getInstallLocationStates();
  1.3362 +    let telemetry = Services.telemetry;
  1.3363 +    telemetry.getHistogramById("CHECK_ADDONS_MODIFIED_MS").add(Date.now() - telemetryCaptureTime);
  1.3364 +
  1.3365 +    // If the install directory state has changed then we must update the database
  1.3366 +    let cache = Prefs.getCharPref(PREF_INSTALL_CACHE, "[]");
  1.3367 +    // For a little while, gather telemetry on whether the deep comparison
  1.3368 +    // makes a difference
  1.3369 +    let newState = JSON.stringify(this.installStates);
  1.3370 +    if (cache != newState) {
  1.3371 +      logger.debug("Directory state JSON differs: cache " + cache + " state " + newState);
  1.3372 +      if (directoryStateDiffers(this.installStates, cache)) {
  1.3373 +        updateReasons.push("directoryState");
  1.3374 +      }
  1.3375 +      else {
  1.3376 +        AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_state_badCompare", 1);
  1.3377 +      }
  1.3378 +    }
  1.3379 +
  1.3380 +    // If the schema appears to have changed then we should update the database
  1.3381 +    if (DB_SCHEMA != Prefs.getIntPref(PREF_DB_SCHEMA, 0)) {
  1.3382 +      // If we don't have any add-ons, just update the pref, since we don't need to
  1.3383 +      // write the database
  1.3384 +      if (this.installStates.length == 0) {
  1.3385 +        logger.debug("Empty XPI database, setting schema version preference to " + DB_SCHEMA);
  1.3386 +        Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
  1.3387 +      }
  1.3388 +      else {
  1.3389 +        updateReasons.push("schemaChanged");
  1.3390 +      }
  1.3391 +    }
  1.3392 +
  1.3393 +    // If the database doesn't exist and there are add-ons installed then we
  1.3394 +    // must update the database however if there are no add-ons then there is
  1.3395 +    // no need to update the database.
  1.3396 +    let dbFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_DATABASE], true);
  1.3397 +    if (!dbFile.exists() && this.installStates.length > 0) {
  1.3398 +      updateReasons.push("needNewDatabase");
  1.3399 +    }
  1.3400 +
  1.3401 +    if (updateReasons.length == 0) {
  1.3402 +      let bootstrapDescriptors = [this.bootstrappedAddons[b].descriptor
  1.3403 +                                  for (b in this.bootstrappedAddons)];
  1.3404 +
  1.3405 +      this.installStates.forEach(function(aInstallLocationState) {
  1.3406 +        for (let id in aInstallLocationState.addons) {
  1.3407 +          let pos = bootstrapDescriptors.indexOf(aInstallLocationState.addons[id].descriptor);
  1.3408 +          if (pos != -1)
  1.3409 +            bootstrapDescriptors.splice(pos, 1);
  1.3410 +        }
  1.3411 +      });
  1.3412 +
  1.3413 +      if (bootstrapDescriptors.length > 0) {
  1.3414 +        logger.warn("Bootstrap state is invalid (missing add-ons: " + bootstrapDescriptors.toSource() + ")");
  1.3415 +        updateReasons.push("missingBootstrapAddon");
  1.3416 +      }
  1.3417 +    }
  1.3418 +
  1.3419 +    // Catch and log any errors during the main startup
  1.3420 +    try {
  1.3421 +      let extensionListChanged = false;
  1.3422 +      // If the database needs to be updated then open it and then update it
  1.3423 +      // from the filesystem
  1.3424 +      if (updateReasons.length > 0) {
  1.3425 +        AddonManagerPrivate.recordSimpleMeasure("XPIDB_startup_load_reasons", updateReasons);
  1.3426 +        XPIDatabase.syncLoadDB(false);
  1.3427 +        try {
  1.3428 +          extensionListChanged = this.processFileChanges(this.installStates, manifests,
  1.3429 +                                                         aAppChanged,
  1.3430 +                                                         aOldAppVersion,
  1.3431 +                                                         aOldPlatformVersion);
  1.3432 +        }
  1.3433 +        catch (e) {
  1.3434 +          logger.error("Failed to process extension changes at startup", e);
  1.3435 +        }
  1.3436 +      }
  1.3437 +
  1.3438 +      if (aAppChanged) {
  1.3439 +        // When upgrading the app and using a custom skin make sure it is still
  1.3440 +        // compatible otherwise switch back the default
  1.3441 +        if (this.currentSkin != this.defaultSkin) {
  1.3442 +          let oldSkin = XPIDatabase.getVisibleAddonForInternalName(this.currentSkin);
  1.3443 +          if (!oldSkin || isAddonDisabled(oldSkin))
  1.3444 +            this.enableDefaultTheme();
  1.3445 +        }
  1.3446 +
  1.3447 +        // When upgrading remove the old extensions cache to force older
  1.3448 +        // versions to rescan the entire list of extensions
  1.3449 +        try {
  1.3450 +          let oldCache = FileUtils.getFile(KEY_PROFILEDIR, [FILE_OLD_CACHE], true);
  1.3451 +          if (oldCache.exists())
  1.3452 +            oldCache.remove(true);
  1.3453 +        }
  1.3454 +        catch (e) {
  1.3455 +          logger.warn("Unable to remove old extension cache " + oldCache.path, e);
  1.3456 +        }
  1.3457 +      }
  1.3458 +
  1.3459 +      // If the application crashed before completing any pending operations then
  1.3460 +      // we should perform them now.
  1.3461 +      if (extensionListChanged || hasPendingChanges) {
  1.3462 +        logger.debug("Updating database with changes to installed add-ons");
  1.3463 +        XPIDatabase.updateActiveAddons();
  1.3464 +        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS,
  1.3465 +                                   !XPIDatabase.writeAddonsList());
  1.3466 +        Services.prefs.setCharPref(PREF_BOOTSTRAP_ADDONS,
  1.3467 +                                   JSON.stringify(this.bootstrappedAddons));
  1.3468 +        return true;
  1.3469 +      }
  1.3470 +
  1.3471 +      logger.debug("No changes found");
  1.3472 +    }
  1.3473 +    catch (e) {
  1.3474 +      logger.error("Error during startup file checks", e);
  1.3475 +    }
  1.3476 +
  1.3477 +    // Check that the add-ons list still exists
  1.3478 +    let addonsList = FileUtils.getFile(KEY_PROFILEDIR, [FILE_XPI_ADDONS_LIST],
  1.3479 +                                       true);
  1.3480 +    if (addonsList.exists() == (this.installStates.length == 0)) {
  1.3481 +      logger.debug("Add-ons list is invalid, rebuilding");
  1.3482 +      XPIDatabase.writeAddonsList();
  1.3483 +    }
  1.3484 +
  1.3485 +    return false;
  1.3486 +  },
  1.3487 +
  1.3488 +  /**
  1.3489 +   * Called to test whether this provider supports installing a particular
  1.3490 +   * mimetype.
  1.3491 +   *
  1.3492 +   * @param  aMimetype
  1.3493 +   *         The mimetype to check for
  1.3494 +   * @return true if the mimetype is application/x-xpinstall
  1.3495 +   */
  1.3496 +  supportsMimetype: function XPI_supportsMimetype(aMimetype) {
  1.3497 +    return aMimetype == "application/x-xpinstall";
  1.3498 +  },
  1.3499 +
  1.3500 +  /**
  1.3501 +   * Called to test whether installing XPI add-ons is enabled.
  1.3502 +   *
  1.3503 +   * @return true if installing is enabled
  1.3504 +   */
  1.3505 +  isInstallEnabled: function XPI_isInstallEnabled() {
  1.3506 +    // Default to enabled if the preference does not exist
  1.3507 +    return Prefs.getBoolPref(PREF_XPI_ENABLED, true);
  1.3508 +  },
  1.3509 +
  1.3510 +  /**
  1.3511 +   * Called to test whether installing XPI add-ons by direct URL requests is
  1.3512 +   * whitelisted.
  1.3513 +   *
  1.3514 +   * @return true if installing by direct requests is whitelisted
  1.3515 +   */
  1.3516 +  isDirectRequestWhitelisted: function XPI_isDirectRequestWhitelisted() {
  1.3517 +    // Default to whitelisted if the preference does not exist.
  1.3518 +    return Prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true);
  1.3519 +  },
  1.3520 +
  1.3521 +  /**
  1.3522 +   * Called to test whether installing XPI add-ons from file referrers is
  1.3523 +   * whitelisted.
  1.3524 +   *
  1.3525 +   * @return true if installing from file referrers is whitelisted
  1.3526 +   */
  1.3527 +  isFileRequestWhitelisted: function XPI_isFileRequestWhitelisted() {
  1.3528 +    // Default to whitelisted if the preference does not exist.
  1.3529 +    return Prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true);
  1.3530 +  },
  1.3531 +
  1.3532 +  /**
  1.3533 +   * Called to test whether installing XPI add-ons from a URI is allowed.
  1.3534 +   *
  1.3535 +   * @param  aUri
  1.3536 +   *         The URI being installed from
  1.3537 +   * @return true if installing is allowed
  1.3538 +   */
  1.3539 +  isInstallAllowed: function XPI_isInstallAllowed(aUri) {
  1.3540 +    if (!this.isInstallEnabled())
  1.3541 +      return false;
  1.3542 +
  1.3543 +    // Direct requests without a referrer are either whitelisted or blocked.
  1.3544 +    if (!aUri)
  1.3545 +      return this.isDirectRequestWhitelisted();
  1.3546 +
  1.3547 +    // Local referrers can be whitelisted.
  1.3548 +    if (this.isFileRequestWhitelisted() &&
  1.3549 +        (aUri.schemeIs("chrome") || aUri.schemeIs("file")))
  1.3550 +      return true;
  1.3551 +
  1.3552 +    this.importPermissions();
  1.3553 +
  1.3554 +    let permission = Services.perms.testPermission(aUri, XPI_PERMISSION);
  1.3555 +    if (permission == Ci.nsIPermissionManager.DENY_ACTION)
  1.3556 +      return false;
  1.3557 +
  1.3558 +    let requireWhitelist = Prefs.getBoolPref(PREF_XPI_WHITELIST_REQUIRED, true);
  1.3559 +    if (requireWhitelist && (permission != Ci.nsIPermissionManager.ALLOW_ACTION))
  1.3560 +      return false;
  1.3561 +
  1.3562 +    return true;
  1.3563 +  },
  1.3564 +
  1.3565 +  /**
  1.3566 +   * Called to get an AddonInstall to download and install an add-on from a URL.
  1.3567 +   *
  1.3568 +   * @param  aUrl
  1.3569 +   *         The URL to be installed
  1.3570 +   * @param  aHash
  1.3571 +   *         A hash for the install
  1.3572 +   * @param  aName
  1.3573 +   *         A name for the install
  1.3574 +   * @param  aIcons
  1.3575 +   *         Icon URLs for the install
  1.3576 +   * @param  aVersion
  1.3577 +   *         A version for the install
  1.3578 +   * @param  aLoadGroup
  1.3579 +   *         An nsILoadGroup to associate requests with
  1.3580 +   * @param  aCallback
  1.3581 +   *         A callback to pass the AddonInstall to
  1.3582 +   */
  1.3583 +  getInstallForURL: function XPI_getInstallForURL(aUrl, aHash, aName, aIcons,
  1.3584 +                                                  aVersion, aLoadGroup, aCallback) {
  1.3585 +    AddonInstall.createDownload(function getInstallForURL_createDownload(aInstall) {
  1.3586 +      aCallback(aInstall.wrapper);
  1.3587 +    }, aUrl, aHash, aName, aIcons, aVersion, aLoadGroup);
  1.3588 +  },
  1.3589 +
  1.3590 +  /**
  1.3591 +   * Called to get an AddonInstall to install an add-on from a local file.
  1.3592 +   *
  1.3593 +   * @param  aFile
  1.3594 +   *         The file to be installed
  1.3595 +   * @param  aCallback
  1.3596 +   *         A callback to pass the AddonInstall to
  1.3597 +   */
  1.3598 +  getInstallForFile: function XPI_getInstallForFile(aFile, aCallback) {
  1.3599 +    AddonInstall.createInstall(function getInstallForFile_createInstall(aInstall) {
  1.3600 +      if (aInstall)
  1.3601 +        aCallback(aInstall.wrapper);
  1.3602 +      else
  1.3603 +        aCallback(null);
  1.3604 +    }, aFile);
  1.3605 +  },
  1.3606 +
  1.3607 +  /**
  1.3608 +   * Removes an AddonInstall from the list of active installs.
  1.3609 +   *
  1.3610 +   * @param  install
  1.3611 +   *         The AddonInstall to remove
  1.3612 +   */
  1.3613 +  removeActiveInstall: function XPI_removeActiveInstall(aInstall) {
  1.3614 +    this.installs = this.installs.filter(function installFilter(i) i != aInstall);
  1.3615 +  },
  1.3616 +
  1.3617 +  /**
  1.3618 +   * Called to get an Addon with a particular ID.
  1.3619 +   *
  1.3620 +   * @param  aId
  1.3621 +   *         The ID of the add-on to retrieve
  1.3622 +   * @param  aCallback
  1.3623 +   *         A callback to pass the Addon to
  1.3624 +   */
  1.3625 +  getAddonByID: function XPI_getAddonByID(aId, aCallback) {
  1.3626 +    XPIDatabase.getVisibleAddonForID (aId, function getAddonByID_getVisibleAddonForID(aAddon) {
  1.3627 +      aCallback(createWrapper(aAddon));
  1.3628 +    });
  1.3629 +  },
  1.3630 +
  1.3631 +  /**
  1.3632 +   * Called to get Addons of a particular type.
  1.3633 +   *
  1.3634 +   * @param  aTypes
  1.3635 +   *         An array of types to fetch. Can be null to get all types.
  1.3636 +   * @param  aCallback
  1.3637 +   *         A callback to pass an array of Addons to
  1.3638 +   */
  1.3639 +  getAddonsByTypes: function XPI_getAddonsByTypes(aTypes, aCallback) {
  1.3640 +    XPIDatabase.getVisibleAddons(aTypes, function getAddonsByTypes_getVisibleAddons(aAddons) {
  1.3641 +      aCallback([createWrapper(a) for each (a in aAddons)]);
  1.3642 +    });
  1.3643 +  },
  1.3644 +
  1.3645 +  /**
  1.3646 +   * Obtain an Addon having the specified Sync GUID.
  1.3647 +   *
  1.3648 +   * @param  aGUID
  1.3649 +   *         String GUID of add-on to retrieve
  1.3650 +   * @param  aCallback
  1.3651 +   *         A callback to pass the Addon to. Receives null if not found.
  1.3652 +   */
  1.3653 +  getAddonBySyncGUID: function XPI_getAddonBySyncGUID(aGUID, aCallback) {
  1.3654 +    XPIDatabase.getAddonBySyncGUID(aGUID, function getAddonBySyncGUID_getAddonBySyncGUID(aAddon) {
  1.3655 +      aCallback(createWrapper(aAddon));
  1.3656 +    });
  1.3657 +  },
  1.3658 +
  1.3659 +  /**
  1.3660 +   * Called to get Addons that have pending operations.
  1.3661 +   *
  1.3662 +   * @param  aTypes
  1.3663 +   *         An array of types to fetch. Can be null to get all types
  1.3664 +   * @param  aCallback
  1.3665 +   *         A callback to pass an array of Addons to
  1.3666 +   */
  1.3667 +  getAddonsWithOperationsByTypes:
  1.3668 +  function XPI_getAddonsWithOperationsByTypes(aTypes, aCallback) {
  1.3669 +    XPIDatabase.getVisibleAddonsWithPendingOperations(aTypes,
  1.3670 +      function getAddonsWithOpsByTypes_getVisibleAddonsWithPendingOps(aAddons) {
  1.3671 +      let results = [createWrapper(a) for each (a in aAddons)];
  1.3672 +      XPIProvider.installs.forEach(function(aInstall) {
  1.3673 +        if (aInstall.state == AddonManager.STATE_INSTALLED &&
  1.3674 +            !(aInstall.addon.inDatabase))
  1.3675 +          results.push(createWrapper(aInstall.addon));
  1.3676 +      });
  1.3677 +      aCallback(results);
  1.3678 +    });
  1.3679 +  },
  1.3680 +
  1.3681 +  /**
  1.3682 +   * Called to get the current AddonInstalls, optionally limiting to a list of
  1.3683 +   * types.
  1.3684 +   *
  1.3685 +   * @param  aTypes
  1.3686 +   *         An array of types or null to get all types
  1.3687 +   * @param  aCallback
  1.3688 +   *         A callback to pass the array of AddonInstalls to
  1.3689 +   */
  1.3690 +  getInstallsByTypes: function XPI_getInstallsByTypes(aTypes, aCallback) {
  1.3691 +    let results = [];
  1.3692 +    this.installs.forEach(function(aInstall) {
  1.3693 +      if (!aTypes || aTypes.indexOf(aInstall.type) >= 0)
  1.3694 +        results.push(aInstall.wrapper);
  1.3695 +    });
  1.3696 +    aCallback(results);
  1.3697 +  },
  1.3698 +
  1.3699 +  /**
  1.3700 +   * Synchronously map a URI to the corresponding Addon ID.
  1.3701 +   *
  1.3702 +   * Mappable URIs are limited to in-application resources belonging to the
  1.3703 +   * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
  1.3704 +   * but do not include URIs from meta data, such as the add-on homepage.
  1.3705 +   *
  1.3706 +   * @param  aURI
  1.3707 +   *         nsIURI to map or null
  1.3708 +   * @return string containing the Addon ID
  1.3709 +   * @see    AddonManager.mapURIToAddonID
  1.3710 +   * @see    amIAddonManager.mapURIToAddonID
  1.3711 +   */
  1.3712 +  mapURIToAddonID: function XPI_mapURIToAddonID(aURI) {
  1.3713 +    this._ensureURIMappings();
  1.3714 +    let resolved = this._resolveURIToFile(aURI);
  1.3715 +    if (!resolved) {
  1.3716 +      return null;
  1.3717 +    }
  1.3718 +    resolved = resolved.spec;
  1.3719 +    for (let [id, spec] in Iterator(this._uriMappings)) {
  1.3720 +      if (resolved.startsWith(spec)) {
  1.3721 +        return id;
  1.3722 +      }
  1.3723 +    }
  1.3724 +    return null;
  1.3725 +  },
  1.3726 +
  1.3727 +  /**
  1.3728 +   * Called when a new add-on has been enabled when only one add-on of that type
  1.3729 +   * can be enabled.
  1.3730 +   *
  1.3731 +   * @param  aId
  1.3732 +   *         The ID of the newly enabled add-on
  1.3733 +   * @param  aType
  1.3734 +   *         The type of the newly enabled add-on
  1.3735 +   * @param  aPendingRestart
  1.3736 +   *         true if the newly enabled add-on will only become enabled after a
  1.3737 +   *         restart
  1.3738 +   */
  1.3739 +  addonChanged: function XPI_addonChanged(aId, aType, aPendingRestart) {
  1.3740 +    // We only care about themes in this provider
  1.3741 +    if (aType != "theme")
  1.3742 +      return;
  1.3743 +
  1.3744 +    if (!aId) {
  1.3745 +      // Fallback to the default theme when no theme was enabled
  1.3746 +      this.enableDefaultTheme();
  1.3747 +      return;
  1.3748 +    }
  1.3749 +
  1.3750 +    // Look for the previously enabled theme and find the internalName of the
  1.3751 +    // currently selected theme
  1.3752 +    let previousTheme = null;
  1.3753 +    let newSkin = this.defaultSkin;
  1.3754 +    let addons = XPIDatabase.getAddonsByType("theme");
  1.3755 +    addons.forEach(function(aTheme) {
  1.3756 +      if (!aTheme.visible)
  1.3757 +        return;
  1.3758 +      if (aTheme.id == aId)
  1.3759 +        newSkin = aTheme.internalName;
  1.3760 +      else if (aTheme.userDisabled == false && !aTheme.pendingUninstall)
  1.3761 +        previousTheme = aTheme;
  1.3762 +    }, this);
  1.3763 +
  1.3764 +    if (aPendingRestart) {
  1.3765 +      Services.prefs.setBoolPref(PREF_DSS_SWITCHPENDING, true);
  1.3766 +      Services.prefs.setCharPref(PREF_DSS_SKIN_TO_SELECT, newSkin);
  1.3767 +    }
  1.3768 +    else if (newSkin == this.currentSkin) {
  1.3769 +      try {
  1.3770 +        Services.prefs.clearUserPref(PREF_DSS_SWITCHPENDING);
  1.3771 +      }
  1.3772 +      catch (e) { }
  1.3773 +      try {
  1.3774 +        Services.prefs.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  1.3775 +      }
  1.3776 +      catch (e) { }
  1.3777 +    }
  1.3778 +    else {
  1.3779 +      Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, newSkin);
  1.3780 +      this.currentSkin = newSkin;
  1.3781 +    }
  1.3782 +    this.selectedSkin = newSkin;
  1.3783 +
  1.3784 +    // Flush the preferences to disk so they don't get out of sync with the
  1.3785 +    // database
  1.3786 +    Services.prefs.savePrefFile(null);
  1.3787 +
  1.3788 +    // Mark the previous theme as disabled. This won't cause recursion since
  1.3789 +    // only enabled calls notifyAddonChanged.
  1.3790 +    if (previousTheme)
  1.3791 +      this.updateAddonDisabledState(previousTheme, true);
  1.3792 +  },
  1.3793 +
  1.3794 +  /**
  1.3795 +   * Update the appDisabled property for all add-ons.
  1.3796 +   */
  1.3797 +  updateAddonAppDisabledStates: function XPI_updateAddonAppDisabledStates() {
  1.3798 +    let addons = XPIDatabase.getAddons();
  1.3799 +    addons.forEach(function(aAddon) {
  1.3800 +      this.updateAddonDisabledState(aAddon);
  1.3801 +    }, this);
  1.3802 +  },
  1.3803 +
  1.3804 +  /**
  1.3805 +   * Update the repositoryAddon property for all add-ons.
  1.3806 +   *
  1.3807 +   * @param  aCallback
  1.3808 +   *         Function to call when operation is complete.
  1.3809 +   */
  1.3810 +  updateAddonRepositoryData: function XPI_updateAddonRepositoryData(aCallback) {
  1.3811 +    let self = this;
  1.3812 +    XPIDatabase.getVisibleAddons(null, function UARD_getVisibleAddonsCallback(aAddons) {
  1.3813 +      let pending = aAddons.length;
  1.3814 +      logger.debug("updateAddonRepositoryData found " + pending + " visible add-ons");
  1.3815 +      if (pending == 0) {
  1.3816 +        aCallback();
  1.3817 +        return;
  1.3818 +      }
  1.3819 +
  1.3820 +      function notifyComplete() {
  1.3821 +        if (--pending == 0)
  1.3822 +          aCallback();
  1.3823 +      }
  1.3824 +
  1.3825 +      for (let addon of aAddons) {
  1.3826 +        AddonRepository.getCachedAddonByID(addon.id,
  1.3827 +                                           function UARD_getCachedAddonCallback(aRepoAddon) {
  1.3828 +          if (aRepoAddon) {
  1.3829 +            logger.debug("updateAddonRepositoryData got info for " + addon.id);
  1.3830 +            addon._repositoryAddon = aRepoAddon;
  1.3831 +            addon.compatibilityOverrides = aRepoAddon.compatibilityOverrides;
  1.3832 +            self.updateAddonDisabledState(addon);
  1.3833 +          }
  1.3834 +
  1.3835 +          notifyComplete();
  1.3836 +        });
  1.3837 +      };
  1.3838 +    });
  1.3839 +  },
  1.3840 +
  1.3841 +  /**
  1.3842 +   * When the previously selected theme is removed this method will be called
  1.3843 +   * to enable the default theme.
  1.3844 +   */
  1.3845 +  enableDefaultTheme: function XPI_enableDefaultTheme() {
  1.3846 +    logger.debug("Activating default theme");
  1.3847 +    let addon = XPIDatabase.getVisibleAddonForInternalName(this.defaultSkin);
  1.3848 +    if (addon) {
  1.3849 +      if (addon.userDisabled) {
  1.3850 +        this.updateAddonDisabledState(addon, false);
  1.3851 +      }
  1.3852 +      else if (!this.extensionsActive) {
  1.3853 +        // During startup we may end up trying to enable the default theme when
  1.3854 +        // the database thinks it is already enabled (see f.e. bug 638847). In
  1.3855 +        // this case just force the theme preferences to be correct
  1.3856 +        Services.prefs.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN,
  1.3857 +                                   addon.internalName);
  1.3858 +        this.currentSkin = this.selectedSkin = addon.internalName;
  1.3859 +        Prefs.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  1.3860 +        Prefs.clearUserPref(PREF_DSS_SWITCHPENDING);
  1.3861 +      }
  1.3862 +      else {
  1.3863 +        logger.warn("Attempting to activate an already active default theme");
  1.3864 +      }
  1.3865 +    }
  1.3866 +    else {
  1.3867 +      logger.warn("Unable to activate the default theme");
  1.3868 +    }
  1.3869 +  },
  1.3870 +
  1.3871 +  onDebugConnectionChange: function(aEvent, aWhat, aConnection) {
  1.3872 +    if (aWhat != "opened")
  1.3873 +      return;
  1.3874 +
  1.3875 +    for (let id of Object.keys(this.bootstrapScopes)) {
  1.3876 +      aConnection.setAddonOptions(id, { global: this.bootstrapScopes[id] });
  1.3877 +    }
  1.3878 +  },
  1.3879 +
  1.3880 +  /**
  1.3881 +   * Notified when a preference we're interested in has changed.
  1.3882 +   *
  1.3883 +   * @see nsIObserver
  1.3884 +   */
  1.3885 +  observe: function XPI_observe(aSubject, aTopic, aData) {
  1.3886 +    if (aTopic == NOTIFICATION_FLUSH_PERMISSIONS) {
  1.3887 +      if (!aData || aData == XPI_PERMISSION) {
  1.3888 +        this.importPermissions();
  1.3889 +      }
  1.3890 +      return;
  1.3891 +    }
  1.3892 +
  1.3893 +    if (aTopic == "nsPref:changed") {
  1.3894 +      switch (aData) {
  1.3895 +      case PREF_EM_MIN_COMPAT_APP_VERSION:
  1.3896 +      case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
  1.3897 +        this.minCompatibleAppVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_APP_VERSION,
  1.3898 +                                                         null);
  1.3899 +        this.minCompatiblePlatformVersion = Prefs.getCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
  1.3900 +                                                              null);
  1.3901 +        this.updateAddonAppDisabledStates();
  1.3902 +        break;
  1.3903 +      }
  1.3904 +    }
  1.3905 +  },
  1.3906 +
  1.3907 +  /**
  1.3908 +   * Tests whether enabling an add-on will require a restart.
  1.3909 +   *
  1.3910 +   * @param  aAddon
  1.3911 +   *         The add-on to test
  1.3912 +   * @return true if the operation requires a restart
  1.3913 +   */
  1.3914 +  enableRequiresRestart: function XPI_enableRequiresRestart(aAddon) {
  1.3915 +    // If the platform couldn't have activated extensions then we can make
  1.3916 +    // changes without any restart.
  1.3917 +    if (!this.extensionsActive)
  1.3918 +      return false;
  1.3919 +
  1.3920 +    // If the application is in safe mode then any change can be made without
  1.3921 +    // restarting
  1.3922 +    if (Services.appinfo.inSafeMode)
  1.3923 +      return false;
  1.3924 +
  1.3925 +    // Anything that is active is already enabled
  1.3926 +    if (aAddon.active)
  1.3927 +      return false;
  1.3928 +
  1.3929 +    if (aAddon.type == "theme") {
  1.3930 +      // If dynamic theme switching is enabled then switching themes does not
  1.3931 +      // require a restart
  1.3932 +      if (Prefs.getBoolPref(PREF_EM_DSS_ENABLED))
  1.3933 +        return false;
  1.3934 +
  1.3935 +      // If the theme is already the theme in use then no restart is necessary.
  1.3936 +      // This covers the case where the default theme is in use but a
  1.3937 +      // lightweight theme is considered active.
  1.3938 +      return aAddon.internalName != this.currentSkin;
  1.3939 +    }
  1.3940 +
  1.3941 +    return !aAddon.bootstrap;
  1.3942 +  },
  1.3943 +
  1.3944 +  /**
  1.3945 +   * Tests whether disabling an add-on will require a restart.
  1.3946 +   *
  1.3947 +   * @param  aAddon
  1.3948 +   *         The add-on to test
  1.3949 +   * @return true if the operation requires a restart
  1.3950 +   */
  1.3951 +  disableRequiresRestart: function XPI_disableRequiresRestart(aAddon) {
  1.3952 +    // If the platform couldn't have activated up extensions then we can make
  1.3953 +    // changes without any restart.
  1.3954 +    if (!this.extensionsActive)
  1.3955 +      return false;
  1.3956 +
  1.3957 +    // If the application is in safe mode then any change can be made without
  1.3958 +    // restarting
  1.3959 +    if (Services.appinfo.inSafeMode)
  1.3960 +      return false;
  1.3961 +
  1.3962 +    // Anything that isn't active is already disabled
  1.3963 +    if (!aAddon.active)
  1.3964 +      return false;
  1.3965 +
  1.3966 +    if (aAddon.type == "theme") {
  1.3967 +      // If dynamic theme switching is enabled then switching themes does not
  1.3968 +      // require a restart
  1.3969 +      if (Prefs.getBoolPref(PREF_EM_DSS_ENABLED))
  1.3970 +        return false;
  1.3971 +
  1.3972 +      // Non-default themes always require a restart to disable since it will
  1.3973 +      // be switching from one theme to another or to the default theme and a
  1.3974 +      // lightweight theme.
  1.3975 +      if (aAddon.internalName != this.defaultSkin)
  1.3976 +        return true;
  1.3977 +
  1.3978 +      // The default theme requires a restart to disable if we are in the
  1.3979 +      // process of switching to a different theme. Note that this makes the
  1.3980 +      // disabled flag of operationsRequiringRestart incorrect for the default
  1.3981 +      // theme (it will be false most of the time). Bug 520124 would be required
  1.3982 +      // to fix it. For the UI this isn't a problem since we never try to
  1.3983 +      // disable or uninstall the default theme.
  1.3984 +      return this.selectedSkin != this.currentSkin;
  1.3985 +    }
  1.3986 +
  1.3987 +    return !aAddon.bootstrap;
  1.3988 +  },
  1.3989 +
  1.3990 +  /**
  1.3991 +   * Tests whether installing an add-on will require a restart.
  1.3992 +   *
  1.3993 +   * @param  aAddon
  1.3994 +   *         The add-on to test
  1.3995 +   * @return true if the operation requires a restart
  1.3996 +   */
  1.3997 +  installRequiresRestart: function XPI_installRequiresRestart(aAddon) {
  1.3998 +    // If the platform couldn't have activated up extensions then we can make
  1.3999 +    // changes without any restart.
  1.4000 +    if (!this.extensionsActive)
  1.4001 +      return false;
  1.4002 +
  1.4003 +    // If the application is in safe mode then any change can be made without
  1.4004 +    // restarting
  1.4005 +    if (Services.appinfo.inSafeMode)
  1.4006 +      return false;
  1.4007 +
  1.4008 +    // Add-ons that are already installed don't require a restart to install.
  1.4009 +    // This wouldn't normally be called for an already installed add-on (except
  1.4010 +    // for forming the operationsRequiringRestart flags) so is really here as
  1.4011 +    // a safety measure.
  1.4012 +    if (aAddon.inDatabase)
  1.4013 +      return false;
  1.4014 +
  1.4015 +    // If we have an AddonInstall for this add-on then we can see if there is
  1.4016 +    // an existing installed add-on with the same ID
  1.4017 +    if ("_install" in aAddon && aAddon._install) {
  1.4018 +      // If there is an existing installed add-on and uninstalling it would
  1.4019 +      // require a restart then installing the update will also require a
  1.4020 +      // restart
  1.4021 +      let existingAddon = aAddon._install.existingAddon;
  1.4022 +      if (existingAddon && this.uninstallRequiresRestart(existingAddon))
  1.4023 +        return true;
  1.4024 +    }
  1.4025 +
  1.4026 +    // If the add-on is not going to be active after installation then it
  1.4027 +    // doesn't require a restart to install.
  1.4028 +    if (isAddonDisabled(aAddon))
  1.4029 +      return false;
  1.4030 +
  1.4031 +    // Themes will require a restart (even if dynamic switching is enabled due
  1.4032 +    // to some caching issues) and non-bootstrapped add-ons will require a
  1.4033 +    // restart
  1.4034 +    return aAddon.type == "theme" || !aAddon.bootstrap;
  1.4035 +  },
  1.4036 +
  1.4037 +  /**
  1.4038 +   * Tests whether uninstalling an add-on will require a restart.
  1.4039 +   *
  1.4040 +   * @param  aAddon
  1.4041 +   *         The add-on to test
  1.4042 +   * @return true if the operation requires a restart
  1.4043 +   */
  1.4044 +  uninstallRequiresRestart: function XPI_uninstallRequiresRestart(aAddon) {
  1.4045 +    // If the platform couldn't have activated up extensions then we can make
  1.4046 +    // changes without any restart.
  1.4047 +    if (!this.extensionsActive)
  1.4048 +      return false;
  1.4049 +
  1.4050 +    // If the application is in safe mode then any change can be made without
  1.4051 +    // restarting
  1.4052 +    if (Services.appinfo.inSafeMode)
  1.4053 +      return false;
  1.4054 +
  1.4055 +    // If the add-on can be disabled without a restart then it can also be
  1.4056 +    // uninstalled without a restart
  1.4057 +    return this.disableRequiresRestart(aAddon);
  1.4058 +  },
  1.4059 +
  1.4060 +  /**
  1.4061 +   * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
  1.4062 +   * values as constants in the scope. This will also add information about the
  1.4063 +   * add-on to the bootstrappedAddons dictionary and notify the crash reporter
  1.4064 +   * that new add-ons have been loaded.
  1.4065 +   *
  1.4066 +   * @param  aId
  1.4067 +   *         The add-on's ID
  1.4068 +   * @param  aFile
  1.4069 +   *         The nsIFile for the add-on
  1.4070 +   * @param  aVersion
  1.4071 +   *         The add-on's version
  1.4072 +   * @param  aType
  1.4073 +   *         The type for the add-on
  1.4074 +   * @return a JavaScript scope
  1.4075 +   */
  1.4076 +  loadBootstrapScope: function XPI_loadBootstrapScope(aId, aFile, aVersion, aType) {
  1.4077 +    // Mark the add-on as active for the crash reporter before loading
  1.4078 +    this.bootstrappedAddons[aId] = {
  1.4079 +      version: aVersion,
  1.4080 +      type: aType,
  1.4081 +      descriptor: aFile.persistentDescriptor
  1.4082 +    };
  1.4083 +    this.persistBootstrappedAddons();
  1.4084 +    this.addAddonsToCrashReporter();
  1.4085 +
  1.4086 +    // Locales only contain chrome and can't have bootstrap scripts
  1.4087 +    if (aType == "locale") {
  1.4088 +      this.bootstrapScopes[aId] = null;
  1.4089 +      return;
  1.4090 +    }
  1.4091 +
  1.4092 +    logger.debug("Loading bootstrap scope from " + aFile.path);
  1.4093 +
  1.4094 +    let principal = Cc["@mozilla.org/systemprincipal;1"].
  1.4095 +                    createInstance(Ci.nsIPrincipal);
  1.4096 +
  1.4097 +    if (!aFile.exists()) {
  1.4098 +      this.bootstrapScopes[aId] =
  1.4099 +        new Cu.Sandbox(principal, { sandboxName: aFile.path,
  1.4100 +                                    wantGlobalProperties: ["indexedDB"],
  1.4101 +                                    metadata: { addonID: aId } });
  1.4102 +      logger.error("Attempted to load bootstrap scope from missing directory " + aFile.path);
  1.4103 +      return;
  1.4104 +    }
  1.4105 +
  1.4106 +    let uri = getURIForResourceInFile(aFile, "bootstrap.js").spec;
  1.4107 +    if (aType == "dictionary")
  1.4108 +      uri = "resource://gre/modules/addons/SpellCheckDictionaryBootstrap.js"
  1.4109 +
  1.4110 +    this.bootstrapScopes[aId] =
  1.4111 +      new Cu.Sandbox(principal, { sandboxName: uri,
  1.4112 +                                  wantGlobalProperties: ["indexedDB"],
  1.4113 +                                  metadata: { addonID: aId, URI: uri } });
  1.4114 +
  1.4115 +    let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
  1.4116 +                 createInstance(Ci.mozIJSSubScriptLoader);
  1.4117 +
  1.4118 +    // Add a mapping for XPIProvider.mapURIToAddonID
  1.4119 +    this._addURIMapping(aId, aFile);
  1.4120 +
  1.4121 +    try {
  1.4122 +      // Copy the reason values from the global object into the bootstrap scope.
  1.4123 +      for (let name in BOOTSTRAP_REASONS)
  1.4124 +        this.bootstrapScopes[aId][name] = BOOTSTRAP_REASONS[name];
  1.4125 +
  1.4126 +      // Add other stuff that extensions want.
  1.4127 +      const features = [ "Worker", "ChromeWorker" ];
  1.4128 +
  1.4129 +      for (let feature of features)
  1.4130 +        this.bootstrapScopes[aId][feature] = gGlobalScope[feature];
  1.4131 +
  1.4132 +      // As we don't want our caller to control the JS version used for the
  1.4133 +      // bootstrap file, we run loadSubScript within the context of the
  1.4134 +      // sandbox with the latest JS version set explicitly.
  1.4135 +      this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ = uri;
  1.4136 +      Components.utils.evalInSandbox(
  1.4137 +        "Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
  1.4138 +                   .createInstance(Components.interfaces.mozIJSSubScriptLoader) \
  1.4139 +                   .loadSubScript(__SCRIPT_URI_SPEC__);", this.bootstrapScopes[aId], "ECMAv5");
  1.4140 +    }
  1.4141 +    catch (e) {
  1.4142 +      logger.warn("Error loading bootstrap.js for " + aId, e);
  1.4143 +    }
  1.4144 +
  1.4145 +    try {
  1.4146 +      BrowserToolboxProcess.setAddonOptions(aId, { global: this.bootstrapScopes[aId] });
  1.4147 +    }
  1.4148 +    catch (e) {
  1.4149 +      // BrowserToolboxProcess is not available in all applications
  1.4150 +    }
  1.4151 +  },
  1.4152 +
  1.4153 +  /**
  1.4154 +   * Unloads a bootstrap scope by dropping all references to it and then
  1.4155 +   * updating the list of active add-ons with the crash reporter.
  1.4156 +   *
  1.4157 +   * @param  aId
  1.4158 +   *         The add-on's ID
  1.4159 +   */
  1.4160 +  unloadBootstrapScope: function XPI_unloadBootstrapScope(aId) {
  1.4161 +    delete this.bootstrapScopes[aId];
  1.4162 +    delete this.bootstrappedAddons[aId];
  1.4163 +    this.persistBootstrappedAddons();
  1.4164 +    this.addAddonsToCrashReporter();
  1.4165 +
  1.4166 +    try {
  1.4167 +      BrowserToolboxProcess.setAddonOptions(aId, { global: null });
  1.4168 +    }
  1.4169 +    catch (e) {
  1.4170 +      // BrowserToolboxProcess is not available in all applications
  1.4171 +    }
  1.4172 +  },
  1.4173 +
  1.4174 +  /**
  1.4175 +   * Calls a bootstrap method for an add-on.
  1.4176 +   *
  1.4177 +   * @param  aId
  1.4178 +   *         The ID of the add-on
  1.4179 +   * @param  aVersion
  1.4180 +   *         The version of the add-on
  1.4181 +   * @param  aType
  1.4182 +   *         The type for the add-on
  1.4183 +   * @param  aFile
  1.4184 +   *         The nsIFile for the add-on
  1.4185 +   * @param  aMethod
  1.4186 +   *         The name of the bootstrap method to call
  1.4187 +   * @param  aReason
  1.4188 +   *         The reason flag to pass to the bootstrap's startup method
  1.4189 +   * @param  aExtraParams
  1.4190 +   *         An object of additional key/value pairs to pass to the method in
  1.4191 +   *         the params argument
  1.4192 +   */
  1.4193 +  callBootstrapMethod: function XPI_callBootstrapMethod(aId, aVersion, aType, aFile,
  1.4194 +                                                        aMethod, aReason, aExtraParams) {
  1.4195 +    // Never call any bootstrap methods in safe mode
  1.4196 +    if (Services.appinfo.inSafeMode)
  1.4197 +      return;
  1.4198 +
  1.4199 +    let timeStart = new Date();
  1.4200 +    if (aMethod == "startup") {
  1.4201 +      logger.debug("Registering manifest for " + aFile.path);
  1.4202 +      Components.manager.addBootstrappedManifestLocation(aFile);
  1.4203 +    }
  1.4204 +
  1.4205 +    try {
  1.4206 +      // Load the scope if it hasn't already been loaded
  1.4207 +      if (!(aId in this.bootstrapScopes))
  1.4208 +        this.loadBootstrapScope(aId, aFile, aVersion, aType);
  1.4209 +
  1.4210 +      // Nothing to call for locales
  1.4211 +      if (aType == "locale")
  1.4212 +        return;
  1.4213 +
  1.4214 +      if (!(aMethod in this.bootstrapScopes[aId])) {
  1.4215 +        logger.warn("Add-on " + aId + " is missing bootstrap method " + aMethod);
  1.4216 +        return;
  1.4217 +      }
  1.4218 +
  1.4219 +      let params = {
  1.4220 +        id: aId,
  1.4221 +        version: aVersion,
  1.4222 +        installPath: aFile.clone(),
  1.4223 +        resourceURI: getURIForResourceInFile(aFile, "")
  1.4224 +      };
  1.4225 +
  1.4226 +      if (aExtraParams) {
  1.4227 +        for (let key in aExtraParams) {
  1.4228 +          params[key] = aExtraParams[key];
  1.4229 +        }
  1.4230 +      }
  1.4231 +
  1.4232 +      logger.debug("Calling bootstrap method " + aMethod + " on " + aId + " version " +
  1.4233 +          aVersion);
  1.4234 +      try {
  1.4235 +        this.bootstrapScopes[aId][aMethod](params, aReason);
  1.4236 +      }
  1.4237 +      catch (e) {
  1.4238 +        logger.warn("Exception running bootstrap method " + aMethod + " on " + aId, e);
  1.4239 +      }
  1.4240 +    }
  1.4241 +    finally {
  1.4242 +      if (aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
  1.4243 +        logger.debug("Removing manifest for " + aFile.path);
  1.4244 +        Components.manager.removeBootstrappedManifestLocation(aFile);
  1.4245 +      }
  1.4246 +      this.setTelemetry(aId, aMethod + "_MS", new Date() - timeStart);
  1.4247 +    }
  1.4248 +  },
  1.4249 +
  1.4250 +  /**
  1.4251 +   * Updates the disabled state for an add-on. Its appDisabled property will be
  1.4252 +   * calculated and if the add-on is changed the database will be saved and
  1.4253 +   * appropriate notifications will be sent out to the registered AddonListeners.
  1.4254 +   *
  1.4255 +   * @param  aAddon
  1.4256 +   *         The DBAddonInternal to update
  1.4257 +   * @param  aUserDisabled
  1.4258 +   *         Value for the userDisabled property. If undefined the value will
  1.4259 +   *         not change
  1.4260 +   * @param  aSoftDisabled
  1.4261 +   *         Value for the softDisabled property. If undefined the value will
  1.4262 +   *         not change. If true this will force userDisabled to be true
  1.4263 +   * @throws if addon is not a DBAddonInternal
  1.4264 +   */
  1.4265 +  updateAddonDisabledState: function XPI_updateAddonDisabledState(aAddon,
  1.4266 +                                                                  aUserDisabled,
  1.4267 +                                                                  aSoftDisabled) {
  1.4268 +    if (!(aAddon.inDatabase))
  1.4269 +      throw new Error("Can only update addon states for installed addons.");
  1.4270 +    if (aUserDisabled !== undefined && aSoftDisabled !== undefined) {
  1.4271 +      throw new Error("Cannot change userDisabled and softDisabled at the " +
  1.4272 +                      "same time");
  1.4273 +    }
  1.4274 +
  1.4275 +    if (aUserDisabled === undefined) {
  1.4276 +      aUserDisabled = aAddon.userDisabled;
  1.4277 +    }
  1.4278 +    else if (!aUserDisabled) {
  1.4279 +      // If enabling the add-on then remove softDisabled
  1.4280 +      aSoftDisabled = false;
  1.4281 +    }
  1.4282 +
  1.4283 +    // If not changing softDisabled or the add-on is already userDisabled then
  1.4284 +    // use the existing value for softDisabled
  1.4285 +    if (aSoftDisabled === undefined || aUserDisabled)
  1.4286 +      aSoftDisabled = aAddon.softDisabled;
  1.4287 +
  1.4288 +    let appDisabled = !isUsableAddon(aAddon);
  1.4289 +    // No change means nothing to do here
  1.4290 +    if (aAddon.userDisabled == aUserDisabled &&
  1.4291 +        aAddon.appDisabled == appDisabled &&
  1.4292 +        aAddon.softDisabled == aSoftDisabled)
  1.4293 +      return;
  1.4294 +
  1.4295 +    let wasDisabled = isAddonDisabled(aAddon);
  1.4296 +    let isDisabled = aUserDisabled || aSoftDisabled || appDisabled;
  1.4297 +
  1.4298 +    // If appDisabled changes but the result of isAddonDisabled() doesn't,
  1.4299 +    // no onDisabling/onEnabling is sent - so send a onPropertyChanged.
  1.4300 +    let appDisabledChanged = aAddon.appDisabled != appDisabled;
  1.4301 +
  1.4302 +    // Update the properties in the database.
  1.4303 +    // We never persist this for experiments because the disabled flags
  1.4304 +    // are controlled by the Experiments Manager.
  1.4305 +    if (aAddon.type != "experiment") {
  1.4306 +      XPIDatabase.setAddonProperties(aAddon, {
  1.4307 +        userDisabled: aUserDisabled,
  1.4308 +        appDisabled: appDisabled,
  1.4309 +        softDisabled: aSoftDisabled
  1.4310 +      });
  1.4311 +    }
  1.4312 +
  1.4313 +    if (appDisabledChanged) {
  1.4314 +      AddonManagerPrivate.callAddonListeners("onPropertyChanged",
  1.4315 +                                            aAddon,
  1.4316 +                                            ["appDisabled"]);
  1.4317 +    }
  1.4318 +
  1.4319 +    // If the add-on is not visible or the add-on is not changing state then
  1.4320 +    // there is no need to do anything else
  1.4321 +    if (!aAddon.visible || (wasDisabled == isDisabled))
  1.4322 +      return;
  1.4323 +
  1.4324 +    // Flag that active states in the database need to be updated on shutdown
  1.4325 +    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
  1.4326 +
  1.4327 +    let wrapper = createWrapper(aAddon);
  1.4328 +    // Have we just gone back to the current state?
  1.4329 +    if (isDisabled != aAddon.active) {
  1.4330 +      AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
  1.4331 +    }
  1.4332 +    else {
  1.4333 +      if (isDisabled) {
  1.4334 +        var needsRestart = this.disableRequiresRestart(aAddon);
  1.4335 +        AddonManagerPrivate.callAddonListeners("onDisabling", wrapper,
  1.4336 +                                               needsRestart);
  1.4337 +      }
  1.4338 +      else {
  1.4339 +        needsRestart = this.enableRequiresRestart(aAddon);
  1.4340 +        AddonManagerPrivate.callAddonListeners("onEnabling", wrapper,
  1.4341 +                                               needsRestart);
  1.4342 +      }
  1.4343 +
  1.4344 +      if (!needsRestart) {
  1.4345 +        XPIDatabase.updateAddonActive(aAddon, !isDisabled);
  1.4346 +        if (isDisabled) {
  1.4347 +          if (aAddon.bootstrap) {
  1.4348 +            let file = aAddon._installLocation.getLocationForID(aAddon.id);
  1.4349 +            this.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file, "shutdown",
  1.4350 +                                     BOOTSTRAP_REASONS.ADDON_DISABLE);
  1.4351 +            this.unloadBootstrapScope(aAddon.id);
  1.4352 +          }
  1.4353 +          AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
  1.4354 +        }
  1.4355 +        else {
  1.4356 +          if (aAddon.bootstrap) {
  1.4357 +            let file = aAddon._installLocation.getLocationForID(aAddon.id);
  1.4358 +            this.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file, "startup",
  1.4359 +                                     BOOTSTRAP_REASONS.ADDON_ENABLE);
  1.4360 +          }
  1.4361 +          AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
  1.4362 +        }
  1.4363 +      }
  1.4364 +    }
  1.4365 +
  1.4366 +    // Notify any other providers that a new theme has been enabled
  1.4367 +    if (aAddon.type == "theme" && !isDisabled)
  1.4368 +      AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, needsRestart);
  1.4369 +  },
  1.4370 +
  1.4371 +  /**
  1.4372 +   * Uninstalls an add-on, immediately if possible or marks it as pending
  1.4373 +   * uninstall if not.
  1.4374 +   *
  1.4375 +   * @param  aAddon
  1.4376 +   *         The DBAddonInternal to uninstall
  1.4377 +   * @throws if the addon cannot be uninstalled because it is in an install
  1.4378 +   *         location that does not allow it
  1.4379 +   */
  1.4380 +  uninstallAddon: function XPI_uninstallAddon(aAddon) {
  1.4381 +    if (!(aAddon.inDatabase))
  1.4382 +      throw new Error("Can only uninstall installed addons.");
  1.4383 +
  1.4384 +    if (aAddon._installLocation.locked)
  1.4385 +      throw new Error("Cannot uninstall addons from locked install locations");
  1.4386 +
  1.4387 +    if ("_hasResourceCache" in aAddon)
  1.4388 +      aAddon._hasResourceCache = new Map();
  1.4389 +
  1.4390 +    if (aAddon._updateCheck) {
  1.4391 +      logger.debug("Cancel in-progress update check for " + aAddon.id);
  1.4392 +      aAddon._updateCheck.cancel();
  1.4393 +    }
  1.4394 +
  1.4395 +    // Inactive add-ons don't require a restart to uninstall
  1.4396 +    let requiresRestart = this.uninstallRequiresRestart(aAddon);
  1.4397 +
  1.4398 +    if (requiresRestart) {
  1.4399 +      // We create an empty directory in the staging directory to indicate that
  1.4400 +      // an uninstall is necessary on next startup.
  1.4401 +      let stage = aAddon._installLocation.getStagingDir();
  1.4402 +      stage.append(aAddon.id);
  1.4403 +      if (!stage.exists())
  1.4404 +        stage.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
  1.4405 +
  1.4406 +      XPIDatabase.setAddonProperties(aAddon, {
  1.4407 +        pendingUninstall: true
  1.4408 +      });
  1.4409 +      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
  1.4410 +    }
  1.4411 +
  1.4412 +    // If the add-on is not visible then there is no need to notify listeners.
  1.4413 +    if (!aAddon.visible)
  1.4414 +      return;
  1.4415 +
  1.4416 +    let wrapper = createWrapper(aAddon);
  1.4417 +    AddonManagerPrivate.callAddonListeners("onUninstalling", wrapper,
  1.4418 +                                           requiresRestart);
  1.4419 +
  1.4420 +    // Reveal the highest priority add-on with the same ID
  1.4421 +    function revealAddon(aAddon) {
  1.4422 +      XPIDatabase.makeAddonVisible(aAddon);
  1.4423 +
  1.4424 +      let wrappedAddon = createWrapper(aAddon);
  1.4425 +      AddonManagerPrivate.callAddonListeners("onInstalling", wrappedAddon, false);
  1.4426 +
  1.4427 +      if (!isAddonDisabled(aAddon) && !XPIProvider.enableRequiresRestart(aAddon)) {
  1.4428 +        XPIDatabase.updateAddonActive(aAddon, true);
  1.4429 +      }
  1.4430 +
  1.4431 +      if (aAddon.bootstrap) {
  1.4432 +        let file = aAddon._installLocation.getLocationForID(aAddon.id);
  1.4433 +        XPIProvider.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file,
  1.4434 +                                        "install", BOOTSTRAP_REASONS.ADDON_INSTALL);
  1.4435 +
  1.4436 +        if (aAddon.active) {
  1.4437 +          XPIProvider.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file,
  1.4438 +                                          "startup", BOOTSTRAP_REASONS.ADDON_INSTALL);
  1.4439 +        }
  1.4440 +        else {
  1.4441 +          XPIProvider.unloadBootstrapScope(aAddon.id);
  1.4442 +        }
  1.4443 +      }
  1.4444 +
  1.4445 +      // We always send onInstalled even if a restart is required to enable
  1.4446 +      // the revealed add-on
  1.4447 +      AddonManagerPrivate.callAddonListeners("onInstalled", wrappedAddon);
  1.4448 +    }
  1.4449 +
  1.4450 +    function checkInstallLocation(aPos) {
  1.4451 +      if (aPos < 0)
  1.4452 +        return;
  1.4453 +
  1.4454 +      let location = XPIProvider.installLocations[aPos];
  1.4455 +      XPIDatabase.getAddonInLocation(aAddon.id, location.name,
  1.4456 +        function checkInstallLocation_getAddonInLocation(aNewAddon) {
  1.4457 +        if (aNewAddon)
  1.4458 +          revealAddon(aNewAddon);
  1.4459 +        else
  1.4460 +          checkInstallLocation(aPos - 1);
  1.4461 +      })
  1.4462 +    }
  1.4463 +
  1.4464 +    if (!requiresRestart) {
  1.4465 +      if (aAddon.bootstrap) {
  1.4466 +        let file = aAddon._installLocation.getLocationForID(aAddon.id);
  1.4467 +        if (aAddon.active) {
  1.4468 +          this.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file,
  1.4469 +                                   "shutdown",
  1.4470 +                                   BOOTSTRAP_REASONS.ADDON_UNINSTALL);
  1.4471 +        }
  1.4472 +
  1.4473 +        this.callBootstrapMethod(aAddon.id, aAddon.version, aAddon.type, file,
  1.4474 +                                 "uninstall",
  1.4475 +                                 BOOTSTRAP_REASONS.ADDON_UNINSTALL);
  1.4476 +        this.unloadBootstrapScope(aAddon.id);
  1.4477 +        flushStartupCache();
  1.4478 +      }
  1.4479 +      aAddon._installLocation.uninstallAddon(aAddon.id);
  1.4480 +      XPIDatabase.removeAddonMetadata(aAddon);
  1.4481 +      AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);
  1.4482 +
  1.4483 +      checkInstallLocation(this.installLocations.length - 1);
  1.4484 +    }
  1.4485 +
  1.4486 +    // Notify any other providers that a new theme has been enabled
  1.4487 +    if (aAddon.type == "theme" && aAddon.active)
  1.4488 +      AddonManagerPrivate.notifyAddonChanged(null, aAddon.type, requiresRestart);
  1.4489 +  },
  1.4490 +
  1.4491 +  /**
  1.4492 +   * Cancels the pending uninstall of an add-on.
  1.4493 +   *
  1.4494 +   * @param  aAddon
  1.4495 +   *         The DBAddonInternal to cancel uninstall for
  1.4496 +   */
  1.4497 +  cancelUninstallAddon: function XPI_cancelUninstallAddon(aAddon) {
  1.4498 +    if (!(aAddon.inDatabase))
  1.4499 +      throw new Error("Can only cancel uninstall for installed addons.");
  1.4500 +
  1.4501 +    aAddon._installLocation.cleanStagingDir([aAddon.id]);
  1.4502 +
  1.4503 +    XPIDatabase.setAddonProperties(aAddon, {
  1.4504 +      pendingUninstall: false
  1.4505 +    });
  1.4506 +
  1.4507 +    if (!aAddon.visible)
  1.4508 +      return;
  1.4509 +
  1.4510 +    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
  1.4511 +
  1.4512 +    // TODO hide hidden add-ons (bug 557710)
  1.4513 +    let wrapper = createWrapper(aAddon);
  1.4514 +    AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);
  1.4515 +
  1.4516 +    // Notify any other providers that this theme is now enabled again.
  1.4517 +    if (aAddon.type == "theme" && aAddon.active)
  1.4518 +      AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
  1.4519 +  }
  1.4520 +};
  1.4521 +
  1.4522 +function getHashStringForCrypto(aCrypto) {
  1.4523 +  // return the two-digit hexadecimal code for a byte
  1.4524 +  function toHexString(charCode)
  1.4525 +    ("0" + charCode.toString(16)).slice(-2);
  1.4526 +
  1.4527 +  // convert the binary hash data to a hex string.
  1.4528 +  let binary = aCrypto.finish(false);
  1.4529 +  return [toHexString(binary.charCodeAt(i)) for (i in binary)].join("").toLowerCase()
  1.4530 +}
  1.4531 +
  1.4532 +/**
  1.4533 + * Instantiates an AddonInstall.
  1.4534 + *
  1.4535 + * @param  aInstallLocation
  1.4536 + *         The install location the add-on will be installed into
  1.4537 + * @param  aUrl
  1.4538 + *         The nsIURL to get the add-on from. If this is an nsIFileURL then
  1.4539 + *         the add-on will not need to be downloaded
  1.4540 + * @param  aHash
  1.4541 + *         An optional hash for the add-on
  1.4542 + * @param  aReleaseNotesURI
  1.4543 + *         An optional nsIURI of release notes for the add-on
  1.4544 + * @param  aExistingAddon
  1.4545 + *         The add-on this install will update if known
  1.4546 + * @param  aLoadGroup
  1.4547 + *         The nsILoadGroup to associate any requests with
  1.4548 + * @throws if the url is the url of a local file and the hash does not match
  1.4549 + *         or the add-on does not contain an valid install manifest
  1.4550 + */
  1.4551 +function AddonInstall(aInstallLocation, aUrl, aHash, aReleaseNotesURI,
  1.4552 +                      aExistingAddon, aLoadGroup) {
  1.4553 +  this.wrapper = new AddonInstallWrapper(this);
  1.4554 +  this.installLocation = aInstallLocation;
  1.4555 +  this.sourceURI = aUrl;
  1.4556 +  this.releaseNotesURI = aReleaseNotesURI;
  1.4557 +  if (aHash) {
  1.4558 +    let hashSplit = aHash.toLowerCase().split(":");
  1.4559 +    this.originalHash = {
  1.4560 +      algorithm: hashSplit[0],
  1.4561 +      data: hashSplit[1]
  1.4562 +    };
  1.4563 +  }
  1.4564 +  this.hash = this.originalHash;
  1.4565 +  this.loadGroup = aLoadGroup;
  1.4566 +  this.listeners = [];
  1.4567 +  this.icons = {};
  1.4568 +  this.existingAddon = aExistingAddon;
  1.4569 +  this.error = 0;
  1.4570 +  if (aLoadGroup)
  1.4571 +    this.window = aLoadGroup.notificationCallbacks
  1.4572 +                            .getInterface(Ci.nsIDOMWindow);
  1.4573 +  else
  1.4574 +    this.window = null;
  1.4575 +
  1.4576 +  // Giving each instance of AddonInstall a reference to the logger.
  1.4577 +  this.logger = logger;
  1.4578 +}
  1.4579 +
  1.4580 +AddonInstall.prototype = {
  1.4581 +  installLocation: null,
  1.4582 +  wrapper: null,
  1.4583 +  stream: null,
  1.4584 +  crypto: null,
  1.4585 +  originalHash: null,
  1.4586 +  hash: null,
  1.4587 +  loadGroup: null,
  1.4588 +  badCertHandler: null,
  1.4589 +  listeners: null,
  1.4590 +  restartDownload: false,
  1.4591 +
  1.4592 +  name: null,
  1.4593 +  type: null,
  1.4594 +  version: null,
  1.4595 +  icons: null,
  1.4596 +  releaseNotesURI: null,
  1.4597 +  sourceURI: null,
  1.4598 +  file: null,
  1.4599 +  ownsTempFile: false,
  1.4600 +  certificate: null,
  1.4601 +  certName: null,
  1.4602 +
  1.4603 +  linkedInstalls: null,
  1.4604 +  existingAddon: null,
  1.4605 +  addon: null,
  1.4606 +
  1.4607 +  state: null,
  1.4608 +  error: null,
  1.4609 +  progress: null,
  1.4610 +  maxProgress: null,
  1.4611 +
  1.4612 +  /**
  1.4613 +   * Initialises this install to be a staged install waiting to be applied
  1.4614 +   *
  1.4615 +   * @param  aManifest
  1.4616 +   *         The cached manifest for the staged install
  1.4617 +   */
  1.4618 +  initStagedInstall: function AI_initStagedInstall(aManifest) {
  1.4619 +    this.name = aManifest.name;
  1.4620 +    this.type = aManifest.type;
  1.4621 +    this.version = aManifest.version;
  1.4622 +    this.icons = aManifest.icons;
  1.4623 +    this.releaseNotesURI = aManifest.releaseNotesURI ?
  1.4624 +                           NetUtil.newURI(aManifest.releaseNotesURI) :
  1.4625 +                           null
  1.4626 +    this.sourceURI = aManifest.sourceURI ?
  1.4627 +                     NetUtil.newURI(aManifest.sourceURI) :
  1.4628 +                     null;
  1.4629 +    this.file = null;
  1.4630 +    this.addon = aManifest;
  1.4631 +
  1.4632 +    this.state = AddonManager.STATE_INSTALLED;
  1.4633 +
  1.4634 +    XPIProvider.installs.push(this);
  1.4635 +  },
  1.4636 +
  1.4637 +  /**
  1.4638 +   * Initialises this install to be an install from a local file.
  1.4639 +   *
  1.4640 +   * @param  aCallback
  1.4641 +   *         The callback to pass the initialised AddonInstall to
  1.4642 +   */
  1.4643 +  initLocalInstall: function AI_initLocalInstall(aCallback) {
  1.4644 +    aCallback = makeSafe(aCallback);
  1.4645 +    this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;
  1.4646 +
  1.4647 +    if (!this.file.exists()) {
  1.4648 +      logger.warn("XPI file " + this.file.path + " does not exist");
  1.4649 +      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.4650 +      this.error = AddonManager.ERROR_NETWORK_FAILURE;
  1.4651 +      aCallback(this);
  1.4652 +      return;
  1.4653 +    }
  1.4654 +
  1.4655 +    this.state = AddonManager.STATE_DOWNLOADED;
  1.4656 +    this.progress = this.file.fileSize;
  1.4657 +    this.maxProgress = this.file.fileSize;
  1.4658 +
  1.4659 +    if (this.hash) {
  1.4660 +      let crypto = Cc["@mozilla.org/security/hash;1"].
  1.4661 +                   createInstance(Ci.nsICryptoHash);
  1.4662 +      try {
  1.4663 +        crypto.initWithString(this.hash.algorithm);
  1.4664 +      }
  1.4665 +      catch (e) {
  1.4666 +        logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
  1.4667 +        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.4668 +        this.error = AddonManager.ERROR_INCORRECT_HASH;
  1.4669 +        aCallback(this);
  1.4670 +        return;
  1.4671 +      }
  1.4672 +
  1.4673 +      let fis = Cc["@mozilla.org/network/file-input-stream;1"].
  1.4674 +                createInstance(Ci.nsIFileInputStream);
  1.4675 +      fis.init(this.file, -1, -1, false);
  1.4676 +      crypto.updateFromStream(fis, this.file.fileSize);
  1.4677 +      let calculatedHash = getHashStringForCrypto(crypto);
  1.4678 +      if (calculatedHash != this.hash.data) {
  1.4679 +        logger.warn("File hash (" + calculatedHash + ") did not match provided hash (" +
  1.4680 +             this.hash.data + ")");
  1.4681 +        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.4682 +        this.error = AddonManager.ERROR_INCORRECT_HASH;
  1.4683 +        aCallback(this);
  1.4684 +        return;
  1.4685 +      }
  1.4686 +    }
  1.4687 +
  1.4688 +    try {
  1.4689 +      let self = this;
  1.4690 +      this.loadManifest(function  initLocalInstall_loadManifest() {
  1.4691 +        XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) {
  1.4692 +          self.existingAddon = aAddon;
  1.4693 +          if (aAddon)
  1.4694 +            applyBlocklistChanges(aAddon, self.addon);
  1.4695 +          self.addon.updateDate = Date.now();
  1.4696 +          self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate;
  1.4697 +
  1.4698 +          if (!self.addon.isCompatible) {
  1.4699 +            // TODO Should we send some event here?
  1.4700 +            self.state = AddonManager.STATE_CHECKING;
  1.4701 +            new UpdateChecker(self.addon, {
  1.4702 +              onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) {
  1.4703 +                self.state = AddonManager.STATE_DOWNLOADED;
  1.4704 +                XPIProvider.installs.push(self);
  1.4705 +                AddonManagerPrivate.callInstallListeners("onNewInstall",
  1.4706 +                                                         self.listeners,
  1.4707 +                                                         self.wrapper);
  1.4708 +
  1.4709 +                aCallback(self);
  1.4710 +              }
  1.4711 +            }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
  1.4712 +          }
  1.4713 +          else {
  1.4714 +            XPIProvider.installs.push(self);
  1.4715 +            AddonManagerPrivate.callInstallListeners("onNewInstall",
  1.4716 +                                                     self.listeners,
  1.4717 +                                                     self.wrapper);
  1.4718 +
  1.4719 +            aCallback(self);
  1.4720 +          }
  1.4721 +        });
  1.4722 +      });
  1.4723 +    }
  1.4724 +    catch (e) {
  1.4725 +      logger.warn("Invalid XPI", e);
  1.4726 +      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.4727 +      this.error = AddonManager.ERROR_CORRUPT_FILE;
  1.4728 +      aCallback(this);
  1.4729 +      return;
  1.4730 +    }
  1.4731 +  },
  1.4732 +
  1.4733 +  /**
  1.4734 +   * Initialises this install to be a download from a remote url.
  1.4735 +   *
  1.4736 +   * @param  aCallback
  1.4737 +   *         The callback to pass the initialised AddonInstall to
  1.4738 +   * @param  aName
  1.4739 +   *         An optional name for the add-on
  1.4740 +   * @param  aType
  1.4741 +   *         An optional type for the add-on
  1.4742 +   * @param  aIcons
  1.4743 +   *         Optional icons for the add-on
  1.4744 +   * @param  aVersion
  1.4745 +   *         An optional version for the add-on
  1.4746 +   */
  1.4747 +  initAvailableDownload: function AI_initAvailableDownload(aName, aType, aIcons, aVersion, aCallback) {
  1.4748 +    this.state = AddonManager.STATE_AVAILABLE;
  1.4749 +    this.name = aName;
  1.4750 +    this.type = aType;
  1.4751 +    this.version = aVersion;
  1.4752 +    this.icons = aIcons;
  1.4753 +    this.progress = 0;
  1.4754 +    this.maxProgress = -1;
  1.4755 +
  1.4756 +    XPIProvider.installs.push(this);
  1.4757 +    AddonManagerPrivate.callInstallListeners("onNewInstall", this.listeners,
  1.4758 +                                             this.wrapper);
  1.4759 +
  1.4760 +    makeSafe(aCallback)(this);
  1.4761 +  },
  1.4762 +
  1.4763 +  /**
  1.4764 +   * Starts installation of this add-on from whatever state it is currently at
  1.4765 +   * if possible.
  1.4766 +   *
  1.4767 +   * @throws if installation cannot proceed from the current state
  1.4768 +   */
  1.4769 +  install: function AI_install() {
  1.4770 +    switch (this.state) {
  1.4771 +    case AddonManager.STATE_AVAILABLE:
  1.4772 +      this.startDownload();
  1.4773 +      break;
  1.4774 +    case AddonManager.STATE_DOWNLOADED:
  1.4775 +      this.startInstall();
  1.4776 +      break;
  1.4777 +    case AddonManager.STATE_DOWNLOAD_FAILED:
  1.4778 +    case AddonManager.STATE_INSTALL_FAILED:
  1.4779 +    case AddonManager.STATE_CANCELLED:
  1.4780 +      this.removeTemporaryFile();
  1.4781 +      this.state = AddonManager.STATE_AVAILABLE;
  1.4782 +      this.error = 0;
  1.4783 +      this.progress = 0;
  1.4784 +      this.maxProgress = -1;
  1.4785 +      this.hash = this.originalHash;
  1.4786 +      XPIProvider.installs.push(this);
  1.4787 +      this.startDownload();
  1.4788 +      break;
  1.4789 +    case AddonManager.STATE_DOWNLOADING:
  1.4790 +    case AddonManager.STATE_CHECKING:
  1.4791 +    case AddonManager.STATE_INSTALLING:
  1.4792 +      // Installation is already running
  1.4793 +      return;
  1.4794 +    default:
  1.4795 +      throw new Error("Cannot start installing from this state");
  1.4796 +    }
  1.4797 +  },
  1.4798 +
  1.4799 +  /**
  1.4800 +   * Cancels installation of this add-on.
  1.4801 +   *
  1.4802 +   * @throws if installation cannot be cancelled from the current state
  1.4803 +   */
  1.4804 +  cancel: function AI_cancel() {
  1.4805 +    switch (this.state) {
  1.4806 +    case AddonManager.STATE_DOWNLOADING:
  1.4807 +      if (this.channel)
  1.4808 +        this.channel.cancel(Cr.NS_BINDING_ABORTED);
  1.4809 +    case AddonManager.STATE_AVAILABLE:
  1.4810 +    case AddonManager.STATE_DOWNLOADED:
  1.4811 +      logger.debug("Cancelling download of " + this.sourceURI.spec);
  1.4812 +      this.state = AddonManager.STATE_CANCELLED;
  1.4813 +      XPIProvider.removeActiveInstall(this);
  1.4814 +      AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
  1.4815 +                                               this.listeners, this.wrapper);
  1.4816 +      this.removeTemporaryFile();
  1.4817 +      break;
  1.4818 +    case AddonManager.STATE_INSTALLED:
  1.4819 +      logger.debug("Cancelling install of " + this.addon.id);
  1.4820 +      let xpi = this.installLocation.getStagingDir();
  1.4821 +      xpi.append(this.addon.id + ".xpi");
  1.4822 +      flushJarCache(xpi);
  1.4823 +      this.installLocation.cleanStagingDir([this.addon.id, this.addon.id + ".xpi",
  1.4824 +                                            this.addon.id + ".json"]);
  1.4825 +      this.state = AddonManager.STATE_CANCELLED;
  1.4826 +      XPIProvider.removeActiveInstall(this);
  1.4827 +
  1.4828 +      if (this.existingAddon) {
  1.4829 +        delete this.existingAddon.pendingUpgrade;
  1.4830 +        this.existingAddon.pendingUpgrade = null;
  1.4831 +      }
  1.4832 +
  1.4833 +      AddonManagerPrivate.callAddonListeners("onOperationCancelled", createWrapper(this.addon));
  1.4834 +
  1.4835 +      AddonManagerPrivate.callInstallListeners("onInstallCancelled",
  1.4836 +                                               this.listeners, this.wrapper);
  1.4837 +      break;
  1.4838 +    default:
  1.4839 +      throw new Error("Cannot cancel install of " + this.sourceURI.spec +
  1.4840 +                      " from this state (" + this.state + ")");
  1.4841 +    }
  1.4842 +  },
  1.4843 +
  1.4844 +  /**
  1.4845 +   * Adds an InstallListener for this instance if the listener is not already
  1.4846 +   * registered.
  1.4847 +   *
  1.4848 +   * @param  aListener
  1.4849 +   *         The InstallListener to add
  1.4850 +   */
  1.4851 +  addListener: function AI_addListener(aListener) {
  1.4852 +    if (!this.listeners.some(function addListener_matchListener(i) { return i == aListener; }))
  1.4853 +      this.listeners.push(aListener);
  1.4854 +  },
  1.4855 +
  1.4856 +  /**
  1.4857 +   * Removes an InstallListener for this instance if it is registered.
  1.4858 +   *
  1.4859 +   * @param  aListener
  1.4860 +   *         The InstallListener to remove
  1.4861 +   */
  1.4862 +  removeListener: function AI_removeListener(aListener) {
  1.4863 +    this.listeners = this.listeners.filter(function removeListener_filterListener(i) {
  1.4864 +      return i != aListener;
  1.4865 +    });
  1.4866 +  },
  1.4867 +
  1.4868 +  /**
  1.4869 +   * Removes the temporary file owned by this AddonInstall if there is one.
  1.4870 +   */
  1.4871 +  removeTemporaryFile: function AI_removeTemporaryFile() {
  1.4872 +    // Only proceed if this AddonInstall owns its XPI file
  1.4873 +    if (!this.ownsTempFile) {
  1.4874 +      this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " does not own temp file");
  1.4875 +      return;
  1.4876 +    }
  1.4877 +
  1.4878 +    try {
  1.4879 +      this.logger.debug("removeTemporaryFile: " + this.sourceURI.spec + " removing temp file " +
  1.4880 +          this.file.path);
  1.4881 +      this.file.remove(true);
  1.4882 +      this.ownsTempFile = false;
  1.4883 +    }
  1.4884 +    catch (e) {
  1.4885 +      this.logger.warn("Failed to remove temporary file " + this.file.path + " for addon " +
  1.4886 +          this.sourceURI.spec,
  1.4887 +          e);
  1.4888 +    }
  1.4889 +  },
  1.4890 +
  1.4891 +  /**
  1.4892 +   * Updates the sourceURI and releaseNotesURI values on the Addon being
  1.4893 +   * installed by this AddonInstall instance.
  1.4894 +   */
  1.4895 +  updateAddonURIs: function AI_updateAddonURIs() {
  1.4896 +    this.addon.sourceURI = this.sourceURI.spec;
  1.4897 +    if (this.releaseNotesURI)
  1.4898 +      this.addon.releaseNotesURI = this.releaseNotesURI.spec;
  1.4899 +  },
  1.4900 +
  1.4901 +  /**
  1.4902 +   * Loads add-on manifests from a multi-package XPI file. Each of the
  1.4903 +   * XPI and JAR files contained in the XPI will be extracted. Any that
  1.4904 +   * do not contain valid add-ons will be ignored. The first valid add-on will
  1.4905 +   * be installed by this AddonInstall instance, the rest will have new
  1.4906 +   * AddonInstall instances created for them.
  1.4907 +   *
  1.4908 +   * @param  aZipReader
  1.4909 +   *         An open nsIZipReader for the multi-package XPI's files. This will
  1.4910 +   *         be closed before this method returns.
  1.4911 +   * @param  aCallback
  1.4912 +   *         A function to call when all of the add-on manifests have been
  1.4913 +   *         loaded. Because this loadMultipackageManifests is an internal API
  1.4914 +   *         we don't exception-wrap this callback
  1.4915 +   */
  1.4916 +  _loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader,
  1.4917 +                                                                   aCallback) {
  1.4918 +    let files = [];
  1.4919 +    let entries = aZipReader.findEntries("(*.[Xx][Pp][Ii]|*.[Jj][Aa][Rr])");
  1.4920 +    while (entries.hasMore()) {
  1.4921 +      let entryName = entries.getNext();
  1.4922 +      var target = getTemporaryFile();
  1.4923 +      try {
  1.4924 +        aZipReader.extract(entryName, target);
  1.4925 +        files.push(target);
  1.4926 +      }
  1.4927 +      catch (e) {
  1.4928 +        logger.warn("Failed to extract " + entryName + " from multi-package " +
  1.4929 +             "XPI", e);
  1.4930 +        target.remove(false);
  1.4931 +      }
  1.4932 +    }
  1.4933 +
  1.4934 +    aZipReader.close();
  1.4935 +
  1.4936 +    if (files.length == 0) {
  1.4937 +      throw new Error("Multi-package XPI does not contain any packages " +
  1.4938 +                      "to install");
  1.4939 +    }
  1.4940 +
  1.4941 +    let addon = null;
  1.4942 +
  1.4943 +    // Find the first file that has a valid install manifest and use it for
  1.4944 +    // the add-on that this AddonInstall instance will install.
  1.4945 +    while (files.length > 0) {
  1.4946 +      this.removeTemporaryFile();
  1.4947 +      this.file = files.shift();
  1.4948 +      this.ownsTempFile = true;
  1.4949 +      try {
  1.4950 +        addon = loadManifestFromZipFile(this.file);
  1.4951 +        break;
  1.4952 +      }
  1.4953 +      catch (e) {
  1.4954 +        logger.warn(this.file.leafName + " cannot be installed from multi-package " +
  1.4955 +             "XPI", e);
  1.4956 +      }
  1.4957 +    }
  1.4958 +
  1.4959 +    if (!addon) {
  1.4960 +      // No valid add-on was found
  1.4961 +      aCallback();
  1.4962 +      return;
  1.4963 +    }
  1.4964 +
  1.4965 +    this.addon = addon;
  1.4966 +
  1.4967 +    this.updateAddonURIs();
  1.4968 +
  1.4969 +    this.addon._install = this;
  1.4970 +    this.name = this.addon.selectedLocale.name;
  1.4971 +    this.type = this.addon.type;
  1.4972 +    this.version = this.addon.version;
  1.4973 +
  1.4974 +    // Setting the iconURL to something inside the XPI locks the XPI and
  1.4975 +    // makes it impossible to delete on Windows.
  1.4976 +    //let newIcon = createWrapper(this.addon).iconURL;
  1.4977 +    //if (newIcon)
  1.4978 +    //  this.iconURL = newIcon;
  1.4979 +
  1.4980 +    // Create new AddonInstall instances for every remaining file
  1.4981 +    if (files.length > 0) {
  1.4982 +      this.linkedInstalls = [];
  1.4983 +      let count = 0;
  1.4984 +      let self = this;
  1.4985 +      files.forEach(function(file) {
  1.4986 +        AddonInstall.createInstall(function loadMultipackageManifests_createInstall(aInstall) {
  1.4987 +          // Ignore bad add-ons (createInstall will have logged the error)
  1.4988 +          if (aInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
  1.4989 +            // Manually remove the temporary file
  1.4990 +            file.remove(true);
  1.4991 +          }
  1.4992 +          else {
  1.4993 +            // Make the new install own its temporary file
  1.4994 +            aInstall.ownsTempFile = true;
  1.4995 +
  1.4996 +            self.linkedInstalls.push(aInstall)
  1.4997 +
  1.4998 +            aInstall.sourceURI = self.sourceURI;
  1.4999 +            aInstall.releaseNotesURI = self.releaseNotesURI;
  1.5000 +            aInstall.updateAddonURIs();
  1.5001 +          }
  1.5002 +
  1.5003 +          count++;
  1.5004 +          if (count == files.length)
  1.5005 +            aCallback();
  1.5006 +        }, file);
  1.5007 +      }, this);
  1.5008 +    }
  1.5009 +    else {
  1.5010 +      aCallback();
  1.5011 +    }
  1.5012 +  },
  1.5013 +
  1.5014 +  /**
  1.5015 +   * Called after the add-on is a local file and the signature and install
  1.5016 +   * manifest can be read.
  1.5017 +   *
  1.5018 +   * @param  aCallback
  1.5019 +   *         A function to call when the manifest has been loaded
  1.5020 +   * @throws if the add-on does not contain a valid install manifest or the
  1.5021 +   *         XPI is incorrectly signed
  1.5022 +   */
  1.5023 +  loadManifest: function AI_loadManifest(aCallback) {
  1.5024 +    aCallback = makeSafe(aCallback);
  1.5025 +    let self = this;
  1.5026 +    function addRepositoryData(aAddon) {
  1.5027 +      // Try to load from the existing cache first
  1.5028 +      AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
  1.5029 +        if (aRepoAddon) {
  1.5030 +          aAddon._repositoryAddon = aRepoAddon;
  1.5031 +          self.name = self.name || aAddon._repositoryAddon.name;
  1.5032 +          aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides;
  1.5033 +          aAddon.appDisabled = !isUsableAddon(aAddon);
  1.5034 +          aCallback();
  1.5035 +          return;
  1.5036 +        }
  1.5037 +
  1.5038 +        // It wasn't there so try to re-download it
  1.5039 +        AddonRepository.cacheAddons([aAddon.id], function loadManifest_cacheAddons() {
  1.5040 +          AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
  1.5041 +            aAddon._repositoryAddon = aRepoAddon;
  1.5042 +            self.name = self.name || aAddon._repositoryAddon.name;
  1.5043 +            aAddon.compatibilityOverrides = aRepoAddon ?
  1.5044 +                                              aRepoAddon.compatibilityOverrides :
  1.5045 +                                              null;
  1.5046 +            aAddon.appDisabled = !isUsableAddon(aAddon);
  1.5047 +            aCallback();
  1.5048 +          });
  1.5049 +        });
  1.5050 +      });
  1.5051 +    }
  1.5052 +
  1.5053 +    let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
  1.5054 +                    createInstance(Ci.nsIZipReader);
  1.5055 +    try {
  1.5056 +      zipreader.open(this.file);
  1.5057 +    }
  1.5058 +    catch (e) {
  1.5059 +      zipreader.close();
  1.5060 +      throw e;
  1.5061 +    }
  1.5062 +
  1.5063 +    let principal = zipreader.getCertificatePrincipal(null);
  1.5064 +    if (principal && principal.hasCertificate) {
  1.5065 +      logger.debug("Verifying XPI signature");
  1.5066 +      if (verifyZipSigning(zipreader, principal)) {
  1.5067 +        let x509 = principal.certificate;
  1.5068 +        if (x509 instanceof Ci.nsIX509Cert)
  1.5069 +          this.certificate = x509;
  1.5070 +        if (this.certificate && this.certificate.commonName.length > 0)
  1.5071 +          this.certName = this.certificate.commonName;
  1.5072 +        else
  1.5073 +          this.certName = principal.prettyName;
  1.5074 +      }
  1.5075 +      else {
  1.5076 +        zipreader.close();
  1.5077 +        throw new Error("XPI is incorrectly signed");
  1.5078 +      }
  1.5079 +    }
  1.5080 +
  1.5081 +    try {
  1.5082 +      this.addon = loadManifestFromZipReader(zipreader);
  1.5083 +    }
  1.5084 +    catch (e) {
  1.5085 +      zipreader.close();
  1.5086 +      throw e;
  1.5087 +    }
  1.5088 +
  1.5089 +    if (this.addon.type == "multipackage") {
  1.5090 +      this._loadMultipackageManifests(zipreader, function loadManifest_loadMultipackageManifests() {
  1.5091 +        addRepositoryData(self.addon);
  1.5092 +      });
  1.5093 +      return;
  1.5094 +    }
  1.5095 +
  1.5096 +    zipreader.close();
  1.5097 +
  1.5098 +    this.updateAddonURIs();
  1.5099 +
  1.5100 +    this.addon._install = this;
  1.5101 +    this.name = this.addon.selectedLocale.name;
  1.5102 +    this.type = this.addon.type;
  1.5103 +    this.version = this.addon.version;
  1.5104 +
  1.5105 +    // Setting the iconURL to something inside the XPI locks the XPI and
  1.5106 +    // makes it impossible to delete on Windows.
  1.5107 +    //let newIcon = createWrapper(this.addon).iconURL;
  1.5108 +    //if (newIcon)
  1.5109 +    //  this.iconURL = newIcon;
  1.5110 +
  1.5111 +    addRepositoryData(this.addon);
  1.5112 +  },
  1.5113 +
  1.5114 +  observe: function AI_observe(aSubject, aTopic, aData) {
  1.5115 +    // Network is going offline
  1.5116 +    this.cancel();
  1.5117 +  },
  1.5118 +
  1.5119 +  /**
  1.5120 +   * Starts downloading the add-on's XPI file.
  1.5121 +   */
  1.5122 +  startDownload: function AI_startDownload() {
  1.5123 +    this.state = AddonManager.STATE_DOWNLOADING;
  1.5124 +    if (!AddonManagerPrivate.callInstallListeners("onDownloadStarted",
  1.5125 +                                                  this.listeners, this.wrapper)) {
  1.5126 +      logger.debug("onDownloadStarted listeners cancelled installation of addon " + this.sourceURI.spec);
  1.5127 +      this.state = AddonManager.STATE_CANCELLED;
  1.5128 +      XPIProvider.removeActiveInstall(this);
  1.5129 +      AddonManagerPrivate.callInstallListeners("onDownloadCancelled",
  1.5130 +                                               this.listeners, this.wrapper)
  1.5131 +      return;
  1.5132 +    }
  1.5133 +
  1.5134 +    // If a listener changed our state then do not proceed with the download
  1.5135 +    if (this.state != AddonManager.STATE_DOWNLOADING)
  1.5136 +      return;
  1.5137 +
  1.5138 +    if (this.channel) {
  1.5139 +      // A previous download attempt hasn't finished cleaning up yet, signal
  1.5140 +      // that it should restart when complete
  1.5141 +      logger.debug("Waiting for previous download to complete");
  1.5142 +      this.restartDownload = true;
  1.5143 +      return;
  1.5144 +    }
  1.5145 +
  1.5146 +    this.openChannel();
  1.5147 +  },
  1.5148 +
  1.5149 +  openChannel: function AI_openChannel() {
  1.5150 +    this.restartDownload = false;
  1.5151 +
  1.5152 +    try {
  1.5153 +      this.file = getTemporaryFile();
  1.5154 +      this.ownsTempFile = true;
  1.5155 +      this.stream = Cc["@mozilla.org/network/file-output-stream;1"].
  1.5156 +                    createInstance(Ci.nsIFileOutputStream);
  1.5157 +      this.stream.init(this.file, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
  1.5158 +                       FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE, 0);
  1.5159 +    }
  1.5160 +    catch (e) {
  1.5161 +      logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
  1.5162 +      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.5163 +      this.error = AddonManager.ERROR_FILE_ACCESS;
  1.5164 +      XPIProvider.removeActiveInstall(this);
  1.5165 +      AddonManagerPrivate.callInstallListeners("onDownloadFailed",
  1.5166 +                                               this.listeners, this.wrapper);
  1.5167 +      return;
  1.5168 +    }
  1.5169 +
  1.5170 +    let listener = Cc["@mozilla.org/network/stream-listener-tee;1"].
  1.5171 +                   createInstance(Ci.nsIStreamListenerTee);
  1.5172 +    listener.init(this, this.stream);
  1.5173 +    try {
  1.5174 +      Components.utils.import("resource://gre/modules/CertUtils.jsm");
  1.5175 +      let requireBuiltIn = Prefs.getBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
  1.5176 +      this.badCertHandler = new BadCertHandler(!requireBuiltIn);
  1.5177 +
  1.5178 +      this.channel = NetUtil.newChannel(this.sourceURI);
  1.5179 +      this.channel.notificationCallbacks = this;
  1.5180 +      if (this.channel instanceof Ci.nsIHttpChannelInternal)
  1.5181 +        this.channel.forceAllowThirdPartyCookie = true;
  1.5182 +      this.channel.asyncOpen(listener, null);
  1.5183 +
  1.5184 +      Services.obs.addObserver(this, "network:offline-about-to-go-offline", false);
  1.5185 +    }
  1.5186 +    catch (e) {
  1.5187 +      logger.warn("Failed to start download for addon " + this.sourceURI.spec, e);
  1.5188 +      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.5189 +      this.error = AddonManager.ERROR_NETWORK_FAILURE;
  1.5190 +      XPIProvider.removeActiveInstall(this);
  1.5191 +      AddonManagerPrivate.callInstallListeners("onDownloadFailed",
  1.5192 +                                               this.listeners, this.wrapper);
  1.5193 +    }
  1.5194 +  },
  1.5195 +
  1.5196 +  /**
  1.5197 +   * Update the crypto hasher with the new data and call the progress listeners.
  1.5198 +   *
  1.5199 +   * @see nsIStreamListener
  1.5200 +   */
  1.5201 +  onDataAvailable: function AI_onDataAvailable(aRequest, aContext, aInputstream,
  1.5202 +                                               aOffset, aCount) {
  1.5203 +    this.crypto.updateFromStream(aInputstream, aCount);
  1.5204 +    this.progress += aCount;
  1.5205 +    if (!AddonManagerPrivate.callInstallListeners("onDownloadProgress",
  1.5206 +                                                  this.listeners, this.wrapper)) {
  1.5207 +      // TODO cancel the download and make it available again (bug 553024)
  1.5208 +    }
  1.5209 +  },
  1.5210 +
  1.5211 +  /**
  1.5212 +   * Check the redirect response for a hash of the target XPI and verify that
  1.5213 +   * we don't end up on an insecure channel.
  1.5214 +   *
  1.5215 +   * @see nsIChannelEventSink
  1.5216 +   */
  1.5217 +  asyncOnChannelRedirect: function AI_asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback) {
  1.5218 +    if (!this.hash && aOldChannel.originalURI.schemeIs("https") &&
  1.5219 +        aOldChannel instanceof Ci.nsIHttpChannel) {
  1.5220 +      try {
  1.5221 +        let hashStr = aOldChannel.getResponseHeader("X-Target-Digest");
  1.5222 +        let hashSplit = hashStr.toLowerCase().split(":");
  1.5223 +        this.hash = {
  1.5224 +          algorithm: hashSplit[0],
  1.5225 +          data: hashSplit[1]
  1.5226 +        };
  1.5227 +      }
  1.5228 +      catch (e) {
  1.5229 +      }
  1.5230 +    }
  1.5231 +
  1.5232 +    // Verify that we don't end up on an insecure channel if we haven't got a
  1.5233 +    // hash to verify with (see bug 537761 for discussion)
  1.5234 +    if (!this.hash)
  1.5235 +      this.badCertHandler.asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback);
  1.5236 +    else
  1.5237 +      aCallback.onRedirectVerifyCallback(Cr.NS_OK);
  1.5238 +
  1.5239 +    this.channel = aNewChannel;
  1.5240 +  },
  1.5241 +
  1.5242 +  /**
  1.5243 +   * This is the first chance to get at real headers on the channel.
  1.5244 +   *
  1.5245 +   * @see nsIStreamListener
  1.5246 +   */
  1.5247 +  onStartRequest: function AI_onStartRequest(aRequest, aContext) {
  1.5248 +    this.crypto = Cc["@mozilla.org/security/hash;1"].
  1.5249 +                  createInstance(Ci.nsICryptoHash);
  1.5250 +    if (this.hash) {
  1.5251 +      try {
  1.5252 +        this.crypto.initWithString(this.hash.algorithm);
  1.5253 +      }
  1.5254 +      catch (e) {
  1.5255 +        logger.warn("Unknown hash algorithm '" + this.hash.algorithm + "' for addon " + this.sourceURI.spec, e);
  1.5256 +        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.5257 +        this.error = AddonManager.ERROR_INCORRECT_HASH;
  1.5258 +        XPIProvider.removeActiveInstall(this);
  1.5259 +        AddonManagerPrivate.callInstallListeners("onDownloadFailed",
  1.5260 +                                                 this.listeners, this.wrapper);
  1.5261 +        aRequest.cancel(Cr.NS_BINDING_ABORTED);
  1.5262 +        return;
  1.5263 +      }
  1.5264 +    }
  1.5265 +    else {
  1.5266 +      // We always need something to consume data from the inputstream passed
  1.5267 +      // to onDataAvailable so just create a dummy cryptohasher to do that.
  1.5268 +      this.crypto.initWithString("sha1");
  1.5269 +    }
  1.5270 +
  1.5271 +    this.progress = 0;
  1.5272 +    if (aRequest instanceof Ci.nsIChannel) {
  1.5273 +      try {
  1.5274 +        this.maxProgress = aRequest.contentLength;
  1.5275 +      }
  1.5276 +      catch (e) {
  1.5277 +      }
  1.5278 +      logger.debug("Download started for " + this.sourceURI.spec + " to file " +
  1.5279 +          this.file.path);
  1.5280 +    }
  1.5281 +  },
  1.5282 +
  1.5283 +  /**
  1.5284 +   * The download is complete.
  1.5285 +   *
  1.5286 +   * @see nsIStreamListener
  1.5287 +   */
  1.5288 +  onStopRequest: function AI_onStopRequest(aRequest, aContext, aStatus) {
  1.5289 +    this.stream.close();
  1.5290 +    this.channel = null;
  1.5291 +    this.badCerthandler = null;
  1.5292 +    Services.obs.removeObserver(this, "network:offline-about-to-go-offline");
  1.5293 +
  1.5294 +    // If the download was cancelled then all events will have already been sent
  1.5295 +    if (aStatus == Cr.NS_BINDING_ABORTED) {
  1.5296 +      this.removeTemporaryFile();
  1.5297 +      if (this.restartDownload)
  1.5298 +        this.openChannel();
  1.5299 +      return;
  1.5300 +    }
  1.5301 +
  1.5302 +    logger.debug("Download of " + this.sourceURI.spec + " completed.");
  1.5303 +
  1.5304 +    if (Components.isSuccessCode(aStatus)) {
  1.5305 +      if (!(aRequest instanceof Ci.nsIHttpChannel) || aRequest.requestSucceeded) {
  1.5306 +        if (!this.hash && (aRequest instanceof Ci.nsIChannel)) {
  1.5307 +          try {
  1.5308 +            checkCert(aRequest,
  1.5309 +                      !Prefs.getBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true));
  1.5310 +          }
  1.5311 +          catch (e) {
  1.5312 +            this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, e);
  1.5313 +            return;
  1.5314 +          }
  1.5315 +        }
  1.5316 +
  1.5317 +        // convert the binary hash data to a hex string.
  1.5318 +        let calculatedHash = getHashStringForCrypto(this.crypto);
  1.5319 +        this.crypto = null;
  1.5320 +        if (this.hash && calculatedHash != this.hash.data) {
  1.5321 +          this.downloadFailed(AddonManager.ERROR_INCORRECT_HASH,
  1.5322 +                              "Downloaded file hash (" + calculatedHash +
  1.5323 +                              ") did not match provided hash (" + this.hash.data + ")");
  1.5324 +          return;
  1.5325 +        }
  1.5326 +        try {
  1.5327 +          let self = this;
  1.5328 +          this.loadManifest(function onStopRequest_loadManifest() {
  1.5329 +            if (self.addon.isCompatible) {
  1.5330 +              self.downloadCompleted();
  1.5331 +            }
  1.5332 +            else {
  1.5333 +              // TODO Should we send some event here (bug 557716)?
  1.5334 +              self.state = AddonManager.STATE_CHECKING;
  1.5335 +              new UpdateChecker(self.addon, {
  1.5336 +                onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) {
  1.5337 +                  self.downloadCompleted();
  1.5338 +                }
  1.5339 +              }, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
  1.5340 +            }
  1.5341 +          });
  1.5342 +        }
  1.5343 +        catch (e) {
  1.5344 +          this.downloadFailed(AddonManager.ERROR_CORRUPT_FILE, e);
  1.5345 +        }
  1.5346 +      }
  1.5347 +      else {
  1.5348 +        if (aRequest instanceof Ci.nsIHttpChannel)
  1.5349 +          this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE,
  1.5350 +                              aRequest.responseStatus + " " +
  1.5351 +                              aRequest.responseStatusText);
  1.5352 +        else
  1.5353 +          this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
  1.5354 +      }
  1.5355 +    }
  1.5356 +    else {
  1.5357 +      this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
  1.5358 +    }
  1.5359 +  },
  1.5360 +
  1.5361 +  /**
  1.5362 +   * Notify listeners that the download failed.
  1.5363 +   *
  1.5364 +   * @param  aReason
  1.5365 +   *         Something to log about the failure
  1.5366 +   * @param  error
  1.5367 +   *         The error code to pass to the listeners
  1.5368 +   */
  1.5369 +  downloadFailed: function AI_downloadFailed(aReason, aError) {
  1.5370 +    logger.warn("Download of " + this.sourceURI.spec + " failed", aError);
  1.5371 +    this.state = AddonManager.STATE_DOWNLOAD_FAILED;
  1.5372 +    this.error = aReason;
  1.5373 +    XPIProvider.removeActiveInstall(this);
  1.5374 +    AddonManagerPrivate.callInstallListeners("onDownloadFailed", this.listeners,
  1.5375 +                                             this.wrapper);
  1.5376 +
  1.5377 +    // If the listener hasn't restarted the download then remove any temporary
  1.5378 +    // file
  1.5379 +    if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
  1.5380 +      logger.debug("downloadFailed: removing temp file for " + this.sourceURI.spec);
  1.5381 +      this.removeTemporaryFile();
  1.5382 +    }
  1.5383 +    else
  1.5384 +      logger.debug("downloadFailed: listener changed AddonInstall state for " +
  1.5385 +          this.sourceURI.spec + " to " + this.state);
  1.5386 +  },
  1.5387 +
  1.5388 +  /**
  1.5389 +   * Notify listeners that the download completed.
  1.5390 +   */
  1.5391 +  downloadCompleted: function AI_downloadCompleted() {
  1.5392 +    let self = this;
  1.5393 +    XPIDatabase.getVisibleAddonForID(this.addon.id, function downloadCompleted_getVisibleAddonForID(aAddon) {
  1.5394 +      if (aAddon)
  1.5395 +        self.existingAddon = aAddon;
  1.5396 +
  1.5397 +      self.state = AddonManager.STATE_DOWNLOADED;
  1.5398 +      self.addon.updateDate = Date.now();
  1.5399 +
  1.5400 +      if (self.existingAddon) {
  1.5401 +        self.addon.existingAddonID = self.existingAddon.id;
  1.5402 +        self.addon.installDate = self.existingAddon.installDate;
  1.5403 +        applyBlocklistChanges(self.existingAddon, self.addon);
  1.5404 +      }
  1.5405 +      else {
  1.5406 +        self.addon.installDate = self.addon.updateDate;
  1.5407 +      }
  1.5408 +
  1.5409 +      if (AddonManagerPrivate.callInstallListeners("onDownloadEnded",
  1.5410 +                                                   self.listeners,
  1.5411 +                                                   self.wrapper)) {
  1.5412 +        // If a listener changed our state then do not proceed with the install
  1.5413 +        if (self.state != AddonManager.STATE_DOWNLOADED)
  1.5414 +          return;
  1.5415 +
  1.5416 +        self.install();
  1.5417 +
  1.5418 +        if (self.linkedInstalls) {
  1.5419 +          self.linkedInstalls.forEach(function(aInstall) {
  1.5420 +            aInstall.install();
  1.5421 +          });
  1.5422 +        }
  1.5423 +      }
  1.5424 +    });
  1.5425 +  },
  1.5426 +
  1.5427 +  // TODO This relies on the assumption that we are always installing into the
  1.5428 +  // highest priority install location so the resulting add-on will be visible
  1.5429 +  // overriding any existing copy in another install location (bug 557710).
  1.5430 +  /**
  1.5431 +   * Installs the add-on into the install location.
  1.5432 +   */
  1.5433 +  startInstall: function AI_startInstall() {
  1.5434 +    this.state = AddonManager.STATE_INSTALLING;
  1.5435 +    if (!AddonManagerPrivate.callInstallListeners("onInstallStarted",
  1.5436 +                                                  this.listeners, this.wrapper)) {
  1.5437 +      this.state = AddonManager.STATE_DOWNLOADED;
  1.5438 +      XPIProvider.removeActiveInstall(this);
  1.5439 +      AddonManagerPrivate.callInstallListeners("onInstallCancelled",
  1.5440 +                                               this.listeners, this.wrapper)
  1.5441 +      return;
  1.5442 +    }
  1.5443 +
  1.5444 +    // Find and cancel any pending installs for the same add-on in the same
  1.5445 +    // install location
  1.5446 +    for (let aInstall of XPIProvider.installs) {
  1.5447 +      if (aInstall.state == AddonManager.STATE_INSTALLED &&
  1.5448 +          aInstall.installLocation == this.installLocation &&
  1.5449 +          aInstall.addon.id == this.addon.id) {
  1.5450 +        logger.debug("Cancelling previous pending install of " + aInstall.addon.id);
  1.5451 +        aInstall.cancel();
  1.5452 +      }
  1.5453 +    }
  1.5454 +
  1.5455 +    let isUpgrade = this.existingAddon &&
  1.5456 +                    this.existingAddon._installLocation == this.installLocation;
  1.5457 +    let requiresRestart = XPIProvider.installRequiresRestart(this.addon);
  1.5458 +
  1.5459 +    logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec);
  1.5460 +    AddonManagerPrivate.callAddonListeners("onInstalling",
  1.5461 +                                           createWrapper(this.addon),
  1.5462 +                                           requiresRestart);
  1.5463 +
  1.5464 +    let stagingDir = this.installLocation.getStagingDir();
  1.5465 +    let stagedAddon = stagingDir.clone();
  1.5466 +
  1.5467 +    Task.spawn((function() {
  1.5468 +      let installedUnpacked = 0;
  1.5469 +      yield this.installLocation.requestStagingDir();
  1.5470 +
  1.5471 +      // First stage the file regardless of whether restarting is necessary
  1.5472 +      if (this.addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
  1.5473 +        logger.debug("Addon " + this.addon.id + " will be installed as " +
  1.5474 +            "an unpacked directory");
  1.5475 +        stagedAddon.append(this.addon.id);
  1.5476 +        yield removeAsync(stagedAddon);
  1.5477 +        yield OS.File.makeDir(stagedAddon.path);
  1.5478 +        yield ZipUtils.extractFilesAsync(this.file, stagedAddon);
  1.5479 +        installedUnpacked = 1;
  1.5480 +      }
  1.5481 +      else {
  1.5482 +        logger.debug("Addon " + this.addon.id + " will be installed as " +
  1.5483 +            "a packed xpi");
  1.5484 +        stagedAddon.append(this.addon.id + ".xpi");
  1.5485 +        yield removeAsync(stagedAddon);
  1.5486 +        yield OS.File.copy(this.file.path, stagedAddon.path);
  1.5487 +      }
  1.5488 +
  1.5489 +      if (requiresRestart) {
  1.5490 +        // Point the add-on to its extracted files as the xpi may get deleted
  1.5491 +        this.addon._sourceBundle = stagedAddon;
  1.5492 +
  1.5493 +        // Cache the AddonInternal as it may have updated compatibility info
  1.5494 +        let stagedJSON = stagedAddon.clone();
  1.5495 +        stagedJSON.leafName = this.addon.id + ".json";
  1.5496 +        if (stagedJSON.exists())
  1.5497 +          stagedJSON.remove(true);
  1.5498 +        let stream = Cc["@mozilla.org/network/file-output-stream;1"].
  1.5499 +                     createInstance(Ci.nsIFileOutputStream);
  1.5500 +        let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
  1.5501 +                        createInstance(Ci.nsIConverterOutputStream);
  1.5502 +
  1.5503 +        try {
  1.5504 +          stream.init(stagedJSON, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
  1.5505 +                                  FileUtils.MODE_TRUNCATE, FileUtils.PERMS_FILE,
  1.5506 +                                 0);
  1.5507 +          converter.init(stream, "UTF-8", 0, 0x0000);
  1.5508 +          converter.writeString(JSON.stringify(this.addon));
  1.5509 +        }
  1.5510 +        finally {
  1.5511 +          converter.close();
  1.5512 +          stream.close();
  1.5513 +        }
  1.5514 +
  1.5515 +        logger.debug("Staged install of " + this.addon.id + " from " + this.sourceURI.spec + " ready; waiting for restart.");
  1.5516 +        this.state = AddonManager.STATE_INSTALLED;
  1.5517 +        if (isUpgrade) {
  1.5518 +          delete this.existingAddon.pendingUpgrade;
  1.5519 +          this.existingAddon.pendingUpgrade = this.addon;
  1.5520 +        }
  1.5521 +        AddonManagerPrivate.callInstallListeners("onInstallEnded",
  1.5522 +                                                 this.listeners, this.wrapper,
  1.5523 +                                                 createWrapper(this.addon));
  1.5524 +      }
  1.5525 +      else {
  1.5526 +        // The install is completed so it should be removed from the active list
  1.5527 +        XPIProvider.removeActiveInstall(this);
  1.5528 +
  1.5529 +        // TODO We can probably reduce the number of DB operations going on here
  1.5530 +        // We probably also want to support rolling back failed upgrades etc.
  1.5531 +        // See bug 553015.
  1.5532 +
  1.5533 +        // Deactivate and remove the old add-on as necessary
  1.5534 +        let reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
  1.5535 +        if (this.existingAddon) {
  1.5536 +          if (Services.vc.compare(this.existingAddon.version, this.addon.version) < 0)
  1.5537 +            reason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
  1.5538 +          else
  1.5539 +            reason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
  1.5540 +
  1.5541 +          if (this.existingAddon.bootstrap) {
  1.5542 +            let file = this.existingAddon._installLocation
  1.5543 +                           .getLocationForID(this.existingAddon.id);
  1.5544 +            if (this.existingAddon.active) {
  1.5545 +              XPIProvider.callBootstrapMethod(this.existingAddon.id,
  1.5546 +                                              this.existingAddon.version,
  1.5547 +                                              this.existingAddon.type, file,
  1.5548 +                                              "shutdown", reason,
  1.5549 +                                              { newVersion: this.addon.version });
  1.5550 +            }
  1.5551 +
  1.5552 +            XPIProvider.callBootstrapMethod(this.existingAddon.id,
  1.5553 +                                            this.existingAddon.version,
  1.5554 +                                            this.existingAddon.type, file,
  1.5555 +                                            "uninstall", reason,
  1.5556 +                                            { newVersion: this.addon.version });
  1.5557 +            XPIProvider.unloadBootstrapScope(this.existingAddon.id);
  1.5558 +            flushStartupCache();
  1.5559 +          }
  1.5560 +
  1.5561 +          if (!isUpgrade && this.existingAddon.active) {
  1.5562 +            XPIDatabase.updateAddonActive(this.existingAddon, false);
  1.5563 +          }
  1.5564 +        }
  1.5565 +
  1.5566 +        // Install the new add-on into its final location
  1.5567 +        let existingAddonID = this.existingAddon ? this.existingAddon.id : null;
  1.5568 +        let file = this.installLocation.installAddon(this.addon.id, stagedAddon,
  1.5569 +                                                     existingAddonID);
  1.5570 +
  1.5571 +        // Update the metadata in the database
  1.5572 +        this.addon._sourceBundle = file;
  1.5573 +        this.addon._installLocation = this.installLocation;
  1.5574 +        let scanStarted = Date.now();
  1.5575 +        let [, mTime, scanItems] = recursiveLastModifiedTime(file);
  1.5576 +        let scanTime = Date.now() - scanStarted;
  1.5577 +        this.addon.updateDate = mTime;
  1.5578 +        this.addon.visible = true;
  1.5579 +        if (isUpgrade) {
  1.5580 +          this.addon =  XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
  1.5581 +                                                        file.persistentDescriptor);
  1.5582 +        }
  1.5583 +        else {
  1.5584 +          this.addon.installDate = this.addon.updateDate;
  1.5585 +          this.addon.active = (this.addon.visible && !isAddonDisabled(this.addon))
  1.5586 +          this.addon = XPIDatabase.addAddonMetadata(this.addon, file.persistentDescriptor);
  1.5587 +        }
  1.5588 +
  1.5589 +        let extraParams = {};
  1.5590 +        if (this.existingAddon) {
  1.5591 +          extraParams.oldVersion = this.existingAddon.version;
  1.5592 +        }
  1.5593 +
  1.5594 +        if (this.addon.bootstrap) {
  1.5595 +          XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
  1.5596 +                                          this.addon.type, file, "install",
  1.5597 +                                          reason, extraParams);
  1.5598 +        }
  1.5599 +
  1.5600 +        AddonManagerPrivate.callAddonListeners("onInstalled",
  1.5601 +                                               createWrapper(this.addon));
  1.5602 +
  1.5603 +        logger.debug("Install of " + this.sourceURI.spec + " completed.");
  1.5604 +        this.state = AddonManager.STATE_INSTALLED;
  1.5605 +        AddonManagerPrivate.callInstallListeners("onInstallEnded",
  1.5606 +                                                 this.listeners, this.wrapper,
  1.5607 +                                                 createWrapper(this.addon));
  1.5608 +
  1.5609 +        if (this.addon.bootstrap) {
  1.5610 +          if (this.addon.active) {
  1.5611 +            XPIProvider.callBootstrapMethod(this.addon.id, this.addon.version,
  1.5612 +                                            this.addon.type, file, "startup",
  1.5613 +                                            reason, extraParams);
  1.5614 +          }
  1.5615 +          else {
  1.5616 +            // XXX this makes it dangerous to do some things in onInstallEnded
  1.5617 +            // listeners because important cleanup hasn't been done yet
  1.5618 +            XPIProvider.unloadBootstrapScope(this.addon.id);
  1.5619 +          }
  1.5620 +        }
  1.5621 +        XPIProvider.setTelemetry(this.addon.id, "unpacked", installedUnpacked);
  1.5622 +        XPIProvider.setTelemetry(this.addon.id, "location", this.installLocation.name);
  1.5623 +        XPIProvider.setTelemetry(this.addon.id, "scan_MS", scanTime);
  1.5624 +        XPIProvider.setTelemetry(this.addon.id, "scan_items", scanItems);
  1.5625 +        let loc = this.addon.defaultLocale;
  1.5626 +        if (loc) {
  1.5627 +          XPIProvider.setTelemetry(this.addon.id, "name", loc.name);
  1.5628 +          XPIProvider.setTelemetry(this.addon.id, "creator", loc.creator);
  1.5629 +        }
  1.5630 +      }
  1.5631 +    }).bind(this)).then(null, (e) => {
  1.5632 +      logger.warn("Failed to install " + this.file.path + " from " + this.sourceURI.spec, e);
  1.5633 +      if (stagedAddon.exists())
  1.5634 +        recursiveRemove(stagedAddon);
  1.5635 +      this.state = AddonManager.STATE_INSTALL_FAILED;
  1.5636 +      this.error = AddonManager.ERROR_FILE_ACCESS;
  1.5637 +      XPIProvider.removeActiveInstall(this);
  1.5638 +      AddonManagerPrivate.callAddonListeners("onOperationCancelled",
  1.5639 +                                             createWrapper(this.addon));
  1.5640 +      AddonManagerPrivate.callInstallListeners("onInstallFailed",
  1.5641 +                                               this.listeners,
  1.5642 +                                               this.wrapper);
  1.5643 +    }).then(() => {
  1.5644 +      this.removeTemporaryFile();
  1.5645 +      return this.installLocation.releaseStagingDir();
  1.5646 +    });
  1.5647 +  },
  1.5648 +
  1.5649 +  getInterface: function AI_getInterface(iid) {
  1.5650 +    if (iid.equals(Ci.nsIAuthPrompt2)) {
  1.5651 +      var factory = Cc["@mozilla.org/prompter;1"].
  1.5652 +                    getService(Ci.nsIPromptFactory);
  1.5653 +      return factory.getPrompt(this.window, Ci.nsIAuthPrompt);
  1.5654 +    }
  1.5655 +    else if (iid.equals(Ci.nsIChannelEventSink)) {
  1.5656 +      return this;
  1.5657 +    }
  1.5658 +
  1.5659 +    return this.badCertHandler.getInterface(iid);
  1.5660 +  }
  1.5661 +}
  1.5662 +
  1.5663 +/**
  1.5664 + * Creates a new AddonInstall for an already staged install. Used when
  1.5665 + * installing the staged install failed for some reason.
  1.5666 + *
  1.5667 + * @param  aDir
  1.5668 + *         The directory holding the staged install
  1.5669 + * @param  aManifest
  1.5670 + *         The cached manifest for the install
  1.5671 + */
  1.5672 +AddonInstall.createStagedInstall = function AI_createStagedInstall(aInstallLocation, aDir, aManifest) {
  1.5673 +  let url = Services.io.newFileURI(aDir);
  1.5674 +
  1.5675 +  let install = new AddonInstall(aInstallLocation, aDir);
  1.5676 +  install.initStagedInstall(aManifest);
  1.5677 +};
  1.5678 +
  1.5679 +/**
  1.5680 + * Creates a new AddonInstall to install an add-on from a local file. Installs
  1.5681 + * always go into the profile install location.
  1.5682 + *
  1.5683 + * @param  aCallback
  1.5684 + *         The callback to pass the new AddonInstall to
  1.5685 + * @param  aFile
  1.5686 + *         The file to install
  1.5687 + */
  1.5688 +AddonInstall.createInstall = function AI_createInstall(aCallback, aFile) {
  1.5689 +  let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
  1.5690 +  let url = Services.io.newFileURI(aFile);
  1.5691 +
  1.5692 +  try {
  1.5693 +    let install = new AddonInstall(location, url);
  1.5694 +    install.initLocalInstall(aCallback);
  1.5695 +  }
  1.5696 +  catch(e) {
  1.5697 +    logger.error("Error creating install", e);
  1.5698 +    makeSafe(aCallback)(null);
  1.5699 +  }
  1.5700 +};
  1.5701 +
  1.5702 +/**
  1.5703 + * Creates a new AddonInstall to download and install a URL.
  1.5704 + *
  1.5705 + * @param  aCallback
  1.5706 + *         The callback to pass the new AddonInstall to
  1.5707 + * @param  aUri
  1.5708 + *         The URI to download
  1.5709 + * @param  aHash
  1.5710 + *         A hash for the add-on
  1.5711 + * @param  aName
  1.5712 + *         A name for the add-on
  1.5713 + * @param  aIcons
  1.5714 + *         An icon URLs for the add-on
  1.5715 + * @param  aVersion
  1.5716 + *         A version for the add-on
  1.5717 + * @param  aLoadGroup
  1.5718 + *         An nsILoadGroup to associate the download with
  1.5719 + */
  1.5720 +AddonInstall.createDownload = function AI_createDownload(aCallback, aUri, aHash, aName, aIcons,
  1.5721 +                                       aVersion, aLoadGroup) {
  1.5722 +  let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
  1.5723 +  let url = NetUtil.newURI(aUri);
  1.5724 +
  1.5725 +  let install = new AddonInstall(location, url, aHash, null, null, aLoadGroup);
  1.5726 +  if (url instanceof Ci.nsIFileURL)
  1.5727 +    install.initLocalInstall(aCallback);
  1.5728 +  else
  1.5729 +    install.initAvailableDownload(aName, null, aIcons, aVersion, aCallback);
  1.5730 +};
  1.5731 +
  1.5732 +/**
  1.5733 + * Creates a new AddonInstall for an update.
  1.5734 + *
  1.5735 + * @param  aCallback
  1.5736 + *         The callback to pass the new AddonInstall to
  1.5737 + * @param  aAddon
  1.5738 + *         The add-on being updated
  1.5739 + * @param  aUpdate
  1.5740 + *         The metadata about the new version from the update manifest
  1.5741 + */
  1.5742 +AddonInstall.createUpdate = function AI_createUpdate(aCallback, aAddon, aUpdate) {
  1.5743 +  let url = NetUtil.newURI(aUpdate.updateURL);
  1.5744 +  let releaseNotesURI = null;
  1.5745 +  try {
  1.5746 +    if (aUpdate.updateInfoURL)
  1.5747 +      releaseNotesURI = NetUtil.newURI(escapeAddonURI(aAddon, aUpdate.updateInfoURL));
  1.5748 +  }
  1.5749 +  catch (e) {
  1.5750 +    // If the releaseNotesURI cannot be parsed then just ignore it.
  1.5751 +  }
  1.5752 +
  1.5753 +  let install = new AddonInstall(aAddon._installLocation, url,
  1.5754 +                                 aUpdate.updateHash, releaseNotesURI, aAddon);
  1.5755 +  if (url instanceof Ci.nsIFileURL) {
  1.5756 +    install.initLocalInstall(aCallback);
  1.5757 +  }
  1.5758 +  else {
  1.5759 +    install.initAvailableDownload(aAddon.selectedLocale.name, aAddon.type,
  1.5760 +                                  aAddon.icons, aUpdate.version, aCallback);
  1.5761 +  }
  1.5762 +};
  1.5763 +
  1.5764 +/**
  1.5765 + * Creates a wrapper for an AddonInstall that only exposes the public API
  1.5766 + *
  1.5767 + * @param  install
  1.5768 + *         The AddonInstall to create a wrapper for
  1.5769 + */
  1.5770 +function AddonInstallWrapper(aInstall) {
  1.5771 +#ifdef MOZ_EM_DEBUG
  1.5772 +  this.__defineGetter__("__AddonInstallInternal__", function AIW_debugGetter() {
  1.5773 +    return aInstall;
  1.5774 +  });
  1.5775 +#endif
  1.5776 +
  1.5777 +  ["name", "type", "version", "icons", "releaseNotesURI", "file", "state", "error",
  1.5778 +   "progress", "maxProgress", "certificate", "certName"].forEach(function(aProp) {
  1.5779 +    this.__defineGetter__(aProp, function AIW_propertyGetter() aInstall[aProp]);
  1.5780 +  }, this);
  1.5781 +
  1.5782 +  this.__defineGetter__("iconURL", function AIW_iconURL() aInstall.icons[32]);
  1.5783 +
  1.5784 +  this.__defineGetter__("existingAddon", function AIW_existingAddonGetter() {
  1.5785 +    return createWrapper(aInstall.existingAddon);
  1.5786 +  });
  1.5787 +  this.__defineGetter__("addon", function AIW_addonGetter() createWrapper(aInstall.addon));
  1.5788 +  this.__defineGetter__("sourceURI", function AIW_sourceURIGetter() aInstall.sourceURI);
  1.5789 +
  1.5790 +  this.__defineGetter__("linkedInstalls", function AIW_linkedInstallsGetter() {
  1.5791 +    if (!aInstall.linkedInstalls)
  1.5792 +      return null;
  1.5793 +    return [i.wrapper for each (i in aInstall.linkedInstalls)];
  1.5794 +  });
  1.5795 +
  1.5796 +  this.install = function AIW_install() {
  1.5797 +    aInstall.install();
  1.5798 +  }
  1.5799 +
  1.5800 +  this.cancel = function AIW_cancel() {
  1.5801 +    aInstall.cancel();
  1.5802 +  }
  1.5803 +
  1.5804 +  this.addListener = function AIW_addListener(listener) {
  1.5805 +    aInstall.addListener(listener);
  1.5806 +  }
  1.5807 +
  1.5808 +  this.removeListener = function AIW_removeListener(listener) {
  1.5809 +    aInstall.removeListener(listener);
  1.5810 +  }
  1.5811 +}
  1.5812 +
  1.5813 +AddonInstallWrapper.prototype = {};
  1.5814 +
  1.5815 +/**
  1.5816 + * Creates a new update checker.
  1.5817 + *
  1.5818 + * @param  aAddon
  1.5819 + *         The add-on to check for updates
  1.5820 + * @param  aListener
  1.5821 + *         An UpdateListener to notify of updates
  1.5822 + * @param  aReason
  1.5823 + *         The reason for the update check
  1.5824 + * @param  aAppVersion
  1.5825 + *         An optional application version to check for updates for
  1.5826 + * @param  aPlatformVersion
  1.5827 + *         An optional platform version to check for updates for
  1.5828 + * @throws if the aListener or aReason arguments are not valid
  1.5829 + */
  1.5830 +function UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion) {
  1.5831 +  if (!aListener || !aReason)
  1.5832 +    throw Cr.NS_ERROR_INVALID_ARG;
  1.5833 +
  1.5834 +  Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm");
  1.5835 +
  1.5836 +  this.addon = aAddon;
  1.5837 +  aAddon._updateCheck = this;
  1.5838 +  XPIProvider.doing(this);
  1.5839 +  this.listener = aListener;
  1.5840 +  this.appVersion = aAppVersion;
  1.5841 +  this.platformVersion = aPlatformVersion;
  1.5842 +  this.syncCompatibility = (aReason == AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
  1.5843 +
  1.5844 +  let updateURL = aAddon.updateURL;
  1.5845 +  if (!updateURL) {
  1.5846 +    if (aReason == AddonManager.UPDATE_WHEN_PERIODIC_UPDATE &&
  1.5847 +        Services.prefs.getPrefType(PREF_EM_UPDATE_BACKGROUND_URL) == Services.prefs.PREF_STRING) {
  1.5848 +      updateURL = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL);
  1.5849 +    } else {
  1.5850 +      updateURL = Services.prefs.getCharPref(PREF_EM_UPDATE_URL);
  1.5851 +    }
  1.5852 +  }
  1.5853 +
  1.5854 +  const UPDATE_TYPE_COMPATIBILITY = 32;
  1.5855 +  const UPDATE_TYPE_NEWVERSION = 64;
  1.5856 +
  1.5857 +  aReason |= UPDATE_TYPE_COMPATIBILITY;
  1.5858 +  if ("onUpdateAvailable" in this.listener)
  1.5859 +    aReason |= UPDATE_TYPE_NEWVERSION;
  1.5860 +
  1.5861 +  let url = escapeAddonURI(aAddon, updateURL, aReason, aAppVersion);
  1.5862 +  this._parser = AddonUpdateChecker.checkForUpdates(aAddon.id, aAddon.updateKey,
  1.5863 +                                                    url, this);
  1.5864 +}
  1.5865 +
  1.5866 +UpdateChecker.prototype = {
  1.5867 +  addon: null,
  1.5868 +  listener: null,
  1.5869 +  appVersion: null,
  1.5870 +  platformVersion: null,
  1.5871 +  syncCompatibility: null,
  1.5872 +
  1.5873 +  /**
  1.5874 +   * Calls a method on the listener passing any number of arguments and
  1.5875 +   * consuming any exceptions.
  1.5876 +   *
  1.5877 +   * @param  aMethod
  1.5878 +   *         The method to call on the listener
  1.5879 +   */
  1.5880 +  callListener: function UC_callListener(aMethod, ...aArgs) {
  1.5881 +    if (!(aMethod in this.listener))
  1.5882 +      return;
  1.5883 +
  1.5884 +    try {
  1.5885 +      this.listener[aMethod].apply(this.listener, aArgs);
  1.5886 +    }
  1.5887 +    catch (e) {
  1.5888 +      logger.warn("Exception calling UpdateListener method " + aMethod, e);
  1.5889 +    }
  1.5890 +  },
  1.5891 +
  1.5892 +  /**
  1.5893 +   * Called when AddonUpdateChecker completes the update check
  1.5894 +   *
  1.5895 +   * @param  updates
  1.5896 +   *         The list of update details for the add-on
  1.5897 +   */
  1.5898 +  onUpdateCheckComplete: function UC_onUpdateCheckComplete(aUpdates) {
  1.5899 +    XPIProvider.done(this.addon._updateCheck);
  1.5900 +    this.addon._updateCheck = null;
  1.5901 +    let AUC = AddonUpdateChecker;
  1.5902 +
  1.5903 +    let ignoreMaxVersion = false;
  1.5904 +    let ignoreStrictCompat = false;
  1.5905 +    if (!AddonManager.checkCompatibility) {
  1.5906 +      ignoreMaxVersion = true;
  1.5907 +      ignoreStrictCompat = true;
  1.5908 +    } else if (this.addon.type in COMPATIBLE_BY_DEFAULT_TYPES &&
  1.5909 +               !AddonManager.strictCompatibility &&
  1.5910 +               !this.addon.strictCompatibility &&
  1.5911 +               !this.addon.hasBinaryComponents) {
  1.5912 +      ignoreMaxVersion = true;
  1.5913 +    }
  1.5914 +
  1.5915 +    // Always apply any compatibility update for the current version
  1.5916 +    let compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
  1.5917 +                                                  this.syncCompatibility,
  1.5918 +                                                  null, null,
  1.5919 +                                                  ignoreMaxVersion,
  1.5920 +                                                  ignoreStrictCompat);
  1.5921 +    // Apply the compatibility update to the database
  1.5922 +    if (compatUpdate)
  1.5923 +      this.addon.applyCompatibilityUpdate(compatUpdate, this.syncCompatibility);
  1.5924 +
  1.5925 +    // If the request is for an application or platform version that is
  1.5926 +    // different to the current application or platform version then look for a
  1.5927 +    // compatibility update for those versions.
  1.5928 +    if ((this.appVersion &&
  1.5929 +         Services.vc.compare(this.appVersion, Services.appinfo.version) != 0) ||
  1.5930 +        (this.platformVersion &&
  1.5931 +         Services.vc.compare(this.platformVersion, Services.appinfo.platformVersion) != 0)) {
  1.5932 +      compatUpdate = AUC.getCompatibilityUpdate(aUpdates, this.addon.version,
  1.5933 +                                                false, this.appVersion,
  1.5934 +                                                this.platformVersion,
  1.5935 +                                                ignoreMaxVersion,
  1.5936 +                                                ignoreStrictCompat);
  1.5937 +    }
  1.5938 +
  1.5939 +    if (compatUpdate)
  1.5940 +      this.callListener("onCompatibilityUpdateAvailable", createWrapper(this.addon));
  1.5941 +    else
  1.5942 +      this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
  1.5943 +
  1.5944 +    function sendUpdateAvailableMessages(aSelf, aInstall) {
  1.5945 +      if (aInstall) {
  1.5946 +        aSelf.callListener("onUpdateAvailable", createWrapper(aSelf.addon),
  1.5947 +                           aInstall.wrapper);
  1.5948 +      }
  1.5949 +      else {
  1.5950 +        aSelf.callListener("onNoUpdateAvailable", createWrapper(aSelf.addon));
  1.5951 +      }
  1.5952 +      aSelf.callListener("onUpdateFinished", createWrapper(aSelf.addon),
  1.5953 +                         AddonManager.UPDATE_STATUS_NO_ERROR);
  1.5954 +    }
  1.5955 +
  1.5956 +    let compatOverrides = AddonManager.strictCompatibility ?
  1.5957 +                            null :
  1.5958 +                            this.addon.compatibilityOverrides;
  1.5959 +
  1.5960 +    let update = AUC.getNewestCompatibleUpdate(aUpdates,
  1.5961 +                                               this.appVersion,
  1.5962 +                                               this.platformVersion,
  1.5963 +                                               ignoreMaxVersion,
  1.5964 +                                               ignoreStrictCompat,
  1.5965 +                                               compatOverrides);
  1.5966 +
  1.5967 +    if (update && Services.vc.compare(this.addon.version, update.version) < 0) {
  1.5968 +      for (let currentInstall of XPIProvider.installs) {
  1.5969 +        // Skip installs that don't match the available update
  1.5970 +        if (currentInstall.existingAddon != this.addon ||
  1.5971 +            currentInstall.version != update.version)
  1.5972 +          continue;
  1.5973 +
  1.5974 +        // If the existing install has not yet started downloading then send an
  1.5975 +        // available update notification. If it is already downloading then
  1.5976 +        // don't send any available update notification
  1.5977 +        if (currentInstall.state == AddonManager.STATE_AVAILABLE) {
  1.5978 +          logger.debug("Found an existing AddonInstall for " + this.addon.id);
  1.5979 +          sendUpdateAvailableMessages(this, currentInstall);
  1.5980 +        }
  1.5981 +        else
  1.5982 +          sendUpdateAvailableMessages(this, null);
  1.5983 +        return;
  1.5984 +      }
  1.5985 +
  1.5986 +      let self = this;
  1.5987 +      AddonInstall.createUpdate(function onUpdateCheckComplete_createUpdate(aInstall) {
  1.5988 +        sendUpdateAvailableMessages(self, aInstall);
  1.5989 +      }, this.addon, update);
  1.5990 +    }
  1.5991 +    else {
  1.5992 +      sendUpdateAvailableMessages(this, null);
  1.5993 +    }
  1.5994 +  },
  1.5995 +
  1.5996 +  /**
  1.5997 +   * Called when AddonUpdateChecker fails the update check
  1.5998 +   *
  1.5999 +   * @param  aError
  1.6000 +   *         An error status
  1.6001 +   */
  1.6002 +  onUpdateCheckError: function UC_onUpdateCheckError(aError) {
  1.6003 +    XPIProvider.done(this.addon._updateCheck);
  1.6004 +    this.addon._updateCheck = null;
  1.6005 +    this.callListener("onNoCompatibilityUpdateAvailable", createWrapper(this.addon));
  1.6006 +    this.callListener("onNoUpdateAvailable", createWrapper(this.addon));
  1.6007 +    this.callListener("onUpdateFinished", createWrapper(this.addon), aError);
  1.6008 +  },
  1.6009 +
  1.6010 +  /**
  1.6011 +   * Called to cancel an in-progress update check
  1.6012 +   */
  1.6013 +  cancel: function UC_cancel() {
  1.6014 +    let parser = this._parser;
  1.6015 +    if (parser) {
  1.6016 +      this._parser = null;
  1.6017 +      // This will call back to onUpdateCheckError with a CANCELLED error
  1.6018 +      parser.cancel();
  1.6019 +    }
  1.6020 +  }
  1.6021 +};
  1.6022 +
  1.6023 +/**
  1.6024 + * The AddonInternal is an internal only representation of add-ons. It may
  1.6025 + * have come from the database (see DBAddonInternal in XPIProviderUtils.jsm)
  1.6026 + * or an install manifest.
  1.6027 + */
  1.6028 +function AddonInternal() {
  1.6029 +}
  1.6030 +
  1.6031 +AddonInternal.prototype = {
  1.6032 +  _selectedLocale: null,
  1.6033 +  active: false,
  1.6034 +  visible: false,
  1.6035 +  userDisabled: false,
  1.6036 +  appDisabled: false,
  1.6037 +  softDisabled: false,
  1.6038 +  sourceURI: null,
  1.6039 +  releaseNotesURI: null,
  1.6040 +  foreignInstall: false,
  1.6041 +
  1.6042 +  get selectedLocale() {
  1.6043 +    if (this._selectedLocale)
  1.6044 +      return this._selectedLocale;
  1.6045 +    let locale = findClosestLocale(this.locales);
  1.6046 +    this._selectedLocale = locale ? locale : this.defaultLocale;
  1.6047 +    return this._selectedLocale;
  1.6048 +  },
  1.6049 +
  1.6050 +  get providesUpdatesSecurely() {
  1.6051 +    return !!(this.updateKey || !this.updateURL ||
  1.6052 +              this.updateURL.substring(0, 6) == "https:");
  1.6053 +  },
  1.6054 +
  1.6055 +  get isCompatible() {
  1.6056 +    return this.isCompatibleWith();
  1.6057 +  },
  1.6058 +
  1.6059 +  get isPlatformCompatible() {
  1.6060 +    if (this.targetPlatforms.length == 0)
  1.6061 +      return true;
  1.6062 +
  1.6063 +    let matchedOS = false;
  1.6064 +
  1.6065 +    // If any targetPlatform matches the OS and contains an ABI then we will
  1.6066 +    // only match a targetPlatform that contains both the current OS and ABI
  1.6067 +    let needsABI = false;
  1.6068 +
  1.6069 +    // Some platforms do not specify an ABI, test against null in that case.
  1.6070 +    let abi = null;
  1.6071 +    try {
  1.6072 +      abi = Services.appinfo.XPCOMABI;
  1.6073 +    }
  1.6074 +    catch (e) { }
  1.6075 +
  1.6076 +    for (let platform of this.targetPlatforms) {
  1.6077 +      if (platform.os == Services.appinfo.OS) {
  1.6078 +        if (platform.abi) {
  1.6079 +          needsABI = true;
  1.6080 +          if (platform.abi === abi)
  1.6081 +            return true;
  1.6082 +        }
  1.6083 +        else {
  1.6084 +          matchedOS = true;
  1.6085 +        }
  1.6086 +      }
  1.6087 +    }
  1.6088 +
  1.6089 +    return matchedOS && !needsABI;
  1.6090 +  },
  1.6091 +
  1.6092 +  isCompatibleWith: function AddonInternal_isCompatibleWith(aAppVersion, aPlatformVersion) {
  1.6093 +    // Experiments are installed through an external mechanism that
  1.6094 +    // limits target audience to compatible clients. We trust it knows what
  1.6095 +    // it's doing and skip compatibility checks.
  1.6096 +    //
  1.6097 +    // This decision does forfeit defense in depth. If the experiments system
  1.6098 +    // is ever wrong about targeting an add-on to a specific application
  1.6099 +    // or platform, the client will likely see errors.
  1.6100 +    if (this.type == "experiment") {
  1.6101 +      return true;
  1.6102 +    }
  1.6103 +
  1.6104 +    let app = this.matchingTargetApplication;
  1.6105 +    if (!app)
  1.6106 +      return false;
  1.6107 +
  1.6108 +    if (!aAppVersion)
  1.6109 +      aAppVersion = Services.appinfo.version;
  1.6110 +    if (!aPlatformVersion)
  1.6111 +      aPlatformVersion = Services.appinfo.platformVersion;
  1.6112 +
  1.6113 +    let version;
  1.6114 +    if (app.id == Services.appinfo.ID)
  1.6115 +      version = aAppVersion;
  1.6116 +    else if (app.id == TOOLKIT_ID)
  1.6117 +      version = aPlatformVersion
  1.6118 +
  1.6119 +    // Only extensions and dictionaries can be compatible by default; themes
  1.6120 +    // and language packs always use strict compatibility checking.
  1.6121 +    if (this.type in COMPATIBLE_BY_DEFAULT_TYPES &&
  1.6122 +        !AddonManager.strictCompatibility && !this.strictCompatibility &&
  1.6123 +        !this.hasBinaryComponents) {
  1.6124 +
  1.6125 +      // The repository can specify compatibility overrides.
  1.6126 +      // Note: For now, only blacklisting is supported by overrides.
  1.6127 +      if (this._repositoryAddon &&
  1.6128 +          this._repositoryAddon.compatibilityOverrides) {
  1.6129 +        let overrides = this._repositoryAddon.compatibilityOverrides;
  1.6130 +        let override = AddonRepository.findMatchingCompatOverride(this.version,
  1.6131 +                                                                  overrides);
  1.6132 +        if (override && override.type == "incompatible")
  1.6133 +          return false;
  1.6134 +      }
  1.6135 +
  1.6136 +      // Extremely old extensions should not be compatible by default.
  1.6137 +      let minCompatVersion;
  1.6138 +      if (app.id == Services.appinfo.ID)
  1.6139 +        minCompatVersion = XPIProvider.minCompatibleAppVersion;
  1.6140 +      else if (app.id == TOOLKIT_ID)
  1.6141 +        minCompatVersion = XPIProvider.minCompatiblePlatformVersion;
  1.6142 +
  1.6143 +      if (minCompatVersion &&
  1.6144 +          Services.vc.compare(minCompatVersion, app.maxVersion) > 0)
  1.6145 +        return false;
  1.6146 +
  1.6147 +      return Services.vc.compare(version, app.minVersion) >= 0;
  1.6148 +    }
  1.6149 +
  1.6150 +    return (Services.vc.compare(version, app.minVersion) >= 0) &&
  1.6151 +           (Services.vc.compare(version, app.maxVersion) <= 0)
  1.6152 +  },
  1.6153 +
  1.6154 +  get matchingTargetApplication() {
  1.6155 +    let app = null;
  1.6156 +    for (let targetApp of this.targetApplications) {
  1.6157 +      if (targetApp.id == Services.appinfo.ID)
  1.6158 +        return targetApp;
  1.6159 +      if (targetApp.id == TOOLKIT_ID)
  1.6160 +        app = targetApp;
  1.6161 +    }
  1.6162 +    return app;
  1.6163 +  },
  1.6164 +
  1.6165 +  get blocklistState() {
  1.6166 +    let staticItem = findMatchingStaticBlocklistItem(this);
  1.6167 +    if (staticItem)
  1.6168 +      return staticItem.level;
  1.6169 +
  1.6170 +    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
  1.6171 +             getService(Ci.nsIBlocklistService);
  1.6172 +    return bs.getAddonBlocklistState(createWrapper(this));
  1.6173 +  },
  1.6174 +
  1.6175 +  get blocklistURL() {
  1.6176 +    let staticItem = findMatchingStaticBlocklistItem(this);
  1.6177 +    if (staticItem) {
  1.6178 +      let url = Services.urlFormatter.formatURLPref("extensions.blocklist.itemURL");
  1.6179 +      return url.replace(/%blockID%/g, staticItem.blockID);
  1.6180 +    }
  1.6181 +
  1.6182 +    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
  1.6183 +             getService(Ci.nsIBlocklistService);
  1.6184 +    return bs.getAddonBlocklistURL(createWrapper(this));
  1.6185 +  },
  1.6186 +
  1.6187 +  applyCompatibilityUpdate: function AddonInternal_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
  1.6188 +    this.targetApplications.forEach(function(aTargetApp) {
  1.6189 +      aUpdate.targetApplications.forEach(function(aUpdateTarget) {
  1.6190 +        if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
  1.6191 +            Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
  1.6192 +          aTargetApp.minVersion = aUpdateTarget.minVersion;
  1.6193 +          aTargetApp.maxVersion = aUpdateTarget.maxVersion;
  1.6194 +        }
  1.6195 +      });
  1.6196 +    });
  1.6197 +    this.appDisabled = !isUsableAddon(this);
  1.6198 +  },
  1.6199 +
  1.6200 +  /**
  1.6201 +   * toJSON is called by JSON.stringify in order to create a filtered version
  1.6202 +   * of this object to be serialized to a JSON file. A new object is returned
  1.6203 +   * with copies of all non-private properties. Functions, getters and setters
  1.6204 +   * are not copied.
  1.6205 +   *
  1.6206 +   * @param  aKey
  1.6207 +   *         The key that this object is being serialized as in the JSON.
  1.6208 +   *         Unused here since this is always the main object serialized
  1.6209 +   *
  1.6210 +   * @return an object containing copies of the properties of this object
  1.6211 +   *         ignoring private properties, functions, getters and setters
  1.6212 +   */
  1.6213 +  toJSON: function AddonInternal_toJSON(aKey) {
  1.6214 +    let obj = {};
  1.6215 +    for (let prop in this) {
  1.6216 +      // Ignore private properties
  1.6217 +      if (prop.substring(0, 1) == "_")
  1.6218 +        continue;
  1.6219 +
  1.6220 +      // Ignore getters
  1.6221 +      if (this.__lookupGetter__(prop))
  1.6222 +        continue;
  1.6223 +
  1.6224 +      // Ignore setters
  1.6225 +      if (this.__lookupSetter__(prop))
  1.6226 +        continue;
  1.6227 +
  1.6228 +      // Ignore functions
  1.6229 +      if (typeof this[prop] == "function")
  1.6230 +        continue;
  1.6231 +
  1.6232 +      obj[prop] = this[prop];
  1.6233 +    }
  1.6234 +
  1.6235 +    return obj;
  1.6236 +  },
  1.6237 +
  1.6238 +  /**
  1.6239 +   * When an add-on install is pending its metadata will be cached in a file.
  1.6240 +   * This method reads particular properties of that metadata that may be newer
  1.6241 +   * than that in the install manifest, like compatibility information.
  1.6242 +   *
  1.6243 +   * @param  aObj
  1.6244 +   *         A JS object containing the cached metadata
  1.6245 +   */
  1.6246 +  importMetadata: function AddonInternal_importMetaData(aObj) {
  1.6247 +    PENDING_INSTALL_METADATA.forEach(function(aProp) {
  1.6248 +      if (!(aProp in aObj))
  1.6249 +        return;
  1.6250 +
  1.6251 +      this[aProp] = aObj[aProp];
  1.6252 +    }, this);
  1.6253 +
  1.6254 +    // Compatibility info may have changed so update appDisabled
  1.6255 +    this.appDisabled = !isUsableAddon(this);
  1.6256 +  }
  1.6257 +};
  1.6258 +
  1.6259 +/**
  1.6260 + * Creates an AddonWrapper for an AddonInternal.
  1.6261 + *
  1.6262 + * @param   addon
  1.6263 + *          The AddonInternal to wrap
  1.6264 + * @return  an AddonWrapper or null if addon was null
  1.6265 + */
  1.6266 +function createWrapper(aAddon) {
  1.6267 +  if (!aAddon)
  1.6268 +    return null;
  1.6269 +  if (!aAddon._wrapper) {
  1.6270 +    aAddon._hasResourceCache = new Map();
  1.6271 +    aAddon._wrapper = new AddonWrapper(aAddon);
  1.6272 +  }
  1.6273 +  return aAddon._wrapper;
  1.6274 +}
  1.6275 +
  1.6276 +/**
  1.6277 + * The AddonWrapper wraps an Addon to provide the data visible to consumers of
  1.6278 + * the public API.
  1.6279 + */
  1.6280 +function AddonWrapper(aAddon) {
  1.6281 +#ifdef MOZ_EM_DEBUG
  1.6282 +  this.__defineGetter__("__AddonInternal__", function AW_debugGetter() {
  1.6283 +    return aAddon;
  1.6284 +  });
  1.6285 +#endif
  1.6286 +
  1.6287 +  function chooseValue(aObj, aProp) {
  1.6288 +    let repositoryAddon = aAddon._repositoryAddon;
  1.6289 +    let objValue = aObj[aProp];
  1.6290 +
  1.6291 +    if (repositoryAddon && (aProp in repositoryAddon) &&
  1.6292 +        (objValue === undefined || objValue === null)) {
  1.6293 +      return [repositoryAddon[aProp], true];
  1.6294 +    }
  1.6295 +
  1.6296 +    return [objValue, false];
  1.6297 +  }
  1.6298 +
  1.6299 +  ["id", "syncGUID", "version", "type", "isCompatible", "isPlatformCompatible",
  1.6300 +   "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
  1.6301 +   "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
  1.6302 +   "strictCompatibility", "compatibilityOverrides", "updateURL"].forEach(function(aProp) {
  1.6303 +     this.__defineGetter__(aProp, function AddonWrapper_propertyGetter() aAddon[aProp]);
  1.6304 +  }, this);
  1.6305 +
  1.6306 +  ["fullDescription", "developerComments", "eula", "supportURL",
  1.6307 +   "contributionURL", "contributionAmount", "averageRating", "reviewCount",
  1.6308 +   "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers",
  1.6309 +   "repositoryStatus"].forEach(function(aProp) {
  1.6310 +    this.__defineGetter__(aProp, function AddonWrapper_repoPropertyGetter() {
  1.6311 +      if (aAddon._repositoryAddon)
  1.6312 +        return aAddon._repositoryAddon[aProp];
  1.6313 +
  1.6314 +      return null;
  1.6315 +    });
  1.6316 +  }, this);
  1.6317 +
  1.6318 +  this.__defineGetter__("aboutURL", function AddonWrapper_aboutURLGetter() {
  1.6319 +    return this.isActive ? aAddon["aboutURL"] : null;
  1.6320 +  });
  1.6321 +
  1.6322 +  ["installDate", "updateDate"].forEach(function(aProp) {
  1.6323 +    this.__defineGetter__(aProp, function AddonWrapper_datePropertyGetter() new Date(aAddon[aProp]));
  1.6324 +  }, this);
  1.6325 +
  1.6326 +  ["sourceURI", "releaseNotesURI"].forEach(function(aProp) {
  1.6327 +    this.__defineGetter__(aProp, function AddonWrapper_URIPropertyGetter() {
  1.6328 +      let [target, fromRepo] = chooseValue(aAddon, aProp);
  1.6329 +      if (!target)
  1.6330 +        return null;
  1.6331 +      if (fromRepo)
  1.6332 +        return target;
  1.6333 +      return NetUtil.newURI(target);
  1.6334 +    });
  1.6335 +  }, this);
  1.6336 +
  1.6337 +  this.__defineGetter__("optionsURL", function AddonWrapper_optionsURLGetter() {
  1.6338 +    if (this.isActive && aAddon.optionsURL)
  1.6339 +      return aAddon.optionsURL;
  1.6340 +
  1.6341 +    if (this.isActive && this.hasResource("options.xul"))
  1.6342 +      return this.getResourceURI("options.xul").spec;
  1.6343 +
  1.6344 +    return null;
  1.6345 +  }, this);
  1.6346 +
  1.6347 +  this.__defineGetter__("optionsType", function AddonWrapper_optionsTypeGetter() {
  1.6348 +    if (!this.isActive)
  1.6349 +      return null;
  1.6350 +
  1.6351 +    let hasOptionsXUL = this.hasResource("options.xul");
  1.6352 +    let hasOptionsURL = !!this.optionsURL;
  1.6353 +
  1.6354 +    if (aAddon.optionsType) {
  1.6355 +      switch (parseInt(aAddon.optionsType, 10)) {
  1.6356 +      case AddonManager.OPTIONS_TYPE_DIALOG:
  1.6357 +      case AddonManager.OPTIONS_TYPE_TAB:
  1.6358 +        return hasOptionsURL ? aAddon.optionsType : null;
  1.6359 +      case AddonManager.OPTIONS_TYPE_INLINE:
  1.6360 +      case AddonManager.OPTIONS_TYPE_INLINE_INFO:
  1.6361 +        return (hasOptionsXUL || hasOptionsURL) ? aAddon.optionsType : null;
  1.6362 +      }
  1.6363 +      return null;
  1.6364 +    }
  1.6365 +
  1.6366 +    if (hasOptionsXUL)
  1.6367 +      return AddonManager.OPTIONS_TYPE_INLINE;
  1.6368 +
  1.6369 +    if (hasOptionsURL)
  1.6370 +      return AddonManager.OPTIONS_TYPE_DIALOG;
  1.6371 +
  1.6372 +    return null;
  1.6373 +  }, this);
  1.6374 +
  1.6375 +  this.__defineGetter__("iconURL", function AddonWrapper_iconURLGetter() {
  1.6376 +    return this.icons[32];
  1.6377 +  }, this);
  1.6378 +
  1.6379 +  this.__defineGetter__("icon64URL", function AddonWrapper_icon64URLGetter() {
  1.6380 +    return this.icons[64];
  1.6381 +  }, this);
  1.6382 +
  1.6383 +  this.__defineGetter__("icons", function AddonWrapper_iconsGetter() {
  1.6384 +    let icons = {};
  1.6385 +    if (aAddon._repositoryAddon) {
  1.6386 +      for (let size in aAddon._repositoryAddon.icons) {
  1.6387 +        icons[size] = aAddon._repositoryAddon.icons[size];
  1.6388 +      }
  1.6389 +    }
  1.6390 +    if (this.isActive && aAddon.iconURL) {
  1.6391 +      icons[32] = aAddon.iconURL;
  1.6392 +    } else if (this.hasResource("icon.png")) {
  1.6393 +      icons[32] = this.getResourceURI("icon.png").spec;
  1.6394 +    }
  1.6395 +    if (this.isActive && aAddon.icon64URL) {
  1.6396 +      icons[64] = aAddon.icon64URL;
  1.6397 +    } else if (this.hasResource("icon64.png")) {
  1.6398 +      icons[64] = this.getResourceURI("icon64.png").spec;
  1.6399 +    }
  1.6400 +    Object.freeze(icons);
  1.6401 +    return icons;
  1.6402 +  }, this);
  1.6403 +
  1.6404 +  PROP_LOCALE_SINGLE.forEach(function(aProp) {
  1.6405 +    this.__defineGetter__(aProp, function AddonWrapper_singleLocaleGetter() {
  1.6406 +      // Override XPI creator if repository creator is defined
  1.6407 +      if (aProp == "creator" &&
  1.6408 +          aAddon._repositoryAddon && aAddon._repositoryAddon.creator) {
  1.6409 +        return aAddon._repositoryAddon.creator;
  1.6410 +      }
  1.6411 +
  1.6412 +      let result = null;
  1.6413 +
  1.6414 +      if (aAddon.active) {
  1.6415 +        try {
  1.6416 +          let pref = PREF_EM_EXTENSION_FORMAT + aAddon.id + "." + aProp;
  1.6417 +          let value = Services.prefs.getComplexValue(pref,
  1.6418 +                                                     Ci.nsIPrefLocalizedString);
  1.6419 +          if (value.data)
  1.6420 +            result = value.data;
  1.6421 +        }
  1.6422 +        catch (e) {
  1.6423 +        }
  1.6424 +      }
  1.6425 +
  1.6426 +      if (result == null)
  1.6427 +        [result, ] = chooseValue(aAddon.selectedLocale, aProp);
  1.6428 +
  1.6429 +      if (aProp == "creator")
  1.6430 +        return result ? new AddonManagerPrivate.AddonAuthor(result) : null;
  1.6431 +
  1.6432 +      return result;
  1.6433 +    });
  1.6434 +  }, this);
  1.6435 +
  1.6436 +  PROP_LOCALE_MULTI.forEach(function(aProp) {
  1.6437 +    this.__defineGetter__(aProp, function AddonWrapper_multiLocaleGetter() {
  1.6438 +      let results = null;
  1.6439 +      let usedRepository = false;
  1.6440 +
  1.6441 +      if (aAddon.active) {
  1.6442 +        let pref = PREF_EM_EXTENSION_FORMAT + aAddon.id + "." +
  1.6443 +                   aProp.substring(0, aProp.length - 1);
  1.6444 +        let list = Services.prefs.getChildList(pref, {});
  1.6445 +        if (list.length > 0) {
  1.6446 +          list.sort();
  1.6447 +          results = [];
  1.6448 +          list.forEach(function(aPref) {
  1.6449 +            let value = Services.prefs.getComplexValue(aPref,
  1.6450 +                                                       Ci.nsIPrefLocalizedString);
  1.6451 +            if (value.data)
  1.6452 +              results.push(value.data);
  1.6453 +          });
  1.6454 +        }
  1.6455 +      }
  1.6456 +
  1.6457 +      if (results == null)
  1.6458 +        [results, usedRepository] = chooseValue(aAddon.selectedLocale, aProp);
  1.6459 +
  1.6460 +      if (results && !usedRepository) {
  1.6461 +        results = results.map(function mapResult(aResult) {
  1.6462 +          return new AddonManagerPrivate.AddonAuthor(aResult);
  1.6463 +        });
  1.6464 +      }
  1.6465 +
  1.6466 +      return results;
  1.6467 +    });
  1.6468 +  }, this);
  1.6469 +
  1.6470 +  this.__defineGetter__("screenshots", function AddonWrapper_screenshotsGetter() {
  1.6471 +    let repositoryAddon = aAddon._repositoryAddon;
  1.6472 +    if (repositoryAddon && ("screenshots" in repositoryAddon)) {
  1.6473 +      let repositoryScreenshots = repositoryAddon.screenshots;
  1.6474 +      if (repositoryScreenshots && repositoryScreenshots.length > 0)
  1.6475 +        return repositoryScreenshots;
  1.6476 +    }
  1.6477 +
  1.6478 +    if (aAddon.type == "theme" && this.hasResource("preview.png")) {
  1.6479 +      let url = this.getResourceURI("preview.png").spec;
  1.6480 +      return [new AddonManagerPrivate.AddonScreenshot(url)];
  1.6481 +    }
  1.6482 +
  1.6483 +    return null;
  1.6484 +  });
  1.6485 +
  1.6486 +  this.__defineGetter__("applyBackgroundUpdates", function AddonWrapper_applyBackgroundUpdatesGetter() {
  1.6487 +    return aAddon.applyBackgroundUpdates;
  1.6488 +  });
  1.6489 +  this.__defineSetter__("applyBackgroundUpdates", function AddonWrapper_applyBackgroundUpdatesSetter(val) {
  1.6490 +    if (this.type == "experiment") {
  1.6491 +      logger.warn("Setting applyBackgroundUpdates on an experiment is not supported.");
  1.6492 +      return;
  1.6493 +    }
  1.6494 +
  1.6495 +    if (val != AddonManager.AUTOUPDATE_DEFAULT &&
  1.6496 +        val != AddonManager.AUTOUPDATE_DISABLE &&
  1.6497 +        val != AddonManager.AUTOUPDATE_ENABLE) {
  1.6498 +      val = val ? AddonManager.AUTOUPDATE_DEFAULT :
  1.6499 +                  AddonManager.AUTOUPDATE_DISABLE;
  1.6500 +    }
  1.6501 +
  1.6502 +    if (val == aAddon.applyBackgroundUpdates)
  1.6503 +      return val;
  1.6504 +
  1.6505 +    XPIDatabase.setAddonProperties(aAddon, {
  1.6506 +      applyBackgroundUpdates: val
  1.6507 +    });
  1.6508 +    AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, ["applyBackgroundUpdates"]);
  1.6509 +
  1.6510 +    return val;
  1.6511 +  });
  1.6512 +
  1.6513 +  this.__defineSetter__("syncGUID", function AddonWrapper_syncGUIDGetter(val) {
  1.6514 +    if (aAddon.syncGUID == val)
  1.6515 +      return val;
  1.6516 +
  1.6517 +    if (aAddon.inDatabase)
  1.6518 +      XPIDatabase.setAddonSyncGUID(aAddon, val);
  1.6519 +
  1.6520 +    aAddon.syncGUID = val;
  1.6521 +
  1.6522 +    return val;
  1.6523 +  });
  1.6524 +
  1.6525 +  this.__defineGetter__("install", function AddonWrapper_installGetter() {
  1.6526 +    if (!("_install" in aAddon) || !aAddon._install)
  1.6527 +      return null;
  1.6528 +    return aAddon._install.wrapper;
  1.6529 +  });
  1.6530 +
  1.6531 +  this.__defineGetter__("pendingUpgrade", function AddonWrapper_pendingUpgradeGetter() {
  1.6532 +    return createWrapper(aAddon.pendingUpgrade);
  1.6533 +  });
  1.6534 +
  1.6535 +  this.__defineGetter__("scope", function AddonWrapper_scopeGetter() {
  1.6536 +    if (aAddon._installLocation)
  1.6537 +      return aAddon._installLocation.scope;
  1.6538 +
  1.6539 +    return AddonManager.SCOPE_PROFILE;
  1.6540 +  });
  1.6541 +
  1.6542 +  this.__defineGetter__("pendingOperations", function AddonWrapper_pendingOperationsGetter() {
  1.6543 +    let pending = 0;
  1.6544 +    if (!(aAddon.inDatabase)) {
  1.6545 +      // Add-on is pending install if there is no associated install (shouldn't
  1.6546 +      // happen here) or if the install is in the process of or has successfully
  1.6547 +      // completed the install. If an add-on is pending install then we ignore
  1.6548 +      // any other pending operations.
  1.6549 +      if (!aAddon._install || aAddon._install.state == AddonManager.STATE_INSTALLING ||
  1.6550 +          aAddon._install.state == AddonManager.STATE_INSTALLED)
  1.6551 +        return AddonManager.PENDING_INSTALL;
  1.6552 +    }
  1.6553 +    else if (aAddon.pendingUninstall) {
  1.6554 +      // If an add-on is pending uninstall then we ignore any other pending
  1.6555 +      // operations
  1.6556 +      return AddonManager.PENDING_UNINSTALL;
  1.6557 +    }
  1.6558 +
  1.6559 +    if (aAddon.active && isAddonDisabled(aAddon))
  1.6560 +      pending |= AddonManager.PENDING_DISABLE;
  1.6561 +    else if (!aAddon.active && !isAddonDisabled(aAddon))
  1.6562 +      pending |= AddonManager.PENDING_ENABLE;
  1.6563 +
  1.6564 +    if (aAddon.pendingUpgrade)
  1.6565 +      pending |= AddonManager.PENDING_UPGRADE;
  1.6566 +
  1.6567 +    return pending;
  1.6568 +  });
  1.6569 +
  1.6570 +  this.__defineGetter__("operationsRequiringRestart", function AddonWrapper_operationsRequiringRestartGetter() {
  1.6571 +    let ops = 0;
  1.6572 +    if (XPIProvider.installRequiresRestart(aAddon))
  1.6573 +      ops |= AddonManager.OP_NEEDS_RESTART_INSTALL;
  1.6574 +    if (XPIProvider.uninstallRequiresRestart(aAddon))
  1.6575 +      ops |= AddonManager.OP_NEEDS_RESTART_UNINSTALL;
  1.6576 +    if (XPIProvider.enableRequiresRestart(aAddon))
  1.6577 +      ops |= AddonManager.OP_NEEDS_RESTART_ENABLE;
  1.6578 +    if (XPIProvider.disableRequiresRestart(aAddon))
  1.6579 +      ops |= AddonManager.OP_NEEDS_RESTART_DISABLE;
  1.6580 +
  1.6581 +    return ops;
  1.6582 +  });
  1.6583 +
  1.6584 +  this.__defineGetter__("isDebuggable", function AddonWrapper_isDebuggable() {
  1.6585 +    return this.isActive && aAddon.bootstrap;
  1.6586 +  });
  1.6587 +
  1.6588 +  this.__defineGetter__("permissions", function AddonWrapper_permisionsGetter() {
  1.6589 +    let permissions = 0;
  1.6590 +
  1.6591 +    // Add-ons that aren't installed cannot be modified in any way
  1.6592 +    if (!(aAddon.inDatabase))
  1.6593 +      return permissions;
  1.6594 +
  1.6595 +    // Experiments can only be uninstalled. An uninstall reflects the user
  1.6596 +    // intent of "disable this experiment." This is partially managed by the
  1.6597 +    // experiments manager.
  1.6598 +    if (aAddon.type == "experiment") {
  1.6599 +      return AddonManager.PERM_CAN_UNINSTALL;
  1.6600 +    }
  1.6601 +
  1.6602 +    if (!aAddon.appDisabled) {
  1.6603 +      if (this.userDisabled) {
  1.6604 +        permissions |= AddonManager.PERM_CAN_ENABLE;
  1.6605 +      }
  1.6606 +      else if (aAddon.type != "theme") {
  1.6607 +        permissions |= AddonManager.PERM_CAN_DISABLE;
  1.6608 +      }
  1.6609 +    }
  1.6610 +
  1.6611 +    // Add-ons that are in locked install locations, or are pending uninstall
  1.6612 +    // cannot be upgraded or uninstalled
  1.6613 +    if (!aAddon._installLocation.locked && !aAddon.pendingUninstall) {
  1.6614 +      // Add-ons that are installed by a file link cannot be upgraded
  1.6615 +      if (!aAddon._installLocation.isLinkedAddon(aAddon.id)) {
  1.6616 +        permissions |= AddonManager.PERM_CAN_UPGRADE;
  1.6617 +      }
  1.6618 +
  1.6619 +      permissions |= AddonManager.PERM_CAN_UNINSTALL;
  1.6620 +    }
  1.6621 +
  1.6622 +    return permissions;
  1.6623 +  });
  1.6624 +
  1.6625 +  this.__defineGetter__("isActive", function AddonWrapper_isActiveGetter() {
  1.6626 +    if (Services.appinfo.inSafeMode)
  1.6627 +      return false;
  1.6628 +    return aAddon.active;
  1.6629 +  });
  1.6630 +
  1.6631 +  this.__defineGetter__("userDisabled", function AddonWrapper_userDisabledGetter() {
  1.6632 +    if (XPIProvider._enabledExperiments.has(aAddon.id)) {
  1.6633 +      return false;
  1.6634 +    }
  1.6635 +
  1.6636 +    return aAddon.softDisabled || aAddon.userDisabled;
  1.6637 +  });
  1.6638 +  this.__defineSetter__("userDisabled", function AddonWrapper_userDisabledSetter(val) {
  1.6639 +    if (val == this.userDisabled) {
  1.6640 +      return val;
  1.6641 +    }
  1.6642 +
  1.6643 +    if (aAddon.type == "experiment") {
  1.6644 +      if (val) {
  1.6645 +        XPIProvider._enabledExperiments.delete(aAddon.id);
  1.6646 +      } else {
  1.6647 +        XPIProvider._enabledExperiments.add(aAddon.id);
  1.6648 +      }
  1.6649 +    }
  1.6650 +
  1.6651 +    if (aAddon.inDatabase) {
  1.6652 +      if (aAddon.type == "theme" && val) {
  1.6653 +        if (aAddon.internalName == XPIProvider.defaultSkin)
  1.6654 +          throw new Error("Cannot disable the default theme");
  1.6655 +        XPIProvider.enableDefaultTheme();
  1.6656 +      }
  1.6657 +      else {
  1.6658 +        XPIProvider.updateAddonDisabledState(aAddon, val);
  1.6659 +      }
  1.6660 +    }
  1.6661 +    else {
  1.6662 +      aAddon.userDisabled = val;
  1.6663 +      // When enabling remove the softDisabled flag
  1.6664 +      if (!val)
  1.6665 +        aAddon.softDisabled = false;
  1.6666 +    }
  1.6667 +
  1.6668 +    return val;
  1.6669 +  });
  1.6670 +
  1.6671 +  this.__defineSetter__("softDisabled", function AddonWrapper_softDisabledSetter(val) {
  1.6672 +    if (val == aAddon.softDisabled)
  1.6673 +      return val;
  1.6674 +
  1.6675 +    if (aAddon.inDatabase) {
  1.6676 +      // When softDisabling a theme just enable the active theme
  1.6677 +      if (aAddon.type == "theme" && val && !aAddon.userDisabled) {
  1.6678 +        if (aAddon.internalName == XPIProvider.defaultSkin)
  1.6679 +          throw new Error("Cannot disable the default theme");
  1.6680 +        XPIProvider.enableDefaultTheme();
  1.6681 +      }
  1.6682 +      else {
  1.6683 +        XPIProvider.updateAddonDisabledState(aAddon, undefined, val);
  1.6684 +      }
  1.6685 +    }
  1.6686 +    else {
  1.6687 +      // Only set softDisabled if not already disabled
  1.6688 +      if (!aAddon.userDisabled)
  1.6689 +        aAddon.softDisabled = val;
  1.6690 +    }
  1.6691 +
  1.6692 +    return val;
  1.6693 +  });
  1.6694 +
  1.6695 +  this.isCompatibleWith = function AddonWrapper_isCompatiblewith(aAppVersion, aPlatformVersion) {
  1.6696 +    return aAddon.isCompatibleWith(aAppVersion, aPlatformVersion);
  1.6697 +  };
  1.6698 +
  1.6699 +  this.uninstall = function AddonWrapper_uninstall() {
  1.6700 +    if (!(aAddon.inDatabase))
  1.6701 +      throw new Error("Cannot uninstall an add-on that isn't installed");
  1.6702 +    if (aAddon.pendingUninstall)
  1.6703 +      throw new Error("Add-on is already marked to be uninstalled");
  1.6704 +    XPIProvider.uninstallAddon(aAddon);
  1.6705 +  };
  1.6706 +
  1.6707 +  this.cancelUninstall = function AddonWrapper_cancelUninstall() {
  1.6708 +    if (!(aAddon.inDatabase))
  1.6709 +      throw new Error("Cannot cancel uninstall for an add-on that isn't installed");
  1.6710 +    if (!aAddon.pendingUninstall)
  1.6711 +      throw new Error("Add-on is not marked to be uninstalled");
  1.6712 +    XPIProvider.cancelUninstallAddon(aAddon);
  1.6713 +  };
  1.6714 +
  1.6715 +  this.findUpdates = function AddonWrapper_findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
  1.6716 +    // Short-circuit updates for experiments because updates are handled
  1.6717 +    // through the Experiments Manager.
  1.6718 +    if (this.type == "experiment") {
  1.6719 +      AddonManagerPrivate.callNoUpdateListeners(this, aListener, aReason,
  1.6720 +                                                aAppVersion, aPlatformVersion);
  1.6721 +      return;
  1.6722 +    }
  1.6723 +
  1.6724 +    new UpdateChecker(aAddon, aListener, aReason, aAppVersion, aPlatformVersion);
  1.6725 +  };
  1.6726 +
  1.6727 +  // Returns true if there was an update in progress, false if there was no update to cancel
  1.6728 +  this.cancelUpdate = function AddonWrapper_cancelUpdate() {
  1.6729 +    if (aAddon._updateCheck) {
  1.6730 +      aAddon._updateCheck.cancel();
  1.6731 +      return true;
  1.6732 +    }
  1.6733 +    return false;
  1.6734 +  };
  1.6735 +
  1.6736 +  this.hasResource = function AddonWrapper_hasResource(aPath) {
  1.6737 +    if (aAddon._hasResourceCache.has(aPath))
  1.6738 +      return aAddon._hasResourceCache.get(aPath);
  1.6739 +
  1.6740 +    let bundle = aAddon._sourceBundle.clone();
  1.6741 +
  1.6742 +    // Bundle may not exist any more if the addon has just been uninstalled,
  1.6743 +    // but explicitly first checking .exists() results in unneeded file I/O.
  1.6744 +    try {
  1.6745 +      var isDir = bundle.isDirectory();
  1.6746 +    } catch (e) {
  1.6747 +      aAddon._hasResourceCache.set(aPath, false);
  1.6748 +      return false;
  1.6749 +    }
  1.6750 +
  1.6751 +    if (isDir) {
  1.6752 +      if (aPath) {
  1.6753 +        aPath.split("/").forEach(function(aPart) {
  1.6754 +          bundle.append(aPart);
  1.6755 +        });
  1.6756 +      }
  1.6757 +      let result = bundle.exists();
  1.6758 +      aAddon._hasResourceCache.set(aPath, result);
  1.6759 +      return result;
  1.6760 +    }
  1.6761 +
  1.6762 +    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
  1.6763 +                    createInstance(Ci.nsIZipReader);
  1.6764 +    try {
  1.6765 +      zipReader.open(bundle);
  1.6766 +      let result = zipReader.hasEntry(aPath);
  1.6767 +      aAddon._hasResourceCache.set(aPath, result);
  1.6768 +      return result;
  1.6769 +    }
  1.6770 +    catch (e) {
  1.6771 +      aAddon._hasResourceCache.set(aPath, false);
  1.6772 +      return false;
  1.6773 +    }
  1.6774 +    finally {
  1.6775 +      zipReader.close();
  1.6776 +    }
  1.6777 +  },
  1.6778 +
  1.6779 +  /**
  1.6780 +   * Returns a URI to the selected resource or to the add-on bundle if aPath
  1.6781 +   * is null. URIs to the bundle will always be file: URIs. URIs to resources
  1.6782 +   * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is
  1.6783 +   * still an XPI file.
  1.6784 +   *
  1.6785 +   * @param  aPath
  1.6786 +   *         The path in the add-on to get the URI for or null to get a URI to
  1.6787 +   *         the file or directory the add-on is installed as.
  1.6788 +   * @return an nsIURI
  1.6789 +   */
  1.6790 +  this.getResourceURI = function AddonWrapper_getResourceURI(aPath) {
  1.6791 +    if (!aPath)
  1.6792 +      return NetUtil.newURI(aAddon._sourceBundle);
  1.6793 +
  1.6794 +    return getURIForResourceInFile(aAddon._sourceBundle, aPath);
  1.6795 +  }
  1.6796 +}
  1.6797 +
  1.6798 +/**
  1.6799 + * An object which identifies a directory install location for add-ons. The
  1.6800 + * location consists of a directory which contains the add-ons installed in the
  1.6801 + * location.
  1.6802 + *
  1.6803 + * Each add-on installed in the location is either a directory containing the
  1.6804 + * add-on's files or a text file containing an absolute path to the directory
  1.6805 + * containing the add-ons files. The directory or text file must have the same
  1.6806 + * name as the add-on's ID.
  1.6807 + *
  1.6808 + * There may also a special directory named "staged" which can contain
  1.6809 + * directories with the same name as an add-on ID. If the directory is empty
  1.6810 + * then it means the add-on will be uninstalled from this location during the
  1.6811 + * next startup. If the directory contains the add-on's files then they will be
  1.6812 + * installed during the next startup.
  1.6813 + *
  1.6814 + * @param  aName
  1.6815 + *         The string identifier for the install location
  1.6816 + * @param  aDirectory
  1.6817 + *         The nsIFile directory for the install location
  1.6818 + * @param  aScope
  1.6819 + *         The scope of add-ons installed in this location
  1.6820 + * @param  aLocked
  1.6821 + *         true if add-ons cannot be installed, uninstalled or upgraded in the
  1.6822 + *         install location
  1.6823 + */
  1.6824 +function DirectoryInstallLocation(aName, aDirectory, aScope, aLocked) {
  1.6825 +  this._name = aName;
  1.6826 +  this.locked = aLocked;
  1.6827 +  this._directory = aDirectory;
  1.6828 +  this._scope = aScope
  1.6829 +  this._IDToFileMap = {};
  1.6830 +  this._FileToIDMap = {};
  1.6831 +  this._linkedAddons = [];
  1.6832 +  this._stagingDirLock = 0;
  1.6833 +
  1.6834 +  if (!aDirectory.exists())
  1.6835 +    return;
  1.6836 +  if (!aDirectory.isDirectory())
  1.6837 +    throw new Error("Location must be a directory.");
  1.6838 +
  1.6839 +  this._readAddons();
  1.6840 +}
  1.6841 +
  1.6842 +DirectoryInstallLocation.prototype = {
  1.6843 +  _name       : "",
  1.6844 +  _directory   : null,
  1.6845 +  _IDToFileMap : null,  // mapping from add-on ID to nsIFile
  1.6846 +  _FileToIDMap : null,  // mapping from add-on path to add-on ID
  1.6847 +
  1.6848 +  /**
  1.6849 +   * Reads a directory linked to in a file.
  1.6850 +   *
  1.6851 +   * @param   file
  1.6852 +   *          The file containing the directory path
  1.6853 +   * @return  An nsIFile object representing the linked directory.
  1.6854 +   */
  1.6855 +  _readDirectoryFromFile: function DirInstallLocation__readDirectoryFromFile(aFile) {
  1.6856 +    let fis = Cc["@mozilla.org/network/file-input-stream;1"].
  1.6857 +              createInstance(Ci.nsIFileInputStream);
  1.6858 +    fis.init(aFile, -1, -1, false);
  1.6859 +    let line = { value: "" };
  1.6860 +    if (fis instanceof Ci.nsILineInputStream)
  1.6861 +      fis.readLine(line);
  1.6862 +    fis.close();
  1.6863 +    if (line.value) {
  1.6864 +      let linkedDirectory = Cc["@mozilla.org/file/local;1"].
  1.6865 +                            createInstance(Ci.nsIFile);
  1.6866 +
  1.6867 +      try {
  1.6868 +        linkedDirectory.initWithPath(line.value);
  1.6869 +      }
  1.6870 +      catch (e) {
  1.6871 +        linkedDirectory.setRelativeDescriptor(aFile.parent, line.value);
  1.6872 +      }
  1.6873 +
  1.6874 +      if (!linkedDirectory.exists()) {
  1.6875 +        logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
  1.6876 +             " which does not exist");
  1.6877 +        return null;
  1.6878 +      }
  1.6879 +
  1.6880 +      if (!linkedDirectory.isDirectory()) {
  1.6881 +        logger.warn("File pointer " + aFile.path + " points to " + linkedDirectory.path +
  1.6882 +             " which is not a directory");
  1.6883 +        return null;
  1.6884 +      }
  1.6885 +
  1.6886 +      return linkedDirectory;
  1.6887 +    }
  1.6888 +
  1.6889 +    logger.warn("File pointer " + aFile.path + " does not contain a path");
  1.6890 +    return null;
  1.6891 +  },
  1.6892 +
  1.6893 +  /**
  1.6894 +   * Finds all the add-ons installed in this location.
  1.6895 +   */
  1.6896 +  _readAddons: function DirInstallLocation__readAddons() {
  1.6897 +    // Use a snapshot of the directory contents to avoid possible issues with
  1.6898 +    // iterating over a directory while removing files from it (the YAFFS2
  1.6899 +    // embedded filesystem has this issue, see bug 772238).
  1.6900 +    let entries = getDirectoryEntries(this._directory);
  1.6901 +    for (let entry of entries) {
  1.6902 +      let id = entry.leafName;
  1.6903 +
  1.6904 +      if (id == DIR_STAGE || id == DIR_XPI_STAGE || id == DIR_TRASH)
  1.6905 +        continue;
  1.6906 +
  1.6907 +      let directLoad = false;
  1.6908 +      if (entry.isFile() &&
  1.6909 +          id.substring(id.length - 4).toLowerCase() == ".xpi") {
  1.6910 +        directLoad = true;
  1.6911 +        id = id.substring(0, id.length - 4);
  1.6912 +      }
  1.6913 +
  1.6914 +      if (!gIDTest.test(id)) {
  1.6915 +        logger.debug("Ignoring file entry whose name is not a valid add-on ID: " +
  1.6916 +             entry.path);
  1.6917 +        continue;
  1.6918 +      }
  1.6919 +
  1.6920 +      if (entry.isFile() && !directLoad) {
  1.6921 +        let newEntry = this._readDirectoryFromFile(entry);
  1.6922 +        if (!newEntry) {
  1.6923 +          logger.debug("Deleting stale pointer file " + entry.path);
  1.6924 +          try {
  1.6925 +            entry.remove(true);
  1.6926 +          }
  1.6927 +          catch (e) {
  1.6928 +            logger.warn("Failed to remove stale pointer file " + entry.path, e);
  1.6929 +            // Failing to remove the stale pointer file is ignorable
  1.6930 +          }
  1.6931 +          continue;
  1.6932 +        }
  1.6933 +
  1.6934 +        entry = newEntry;
  1.6935 +        this._linkedAddons.push(id);
  1.6936 +      }
  1.6937 +
  1.6938 +      this._IDToFileMap[id] = entry;
  1.6939 +      this._FileToIDMap[entry.path] = id;
  1.6940 +    }
  1.6941 +  },
  1.6942 +
  1.6943 +  /**
  1.6944 +   * Gets the name of this install location.
  1.6945 +   */
  1.6946 +  get name() {
  1.6947 +    return this._name;
  1.6948 +  },
  1.6949 +
  1.6950 +  /**
  1.6951 +   * Gets the scope of this install location.
  1.6952 +   */
  1.6953 +  get scope() {
  1.6954 +    return this._scope;
  1.6955 +  },
  1.6956 +
  1.6957 +  /**
  1.6958 +   * Gets an array of nsIFiles for add-ons installed in this location.
  1.6959 +   */
  1.6960 +  get addonLocations() {
  1.6961 +    let locations = [];
  1.6962 +    for (let id in this._IDToFileMap) {
  1.6963 +      locations.push(this._IDToFileMap[id].clone());
  1.6964 +    }
  1.6965 +    return locations;
  1.6966 +  },
  1.6967 +
  1.6968 +  /**
  1.6969 +   * Gets the staging directory to put add-ons that are pending install and
  1.6970 +   * uninstall into.
  1.6971 +   *
  1.6972 +   * @return an nsIFile
  1.6973 +   */
  1.6974 +  getStagingDir: function DirInstallLocation_getStagingDir() {
  1.6975 +    let dir = this._directory.clone();
  1.6976 +    dir.append(DIR_STAGE);
  1.6977 +    return dir;
  1.6978 +  },
  1.6979 +
  1.6980 +  requestStagingDir: function() {
  1.6981 +    this._stagingDirLock++;
  1.6982 +
  1.6983 +    if (this._stagingDirPromise)
  1.6984 +      return this._stagingDirPromise;
  1.6985 +
  1.6986 +    OS.File.makeDir(this._directory.path);
  1.6987 +    let stagepath = OS.Path.join(this._directory.path, DIR_STAGE);
  1.6988 +    return this._stagingDirPromise = OS.File.makeDir(stagepath).then(null, (e) => {
  1.6989 +      if (e instanceof OS.File.Error && e.becauseExists)
  1.6990 +        return;
  1.6991 +      logger.error("Failed to create staging directory", e);
  1.6992 +      throw e;
  1.6993 +    });
  1.6994 +  },
  1.6995 +
  1.6996 +  releaseStagingDir: function() {
  1.6997 +    this._stagingDirLock--;
  1.6998 +
  1.6999 +    if (this._stagingDirLock == 0) {
  1.7000 +      this._stagingDirPromise = null;
  1.7001 +      this.cleanStagingDir();
  1.7002 +    }
  1.7003 +
  1.7004 +    return Promise.resolve();
  1.7005 +  },
  1.7006 +
  1.7007 +  /**
  1.7008 +   * Removes the specified files or directories in the staging directory and
  1.7009 +   * then if the staging directory is empty attempts to remove it.
  1.7010 +   *
  1.7011 +   * @param  aLeafNames
  1.7012 +   *         An array of file or directory to remove from the directory, the
  1.7013 +   *         array may be empty
  1.7014 +   */
  1.7015 +  cleanStagingDir: function(aLeafNames = []) {
  1.7016 +    let dir = this.getStagingDir();
  1.7017 +
  1.7018 +    for (let name of aLeafNames) {
  1.7019 +      let file = dir.clone();
  1.7020 +      file.append(name);
  1.7021 +      recursiveRemove(file);
  1.7022 +    }
  1.7023 +
  1.7024 +    if (this._stagingDirLock > 0)
  1.7025 +      return;
  1.7026 +
  1.7027 +    let dirEntries = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
  1.7028 +    try {
  1.7029 +      if (dirEntries.nextFile)
  1.7030 +        return;
  1.7031 +    }
  1.7032 +    finally {
  1.7033 +      dirEntries.close();
  1.7034 +    }
  1.7035 +
  1.7036 +    try {
  1.7037 +      setFilePermissions(dir, FileUtils.PERMS_DIRECTORY);
  1.7038 +      dir.remove(false);
  1.7039 +    }
  1.7040 +    catch (e) {
  1.7041 +      logger.warn("Failed to remove staging dir", e);
  1.7042 +      // Failing to remove the staging directory is ignorable
  1.7043 +    }
  1.7044 +  },
  1.7045 +
  1.7046 +  /**
  1.7047 +   * Gets the directory used by old versions for staging XPI and JAR files ready
  1.7048 +   * to be installed.
  1.7049 +   *
  1.7050 +   * @return an nsIFile
  1.7051 +   */
  1.7052 +  getXPIStagingDir: function DirInstallLocation_getXPIStagingDir() {
  1.7053 +    let dir = this._directory.clone();
  1.7054 +    dir.append(DIR_XPI_STAGE);
  1.7055 +    return dir;
  1.7056 +  },
  1.7057 +
  1.7058 +  /**
  1.7059 +   * Returns a directory that is normally on the same filesystem as the rest of
  1.7060 +   * the install location and can be used for temporarily storing files during
  1.7061 +   * safe move operations. Calling this method will delete the existing trash
  1.7062 +   * directory and its contents.
  1.7063 +   *
  1.7064 +   * @return an nsIFile
  1.7065 +   */
  1.7066 +  getTrashDir: function DirInstallLocation_getTrashDir() {
  1.7067 +    let trashDir = this._directory.clone();
  1.7068 +    trashDir.append(DIR_TRASH);
  1.7069 +    if (trashDir.exists())
  1.7070 +      recursiveRemove(trashDir);
  1.7071 +    trashDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
  1.7072 +    return trashDir;
  1.7073 +  },
  1.7074 +
  1.7075 +  /**
  1.7076 +   * Installs an add-on into the install location.
  1.7077 +   *
  1.7078 +   * @param  aId
  1.7079 +   *         The ID of the add-on to install
  1.7080 +   * @param  aSource
  1.7081 +   *         The source nsIFile to install from
  1.7082 +   * @param  aExistingAddonID
  1.7083 +   *         The ID of an existing add-on to uninstall at the same time
  1.7084 +   * @param  aCopy
  1.7085 +   *         If false the source files will be moved to the new location,
  1.7086 +   *         otherwise they will only be copied
  1.7087 +   * @return an nsIFile indicating where the add-on was installed to
  1.7088 +   */
  1.7089 +  installAddon: function DirInstallLocation_installAddon(aId, aSource,
  1.7090 +                                                         aExistingAddonID,
  1.7091 +                                                         aCopy) {
  1.7092 +    let trashDir = this.getTrashDir();
  1.7093 +
  1.7094 +    let transaction = new SafeInstallOperation();
  1.7095 +
  1.7096 +    let self = this;
  1.7097 +    function moveOldAddon(aId) {
  1.7098 +      let file = self._directory.clone();
  1.7099 +      file.append(aId);
  1.7100 +
  1.7101 +      if (file.exists())
  1.7102 +        transaction.move(file, trashDir);
  1.7103 +
  1.7104 +      file = self._directory.clone();
  1.7105 +      file.append(aId + ".xpi");
  1.7106 +      if (file.exists()) {
  1.7107 +        flushJarCache(file);
  1.7108 +        transaction.move(file, trashDir);
  1.7109 +      }
  1.7110 +    }
  1.7111 +
  1.7112 +    // If any of these operations fails the finally block will clean up the
  1.7113 +    // temporary directory
  1.7114 +    try {
  1.7115 +      moveOldAddon(aId);
  1.7116 +      if (aExistingAddonID && aExistingAddonID != aId)
  1.7117 +        moveOldAddon(aExistingAddonID);
  1.7118 +
  1.7119 +      if (aCopy) {
  1.7120 +        transaction.copy(aSource, this._directory);
  1.7121 +      }
  1.7122 +      else {
  1.7123 +        if (aSource.isFile())
  1.7124 +          flushJarCache(aSource);
  1.7125 +
  1.7126 +        transaction.move(aSource, this._directory);
  1.7127 +      }
  1.7128 +    }
  1.7129 +    finally {
  1.7130 +      // It isn't ideal if this cleanup fails but it isn't worth rolling back
  1.7131 +      // the install because of it.
  1.7132 +      try {
  1.7133 +        recursiveRemove(trashDir);
  1.7134 +      }
  1.7135 +      catch (e) {
  1.7136 +        logger.warn("Failed to remove trash directory when installing " + aId, e);
  1.7137 +      }
  1.7138 +    }
  1.7139 +
  1.7140 +    let newFile = this._directory.clone();
  1.7141 +    newFile.append(aSource.leafName);
  1.7142 +    try {
  1.7143 +      newFile.lastModifiedTime = Date.now();
  1.7144 +    } catch (e)  {
  1.7145 +      logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
  1.7146 +    }
  1.7147 +    this._FileToIDMap[newFile.path] = aId;
  1.7148 +    this._IDToFileMap[aId] = newFile;
  1.7149 +
  1.7150 +    if (aExistingAddonID && aExistingAddonID != aId &&
  1.7151 +        aExistingAddonID in this._IDToFileMap) {
  1.7152 +      delete this._FileToIDMap[this._IDToFileMap[aExistingAddonID]];
  1.7153 +      delete this._IDToFileMap[aExistingAddonID];
  1.7154 +    }
  1.7155 +
  1.7156 +    return newFile;
  1.7157 +  },
  1.7158 +
  1.7159 +  /**
  1.7160 +   * Uninstalls an add-on from this location.
  1.7161 +   *
  1.7162 +   * @param  aId
  1.7163 +   *         The ID of the add-on to uninstall
  1.7164 +   * @throws if the ID does not match any of the add-ons installed
  1.7165 +   */
  1.7166 +  uninstallAddon: function DirInstallLocation_uninstallAddon(aId) {
  1.7167 +    let file = this._IDToFileMap[aId];
  1.7168 +    if (!file) {
  1.7169 +      logger.warn("Attempted to remove " + aId + " from " +
  1.7170 +           this._name + " but it was already gone");
  1.7171 +      return;
  1.7172 +    }
  1.7173 +
  1.7174 +    file = this._directory.clone();
  1.7175 +    file.append(aId);
  1.7176 +    if (!file.exists())
  1.7177 +      file.leafName += ".xpi";
  1.7178 +
  1.7179 +    if (!file.exists()) {
  1.7180 +      logger.warn("Attempted to remove " + aId + " from " +
  1.7181 +           this._name + " but it was already gone");
  1.7182 +
  1.7183 +      delete this._FileToIDMap[file.path];
  1.7184 +      delete this._IDToFileMap[aId];
  1.7185 +      return;
  1.7186 +    }
  1.7187 +
  1.7188 +    let trashDir = this.getTrashDir();
  1.7189 +
  1.7190 +    if (file.leafName != aId) {
  1.7191 +      logger.debug("uninstallAddon: flushing jar cache " + file.path + " for addon " + aId);
  1.7192 +      flushJarCache(file);
  1.7193 +    }
  1.7194 +
  1.7195 +    let transaction = new SafeInstallOperation();
  1.7196 +
  1.7197 +    try {
  1.7198 +      transaction.move(file, trashDir);
  1.7199 +    }
  1.7200 +    finally {
  1.7201 +      // It isn't ideal if this cleanup fails, but it is probably better than
  1.7202 +      // rolling back the uninstall at this point
  1.7203 +      try {
  1.7204 +        recursiveRemove(trashDir);
  1.7205 +      }
  1.7206 +      catch (e) {
  1.7207 +        logger.warn("Failed to remove trash directory when uninstalling " + aId, e);
  1.7208 +      }
  1.7209 +    }
  1.7210 +
  1.7211 +    delete this._FileToIDMap[file.path];
  1.7212 +    delete this._IDToFileMap[aId];
  1.7213 +  },
  1.7214 +
  1.7215 +  /**
  1.7216 +   * Gets the ID of the add-on installed in the given nsIFile.
  1.7217 +   *
  1.7218 +   * @param  aFile
  1.7219 +   *         The nsIFile to look in
  1.7220 +   * @return the ID
  1.7221 +   * @throws if the file does not represent an installed add-on
  1.7222 +   */
  1.7223 +  getIDForLocation: function DirInstallLocation_getIDForLocation(aFile) {
  1.7224 +    if (aFile.path in this._FileToIDMap)
  1.7225 +      return this._FileToIDMap[aFile.path];
  1.7226 +    throw new Error("Unknown add-on location " + aFile.path);
  1.7227 +  },
  1.7228 +
  1.7229 +  /**
  1.7230 +   * Gets the directory that the add-on with the given ID is installed in.
  1.7231 +   *
  1.7232 +   * @param  aId
  1.7233 +   *         The ID of the add-on
  1.7234 +   * @return The nsIFile
  1.7235 +   * @throws if the ID does not match any of the add-ons installed
  1.7236 +   */
  1.7237 +  getLocationForID: function DirInstallLocation_getLocationForID(aId) {
  1.7238 +    if (aId in this._IDToFileMap)
  1.7239 +      return this._IDToFileMap[aId].clone();
  1.7240 +    throw new Error("Unknown add-on ID " + aId);
  1.7241 +  },
  1.7242 +
  1.7243 +  /**
  1.7244 +   * Returns true if the given addon was installed in this location by a text
  1.7245 +   * file pointing to its real path.
  1.7246 +   *
  1.7247 +   * @param aId
  1.7248 +   *        The ID of the addon
  1.7249 +   */
  1.7250 +  isLinkedAddon: function DirInstallLocation__isLinkedAddon(aId) {
  1.7251 +    return this._linkedAddons.indexOf(aId) != -1;
  1.7252 +  }
  1.7253 +};
  1.7254 +
  1.7255 +#ifdef XP_WIN
  1.7256 +/**
  1.7257 + * An object that identifies a registry install location for add-ons. The location
  1.7258 + * consists of a registry key which contains string values mapping ID to the
  1.7259 + * path where an add-on is installed
  1.7260 + *
  1.7261 + * @param  aName
  1.7262 + *         The string identifier of this Install Location.
  1.7263 + * @param  aRootKey
  1.7264 + *         The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
  1.7265 + * @param  scope
  1.7266 + *         The scope of add-ons installed in this location
  1.7267 + */
  1.7268 +function WinRegInstallLocation(aName, aRootKey, aScope) {
  1.7269 +  this.locked = true;
  1.7270 +  this._name = aName;
  1.7271 +  this._rootKey = aRootKey;
  1.7272 +  this._scope = aScope;
  1.7273 +  this._IDToFileMap = {};
  1.7274 +  this._FileToIDMap = {};
  1.7275 +
  1.7276 +  let path = this._appKeyPath + "\\Extensions";
  1.7277 +  let key = Cc["@mozilla.org/windows-registry-key;1"].
  1.7278 +            createInstance(Ci.nsIWindowsRegKey);
  1.7279 +
  1.7280 +  // Reading the registry may throw an exception, and that's ok.  In error
  1.7281 +  // cases, we just leave ourselves in the empty state.
  1.7282 +  try {
  1.7283 +    key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ);
  1.7284 +  }
  1.7285 +  catch (e) {
  1.7286 +    return;
  1.7287 +  }
  1.7288 +
  1.7289 +  this._readAddons(key);
  1.7290 +  key.close();
  1.7291 +}
  1.7292 +
  1.7293 +WinRegInstallLocation.prototype = {
  1.7294 +  _name       : "",
  1.7295 +  _rootKey    : null,
  1.7296 +  _scope      : null,
  1.7297 +  _IDToFileMap : null,  // mapping from ID to nsIFile
  1.7298 +  _FileToIDMap : null,  // mapping from path to ID
  1.7299 +
  1.7300 +  /**
  1.7301 +   * Retrieves the path of this Application's data key in the registry.
  1.7302 +   */
  1.7303 +  get _appKeyPath() {
  1.7304 +    let appVendor = Services.appinfo.vendor;
  1.7305 +    let appName = Services.appinfo.name;
  1.7306 +
  1.7307 +#ifdef MOZ_THUNDERBIRD
  1.7308 +    // XXX Thunderbird doesn't specify a vendor string
  1.7309 +    if (appVendor == "")
  1.7310 +      appVendor = "Mozilla";
  1.7311 +#endif
  1.7312 +
  1.7313 +    // XULRunner-based apps may intentionally not specify a vendor
  1.7314 +    if (appVendor != "")
  1.7315 +      appVendor += "\\";
  1.7316 +
  1.7317 +    return "SOFTWARE\\" + appVendor + appName;
  1.7318 +  },
  1.7319 +
  1.7320 +  /**
  1.7321 +   * Read the registry and build a mapping between ID and path for each
  1.7322 +   * installed add-on.
  1.7323 +   *
  1.7324 +   * @param  key
  1.7325 +   *         The key that contains the ID to path mapping
  1.7326 +   */
  1.7327 +  _readAddons: function RegInstallLocation__readAddons(aKey) {
  1.7328 +    let count = aKey.valueCount;
  1.7329 +    for (let i = 0; i < count; ++i) {
  1.7330 +      let id = aKey.getValueName(i);
  1.7331 +
  1.7332 +      let file = Cc["@mozilla.org/file/local;1"].
  1.7333 +                createInstance(Ci.nsIFile);
  1.7334 +      file.initWithPath(aKey.readStringValue(id));
  1.7335 +
  1.7336 +      if (!file.exists()) {
  1.7337 +        logger.warn("Ignoring missing add-on in " + file.path);
  1.7338 +        continue;
  1.7339 +      }
  1.7340 +
  1.7341 +      this._IDToFileMap[id] = file;
  1.7342 +      this._FileToIDMap[file.path] = id;
  1.7343 +    }
  1.7344 +  },
  1.7345 +
  1.7346 +  /**
  1.7347 +   * Gets the name of this install location.
  1.7348 +   */
  1.7349 +  get name() {
  1.7350 +    return this._name;
  1.7351 +  },
  1.7352 +
  1.7353 +  /**
  1.7354 +   * Gets the scope of this install location.
  1.7355 +   */
  1.7356 +  get scope() {
  1.7357 +    return this._scope;
  1.7358 +  },
  1.7359 +
  1.7360 +  /**
  1.7361 +   * Gets an array of nsIFiles for add-ons installed in this location.
  1.7362 +   */
  1.7363 +  get addonLocations() {
  1.7364 +    let locations = [];
  1.7365 +    for (let id in this._IDToFileMap) {
  1.7366 +      locations.push(this._IDToFileMap[id].clone());
  1.7367 +    }
  1.7368 +    return locations;
  1.7369 +  },
  1.7370 +
  1.7371 +  /**
  1.7372 +   * Gets the ID of the add-on installed in the given nsIFile.
  1.7373 +   *
  1.7374 +   * @param  aFile
  1.7375 +   *         The nsIFile to look in
  1.7376 +   * @return the ID
  1.7377 +   * @throws if the file does not represent an installed add-on
  1.7378 +   */
  1.7379 +  getIDForLocation: function RegInstallLocation_getIDForLocation(aFile) {
  1.7380 +    if (aFile.path in this._FileToIDMap)
  1.7381 +      return this._FileToIDMap[aFile.path];
  1.7382 +    throw new Error("Unknown add-on location");
  1.7383 +  },
  1.7384 +
  1.7385 +  /**
  1.7386 +   * Gets the nsIFile that the add-on with the given ID is installed in.
  1.7387 +   *
  1.7388 +   * @param  aId
  1.7389 +   *         The ID of the add-on
  1.7390 +   * @return the nsIFile
  1.7391 +   */
  1.7392 +  getLocationForID: function RegInstallLocation_getLocationForID(aId) {
  1.7393 +    if (aId in this._IDToFileMap)
  1.7394 +      return this._IDToFileMap[aId].clone();
  1.7395 +    throw new Error("Unknown add-on ID");
  1.7396 +  },
  1.7397 +
  1.7398 +  /**
  1.7399 +   * @see DirectoryInstallLocation
  1.7400 +   */
  1.7401 +  isLinkedAddon: function RegInstallLocation_isLinkedAddon(aId) {
  1.7402 +    return true;
  1.7403 +  }
  1.7404 +};
  1.7405 +#endif
  1.7406 +
  1.7407 +let addonTypes = [
  1.7408 +  new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
  1.7409 +                                    STRING_TYPE_NAME,
  1.7410 +                                    AddonManager.VIEW_TYPE_LIST, 4000),
  1.7411 +  new AddonManagerPrivate.AddonType("theme", URI_EXTENSION_STRINGS,
  1.7412 +                                    STRING_TYPE_NAME,
  1.7413 +                                    AddonManager.VIEW_TYPE_LIST, 5000),
  1.7414 +  new AddonManagerPrivate.AddonType("dictionary", URI_EXTENSION_STRINGS,
  1.7415 +                                    STRING_TYPE_NAME,
  1.7416 +                                    AddonManager.VIEW_TYPE_LIST, 7000,
  1.7417 +                                    AddonManager.TYPE_UI_HIDE_EMPTY),
  1.7418 +  new AddonManagerPrivate.AddonType("locale", URI_EXTENSION_STRINGS,
  1.7419 +                                    STRING_TYPE_NAME,
  1.7420 +                                    AddonManager.VIEW_TYPE_LIST, 8000,
  1.7421 +                                    AddonManager.TYPE_UI_HIDE_EMPTY),
  1.7422 +];
  1.7423 +
  1.7424 +// We only register experiments support if the application supports them.
  1.7425 +// Ideally, we would install an observer to watch the pref. Installing
  1.7426 +// an observer for this pref is not necessary here and may be buggy with
  1.7427 +// regards to registering this XPIProvider twice.
  1.7428 +if (Prefs.getBoolPref("experiments.supported", false)) {
  1.7429 +  addonTypes.push(
  1.7430 +    new AddonManagerPrivate.AddonType("experiment",
  1.7431 +                                      URI_EXTENSION_STRINGS,
  1.7432 +                                      STRING_TYPE_NAME,
  1.7433 +                                      AddonManager.VIEW_TYPE_LIST, 11000,
  1.7434 +                                      AddonManager.TYPE_UI_HIDE_EMPTY));
  1.7435 +}
  1.7436 +
  1.7437 +AddonManagerPrivate.registerProvider(XPIProvider, addonTypes);

mercurial