browser/components/migration/src/MigrationUtils.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/migration/src/MigrationUtils.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,635 @@
     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 +this.EXPORTED_SYMBOLS = ["MigrationUtils", "MigratorPrototype"];
    1.11 +
    1.12 +const Cu = Components.utils;
    1.13 +const Ci = Components.interfaces;
    1.14 +const Cc = Components.classes;
    1.15 +
    1.16 +const TOPIC_WILL_IMPORT_BOOKMARKS = "initial-migration-will-import-default-bookmarks";
    1.17 +const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmarks";
    1.18 +
    1.19 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.20 +Cu.import("resource://gre/modules/Services.jsm");
    1.21 +
    1.22 +XPCOMUtils.defineLazyModuleGetter(this, "Dict",
    1.23 +                                  "resource://gre/modules/Dict.jsm");
    1.24 +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
    1.25 +                                  "resource://gre/modules/PlacesUtils.jsm");
    1.26 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    1.27 +                                  "resource://gre/modules/NetUtil.jsm");
    1.28 +XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
    1.29 +                                  "resource://gre/modules/BookmarkHTMLUtils.jsm");
    1.30 +
    1.31 +let gMigrators = null;
    1.32 +let gProfileStartup = null;
    1.33 +let gMigrationBundle = null;
    1.34 +
    1.35 +function getMigrationBundle() {
    1.36 +  if (!gMigrationBundle) {
    1.37 +    gMigrationBundle = Services.strings.createBundle(
    1.38 +     "chrome://browser/locale/migration/migration.properties"); 
    1.39 +  }
    1.40 +  return gMigrationBundle;
    1.41 +}
    1.42 +
    1.43 +/**
    1.44 + * Figure out what is the default browser, and if there is a migrator
    1.45 + * for it, return that migrator's internal name.
    1.46 + * For the time being, the "internal name" of a migraotr is its contract-id
    1.47 + * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
    1.48 + * but it will soon be exposed properly.
    1.49 + */
    1.50 +function getMigratorKeyForDefaultBrowser() {
    1.51 +  // Don't map Firefox to the Firefox migrator, because we don't
    1.52 +  // expect it to ever show up as an option in the wizard.
    1.53 +  // We may want to revise this if/when we use separate profiles
    1.54 +  // for each Firefox-update channel.
    1.55 +  const APP_DESC_TO_KEY = {
    1.56 +    "Internet Explorer": "ie",
    1.57 +    "Safari":            "safari",
    1.58 +    "Google Chrome":     "chrome",  // Windows, Linux
    1.59 +    "Chrome":            "chrome",  // OS X
    1.60 +  };
    1.61 +
    1.62 +  let browserDesc = "";
    1.63 +  try {
    1.64 +    let browserDesc =
    1.65 +      Cc["@mozilla.org/uriloader/external-protocol-service;1"].
    1.66 +      getService(Ci.nsIExternalProtocolService).
    1.67 +      getApplicationDescription("http");
    1.68 +    return APP_DESC_TO_KEY[browserDesc] || "";
    1.69 +  }
    1.70 +  catch(ex) {
    1.71 +    Cu.reportError("Could not detect default browser: " + ex);
    1.72 +  }
    1.73 +  return "";
    1.74 +}
    1.75 +
    1.76 +/**
    1.77 + * Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
    1.78 + *
    1.79 + * To implement a migrator:
    1.80 + * 1. Import this module.
    1.81 + * 2. Create the prototype for the migrator, extending MigratorPrototype.
    1.82 + *    Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
    1.83 + * 3. Set classDescription, contractID and classID for your migrator, and set
    1.84 + *    NSGetFactory appropriately.
    1.85 + * 4. If the migrator supports multiple profiles, override the sourceProfiles
    1.86 + *    Here we default for single-profile migrator.
    1.87 + * 5. Implement getResources(aProfile) (see below).
    1.88 + * 6. If the migrator supports reading the home page of the source browser,
    1.89 + *    override |sourceHomePageURL| getter.
    1.90 + * 7. For startup-only migrators, override |startupOnlyMigrator|.
    1.91 + */
    1.92 +this.MigratorPrototype = {
    1.93 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
    1.94 +
    1.95 +  /**
    1.96 +   * OVERRIDE IF AND ONLY IF the source supports multiple profiles.
    1.97 +   *
    1.98 +   * Returns array of profiles (by names) from which data may be imported.
    1.99 +   *
   1.100 +   * Only profiles from which data can be imported should be listed.  Otherwise
   1.101 +   * the behavior of the migration wizard isn't well-defined.
   1.102 +   *
   1.103 +   * For a single-profile source (e.g. safari, ie), this returns null,
   1.104 +   * and not an empty array.  That is the default implementation.
   1.105 +   */
   1.106 +  get sourceProfiles() null,
   1.107 +
   1.108 +  /**
   1.109 +   * MUST BE OVERRIDDEN.
   1.110 +   *
   1.111 +   * Returns an array of "migration resources" objects for the given profile,
   1.112 +   * or for the "default" profile, if the migrator does not support multiple
   1.113 +   * profiles.
   1.114 +   *
   1.115 +   * Each migration resource should provide:
   1.116 +   * - a |type| getter, retunring any of the migration types (see
   1.117 +   *   nsIBrowserProfileMigrator).
   1.118 +   *
   1.119 +   * - a |migrate| method, taking a single argument, aCallback(bool success),
   1.120 +   *   for migrating the data for this resource.  It may do its job
   1.121 +   *   synchronously or asynchronously.  Either way, it must call
   1.122 +   *   aCallback(bool aSuccess) when it's done.  In the case of an exception
   1.123 +   *   thrown from |migrate|, it's taken as if aCallback(false) is called.
   1.124 +   *
   1.125 +   *   Note: In the case of a simple asynchronous implementation, you may find
   1.126 +   *   MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
   1.127 +   *
   1.128 +   * For each migration type listed in nsIBrowserProfileMigrator, multiple
   1.129 +   * migration resources may be provided.  This practice is useful when the
   1.130 +   * data for a certain migration type is independently stored in few
   1.131 +   * locations.  For example, the mac version of Safari stores its "reading list"
   1.132 +   * bookmarks in a separate property list.
   1.133 +   *
   1.134 +   * Note that the importation of a particular migration type is reported as
   1.135 +   * successful if _any_ of its resources succeeded to import (that is, called,
   1.136 +   * |aCallback(true)|).  However, completion-status for a particular migration
   1.137 +   * type is reported to the UI only once all of its migrators have called
   1.138 +   * aCallback.
   1.139 +   *
   1.140 +   * @note  The returned array should only include resources from which data
   1.141 +   *        can be imported.  So, for example, before adding a resource for the
   1.142 +   *        BOOKMARKS migration type, you should check if you should check that the
   1.143 +   *        bookmarks file exists.
   1.144 +   *
   1.145 +   * @param aProfile
   1.146 +   *        The profile from which data may be imported, or an empty string
   1.147 +   *        in the case of a single-profile migrator.
   1.148 +   *        In the case of multiple-profiles migrator, it is guaranteed that
   1.149 +   *        aProfile is a value returned by the sourceProfiles getter (see
   1.150 +   *        above).
   1.151 +   */
   1.152 +  getResources: function MP_getResources(aProfile) {
   1.153 +    throw new Error("getResources must be overridden");
   1.154 +  },
   1.155 +
   1.156 +  /**
   1.157 +   * OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
   1.158 +   * that is just the Firefox migrator, see bug 737381).  Default: false.
   1.159 +   *
   1.160 +   * Startup-only migrators are different in two ways:
   1.161 +   * - they may only be used during startup.
   1.162 +   * - the user-profile is half baked during migration.  The folder exists,
   1.163 +   *   but it's only accessible through MigrationUtils.profileStartup.
   1.164 +   *   The migrator can call MigrationUtils.profileStartup.doStartup
   1.165 +   *   at any point in order to initialize the profile.
   1.166 +   */
   1.167 +  get startupOnlyMigrator() false,
   1.168 +
   1.169 +  /**
   1.170 +   * OVERRIDE IF AND ONLY IF your migrator supports importing the homepage.
   1.171 +   * @see nsIBrowserProfileMigrator
   1.172 +   */
   1.173 +  get sourceHomePageURL() "",
   1.174 +
   1.175 +  /**
   1.176 +   * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
   1.177 +   * getResources.
   1.178 +   *
   1.179 +   * @see nsIBrowserProfileMigrator
   1.180 +   */
   1.181 +  getMigrateData: function MP_getMigrateData(aProfile) {
   1.182 +    let types = [r.type for each (r in this._getMaybeCachedResources(aProfile))];
   1.183 +    return types.reduce(function(a, b) a |= b, 0);
   1.184 +  },
   1.185 +
   1.186 +  /**
   1.187 +   * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
   1.188 +   * migrate for each resource.
   1.189 +   *
   1.190 +   * @see nsIBrowserProfileMigrator
   1.191 +   */
   1.192 +  migrate: function MP_migrate(aItems, aStartup, aProfile) {
   1.193 +    let resources = this._getMaybeCachedResources(aProfile);
   1.194 +    if (resources.length == 0)
   1.195 +      throw new Error("migrate called for a non-existent source");
   1.196 +
   1.197 +    if (aItems != Ci.nsIBrowserProfileMigrator.ALL)
   1.198 +      resources = [r for each (r in resources) if (aItems & r.type)];
   1.199 +
   1.200 +    // Called either directly or through the bookmarks import callback.
   1.201 +    function doMigrate() {
   1.202 +      // TODO: use Map (for the items) and Set (for the resources)
   1.203 +      // once they are iterable.
   1.204 +      let resourcesGroupedByItems = new Dict();
   1.205 +      resources.forEach(function(resource) {
   1.206 +        if (resourcesGroupedByItems.has(resource.type))
   1.207 +          resourcesGroupedByItems.get(resource.type).push(resource);
   1.208 +        else
   1.209 +          resourcesGroupedByItems.set(resource.type, [resource]);
   1.210 +      });
   1.211 +
   1.212 +      if (resourcesGroupedByItems.count == 0)
   1.213 +        throw new Error("No items to import");
   1.214 +
   1.215 +      let notify = function(aMsg, aItemType) {
   1.216 +        Services.obs.notifyObservers(null, aMsg, aItemType);
   1.217 +      }
   1.218 +
   1.219 +      notify("Migration:Started");
   1.220 +      resourcesGroupedByItems.listkeys().forEach(function(migrationType) {
   1.221 +        let migrationTypeA = migrationType;
   1.222 +        let itemResources = resourcesGroupedByItems.get(migrationType);
   1.223 +        notify("Migration:ItemBeforeMigrate", migrationType);
   1.224 +
   1.225 +        let itemSuccess = false;
   1.226 +        itemResources.forEach(function(resource) {
   1.227 +          let resourceDone = function(aSuccess) {
   1.228 +            let resourceIndex = itemResources.indexOf(resource);
   1.229 +            if (resourceIndex != -1) {
   1.230 +              itemResources.splice(resourceIndex, 1);
   1.231 +              itemSuccess |= aSuccess;
   1.232 +              if (itemResources.length == 0) {
   1.233 +                resourcesGroupedByItems.del(migrationType);
   1.234 +                notify(itemSuccess ?
   1.235 +                       "Migration:ItemAfterMigrate" : "Migration:ItemError",
   1.236 +                       migrationType);
   1.237 +                if (resourcesGroupedByItems.count == 0)
   1.238 +                  notify("Migration:Ended");
   1.239 +              }
   1.240 +            }
   1.241 +          };
   1.242 +
   1.243 +          Services.tm.mainThread.dispatch(function() {
   1.244 +            // If migrate throws, an error occurred, and the callback
   1.245 +            // (itemMayBeDone) might haven't been called.
   1.246 +            try {
   1.247 +              resource.migrate(resourceDone);
   1.248 +            }
   1.249 +            catch(ex) {
   1.250 +              Cu.reportError(ex);
   1.251 +              resourceDone(false);
   1.252 +            }
   1.253 +          }, Ci.nsIThread.DISPATCH_NORMAL);
   1.254 +        });
   1.255 +      });
   1.256 +    }
   1.257 +
   1.258 +    if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) {
   1.259 +      MigrationUtils.profileStartup.doStartup();
   1.260 +
   1.261 +      // If we're about to migrate bookmarks, first import the default bookmarks.
   1.262 +      // Note We do not need to do so for the Firefox migrator
   1.263 +      // (=startupOnlyMigrator), as it just copies over the places database
   1.264 +      // from another profile.
   1.265 +      const BOOKMARKS = MigrationUtils.resourceTypes.BOOKMARKS;
   1.266 +      let migratingBookmarks = resources.some(function(r) r.type == BOOKMARKS);
   1.267 +      if (migratingBookmarks) {
   1.268 +        let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].
   1.269 +                          getService(Ci.nsIObserver);
   1.270 +        browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
   1.271 +
   1.272 +        // Note doMigrate doesn't care about the success of the import.
   1.273 +        let onImportComplete = function() {
   1.274 +          browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
   1.275 +          doMigrate();
   1.276 +        };
   1.277 +        BookmarkHTMLUtils.importFromURL(
   1.278 +          "resource:///defaults/profile/bookmarks.html", true).then(
   1.279 +          onImportComplete, onImportComplete);
   1.280 +        return;
   1.281 +      }
   1.282 +    }
   1.283 +    doMigrate();
   1.284 +  },
   1.285 +
   1.286 +  /**
   1.287 +   * DO NOT OVERRIDE - After deCOMing migration, this code
   1.288 +   * won't be part of the migrator itself.
   1.289 +   *
   1.290 +   * @see nsIBrowserProfileMigrator
   1.291 +   */
   1.292 +  get sourceExists() {
   1.293 +    if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
   1.294 +      return false;
   1.295 +
   1.296 +    // For a single-profile source, check if any data is available.
   1.297 +    // For multiple-profiles source, make sure that at least one
   1.298 +    // profile is available.
   1.299 +    let exists = false;
   1.300 +    try {
   1.301 +      let profiles = this.sourceProfiles;
   1.302 +      if (!profiles) {
   1.303 +        let resources = this._getMaybeCachedResources("");
   1.304 +        if (resources && resources.length > 0)
   1.305 +          exists = true;
   1.306 +      }
   1.307 +      else {
   1.308 +        exists = profiles.length > 0;
   1.309 +      }
   1.310 +    }
   1.311 +    catch(ex) {
   1.312 +      Cu.reportError(ex);
   1.313 +    }
   1.314 +    return exists;
   1.315 +  },
   1.316 +
   1.317 +  /*** PRIVATE STUFF - DO NOT OVERRIDE ***/
   1.318 +  _getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
   1.319 +    if (this._resourcesByProfile) {
   1.320 +      if (aProfile in this._resourcesByProfile)
   1.321 +        return this._resourcesByProfile[aProfile];
   1.322 +    }
   1.323 +    else {
   1.324 +      this._resourcesByProfile = { };
   1.325 +    }
   1.326 +    return this._resourcesByProfile[aProfile] = this.getResources(aProfile);
   1.327 +  }
   1.328 +};
   1.329 +
   1.330 +this.MigrationUtils = Object.freeze({
   1.331 +  resourceTypes: {
   1.332 +    SETTINGS:   Ci.nsIBrowserProfileMigrator.SETTINGS,
   1.333 +    COOKIES:    Ci.nsIBrowserProfileMigrator.COOKIES,
   1.334 +    HISTORY:    Ci.nsIBrowserProfileMigrator.HISTORY,
   1.335 +    FORMDATA:   Ci.nsIBrowserProfileMigrator.FORMDATA,
   1.336 +    PASSWORDS:  Ci.nsIBrowserProfileMigrator.PASSWORDS,
   1.337 +    BOOKMARKS:  Ci.nsIBrowserProfileMigrator.BOOKMARKS,
   1.338 +    OTHERDATA:  Ci.nsIBrowserProfileMigrator.OTHERDATA,
   1.339 +    SESSION:    Ci.nsIBrowserProfileMigrator.SESSION,
   1.340 +  },
   1.341 +
   1.342 +  /**
   1.343 +   * Helper for implementing simple asynchronous cases of migration resources'
   1.344 +   * |migrate(aCallback)| (see MigratorPrototype).  If your |migrate| method
   1.345 +   * just waits for some file to be read, for example, and then migrates
   1.346 +   * everything right away, you can wrap the async-function with this helper
   1.347 +   * and not worry about notifying the callback.
   1.348 +   *
   1.349 +   * For example, instead of writing:
   1.350 +   * setTimeout(function() {
   1.351 +   *   try {
   1.352 +   *     ....
   1.353 +   *     aCallback(true);
   1.354 +   *   }
   1.355 +   *   catch() {
   1.356 +   *     aCallback(false);
   1.357 +   *   }
   1.358 +   * }, 0);
   1.359 +   *
   1.360 +   * You may write:
   1.361 +   * setTimeout(MigrationUtils.wrapMigrateFunction(function() {
   1.362 +   *   if (importingFromMosaic)
   1.363 +   *     throw Cr.NS_ERROR_UNEXPECTED;
   1.364 +   * }, aCallback), 0);
   1.365 +   *
   1.366 +   * ... and aCallback will be called with aSuccess=false when importing
   1.367 +   * from Mosaic, or with aSuccess=true otherwise.
   1.368 +   *
   1.369 +   * @param aFunction
   1.370 +   *        the function that will be called sometime later.  If aFunction
   1.371 +   *        throws when it's called, aCallback(false) is called, otherwise
   1.372 +   *        aCallback(true) is called.
   1.373 +   * @param aCallback
   1.374 +   *        the callback function passed to |migrate|.
   1.375 +   * @return the wrapped function.
   1.376 +   */
   1.377 +  wrapMigrateFunction: function MU_wrapMigrateFunction(aFunction, aCallback) {
   1.378 +    return function() {
   1.379 +      let success = false;
   1.380 +      try {
   1.381 +        aFunction.apply(null, arguments);
   1.382 +        success = true;
   1.383 +      }
   1.384 +      catch(ex) {
   1.385 +        Cu.reportError(ex);
   1.386 +      }
   1.387 +      // Do not change this to call aCallback directly in try try & catch
   1.388 +      // blocks, because if aCallback throws, we may end up calling aCallback
   1.389 +      // twice.
   1.390 +      aCallback(success);
   1.391 +    }
   1.392 +  },
   1.393 +
   1.394 +  /**
   1.395 +   * Gets a string from the migration bundle.  Shorthand for
   1.396 +   * nsIStringBundle.GetStringFromName, if aReplacements isn't passed, or for
   1.397 +   * nsIStringBundle.formatStringFromName if it is.
   1.398 +   *
   1.399 +   * This method also takes care of "bumped" keys (See bug 737381 comment 8 for
   1.400 +   * details).
   1.401 +   *
   1.402 +   * @param aKey
   1.403 +   *        The key of the string to retrieve.
   1.404 +   * @param aReplacemts
   1.405 +   *        [optioanl] Array of replacements to run on the retrieved string.
   1.406 +   * @return the retrieved string.
   1.407 +   *
   1.408 +   * @see nsIStringBundle
   1.409 +   */
   1.410 +  getLocalizedString: function MU_getLocalizedString(aKey, aReplacements) {
   1.411 +    const OVERRIDES = {
   1.412 +      "4_firefox": "4_firefox_history_and_bookmarks",
   1.413 +      "64_firefox": "64_firefox_other"
   1.414 +    };
   1.415 +    aKey = OVERRIDES[aKey] || aKey;
   1.416 +
   1.417 +    if (aReplacements === undefined)
   1.418 +      return getMigrationBundle().GetStringFromName(aKey);
   1.419 +    return getMigrationBundle().formatStringFromName(
   1.420 +      aKey, aReplacements, aReplacements.length);
   1.421 +  },
   1.422 +
   1.423 +  /**
   1.424 +   * Helper for creating a folder for imported bookmarks from a particular
   1.425 +   * migration source.  The folder is created at the end of the given folder.
   1.426 +   *
   1.427 +   * @param aSourceNameStr
   1.428 +   *        the source name (first letter capitalized).  This is used
   1.429 +   *        for reading the localized source name from the migration
   1.430 +   *        bundle (e.g. if aSourceNameStr is Mosaic, this will try to read
   1.431 +   *        sourceNameMosaic from the migration bundle).
   1.432 +   * @param aParentId
   1.433 +   *        the item-id of the folder in which the new folder should be
   1.434 +   *        created.
   1.435 +   * @return the item-id of the new folder.
   1.436 +   */
   1.437 +  createImportedBookmarksFolder:
   1.438 +  function MU_createImportedBookmarksFolder(aSourceNameStr, aParentId) {
   1.439 +    let source = this.getLocalizedString("sourceName" + aSourceNameStr);
   1.440 +    let label = this.getLocalizedString("importedBookmarksFolder", [source]);
   1.441 +    return PlacesUtils.bookmarks.createFolder(
   1.442 +      aParentId, label, PlacesUtils.bookmarks.DEFAULT_INDEX);
   1.443 +  },
   1.444 +
   1.445 +  get _migrators() gMigrators ? gMigrators : gMigrators = new Dict(),
   1.446 +
   1.447 +  /*
   1.448 +   * Returns the migrator for the given source, if any data is available
   1.449 +   * for this source, or null otherwise.
   1.450 +   *
   1.451 +   * @param aKey internal name of the migration source.
   1.452 +   *             Supported values: ie (windows),
   1.453 +   *                               safari (mac/windows),
   1.454 +   *                               chrome (mac/windows/linux),
   1.455 +   *                               firefox.
   1.456 +   *
   1.457 +   * If null is returned,  either no data can be imported
   1.458 +   * for the given migrator, or aMigratorKey is invalid  (e.g. ie on mac,
   1.459 +   * or mosaic everywhere).  This method should be used rather than direct
   1.460 +   * getService for future compatibility (see bug 718280).
   1.461 +   *
   1.462 +   * @return profile migrator implementing nsIBrowserProfileMigrator, if it can
   1.463 +   *         import any data, null otherwise.
   1.464 +   */
   1.465 +  getMigrator: function MU_getMigrator(aKey) {
   1.466 +    let migrator = null;
   1.467 +    if (this._migrators.has(aKey)) {
   1.468 +      migrator = this._migrators.get(aKey);
   1.469 +    }
   1.470 +    else {
   1.471 +      try {
   1.472 +        migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" +
   1.473 +                      aKey].createInstance(Ci.nsIBrowserProfileMigrator);
   1.474 +      }
   1.475 +      catch(ex) { }
   1.476 +      this._migrators.set(aKey, migrator);
   1.477 +    }
   1.478 +
   1.479 +    return migrator && migrator.sourceExists ? migrator : null;
   1.480 +  },
   1.481 +
   1.482 +  // Iterates the available migrators, in the most suitable
   1.483 +  // order for the running platform.
   1.484 +  get migrators() {
   1.485 +    let migratorKeysOrdered = [
   1.486 +#ifdef XP_WIN
   1.487 +      "ie", "chrome", "safari"
   1.488 +#elifdef XP_MACOSX
   1.489 +      "safari", "chrome"
   1.490 +#elifdef XP_UNIX
   1.491 +      "chrome"
   1.492 +#endif
   1.493 +    ];
   1.494 +
   1.495 +    // If a supported default browser is found check it first
   1.496 +    // so that the wizard defaults to import from that browser.
   1.497 +    let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
   1.498 +    if (defaultBrowserKey)
   1.499 +      migratorKeysOrdered.sort(function (a, b) b == defaultBrowserKey ? 1 : 0);
   1.500 +
   1.501 +    for (let migratorKey of migratorKeysOrdered) {
   1.502 +      let migrator = this.getMigrator(migratorKey);
   1.503 +      if (migrator)
   1.504 +        yield migrator;
   1.505 +    }
   1.506 +  },
   1.507 +
   1.508 +  // Whether or not we're in the process of startup migration
   1.509 +  get isStartupMigration() gProfileStartup != null,
   1.510 +
   1.511 +  /**
   1.512 +   * In the case of startup migration, this is set to the nsIProfileStartup
   1.513 +   * instance passed to ProfileMigrator's migrate.
   1.514 +   *
   1.515 +   * @see showMigrationWizard
   1.516 +   */
   1.517 +  get profileStartup() gProfileStartup,
   1.518 +
   1.519 +  /**
   1.520 +   * Show the migration wizard.  On mac, this may just focus the wizard if it's
   1.521 +   * already running, in which case aOpener and aParams are ignored.
   1.522 +   *
   1.523 +   * @param [optional] aOpener
   1.524 +   *        the window that asks to open the wizard.
   1.525 +   * @param [optioanl] aParams
   1.526 +   *        arguments for the migration wizard, in the form of an nsIArray.
   1.527 +   *        This is passed as-is for the params argument of
   1.528 +   *        nsIWindowWatcher.openWindow.
   1.529 +   */
   1.530 +  showMigrationWizard:
   1.531 +  function MU_showMigrationWizard(aOpener, aParams) {
   1.532 +    let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no";
   1.533 +#ifdef XP_MACOSX
   1.534 +    if (!this.isStartupMigration) {
   1.535 +      let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
   1.536 +      if (win) {
   1.537 +        win.focus();
   1.538 +        return;
   1.539 +      }
   1.540 +      // On mac, the migration wiazrd should only be modal in the case of
   1.541 +      // startup-migration.
   1.542 +      features = "centerscreen,chrome,resizable=no";
   1.543 +    }
   1.544 +#endif
   1.545 +
   1.546 +    Services.ww.openWindow(aOpener,
   1.547 +                           "chrome://browser/content/migration/migration.xul",
   1.548 +                           "_blank",
   1.549 +                           features,
   1.550 +                           aParams);
   1.551 +  },
   1.552 +
   1.553 +  /**
   1.554 +   * Show the migration wizard for startup-migration.  This should only be
   1.555 +   * called by ProfileMigrator (see ProfileMigrator.js), which implements
   1.556 +   * nsIProfileMigrator.
   1.557 +   *
   1.558 +   * @param aProfileStartup
   1.559 +   *        the nsIProfileStartup instance provided to ProfileMigrator.migrate.
   1.560 +   * @param [optional] aMigratorKey
   1.561 +   *        If set, the migration wizard will import from the corresponding
   1.562 +   *        migrator, bypassing the source-selection page.  Otherwise, the
   1.563 +   *        source-selection page will be displayed, either with the default
   1.564 +   *        browser selected, if it could be detected and if there is a
   1.565 +   *        migrator for it, or with the first option selected as a fallback
   1.566 +   *        (The first option is hardcoded to be the most common browser for
   1.567 +   *         the OS we run on.  See migration.xul).
   1.568 +   *          
   1.569 +   * @throws if aMigratorKey is invalid or if it points to a non-existent
   1.570 +   *         source.
   1.571 +   */
   1.572 +  startupMigration:
   1.573 +  function MU_startupMigrator(aProfileStartup, aMigratorKey) {
   1.574 +    if (!aProfileStartup) {
   1.575 +      throw new Error("an profile-startup instance is required for startup-migration");
   1.576 +    }
   1.577 +    gProfileStartup = aProfileStartup;
   1.578 +
   1.579 +    let skipSourcePage = false, migrator = null, migratorKey = "";
   1.580 +    if (aMigratorKey) {
   1.581 +      migrator = this.getMigrator(aMigratorKey);
   1.582 +      if (!migrator) {
   1.583 +        // aMigratorKey must point to a valid source, so, if it doesn't
   1.584 +        // cleanup and throw.
   1.585 +        this.finishMigration();
   1.586 +        throw new Error("startMigration was asked to open auto-migrate from " +
   1.587 +                        "a non-existent source: " + aMigratorKey);
   1.588 +      }
   1.589 +      migratorKey = aMigratorKey;
   1.590 +      skipSourcePage = true;
   1.591 +    }
   1.592 +    else {
   1.593 +      let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
   1.594 +      if (defaultBrowserKey) {
   1.595 +        migrator = this.getMigrator(defaultBrowserKey);
   1.596 +        if (migrator)
   1.597 +          migratorKey = defaultBrowserKey;
   1.598 +      }
   1.599 +    }
   1.600 +
   1.601 +    if (!migrator) {
   1.602 +      // If there's no migrator set so far, ensure that there is at least one
   1.603 +      // migrator available before opening the wizard.
   1.604 +      try {
   1.605 +        this.migrators.next();
   1.606 +      }
   1.607 +      catch(ex) {
   1.608 +        this.finishMigration();
   1.609 +        if (!(ex instanceof StopIteration))
   1.610 +          throw ex;
   1.611 +        return;
   1.612 +      }
   1.613 +    }
   1.614 +
   1.615 +    let params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
   1.616 +    let keyCSTR = Cc["@mozilla.org/supports-cstring;1"].
   1.617 +                  createInstance(Ci.nsISupportsCString);
   1.618 +    keyCSTR.data = migratorKey;
   1.619 +    let skipImportSourcePageBool = Cc["@mozilla.org/supports-PRBool;1"].
   1.620 +                                   createInstance(Ci.nsISupportsPRBool);
   1.621 +    skipImportSourcePageBool.data = skipSourcePage;
   1.622 +    params.appendElement(keyCSTR, false);
   1.623 +    params.appendElement(migrator, false);
   1.624 +    params.appendElement(aProfileStartup, false);
   1.625 +    params.appendElement(skipImportSourcePageBool, false);
   1.626 +
   1.627 +    this.showMigrationWizard(null, params);
   1.628 +  },
   1.629 +
   1.630 +  /**
   1.631 +   * Cleans up references to migrators and nsIProfileInstance instances.
   1.632 +   */
   1.633 +  finishMigration: function MU_finishMigration() {
   1.634 +    gMigrators = null;
   1.635 +    gProfileStartup = null;
   1.636 +    gMigrationBundle = null;
   1.637 +  }
   1.638 +});

mercurial