summaryrefslogtreecommitdiffstats
path: root/application/palemoon/components/migration/MigrationUtils.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'application/palemoon/components/migration/MigrationUtils.jsm')
-rw-r--r--application/palemoon/components/migration/MigrationUtils.jsm644
1 files changed, 644 insertions, 0 deletions
diff --git a/application/palemoon/components/migration/MigrationUtils.jsm b/application/palemoon/components/migration/MigrationUtils.jsm
new file mode 100644
index 000000000..fcd73a798
--- /dev/null
+++ b/application/palemoon/components/migration/MigrationUtils.jsm
@@ -0,0 +1,644 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["MigrationUtils", "MigratorPrototype"];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+const TOPIC_WILL_IMPORT_BOOKMARKS = "initial-migration-will-import-default-bookmarks";
+const TOPIC_DID_IMPORT_BOOKMARKS = "initial-migration-did-import-default-bookmarks";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Dict",
+ "resource://gre/modules/Dict.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
+ "resource://gre/modules/BookmarkHTMLUtils.jsm");
+
+let gMigrators = null;
+let gProfileStartup = null;
+let gMigrationBundle = null;
+
+function getMigrationBundle() {
+ if (!gMigrationBundle) {
+ gMigrationBundle = Services.strings.createBundle(
+ "chrome://browser/locale/migration/migration.properties");
+ }
+ return gMigrationBundle;
+}
+
+/**
+ * Figure out what is the default browser, and if there is a migrator
+ * for it, return that migrator's internal name.
+ * For the time being, the "internal name" of a migraotr is its contract-id
+ * trailer (e.g. ie for @mozilla.org/profile/migrator;1?app=browser&type=ie),
+ * but it will soon be exposed properly.
+ */
+function getMigratorKeyForDefaultBrowser() {
+ const APP_DESC_TO_KEY = {
+ "Internet Explorer": "ie",
+ "Safari": "safari",
+ "Pale Moon web browser": "firefox",
+ "Google Chrome": "chrome", // Windows, Linux
+ "Chrome": "chrome", // OS X
+ };
+
+ let browserDesc = "";
+ try {
+ let browserDesc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService).
+ getApplicationDescription("http");
+ return APP_DESC_TO_KEY[browserDesc] || "";
+ }
+ catch(ex) {
+ Cu.reportError("Could not detect default browser: " + ex);
+ }
+ return "";
+}
+
+/**
+ * Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
+ *
+ * To implement a migrator:
+ * 1. Import this module.
+ * 2. Create the prototype for the migrator, extending MigratorPrototype.
+ * Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
+ * 3. Set classDescription, contractID and classID for your migrator, and set
+ * NSGetFactory appropriately.
+ * 4. If the migrator supports multiple profiles, override the sourceProfiles
+ * Here we default for single-profile migrator.
+ * 5. Implement getResources(aProfile) (see below).
+ * 6. If the migrator supports reading the home page of the source browser,
+ * override |sourceHomePageURL| getter.
+ * 7. For startup-only migrators, override |startupOnlyMigrator|.
+ */
+this.MigratorPrototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
+
+ /**
+ * OVERRIDE IF AND ONLY IF the source supports multiple profiles.
+ *
+ * Returns array of profile objects from which data may be imported. The object
+ * should have the following keys:
+ * id - a unique string identifier for the profile
+ * name - a pretty name to display to the user in the UI
+ *
+ * Only profiles from which data can be imported should be listed. Otherwise
+ * the behavior of the migration wizard isn't well-defined.
+ *
+ * For a single-profile source (e.g. safari, ie), this returns null,
+ * and not an empty array. That is the default implementation.
+ */
+ get sourceProfiles() null,
+
+ /**
+ * MUST BE OVERRIDDEN.
+ *
+ * Returns an array of "migration resources" objects for the given profile,
+ * or for the "default" profile, if the migrator does not support multiple
+ * profiles.
+ *
+ * Each migration resource should provide:
+ * - a |type| getter, retunring any of the migration types (see
+ * nsIBrowserProfileMigrator).
+ *
+ * - a |migrate| method, taking a single argument, aCallback(bool success),
+ * for migrating the data for this resource. It may do its job
+ * synchronously or asynchronously. Either way, it must call
+ * aCallback(bool aSuccess) when it's done. In the case of an exception
+ * thrown from |migrate|, it's taken as if aCallback(false) is called.
+ *
+ * Note: In the case of a simple asynchronous implementation, you may find
+ * MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
+ *
+ * For each migration type listed in nsIBrowserProfileMigrator, multiple
+ * migration resources may be provided. This practice is useful when the
+ * data for a certain migration type is independently stored in few
+ * locations. For example, the mac version of Safari stores its "reading list"
+ * bookmarks in a separate property list.
+ *
+ * Note that the importation of a particular migration type is reported as
+ * successful if _any_ of its resources succeeded to import (that is, called,
+ * |aCallback(true)|). However, completion-status for a particular migration
+ * type is reported to the UI only once all of its migrators have called
+ * aCallback.
+ *
+ * @note The returned array should only include resources from which data
+ * can be imported. So, for example, before adding a resource for the
+ * BOOKMARKS migration type, you should check if you should check that the
+ * bookmarks file exists.
+ *
+ * @param aProfile
+ * The profile from which data may be imported, or an empty string
+ * in the case of a single-profile migrator.
+ * In the case of multiple-profiles migrator, it is guaranteed that
+ * aProfile is a value returned by the sourceProfiles getter (see
+ * above).
+ */
+ getResources: function MP_getResources(aProfile) {
+ throw new Error("getResources must be overridden");
+ },
+
+ /**
+ * OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
+ * that is just the Firefox migrator, see bug 737381). Default: false.
+ *
+ * Startup-only migrators are different in two ways:
+ * - they may only be used during startup.
+ * - the user-profile is half baked during migration. The folder exists,
+ * but it's only accessible through MigrationUtils.profileStartup.
+ * The migrator can call MigrationUtils.profileStartup.doStartup
+ * at any point in order to initialize the profile.
+ */
+ get startupOnlyMigrator() false,
+
+ /**
+ * OVERRIDE IF AND ONLY IF your migrator supports importing the homepage.
+ * @see nsIBrowserProfileMigrator
+ */
+ get sourceHomePageURL() "",
+
+ /**
+ * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
+ * getResources.
+ *
+ * @see nsIBrowserProfileMigrator
+ */
+ getMigrateData: function MP_getMigrateData(aProfile) {
+ let types = [r.type for each (r in this._getMaybeCachedResources(aProfile))];
+ return types.reduce(function(a, b) a |= b, 0);
+ },
+
+ /**
+ * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
+ * migrate for each resource.
+ *
+ * @see nsIBrowserProfileMigrator
+ */
+ migrate: function MP_migrate(aItems, aStartup, aProfile) {
+ let resources = this._getMaybeCachedResources(aProfile);
+ if (resources.length == 0)
+ throw new Error("migrate called for a non-existent source");
+
+ if (aItems != Ci.nsIBrowserProfileMigrator.ALL)
+ resources = [r for each (r in resources) if (aItems & r.type)];
+
+ // Called either directly or through the bookmarks import callback.
+ function doMigrate() {
+ // TODO: use Map (for the items) and Set (for the resources)
+ // once they are iterable.
+ let resourcesGroupedByItems = new Dict();
+ resources.forEach(function(resource) {
+ if (resourcesGroupedByItems.has(resource.type))
+ resourcesGroupedByItems.get(resource.type).push(resource);
+ else
+ resourcesGroupedByItems.set(resource.type, [resource]);
+ });
+
+ if (resourcesGroupedByItems.count == 0)
+ throw new Error("No items to import");
+
+ let notify = function(aMsg, aItemType) {
+ Services.obs.notifyObservers(null, aMsg, aItemType);
+ }
+
+ notify("Migration:Started");
+ resourcesGroupedByItems.listkeys().forEach(function(migrationType) {
+ let migrationTypeA = migrationType;
+ let itemResources = resourcesGroupedByItems.get(migrationType);
+ notify("Migration:ItemBeforeMigrate", migrationType);
+
+ let itemSuccess = false;
+ itemResources.forEach(function(resource) {
+ let resourceDone = function(aSuccess) {
+ let resourceIndex = itemResources.indexOf(resource);
+ if (resourceIndex != -1) {
+ itemResources.splice(resourceIndex, 1);
+ itemSuccess |= aSuccess;
+ if (itemResources.length == 0) {
+ resourcesGroupedByItems.del(migrationType);
+ notify(itemSuccess ?
+ "Migration:ItemAfterMigrate" : "Migration:ItemError",
+ migrationType);
+ if (resourcesGroupedByItems.count == 0)
+ notify("Migration:Ended");
+ }
+ }
+ };
+
+ Services.tm.mainThread.dispatch(function() {
+ // If migrate throws, an error occurred, and the callback
+ // (itemMayBeDone) might haven't been called.
+ try {
+ resource.migrate(resourceDone);
+ }
+ catch(ex) {
+ Cu.reportError(ex);
+ resourceDone(false);
+ }
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ });
+ }
+
+ if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) {
+ MigrationUtils.profileStartup.doStartup();
+
+ // If we're about to migrate bookmarks, first import the default bookmarks.
+ // Note We do not need to do so for the Firefox migrator
+ // (=startupOnlyMigrator), as it just copies over the places database
+ // from another profile.
+ const BOOKMARKS = MigrationUtils.resourceTypes.BOOKMARKS;
+ let migratingBookmarks = resources.some(function(r) r.type == BOOKMARKS);
+ if (migratingBookmarks) {
+ let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].
+ getService(Ci.nsIObserver);
+ browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
+
+ // Note doMigrate doesn't care about the success of the import.
+ let onImportComplete = function() {
+ browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
+ doMigrate();
+ };
+ BookmarkHTMLUtils.importFromURL(
+ "resource:///defaults/profile/bookmarks.html", true).then(
+ onImportComplete, onImportComplete);
+ return;
+ }
+ }
+ doMigrate();
+ },
+
+ /**
+ * DO NOT OVERRIDE - After deCOMing migration, this code
+ * won't be part of the migrator itself.
+ *
+ * @see nsIBrowserProfileMigrator
+ */
+ get sourceExists() {
+ if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
+ return false;
+
+ // For a single-profile source, check if any data is available.
+ // For multiple-profiles source, make sure that at least one
+ // profile is available.
+ let exists = false;
+ try {
+ let profiles = this.sourceProfiles;
+ if (!profiles) {
+ let resources = this._getMaybeCachedResources("");
+ if (resources && resources.length > 0)
+ exists = true;
+ }
+ else {
+ exists = profiles.length > 0;
+ }
+ }
+ catch(ex) {
+ Cu.reportError(ex);
+ }
+ return exists;
+ },
+
+ /*** PRIVATE STUFF - DO NOT OVERRIDE ***/
+ _getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
+ let profileKey = aProfile ? aProfile.id : "";
+ if (this._resourcesByProfile) {
+ if (profileKey in this._resourcesByProfile)
+ return this._resourcesByProfile[profileKey];
+ }
+ else {
+ this._resourcesByProfile = { };
+ }
+ return this._resourcesByProfile[profileKey] = this.getResources(aProfile);
+ }
+};
+
+this.MigrationUtils = Object.freeze({
+ resourceTypes: {
+ SETTINGS: Ci.nsIBrowserProfileMigrator.SETTINGS,
+ COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
+ HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,
+ FORMDATA: Ci.nsIBrowserProfileMigrator.FORMDATA,
+ PASSWORDS: Ci.nsIBrowserProfileMigrator.PASSWORDS,
+ BOOKMARKS: Ci.nsIBrowserProfileMigrator.BOOKMARKS,
+ OTHERDATA: Ci.nsIBrowserProfileMigrator.OTHERDATA
+ },
+
+ /**
+ * Helper for implementing simple asynchronous cases of migration resources'
+ * |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method
+ * just waits for some file to be read, for example, and then migrates
+ * everything right away, you can wrap the async-function with this helper
+ * and not worry about notifying the callback.
+ *
+ * For example, instead of writing:
+ * setTimeout(function() {
+ * try {
+ * ....
+ * aCallback(true);
+ * }
+ * catch() {
+ * aCallback(false);
+ * }
+ * }, 0);
+ *
+ * You may write:
+ * setTimeout(MigrationUtils.wrapMigrateFunction(function() {
+ * if (importingFromMosaic)
+ * throw Cr.NS_ERROR_UNEXPECTED;
+ * }, aCallback), 0);
+ *
+ * ... and aCallback will be called with aSuccess=false when importing
+ * from Mosaic, or with aSuccess=true otherwise.
+ *
+ * @param aFunction
+ * the function that will be called sometime later. If aFunction
+ * throws when it's called, aCallback(false) is called, otherwise
+ * aCallback(true) is called.
+ * @param aCallback
+ * the callback function passed to |migrate|.
+ * @return the wrapped function.
+ */
+ wrapMigrateFunction: function MU_wrapMigrateFunction(aFunction, aCallback) {
+ return function() {
+ let success = false;
+ try {
+ aFunction.apply(null, arguments);
+ success = true;
+ }
+ catch(ex) {
+ Cu.reportError(ex);
+ }
+ // Do not change this to call aCallback directly in try try & catch
+ // blocks, because if aCallback throws, we may end up calling aCallback
+ // twice.
+ aCallback(success);
+ }
+ },
+
+ /**
+ * Gets a string from the migration bundle. Shorthand for
+ * nsIStringBundle.GetStringFromName, if aReplacements isn't passed, or for
+ * nsIStringBundle.formatStringFromName if it is.
+ *
+ * This method also takes care of "bumped" keys (See bug 737381 comment 8 for
+ * details).
+ *
+ * @param aKey
+ * The key of the string to retrieve.
+ * @param aReplacemts
+ * [optioanl] Array of replacements to run on the retrieved string.
+ * @return the retrieved string.
+ *
+ * @see nsIStringBundle
+ */
+ getLocalizedString: function MU_getLocalizedString(aKey, aReplacements) {
+ const OVERRIDES = {
+ "4_firefox": "4_firefox_history_and_bookmarks",
+ "64_firefox": "64_firefox_other"
+ };
+ aKey = OVERRIDES[aKey] || aKey;
+
+ if (aReplacements === undefined)
+ return getMigrationBundle().GetStringFromName(aKey);
+ return getMigrationBundle().formatStringFromName(
+ aKey, aReplacements, aReplacements.length);
+ },
+
+ /**
+ * Helper for creating a folder for imported bookmarks from a particular
+ * migration source. The folder is created at the end of the given folder.
+ *
+ * @param aSourceNameStr
+ * the source name (first letter capitalized). This is used
+ * for reading the localized source name from the migration
+ * bundle (e.g. if aSourceNameStr is Mosaic, this will try to read
+ * sourceNameMosaic from the migration bundle).
+ * @param aParentId
+ * the item-id of the folder in which the new folder should be
+ * created.
+ * @return the item-id of the new folder.
+ */
+ createImportedBookmarksFolder:
+ function MU_createImportedBookmarksFolder(aSourceNameStr, aParentId) {
+ let source = this.getLocalizedString("sourceName" + aSourceNameStr);
+ let label = this.getLocalizedString("importedBookmarksFolder", [source]);
+ return PlacesUtils.bookmarks.createFolder(
+ aParentId, label, PlacesUtils.bookmarks.DEFAULT_INDEX);
+ },
+
+ get _migrators() gMigrators ? gMigrators : gMigrators = new Dict(),
+
+ /*
+ * Returns the migrator for the given source, if any data is available
+ * for this source, or null otherwise.
+ *
+ * @param aKey internal name of the migration source.
+ * Supported values: ie (windows),
+ * safari (mac/windows),
+ * chrome (mac/windows/linux),
+ * firefox.
+ *
+ * If null is returned, either no data can be imported
+ * for the given migrator, or aMigratorKey is invalid (e.g. ie on mac,
+ * or mosaic everywhere). This method should be used rather than direct
+ * getService for future compatibility (see bug 718280).
+ *
+ * @return profile migrator implementing nsIBrowserProfileMigrator, if it can
+ * import any data, null otherwise.
+ */
+ getMigrator: function MU_getMigrator(aKey) {
+ let migrator = null;
+ if (this._migrators.has(aKey)) {
+ migrator = this._migrators.get(aKey);
+ }
+ else {
+ try {
+ migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" +
+ aKey].createInstance(Ci.nsIBrowserProfileMigrator);
+ }
+ catch(ex) { }
+ this._migrators.set(aKey, migrator);
+ }
+
+ return migrator && migrator.sourceExists ? migrator : null;
+ },
+
+ // Iterates the available migrators, in the most suitable
+ // order for the running platform.
+ get migrators() {
+ let migratorKeysOrdered = [
+#ifdef XP_WIN
+ "firefox", "ie", "chrome", "safari"
+#elifdef XP_MACOSX
+ "firefox", "safari", "chrome"
+#elifdef XP_UNIX
+ "firefox", "chrome"
+#endif
+ ];
+
+ // If a supported default browser is found check it first
+ // so that the wizard defaults to import from that browser.
+ let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
+ if (defaultBrowserKey)
+ migratorKeysOrdered.sort(function (a, b) b == defaultBrowserKey ? 1 : 0);
+
+ for (let migratorKey of migratorKeysOrdered) {
+ let migrator = this.getMigrator(migratorKey);
+ if (migrator)
+ yield migrator;
+ }
+ },
+
+ // Whether or not we're in the process of startup migration
+ get isStartupMigration() gProfileStartup != null,
+
+ /**
+ * In the case of startup migration, this is set to the nsIProfileStartup
+ * instance passed to ProfileMigrator's migrate.
+ *
+ * @see showMigrationWizard
+ */
+ get profileStartup() gProfileStartup,
+
+ /**
+ * Show the migration wizard. On mac, this may just focus the wizard if it's
+ * already running, in which case aOpener and aParams are ignored.
+ *
+ * @param [optional] aOpener
+ * the window that asks to open the wizard.
+ * @param [optional] aParams
+ * arguments for the migration wizard, in the form of an nsIArray.
+ * This is passed as-is for the params argument of
+ * nsIWindowWatcher.openWindow.
+ */
+ showMigrationWizard:
+ function MU_showMigrationWizard(aOpener, aParams) {
+ let features = "chrome,dialog,modal,centerscreen,titlebar,resizable=no";
+#ifdef XP_MACOSX
+ if (!this.isStartupMigration) {
+ let win = Services.wm.getMostRecentWindow("Browser:MigrationWizard");
+ if (win) {
+ win.focus();
+ return;
+ }
+ // On mac, the migration wiazrd should only be modal in the case of
+ // startup-migration.
+ features = "centerscreen,chrome,resizable=no";
+ }
+#endif
+
+ Services.ww.openWindow(aOpener,
+ "chrome://browser/content/migration/migration.xul",
+ "_blank",
+ features,
+ aParams);
+ },
+
+ /**
+ * Show the migration wizard for startup-migration. This should only be
+ * called by ProfileMigrator (see ProfileMigrator.js), which implements
+ * nsIProfileMigrator.
+ *
+ * @param aProfileStartup
+ * the nsIProfileStartup instance provided to ProfileMigrator.migrate.
+ * @param [optional] aMigratorKey
+ * If set, the migration wizard will import from the corresponding
+ * migrator, bypassing the source-selection page. Otherwise, the
+ * source-selection page will be displayed, either with the default
+ * browser selected, if it could be detected and if there is a
+ * migrator for it, or with the first option selected as a fallback
+ * (The first option is hardcoded to be the most common browser for
+ * the OS we run on. See migration.xul).
+ * @param [optional] aProfileToMigrate
+ * If set, the migration wizard will import from the profile indicated.
+ *
+ * @throws if aMigratorKey is invalid or if it points to a non-existent
+ * source.
+ */
+ startupMigration:
+ function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
+ if (!aProfileStartup) {
+ throw new Error("a profile-startup instance is required for startup-migration");
+ }
+ gProfileStartup = aProfileStartup;
+
+ let skipSourcePage = false, migrator = null, migratorKey = "";
+ if (aMigratorKey) {
+ migrator = this.getMigrator(aMigratorKey);
+ if (!migrator) {
+ // aMigratorKey must point to a valid source, so, if it doesn't
+ // cleanup and throw.
+ this.finishMigration();
+ throw new Error("startMigration was asked to open auto-migrate from " +
+ "a non-existent source: " + aMigratorKey);
+ }
+ migratorKey = aMigratorKey;
+ skipSourcePage = true;
+ }
+ else {
+ let defaultBrowserKey = getMigratorKeyForDefaultBrowser();
+ if (defaultBrowserKey) {
+ migrator = this.getMigrator(defaultBrowserKey);
+ if (migrator)
+ migratorKey = defaultBrowserKey;
+ }
+ }
+
+ if (!migrator) {
+ // If there's no migrator set so far, ensure that there is at least one
+ // migrator available before opening the wizard.
+ try {
+ this.migrators.next();
+ }
+ catch(ex) {
+ this.finishMigration();
+ if (!(ex instanceof StopIteration))
+ throw ex;
+ return;
+ }
+ }
+
+ let params = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ let keyCSTR = Cc["@mozilla.org/supports-cstring;1"].
+ createInstance(Ci.nsISupportsCString);
+ keyCSTR.data = migratorKey;
+ let skipImportSourcePageBool = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ skipImportSourcePageBool.data = skipSourcePage;
+ let profileToMigrate = null;
+ if (aProfileToMigrate) {
+ profileToMigrate = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ profileToMigrate.data = aProfileToMigrate;
+ }
+ params.appendElement(keyCSTR, false);
+ params.appendElement(migrator, false);
+ params.appendElement(aProfileStartup, false);
+ params.appendElement(skipImportSourcePageBool, false);
+ params.appendElement(profileToMigrate, false);
+
+ this.showMigrationWizard(null, params);
+ },
+
+ /**
+ * Cleans up references to migrators and nsIProfileInstance instances.
+ */
+ finishMigration: function MU_finishMigration() {
+ gMigrators = null;
+ gProfileStartup = null;
+ gMigrationBundle = null;
+ }
+});