forked from mirrors/gecko-dev
MozReview-Commit-ID: DINI8rnyMfe --HG-- extra : rebase_source : b215ff2344417c844ffa9f73ed61de0fadf82998
392 lines
14 KiB
JavaScript
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();
|