Bug 1271775 - allow bypassing the initial migration dialog, r?jaws
MozReview-Commit-ID: LkhHl7ipGEb
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1403,12 +1403,14 @@ pref("toolkit.pageThumbs.minHeight", 190
// Enable speech synthesis
pref("media.webspeech.synth.enabled", true);
pref("browser.esedbreader.loglevel", "Error");
pref("browser.laterrun.enabled", false);
+pref("browser.migration.automigrate", false);
+
// Enable browser frames for use on desktop. Only exposed to chrome callers.
pref("dom.mozBrowserFramesEnabled", true);
pref("extensions.pocket.enabled", true);
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/AutoMigrate.jsm
@@ -0,0 +1,113 @@
+/* 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 = ["AutoMigrate"];
+
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const AutoMigrate = {
+ get resourceTypesToUse() {
+ let {BOOKMARKS, HISTORY, FORMDATA, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ return BOOKMARKS | HISTORY | FORMDATA | PASSWORDS;
+ },
+
+ /**
+ * Automatically pick a migrator and resources to migrate,
+ * then migrate those and start up.
+ *
+ * @throws if automatically deciding on migrators/data
+ * failed for some reason.
+ */
+ migrate(profileStartup, migratorKey, profileToMigrate) {
+ let histogram = Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_SUCCEEDED");
+ histogram.add("initialized");
+ let migrator = this.pickMigrator(migratorKey);
+ histogram.add("got-browser");
+
+ profileToMigrate = this.pickProfile(migrator, profileToMigrate);
+ histogram.add("got-profile");
+
+ let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
+ if (!(resourceTypes & this.resourceTypesToUse)) {
+ throw new Error("No usable resources were found for the selected browser!");
+ }
+ histogram.add("got-data");
+
+ let sawErrors = false;
+ let migrationObserver = function(subject, topic, data) {
+ if (topic == "Migration:ItemError") {
+ sawErrors = true;
+ } else if (topic == "Migration:Ended") {
+ histogram.add(sawErrors ? "finished-with-errors" : "finished");
+ Services.obs.removeObserver(migrationObserver, "Migration:Ended");
+ Services.obs.removeObserver(migrationObserver, "Migration:ItemError");
+ }
+ };
+
+ Services.obs.addObserver(migrationObserver, "Migration:Ended", false);
+ Services.obs.addObserver(migrationObserver, "Migration:ItemError", false);
+ migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
+ histogram.add("migrate-called-without-exceptions");
+ },
+
+ /**
+ * Pick and return a migrator to use for automatically migrating.
+ *
+ * @param {String} migratorKey optional, a migrator key to prefer/pick.
+ * @returns the migrator to use for migrating.
+ */
+ pickMigrator(migratorKey) {
+ if (!migratorKey) {
+ let defaultKey = MigrationUtils.getMigratorKeyForDefaultBrowser();
+ if (!defaultKey) {
+ throw new Error("Could not determine default browser key to migrate from");
+ }
+ migratorKey = defaultKey;
+ }
+ if (migratorKey == "firefox") {
+ throw new Error("Can't automatically migrate from Firefox.");
+ }
+
+ let migrator = MigrationUtils.getMigrator(migratorKey);
+ if (!migrator) {
+ throw new Error("Migrator specified or a default was found, but the migrator object is not available.");
+ }
+ return migrator;
+ },
+
+ /**
+ * Pick a source profile (from the original browser) to use.
+ *
+ * @param {Migrator} migrator the migrator object to use
+ * @param {String} suggestedId the id of the profile to migrate, if pre-specified, or null
+ * @returns the id of the profile to migrate, or null if migrating
+ * from the default profile.
+ */
+ pickProfile(migrator, suggestedId) {
+ let profiles = migrator.sourceProfiles;
+ if (profiles && !profiles.length) {
+ throw new Error("No profile data found to migrate.");
+ }
+ if (suggestedId) {
+ if (!profiles) {
+ throw new Error("Profile specified but only a default profile found.");
+ }
+ let suggestedProfile = profiles.find(profile => profile.id == suggestedId);
+ if (!suggestedProfile) {
+ throw new Error("Profile specified was not found.");
+ }
+ return suggestedProfile.id;
+ }
+ if (profiles && profiles.length > 1) {
+ throw new Error("Don't know how to pick a profile when more than 1 profile is present.");
+ }
+ return profiles ? profiles[0].id : null;
+ },
+};
+
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -16,16 +16,18 @@ Cu.import("resource://gre/modules/Task.j
Cu.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
"resource://gre/modules/BookmarkHTMLUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
"resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AutoMigrate",
+ "resource:///modules/AutoMigrate.jsm");
var gMigrators = null;
var gProfileStartup = null;
var gMigrationBundle = null;
XPCOMUtils.defineLazyGetter(this, "gAvailableMigratorKeys", function() {
if (AppConstants.platform == "win") {
return [
@@ -90,17 +92,17 @@ this.MigratorPrototype = {
/**
* 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
+ * - a |type| getter, returning 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.
*
@@ -691,18 +693,31 @@ this.MigrationUtils = Object.freeze({
// if that one existed we would have used it in the block above this one.
if (!gAvailableMigratorKeys.some(key => !!this.getMigrator(key))) {
// None of the keys produced a usable migrator, so finish up here:
this.finishMigration();
return;
}
}
+ let isRefresh = migrator && skipSourcePage &&
+ migratorKey == AppConstants.MOZ_APP_NAME;
+
+ if (!isRefresh &&
+ Services.prefs.getBoolPref("browser.migration.automigrate")) {
+ try {
+ return AutoMigrate.migrate(aProfileStartup, aMigratorKey, aProfileToMigrate);
+ } catch (ex) {
+ // If automigration failed, continue and show the dialog.
+ Cu.reportError(ex);
+ }
+ }
+
let migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FIRSTRUN;
- if (migrator && skipSourcePage && migratorKey == AppConstants.MOZ_APP_NAME) {
+ if (isRefresh) {
migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FXREFRESH;
}
let params = [
migrationEntryPoint,
migratorKey,
migrator,
aProfileStartup,
@@ -716,16 +731,18 @@ this.MigrationUtils = Object.freeze({
* Cleans up references to migrators and nsIProfileInstance instances.
*/
finishMigration: function MU_finishMigration() {
gMigrators = null;
gProfileStartup = null;
gMigrationBundle = null;
},
+ gAvailableMigratorKeys,
+
MIGRATION_ENTRYPOINT_UNKNOWN: 0,
MIGRATION_ENTRYPOINT_FIRSTRUN: 1,
MIGRATION_ENTRYPOINT_FXREFRESH: 2,
MIGRATION_ENTRYPOINT_PLACES: 3,
MIGRATION_ENTRYPOINT_PASSWORDS: 4,
_sourceNameToIdMapping: {
"nothing": 1,
--- a/browser/components/migration/moz.build
+++ b/browser/components/migration/moz.build
@@ -22,16 +22,17 @@ EXTRA_COMPONENTS += [
'ProfileMigrator.js',
]
EXTRA_PP_COMPONENTS += [
'BrowserProfileMigrators.manifest',
]
EXTRA_JS_MODULES += [
+ 'AutoMigrate.jsm',
'MigrationUtils.jsm',
]
if CONFIG['OS_ARCH'] == 'WINNT':
SOURCES += [
'nsIEHistoryEnumerator.cpp',
]
EXTRA_COMPONENTS += [
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_automigration.js
@@ -0,0 +1,119 @@
+Cu.import("resource:///modules/MigrationUtils.jsm");
+let AutoMigrateBackstage = Cu.import("resource:///modules/AutoMigrate.jsm");
+
+let gShimmedMigratorKeyPicker = null;
+let gShimmedMigrator = null;
+
+// This is really a proxy on MigrationUtils, but if we specify that directly,
+// we get in trouble because the object itself is frozen, and Proxies can't
+// return a different value to an object when directly proxying a frozen
+// object.
+AutoMigrateBackstage.MigrationUtils = new Proxy({}, {
+ get(target, name) {
+ if (name == "getMigratorKeyForDefaultBrowser" && gShimmedMigratorKeyPicker) {
+ return gShimmedMigratorKeyPicker;
+ }
+ if (name == "getMigrator" && gShimmedMigrator) {
+ return function() { return gShimmedMigrator };
+ }
+ return MigrationUtils[name];
+ },
+});
+
+do_register_cleanup(function() {
+ AutoMigrateBackstage.MigrationUtils = MigrationUtils;
+});
+
+/**
+ * Test automatically picking a browser to migrate from
+ */
+add_task(function* checkMigratorPicking() {
+ Assert.throws(() => AutoMigrate.pickMigrator("firefox"),
+ /Can't automatically migrate from Firefox/,
+ "Should throw when explicitly picking Firefox.");
+
+ Assert.throws(() => AutoMigrate.pickMigrator("gobbledygook"),
+ /migrator object is not available/,
+ "Should throw when passing unknown migrator key");
+ gShimmedMigratorKeyPicker = function() {
+ return "firefox";
+ };
+ Assert.throws(() => AutoMigrate.pickMigrator(),
+ /Can't automatically migrate from Firefox/,
+ "Should throw when implicitly picking Firefox.");
+ gShimmedMigratorKeyPicker = function() {
+ return "gobbledygook";
+ };
+ Assert.throws(() => AutoMigrate.pickMigrator(),
+ /migrator object is not available/,
+ "Should throw when an unknown migrator is the default");
+ gShimmedMigratorKeyPicker = function() {
+ return "";
+ };
+ Assert.throws(() => AutoMigrate.pickMigrator(),
+ /Could not determine default browser key/,
+ "Should throw when an unknown migrator is the default");
+});
+
+
+/**
+ * Test automatically picking a profile to migrate from
+ */
+add_task(function* checkProfilePicking() {
+ let fakeMigrator = {sourceProfiles: [{id: "a"}, {id: "b"}]};
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
+ /Don't know how to pick a profile when more/,
+ "Should throw when there are multiple profiles.");
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
+ /Profile specified was not found/,
+ "Should throw when the profile supplied doesn't exist.");
+ let profileToMigrate = AutoMigrate.pickProfile(fakeMigrator, "b");
+ Assert.equal(profileToMigrate, "b", "Should return profile supplied");
+
+ fakeMigrator.sourceProfiles = null;
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
+ /Profile specified but only a default profile found./,
+ "Should throw when the profile supplied doesn't exist.");
+ profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
+ Assert.equal(profileToMigrate, null, "Should return default profile when that's the only one.");
+
+ fakeMigrator.sourceProfiles = [];
+ Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
+ /No profile data found/,
+ "Should throw when no profile data is present.");
+
+ fakeMigrator.sourceProfiles = [{id: "a"}];
+ profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
+ Assert.equal(profileToMigrate, "a", "Should return the only profile if only one is present.");
+});
+
+/**
+ * Test the complete automatic process including browser and profile selection,
+ * and actual migration (which implies startup)
+ */
+add_task(function* checkIntegration() {
+ gShimmedMigrator = {
+ get sourceProfiles() {
+ dump("Read sourceProfiles");
+ return null;
+ },
+ getMigrateData(profileToMigrate) {
+ this._getMigrateDataArgs = profileToMigrate;
+ return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
+ },
+ migrate(types, startup, profileToMigrate) {
+ this._migrateArgs = [types, startup, profileToMigrate];
+ },
+ };
+ gShimmedMigratorKeyPicker = function() {
+ return "gobbledygook";
+ };
+ AutoMigrate.migrate("startup");
+ Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
+ "getMigrateData called with 'null' as a profile");
+
+ let {BOOKMARKS, HISTORY, FORMDATA, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ let expectedTypes = BOOKMARKS | HISTORY | FORMDATA | PASSWORDS;
+ Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
+ "getMigrateData called with 'null' as a profile");
+});
--- a/browser/components/migration/tests/unit/xpcshell.ini
+++ b/browser/components/migration/tests/unit/xpcshell.ini
@@ -2,16 +2,17 @@
head = head_migration.js
tail =
firefox-appdir = browser
skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files =
Library/**
AppData/**
+[test_automigration.js]
[test_Chrome_cookies.js]
skip-if = os != "mac" # Relies on ULibDir
[test_Chrome_passwords.js]
skip-if = os != "win"
[test_Edge_availability.js]
[test_Edge_db_migration.js]
skip-if = os != "win" || os_version == "5.1" || os_version == "5.2" # Relies on post-XP bits of ESEDB
[test_fx_telemetry.js]
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4447,16 +4447,25 @@
"bug_numbers": [1275114],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "53",
"kind": "enumerated",
"n_values": 15,
"releaseChannelCollection": "opt-out",
"description": "The browser that was the default on the initial profile migration. The values correspond to the internal browser ID (see MigrationUtils.jsm)"
},
+ "FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_SUCCEEDED": {
+ "bug_numbers": [1271775],
+ "alert_emails": ["gijs@mozilla.com"],
+ "expires_in_version": "53",
+ "kind": "count",
+ "keyed": true,
+ "releaseChannelCollection": "opt-out",
+ "description": "Where automatic migration was attempted, indicates to what degree we succeeded."
+ },
"FX_STARTUP_EXTERNAL_CONTENT_HANDLER": {
"bug_numbers": [1276027],
"alert_emails": ["jaws@mozilla.com"],
"expires_in_version": "53",
"kind": "count",
"description": "Count how often the browser is opened as an external app handler. This is generally used when the browser is set as the default browser."
},
"INPUT_EVENT_RESPONSE_MS": {