fune/browser/components/migration/AutoMigrate.jsm
Gijs Kruitbosch b30d0cb67b Bug 1299445 - use distribution.ini directly to check if automigration is enabled, r=mkaply
MozReview-Commit-ID: DINI8rnyMfe

--HG--
extra : rebase_source : b215ff2344417c844ffa9f73ed61de0fadf82998
2016-08-31 14:58:23 +01:00

392 lines
14 KiB
JavaScript

/* 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;
const kAutoMigrateEnabledPref = "browser.migrate.automigrate.enabled";
const kUndoUIEnabledPref = "browser.migrate.automigrate.ui.enabled";
const kAutoMigrateStartedPref = "browser.migrate.automigrate.started";
const kAutoMigrateFinishedPref = "browser.migrate.automigrate.finished";
const kAutoMigrateBrowserPref = "browser.migrate.automigrate.browser";
const kAutoMigrateLastUndoPromptDateMsPref = "browser.migrate.automigrate.lastUndoPromptDateMs";
const kAutoMigrateDaysToOfferUndoPref = "browser.migrate.automigrate.daysToOfferUndo";
const kPasswordManagerTopic = "passwordmgr-storage-changed";
const kPasswordManagerTopicTypes = new Set([
"addLogin",
"modifyLogin",
]);
const kSyncTopic = "fxaccounts:onlogin";
const kNotificationId = "abouthome-automigration-undo";
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const AutoMigrate = {
get resourceTypesToUse() {
let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
return BOOKMARKS | HISTORY | PASSWORDS;
},
_checkIfEnabled() {
let pref = Preferences.get(kAutoMigrateEnabledPref, false);
// User-set values should take precedence:
if (Services.prefs.prefHasUserValue(kAutoMigrateEnabledPref)) {
return pref;
}
// If we're using the default value, make sure the distribution.ini
// value is taken into account even early on startup.
try {
let distributionFile = Services.dirsvc.get("XREAppDist", Ci.nsIFile);
distributionFile.append("distribution.ini");
let parser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
getService(Ci.nsIINIParserFactory).
createINIParser(distributionFile);
return JSON.parse(parser.getString("Preferences", kAutoMigrateEnabledPref));
} catch (ex) { /* ignore exceptions (file doesn't exist, invalid value, etc.) */ }
return pref;
},
init() {
this.enabled = this._checkIfEnabled();
if (this.enabled) {
this.maybeInitUndoObserver();
}
},
maybeInitUndoObserver() {
if (!this.canUndo()) {
return;
}
// Now register places, password and sync observers:
this.onItemAdded = this.onItemMoved = this.onItemChanged =
this.removeUndoOption.bind(this, this.UNDO_REMOVED_REASON_BOOKMARK_CHANGE);
PlacesUtils.addLazyBookmarkObserver(this, true);
for (let topic of [kSyncTopic, kPasswordManagerTopic]) {
Services.obs.addObserver(this, topic, true);
}
},
observe(subject, topic, data) {
if (topic == kPasswordManagerTopic) {
// As soon as any login gets added or modified, disable undo:
// (Note that this ignores logins being removed as that doesn't
// impair the 'undo' functionality of the import.)
if (kPasswordManagerTopicTypes.has(data)) {
this.removeUndoOption(this.UNDO_REMOVED_REASON_PASSWORD_CHANGE);
}
} else if (topic == kSyncTopic) {
this.removeUndoOption(this.UNDO_REMOVED_REASON_SYNC_SIGNIN);
}
},
/**
* 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.getHistogramById(
"FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
histogram.add(0);
let {migrator, pickedKey} = this.pickMigrator(migratorKey);
histogram.add(5);
profileToMigrate = this.pickProfile(migrator, profileToMigrate);
histogram.add(10);
let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
if (!(resourceTypes & this.resourceTypesToUse)) {
throw new Error("No usable resources were found for the selected browser!");
}
histogram.add(15);
let sawErrors = false;
let migrationObserver = (subject, topic, data) => {
if (topic == "Migration:ItemError") {
sawErrors = true;
} else if (topic == "Migration:Ended") {
histogram.add(25);
if (sawErrors) {
histogram.add(26);
}
Services.obs.removeObserver(migrationObserver, "Migration:Ended");
Services.obs.removeObserver(migrationObserver, "Migration:ItemError");
Services.prefs.setCharPref(kAutoMigrateStartedPref, startTime.toString());
Services.prefs.setCharPref(kAutoMigrateFinishedPref, Date.now().toString());
Services.prefs.setCharPref(kAutoMigrateBrowserPref, pickedKey);
// Need to manually start listening to new bookmarks/logins being created,
// because, when we were initialized, there wasn't the possibility to
// 'undo' anything.
this.maybeInitUndoObserver();
}
};
Services.obs.addObserver(migrationObserver, "Migration:Ended", false);
Services.obs.addObserver(migrationObserver, "Migration:ItemError", false);
// We'll save this when the migration has finished, at which point the pref
// service will be available.
let startTime = Date.now();
migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
histogram.add(20);
},
/**
* Pick and return a migrator to use for automatically migrating.
*
* @param {String} migratorKey optional, a migrator key to prefer/pick.
* @returns {Object} an object with the migrator to use for migrating, as
* well as the key we eventually ended up using to obtain it.
*/
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, pickedKey: migratorKey};
},
/**
* 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 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;
}
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] : null;
},
getUndoRange() {
let start, finish;
try {
start = parseInt(Preferences.get(kAutoMigrateStartedPref, "0"), 10);
finish = parseInt(Preferences.get(kAutoMigrateFinishedPref, "0"), 10);
} catch (ex) {
Cu.reportError(ex);
}
if (!finish || !start) {
return null;
}
return [new Date(start), new Date(finish)];
},
canUndo() {
return !!this.getUndoRange();
},
undo: Task.async(function* () {
let histogram = Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_UNDO");
histogram.add(0);
if (!this.canUndo()) {
histogram.add(5);
throw new Error("Can't undo!");
}
histogram.add(10);
yield PlacesUtils.bookmarks.eraseEverything();
histogram.add(15);
// NB: we drop the start time of the migration for now. This is because
// imported history will always end up being 'backdated' to the actual
// visit time recorded by the browser from which we imported. As a result,
// a lower bound on this item doesn't really make sense.
// Note that for form data this could be different, but we currently don't
// support form data import from any non-Firefox browser, so it isn't
// imported from other browsers by the automigration code, nor do we
// remove it here.
let range = this.getUndoRange();
yield PlacesUtils.history.removeVisitsByFilter({
beginDate: new Date(0),
endDate: range[1]
});
histogram.add(20);
try {
Services.logins.removeAllLogins();
} catch (ex) {
// ignore failure.
}
histogram.add(25);
this.removeUndoOption(this.UNDO_REMOVED_REASON_UNDO_USED);
histogram.add(30);
}),
removeUndoOption(reason) {
// Remove observers, and ensure that exceptions doing so don't break
// removing the pref.
for (let topic of [kSyncTopic, kPasswordManagerTopic]) {
try {
Services.obs.removeObserver(this, topic);
} catch (ex) {
Cu.reportError("Error removing observer for " + topic + ": " + ex);
}
}
try {
PlacesUtils.removeLazyBookmarkObserver(this);
} catch (ex) {
Cu.reportError("Error removing lazy bookmark observer: " + ex);
}
let migrationBrowser = Preferences.get(kAutoMigrateBrowserPref, "unknown");
Services.prefs.clearUserPref(kAutoMigrateStartedPref);
Services.prefs.clearUserPref(kAutoMigrateFinishedPref);
Services.prefs.clearUserPref(kAutoMigrateBrowserPref);
let browserWindows = Services.wm.getEnumerator("navigator:browser");
while (browserWindows.hasMoreElements()) {
let win = browserWindows.getNext();
if (!win.closed) {
for (let browser of win.gBrowser.browsers) {
let nb = win.gBrowser.getNotificationBox(browser);
let notification = nb.getNotificationWithValue(kNotificationId);
if (notification) {
nb.removeNotification(notification);
}
}
}
}
let histogram =
Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_UNDO_REASON");
histogram.add(migrationBrowser, reason);
},
getBrowserUsedForMigration() {
let browserId = Services.prefs.getCharPref(kAutoMigrateBrowserPref);
if (browserId) {
return MigrationUtils.getBrowserName(browserId);
}
return null;
},
maybeShowUndoNotification(target) {
// The tab might have navigated since we requested the undo state:
if (!this.canUndo() || target.currentURI.spec != "about:home" ||
!Preferences.get(kUndoUIEnabledPref, false)) {
return;
}
let win = target.ownerGlobal;
let notificationBox = win.gBrowser.getNotificationBox(target);
if (!notificationBox || notificationBox.getNotificationWithValue("abouthome-automigration-undo")) {
return;
}
// At this stage we're committed to show the prompt - unless we shouldn't,
// in which case we remove the undo prefs (which will cause canUndo() to
// return false from now on.):
if (!this.shouldStillShowUndoPrompt()) {
this.removeUndoOption(this.UNDO_REMOVED_REASON_OFFER_EXPIRED);
return;
}
let browserName = this.getBrowserUsedForMigration();
let message;
if (browserName) {
message = MigrationUtils.getLocalizedString("automigration.undo.message",
[browserName]);
} else {
message = MigrationUtils.getLocalizedString("automigration.undo.unknownBrowserMessage");
}
let buttons = [
{
label: MigrationUtils.getLocalizedString("automigration.undo.keep.label"),
accessKey: MigrationUtils.getLocalizedString("automigration.undo.keep.accesskey"),
callback: () => {
this.removeUndoOption(this.UNDO_REMOVED_REASON_OFFER_REJECTED);
},
},
{
label: MigrationUtils.getLocalizedString("automigration.undo.dontkeep.label"),
accessKey: MigrationUtils.getLocalizedString("automigration.undo.dontkeep.accesskey"),
callback: () => {
this.undo();
},
},
];
notificationBox.appendNotification(
message, kNotificationId, null, notificationBox.PRIORITY_INFO_HIGH, buttons
);
},
shouldStillShowUndoPrompt() {
let today = new Date();
// Round down to midnight:
today = new Date(today.getFullYear(), today.getMonth(), today.getDate());
// We store the unix timestamp corresponding to midnight on the last day
// on which we prompted. Fetch that and compare it to today's date.
// (NB: stored as a string because int prefs are too small for unix
// timestamps.)
let previousPromptDateMsStr = Preferences.get(kAutoMigrateLastUndoPromptDateMsPref, "0");
let previousPromptDate = new Date(parseInt(previousPromptDateMsStr, 10));
if (previousPromptDate < today) {
let remainingDays = Preferences.get(kAutoMigrateDaysToOfferUndoPref, 4) - 1;
Preferences.set(kAutoMigrateDaysToOfferUndoPref, remainingDays);
Preferences.set(kAutoMigrateLastUndoPromptDateMsPref, today.valueOf().toString());
if (remainingDays <= 0) {
return false;
}
}
return true;
},
UNDO_REMOVED_REASON_UNDO_USED: 0,
UNDO_REMOVED_REASON_SYNC_SIGNIN: 1,
UNDO_REMOVED_REASON_PASSWORD_CHANGE: 2,
UNDO_REMOVED_REASON_BOOKMARK_CHANGE: 3,
UNDO_REMOVED_REASON_OFFER_EXPIRED: 4,
UNDO_REMOVED_REASON_OFFER_REJECTED: 5,
QueryInterface: XPCOMUtils.generateQI(
[Ci.nsIObserver, Ci.nsINavBookmarkObserver, Ci.nsISupportsWeakReference]
),
};
AutoMigrate.init();