-
$ <FF_FOLDER>/firefox --migration
(on Windows-migration
) -
Checked during XREMain::XRE_mainStartup
-
SelectProfile
checks if "--migration" is passed in commandline, then set the migration flag @nsAppRunner.cppar = CheckArg("migration", true); // ...... if (ar == ARG_FOUND) { gDoMigration = true; }
- But will be disabled if no profile found (for fresh install)
uint32_t count; rv = aProfileSvc->GetProfileCount(&count); // ...... if (!count) { // For a fresh install, we would like to let users decide // to do profile migration on their own later after using. gDoMigration = false; gDoProfileReset = false;
- But will be disabled if no profile found (for fresh install)
-
In
XREMain::XRE_mainRun
, call the profile migrator to migrate profileif (mAppData->flags & NS_XRE_ENABLE_PROFILE_MIGRATOR && gDoMigration) { gDoMigration = false; nsCOMPtr<nsIProfileMigrator> pm(do_CreateInstance(NS_PROFILEMIGRATOR_CONTRACTID)); if (pm) { nsAutoCString aKey; if (gDoProfileReset) { // Automatically migrate from the current application if we just reset the profile. aKey = MOZ_APP_NAME; } // In fact, this would invoke the JS implementation in ProfileMigrator.js pm->Migrate(&mDirProvider, aKey, gResetOldProfileName); } }
-
- User clicks the button, which then invokes the migration wizard:
MigrationUtils.showMigrationWizard(aOpener, aParams)
@ MigrationUtils.jsm
-
Entry point
- IDL: nsIProfileMigrator.idl
- Implementation: ProfileMigrator.js
ProfileMigrator.prototype = { // ProfileMigrator is REALLY just an entry point. // The real works are performed in MigrationUtils. migrate: MigrationUtils.startupMigration.bind(MigrationUtils), ... ... };
- Usage:
// JS let profileMigrator = Cc["@mozilla.org/toolkit/profile-migrator;1"].createInstance(Ci.nsIProfileMigrator); profileMigrator.migrate(aProfileStartup, aMigratorKey, aProfileToMigrate);
// C++ nsCOMPtr<nsIProfileMigrator> profileMigrator(do_CreateInstance(NS_PROFILEMIGRATOR_CONTRACTID)); profileMigrator->migrate(aProfileStartup, aMigratorKey, aProfileToMigrate);
-
MigrationUtils.startupMigration
-
DO NOT call it directly
/** * Show the migration wizard for startup-migration. This should only be * called by ProfileMigrator (see ProfileMigrator.js), which implements * nsIProfileMigrator. * * ... ... */ startupMigration: function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
-
Get the browser migrator
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; // No need for the source page to let user chose which browser to migrate // since a given browser has been provided by `aMigratorKey` skipSourcePage = true; } else { // No given target browser. Try to locate user's default browser let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser(); if (defaultBrowserKey) { migrator = this.getMigrator(defaultBrowserKey); if (migrator) migratorKey = defaultBrowserKey; } }
-
MigrationUtils.getMigrator
- Get mirgrator from the contractID and the
nsIBrowserProfileMigrator
interface. See the Migrator Interface section.try { // All migrators should implement this contractID pattern and the interface. migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=" + aKey].createInstance(Ci.nsIBrowserProfileMigrator); } catch (ex) { Cu.reportError(ex) } this._migrators.set(aKey, migrator);
- Get mirgrator from the contractID and the
-
MigrationUtils.getMigratorKeyForDefaultBrowser
- Get user's default application for HTTP and map it to the browser we know
// Canary uses the same description as Chrome so we can't distinguish them. const APP_DESC_TO_KEY = { "Internet Explorer": "ie", "Microsoft Edge": "edge", "Safari": "safari", "Firefox": "firefox", "Nightly": "firefox", "Google Chrome": "chrome", // Windows, Linux "Chrome": "chrome", // OS X "Chromium": "chromium", // Windows, OS X "Chromium Web Browser": "chromium", // Linux "360\u5b89\u5168\u6d4f\u89c8\u5668": "360se", }; let key = ""; try { let browserDesc = Cc["@mozilla.org/uriloader/external-protocol-service;1"] .getService(Ci.nsIExternalProtocolService).getApplicationDescription("http"); key = APP_DESC_TO_KEY[browserDesc] || ""; // Handle devedition, as well as "FirefoxNightly" on OS X. if (!key && browserDesc.startsWith("Firefox")) { key = "firefox"; } } catch (ex) { Cu.reportError("Could not detect default browser: " + ex); } // ... ... return key;
- Get user's default application for HTTP and map it to the browser we know
-
-
Check if we are doing profile refresh
// If comes here because of profile refresh, `migratorKey` will be `MOZ_APP_NAME`. // So very important not to call `MigrationUtils.startupMigration` directly let isRefresh = migrator && skipSourcePage && migratorKey == AppConstants.MOZ_APP_NAME;
- Only do auto migration if it is enabled and this is not profile refresh
if (!isRefresh && AutoMigrate.enabled) { try { AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate); return; } catch (ex) { // If automigration failed, continue and show the dialog. Cu.reportError(ex); } }
- Only do auto migration if it is enabled and this is not profile refresh
-
Bring up the migration wizard
let params = [ migrationEntryPoint, migratorKey, migrator, aProfileStartup, skipSourcePage, aProfileToMigrate, ]; this.showMigrationWizard(null, params);
-
-
MigrationUtils.showMigrationWizard
Services.ww.openWindow(aOpener, "chrome://browser/content/migration/migration.xul", "_blank", features, params);
-
browser/components/migration/content/migration.xul
<wizard id="migrationWizard" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" windowtype="Browser:MigrationWizard" title="&migrationWizard.title;" onload="MigrationWizard.init()" onunload="MigrationWizard.uninit()" style="width: 40em;" buttons="accept,cancel" branded="true"> <script type="application/javascript" src="chrome://browser/content/migration/migration.js"/>
-
XBL: /toolkit/content/widgets/wizard.xml
-
Page advance and rewind Each
<wizardpage>
should handle event on page advancing/rewinding// ... ... <wizardpage id="importSource" pageid="importSource" next="selectProfile" label="&importSource.title;" onpageadvanced="return MigrationWizard.onImportSourcePageAdvanced();"> // ... ... </wizardpage> <wizardpage id="selectProfile" pageid="selectProfile" label="&selectProfile.title;" next="importItems" onpageshow="return MigrationWizard.onSelectProfilePageShow();" onpagerewound="return MigrationWizard.onSelectProfilePageRewound();" onpageadvanced="return MigrationWizard.onSelectProfilePageAdvanced();"> // ... ... </wizardpage> <wizardpage id="importItems" pageid="importItems" label="&importItems.title;" next="homePageImport" onpageshow="return MigrationWizard.onImportItemsPageShow();" onpagerewound="return MigrationWizard.onImportItemsPageRewound();" onpageadvanced="return MigrationWizard.onImportItemsPageAdvanced();" oncommand="MigrationWizard.onImportItemCommand();"> // ... ... </wizardpage> // ... ...
-
Advance to the next page: call
advance
of<wizard>
pagehide
event is fired on thecurrentPage
, thenpageadvanced
event is fired on thecurrentPage
, then- let
currentPage
be the next page, then pageshow
event is fired on thecurrentPage
-
Rewind to the previous page: call
rewind
of<wizard>
pagehide
event is fired on thecurrentPage
, thenpagerewound
event is fired on thecurrentPage
, thenwizardback
event is fired on the<wizard>
, then- let
currentPage
be the previous page, then pageshow
event is fired on thecurrentPage
-
-
-
MigrationWizard.init
@ migration.js- Start the 1st migration wizard page
this.onImportSourcePageShow();
- Start the 1st migration wizard page
-
MigrationWizard.onMigratingPageShow
- Decide the import items for user in auto migration
// When automigrating, show all of the data that can be received from this source. if (this._autoMigrate) this._itemsFlags = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
- Decide the import items for user in auto migration
-
MigrationWizard.onMigratingMigrate
- Start migration
this._migrator.migrate(this._itemsFlags, this._autoMigrate, this._selectedProfile);
- Start migration
-
Migrator.Prototype.migrate
-
Get resources to migrate from other browser
let resources = this._getMaybeCachedResources(aProfile); if (resources.length == 0) throw new Error("migrate called for a non-existent source");
MigratorPrototype._getMaybeCachedResources
-
All browser migrators should implement
getResources
to get import resources.// One resource is an obj of { // type: Indicate what type this resource is, // migrate(aCallback): Method in charge of importing actual files/data // } this._resourcesByProfile[profileKey] = this.getResources(aProfile);
-
See the ChromeProfileMigrator section for Chrome example
-
-
Filter out the resources we want
if (aItems != Ci.nsIBrowserProfileMigrator.ALL) resources = resources.filter(r => aItems & r.type);
- The Supported resource types
MigrationUtils.resourceTypes = { // ALL: Ci.nsIBrowserProfileMigrator.ALL 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, SESSION: Ci.nsIBrowserProfileMigrator.SESSION, }
- The Supported resource types
-
Before migrating, Import the default bookmarks if this migration is during startup and not from the old FF profile
if (MigrationUtils.isStartupMigration && !this.startupOnlyMigrator) { MigrationUtils.profileStartup.doStartup(); // 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. (async function() { // ... ... // Import the default bookmarks[1]. We ignore whether or not we succeed. await BookmarkHTMLUtils.importFromURL( "chrome://browser/locale/bookmarks.html", true).catch(r => r); // We'll tell nsBrowserGlue we've imported bookmarks, but before that // we need to make sure we're going to know when it's finished initializing places let placesInitedPromise = new Promise(resolve => { let onPlacesInited = function() { Services.obs.removeObserver(onPlacesInited, TOPIC_PLACES_DEFAULTS_FINISHED); resolve(); }; Services.obs.addObserver(onPlacesInited, TOPIC_PLACES_DEFAULTS_FINISHED); }); // This will tell `browserGlue` we've imported bookmarks. // Have `browserGlue` know time to call `BrowserGlue.prototype._initPlaces` browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, ""); await placesInitedPromise; doMigrate(); })(); return; } doMigrate();
[1] The default bookmarks template: browser/locales/generic/profile/bookmarks.html.in
-
BookmarkHTMLUtils.importFromURL
let importer = new BookmarkImporter(aInitialImport); await importer.importFromURL(aSpec);
-
BookmarkImporter.prototype.importFromURL
This will load default bookmarks.html, then walk the DOM to get bookmark trees -
BookmarkImporter.prototype._importFromURL
// Insert bookmark into the places db await PlacesUtils.bookmarks.insertTree(tree);
-
-
doMigrate
// ... ... for (let [migrationType, itemResources] of resourcesGroupedByItems) { // Start measuring the telemetry about migration time we spent let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType); let {responsivenessMonitor, responsivenessHistogramId} = maybeStartResponsivenessMonitor(migrationType); // Loop resource obj one by one for (let res of itemResources) { let completeDeferred = PromiseUtils.defer(); let resourceDone = function(aSuccess) { itemResources.delete(res); itemSuccess |= aSuccess; if (itemResources.size == 0) { // ... ... // Send the telmetry back if (stopwatchHistogramId) { TelemetryStopwatch.finishKeyed(stopwatchHistogramId, browserKey); } maybeFinishResponsivenessMonitor(responsivenessMonitor, responsivenessHistogramId); if (resourcesGroupedByItems.size == 0) { collectQuantityTelemetry(); notify("Migration:Ended"); } } completeDeferred.resolve(); }; // Finally we start migrating stuff... try { res.migrate(resourceDone); // See ChromeProfileMigrator section for example } catch (ex) { Cu.reportError(ex); resourceDone(false); } // ... ... // This is important!!! // Importing is expensive. We have to yeild the way!!! // `unblockMainThread` will queue the next loop into the main thread. await unblockMainThread(); } }
unblockMainThread
// Used to periodically give back control to the main-thread loop. let unblockMainThread = function() { return new Promise(resolve => { Services.tm.dispatchToMainThread(resolve); }); };
-
-
-
The contractID pattern:
- "@mozilla.org/profile/migrator;1?app=browser&type=" + <MIGRATOR_KEY>
- MIGRATOR_KEY = "ie", "edge", "chrome", etc
-
The
nsIBrowserProfileMigrator
interface:- Implemented by
MigratorPrototype
@ MigrationUtils.jsmthis.MigratorPrototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserProfileMigrator]),
- Implemented by
-
Example: Chrome migrator
function ChromeProfileMigrator() { // ... ... } ChromeProfileMigrator.prototype = Object.create(MigratorPrototype); ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator"; ChromeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chrome"; ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
-
Initialization
- Get Chrome's profile folder
function ChromeProfileMigrator() { // `getDataFolder` returns an nsIFile instance of profiles on Windows or OSX or *nix system let chromeUserDataFolder = getDataFolder(["Google", "Chrome"], ["Google", "Chrome"], ["google-chrome"]); this._chromeUserDataFolder = chromeUserDataFolder.exists() ? chromeUserDataFolder : null; }
- Get Chrome's profile folder
-
ChromeProfileMigrator.prototype.getResources
// Clone Chrome's profile first. We don't want to mess the original one. let profileFolder = this._chromeUserDataFolder.clone(); profileFolder.append(aProfile.id); // Lets get Chrome's resources!!! if (profileFolder.exists()) { let possibleResources = [ GetBookmarksResource(profileFolder), GetHistoryResource(profileFolder), GetCookiesResource(profileFolder), ]; if (AppConstants.platform == "win") { possibleResources.push(GetWindowsPasswordsResource(profileFolder)); } return possibleResources.filter(r => r != null); }
GetBookmarksResource
function GetBookmarksResource(aProfileFolder) { let bookmarksFile = aProfileFolder.clone(); bookmarksFile.append("Bookmarks"); if (!bookmarksFile.exists()) return null; return { type: MigrationUtils.resourceTypes.BOOKMARKS, migrate(aCallback) { // ... ... } }; }
-
Migrate Chrome bookmarks resource
-
Parse Chrome bookmark file that is JSON format
let bookmarkJSON = await OS.File.read(bookmarksFile.path, {encoding: "UTF-8"}); let roots = JSON.parse(bookmarkJSON).roots;
-
Importing bookmark bar items
if (roots.bookmark_bar.children && roots.bookmark_bar.children.length > 0) { // Get Toolbar guid let parentGuid = PlacesUtils.bookmarks.toolbarGuid; // `convertBookmarks` will convert Chrome's bookmarks data into FF's bookmarks format let bookmarks = convertBookmarks(roots.bookmark_bar.children, errorGatherer); if (!MigrationUtils.isStartupMigration) { // This will create the folder of "Bookmarks imported from Chrome" and get the guid of that folder parentGuid = await MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid); } // `insertManyBookmarksWrapper` will call `PlacesUtils.bookmarks.insertTree` // to save imported bookmarks in the folder of "Bookmarks imported from Chrome" await MigrationUtils.insertManyBookmarksWrapper(bookmarks, parentGuid); }
-
Importing bookmark menu items
if (roots.other.children && roots.other.children.length > 0) { // Get Bookmark menu guid let parentGuid = PlacesUtils.bookmarks.menuGuid; let bookmarks = convertBookmarks(roots.other.children, errorGatherer); if (!MigrationUtils.isStartupMigration) { parentGuid = await MigrationUtils.createImportedBookmarksFolder("Chrome", parentGuid); } await MigrationUtils.insertManyBookmarksWrapper(bookmarks, parentGuid); }
-