Bug 1488926 - remove automigration code (already preffed off), r=dthayer

Differential Revision: https://phabricator.services.mozilla.com/D8425

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Gijs Kruitbosch 2018-10-11 17:04:59 +00:00
Родитель 361deac7ee
Коммит 909f7acef8
15 изменённых файлов: 3 добавлений и 1847 удалений

Просмотреть файл

@ -1634,13 +1634,6 @@ pref("browser.laterrun.enabled", false);
pref("dom.ipc.processPrelaunch.enabled", true);
pref("browser.migrate.automigrate.enabled", false);
// 4 here means the suggestion notification will be automatically
// hidden the 4th day, so it will actually be shown on 3 different days.
pref("browser.migrate.automigrate.daysToOfferUndo", 4);
pref("browser.migrate.automigrate.ui.enabled", true);
pref("browser.migrate.automigrate.inpage.ui.enabled", false);
// See comments in bug 1340115 on how we got to these numbers.
pref("browser.migrate.chrome.history.limit", 2000);
pref("browser.migrate.chrome.history.maxAgeInDays", 180);

Просмотреть файл

@ -1,701 +0,0 @@
/* 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";
var EXPORTED_SYMBOLS = ["AutoMigrate"];
const kAutoMigrateEnabledPref = "browser.migrate.automigrate.enabled";
const kUndoUIEnabledPref = "browser.migrate.automigrate.ui.enabled";
const kInPageUIEnabledPref = "browser.migrate.automigrate.inpage.ui.enabled";
const kAutoMigrateBrowserPref = "browser.migrate.automigrate.browser";
const kAutoMigrateImportedItemIds = "browser.migrate.automigrate.imported-items";
const kAutoMigrateLastUndoPromptDateMsPref = "browser.migrate.automigrate.lastUndoPromptDateMs";
const kAutoMigrateDaysToOfferUndoPref = "browser.migrate.automigrate.daysToOfferUndo";
const kAutoMigrateUndoSurveyPref = "browser.migrate.automigrate.undo-survey";
const kAutoMigrateUndoSurveyLocalePref = "browser.migrate.automigrate.undo-survey-locales";
const kNotificationId = "automigration-undo";
ChromeUtils.import("resource:///modules/MigrationUtils.jsm");
ChromeUtils.import("resource://gre/modules/Preferences.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "AsyncShutdown",
"resource://gre/modules/AsyncShutdown.jsm");
ChromeUtils.defineModuleGetter(this, "LoginHelper",
"resource://gre/modules/LoginHelper.jsm");
ChromeUtils.defineModuleGetter(this, "NewTabUtils",
"resource://gre/modules/NewTabUtils.jsm");
ChromeUtils.defineModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
ChromeUtils.defineModuleGetter(this, "TelemetryStopwatch",
"resource://gre/modules/TelemetryStopwatch.jsm");
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
const kBrandBundle = "chrome://branding/locale/brand.properties";
return Services.strings.createBundle(kBrandBundle);
});
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
XPCOMUtils.defineLazyGetter(this, "kUndoStateFullPath", function() {
return OS.Path.join(OS.Constants.Path.profileDir, "initialMigrationMetadata.jsonlz4");
});
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();
},
/**
* 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.
*/
async migrate(profileStartup, migratorKey, profileToMigrate) {
let histogram = Services.telemetry.getHistogramById(
"FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
histogram.add(0);
let {migrator, pickedKey} = await this.pickMigrator(migratorKey);
histogram.add(5);
profileToMigrate = await this.pickProfile(migrator, profileToMigrate);
histogram.add(10);
let resourceTypes = await 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) => {
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(kAutoMigrateBrowserPref, pickedKey);
// Save the undo history and block shutdown on that save completing.
AsyncShutdown.profileBeforeChange.addBlocker(
"AutoMigrate Undo saving", this.saveUndoState(), () => {
return {state: this._saveUndoStateTrackerForShutdown};
});
}
};
MigrationUtils.initializeUndoData();
Services.obs.addObserver(migrationObserver, "Migration:Ended");
Services.obs.addObserver(migrationObserver, "Migration:ItemError");
await 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.
*/
async 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 = await MigrationUtils.getMigrator(migratorKey);
if (!migrator) {
throw new Error("Migrator specified or a default was found, but the migrator object is not available (or has no data).");
}
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.
*/
async pickProfile(migrator, suggestedId) {
let profiles = await migrator.getSourceProfiles();
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;
},
_pendingUndoTasks: false,
async canUndo() {
if (this._savingPromise) {
await this._savingPromise;
}
if (this._pendingUndoTasks) {
return false;
}
let fileExists = false;
try {
fileExists = await OS.File.exists(kUndoStateFullPath);
} catch (ex) {
Cu.reportError(ex);
}
return fileExists;
},
async undo() {
let browserId = Preferences.get(kAutoMigrateBrowserPref, "unknown");
TelemetryStopwatch.startKeyed("FX_STARTUP_MIGRATION_UNDO_TOTAL_MS", browserId);
let histogram = Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_UNDO");
histogram.add(0);
if (!(await this.canUndo())) {
histogram.add(5);
throw new Error("Can't undo!");
}
this._pendingUndoTasks = true;
this._removeNotificationBars();
histogram.add(10);
let readPromise = OS.File.read(kUndoStateFullPath, {
encoding: "utf-8",
compression: "lz4",
});
let stateData = this._dejsonifyUndoState(await readPromise);
histogram.add(12);
this._errorMap = {bookmarks: 0, visits: 0, logins: 0};
let reportErrorTelemetry = (type) => {
let histogramId = `FX_STARTUP_MIGRATION_UNDO_${type.toUpperCase()}_ERRORCOUNT`;
Services.telemetry.getKeyedHistogramById(histogramId).add(browserId, this._errorMap[type]);
};
let startTelemetryStopwatch = resourceType => {
let histogramId = `FX_STARTUP_MIGRATION_UNDO_${resourceType.toUpperCase()}_MS`;
TelemetryStopwatch.startKeyed(histogramId, browserId);
};
let stopTelemetryStopwatch = resourceType => {
let histogramId = `FX_STARTUP_MIGRATION_UNDO_${resourceType.toUpperCase()}_MS`;
TelemetryStopwatch.finishKeyed(histogramId, browserId);
};
startTelemetryStopwatch("bookmarks");
await this._removeUnchangedBookmarks(stateData.get("bookmarks")).catch(ex => {
Cu.reportError("Uncaught exception when removing unchanged bookmarks!");
Cu.reportError(ex);
});
stopTelemetryStopwatch("bookmarks");
reportErrorTelemetry("bookmarks");
histogram.add(15);
startTelemetryStopwatch("visits");
await this._removeSomeVisits(stateData.get("visits")).catch(ex => {
Cu.reportError("Uncaught exception when removing history visits!");
Cu.reportError(ex);
});
stopTelemetryStopwatch("visits");
reportErrorTelemetry("visits");
histogram.add(20);
startTelemetryStopwatch("logins");
await this._removeUnchangedLogins(stateData.get("logins")).catch(ex => {
Cu.reportError("Uncaught exception when removing unchanged logins!");
Cu.reportError(ex);
});
stopTelemetryStopwatch("logins");
reportErrorTelemetry("logins");
histogram.add(25);
// This is async, but no need to wait for it.
NewTabUtils.links.populateCache(() => {
NewTabUtils.allPages.update();
}, true);
this._purgeUndoState(this.UNDO_REMOVED_REASON_UNDO_USED);
histogram.add(30);
TelemetryStopwatch.finishKeyed("FX_STARTUP_MIGRATION_UNDO_TOTAL_MS", browserId);
},
_removeNotificationBars() {
for (let win of Services.wm.getEnumerator("navigator:browser")) {
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);
}
}
}
}
},
_purgeUndoState(reason) {
// We don't wait for the off-main-thread removal to complete. OS.File will
// ensure it happens before shutdown.
OS.File.remove(kUndoStateFullPath, {ignoreAbsent: true}).then(() => {
this._pendingUndoTasks = false;
});
let migrationBrowser = Preferences.get(kAutoMigrateBrowserPref, "unknown");
Services.prefs.clearUserPref(kAutoMigrateBrowserPref);
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;
},
/**
* Decide if we need to show [the user] a prompt indicating we automatically
* imported their data.
* @param target (xul:browser)
* The browser in which we should show the notification.
* @returns {Boolean} return true when need to show the prompt.
*/
async shouldShowMigratePrompt(target) {
if (!(await this.canUndo())) {
return false;
}
// The tab might have navigated since we requested the undo state:
let canUndoFromThisPage = ["about:home", "about:newtab"].includes(target.currentURI.spec);
if (!canUndoFromThisPage ||
!Preferences.get(kUndoUIEnabledPref, false)) {
return false;
}
// 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.isMigratePromptExpired()) {
this._purgeUndoState(this.UNDO_REMOVED_REASON_OFFER_EXPIRED);
this._removeNotificationBars();
return false;
}
let remainingDays = Preferences.get(kAutoMigrateDaysToOfferUndoPref, 0);
Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_UNDO_OFFERED").add(4 - remainingDays);
return true;
},
/**
* Return the message that denotes the user data is migrated from the other browser.
* @returns {String} imported message with the brand and the browser name
*/
getUndoMigrationMessage() {
let browserName = this.getBrowserUsedForMigration();
if (!browserName) {
browserName = MigrationUtils.getLocalizedString("automigration.undo.unknownbrowser");
}
const kMessageId = "automigration.undo.message2." +
Preferences.get(kAutoMigrateImportedItemIds, "all");
const kBrandShortName = gBrandBundle.GetStringFromName("brandShortName");
return MigrationUtils.getLocalizedString(kMessageId,
[kBrandShortName, browserName]);
},
/**
* Show the user a notification bar indicating we automatically imported
* their data and offering them the possibility of removing it.
* @param target (xul:browser)
* The browser in which we should show the notification.
*/
showUndoNotificationBar(target) {
let isInPage = Preferences.get(kInPageUIEnabledPref, false);
let win = target.ownerGlobal;
let notificationBox = win.gBrowser.getNotificationBox(target);
if (isInPage || !notificationBox || notificationBox.getNotificationWithValue(kNotificationId)) {
return;
}
let message = this.getUndoMigrationMessage();
let buttons = [
{
label: MigrationUtils.getLocalizedString("automigration.undo.keep2.label"),
accessKey: MigrationUtils.getLocalizedString("automigration.undo.keep2.accesskey"),
callback: () => {
this.keepAutoMigration();
this._removeNotificationBars();
},
},
{
label: MigrationUtils.getLocalizedString("automigration.undo.dontkeep2.label"),
accessKey: MigrationUtils.getLocalizedString("automigration.undo.dontkeep2.accesskey"),
callback: () => {
this.undoAutoMigration(win);
},
},
];
notificationBox.appendNotification(
message, kNotificationId, null, notificationBox.PRIORITY_INFO_HIGH, buttons
);
},
/**
* Return true if we have shown the prompt to user several days.
* (defined in kAutoMigrateDaysToOfferUndoPref)
*/
isMigratePromptExpired() {
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 true;
}
}
return false;
},
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,
_jsonifyUndoState(state) {
if (!state) {
return "null";
}
// Deal with date serialization.
let bookmarks = state.get("bookmarks");
for (let bm of bookmarks) {
bm.lastModified = bm.lastModified.getTime();
}
let serializableState = {
bookmarks,
logins: state.get("logins"),
visits: state.get("visits"),
};
return JSON.stringify(serializableState);
},
_dejsonifyUndoState(state) {
state = JSON.parse(state);
if (!state) {
return new Map();
}
for (let bm of state.bookmarks) {
bm.lastModified = new Date(bm.lastModified);
}
return new Map([
["bookmarks", state.bookmarks],
["logins", state.logins],
["visits", state.visits],
]);
},
/**
* Store the items we've saved into a pref. We use this to be able to show
* a detailed message to the user indicating what we've imported.
* @param state (Map)
* The 'undo' state for the import, which contains info about
* how many items of each kind we've (tried to) import.
*/
_setImportedItemPrefFromState(state) {
let itemsWithData = [];
if (state) {
for (let itemType of state.keys()) {
if (state.get(itemType).length) {
itemsWithData.push(itemType);
}
}
}
if (itemsWithData.length == 3) {
itemsWithData = "all";
} else {
itemsWithData = itemsWithData.sort().join(".");
}
if (itemsWithData) {
Preferences.set(kAutoMigrateImportedItemIds, itemsWithData);
}
},
/**
* Used for the shutdown blocker's information field.
*/
_saveUndoStateTrackerForShutdown: "not running",
/**
* Store the information required for using 'undo' of the automatic
* migration in the user's profile.
*/
async saveUndoState() {
let resolveSavingPromise;
this._saveUndoStateTrackerForShutdown = "processing undo history";
this._savingPromise = new Promise(resolve => { resolveSavingPromise = resolve; });
let state = await MigrationUtils.stopAndRetrieveUndoData();
if (!state || ![...state.values()].some(ary => ary.length > 0)) {
// If we didn't import anything, abort now.
resolveSavingPromise();
return Promise.resolve();
}
this._saveUndoStateTrackerForShutdown = "saving imported item list";
this._setImportedItemPrefFromState(state);
this._saveUndoStateTrackerForShutdown = "writing undo history";
this._undoSavePromise = OS.File.writeAtomic(
kUndoStateFullPath, this._jsonifyUndoState(state), {
encoding: "utf-8",
compression: "lz4",
tmpPath: kUndoStateFullPath + ".tmp",
});
this._undoSavePromise.then(
rv => {
resolveSavingPromise(rv);
delete this._savingPromise;
},
e => {
Cu.reportError("Could not write undo state for automatic migration.");
throw e;
});
return this._undoSavePromise;
},
async _removeUnchangedBookmarks(bookmarks) {
if (!bookmarks.length) {
return;
}
let guidToLMMap = new Map(bookmarks.map(b => [b.guid, b.lastModified]));
let bookmarksFromDB = [];
let bmPromises = Array.from(guidToLMMap.keys()).map(guid => {
// Ignore bookmarks where the promise doesn't resolve (ie that are missing)
// Also check that the bookmark fetch returns isn't null before adding it.
try {
return PlacesUtils.bookmarks.fetch(guid).then(bm => bm && bookmarksFromDB.push(bm), () => {});
} catch (ex) {
// Ignore immediate exceptions, too.
}
return Promise.resolve();
});
// We can't use the result of Promise.all because that would include nulls
// for bookmarks that no longer exist (which we're catching above).
await Promise.all(bmPromises);
let unchangedBookmarks = bookmarksFromDB.filter(bm => {
return bm.lastModified.getTime() == guidToLMMap.get(bm.guid).getTime();
});
// We need to remove items without children first, followed by their
// parents, etc. In order to do this, find out how many ancestors each item
// has that also appear in our list of things to remove, and sort the items
// by those numbers. This ensures that children are always removed before
// their parents.
function determineAncestorCount(bm) {
if (bm._ancestorCount) {
return bm._ancestorCount;
}
let myCount = 0;
let parentBM = unchangedBookmarks.find(item => item.guid == bm.parentGuid);
if (parentBM) {
myCount = determineAncestorCount(parentBM) + 1;
}
bm._ancestorCount = myCount;
return myCount;
}
unchangedBookmarks.forEach(determineAncestorCount);
unchangedBookmarks.sort((a, b) => b._ancestorCount - a._ancestorCount);
for (let {guid} of unchangedBookmarks) {
// Can't just use a .catch() because Bookmarks.remove() can throw (rather
// than returning rejected promises).
try {
await PlacesUtils.bookmarks.remove(guid, {preventRemovalOfNonEmptyFolders: true});
} catch (err) {
if (err && err.message != "Cannot remove a non-empty folder.") {
this._errorMap.bookmarks++;
Cu.reportError(err);
}
}
}
},
async _removeUnchangedLogins(logins) {
for (let login of logins) {
let foundLogins = LoginHelper.searchLoginsWithObject({guid: login.guid});
if (foundLogins.length) {
let foundLogin = foundLogins[0];
foundLogin.QueryInterface(Ci.nsILoginMetaInfo);
if (foundLogin.timePasswordChanged == login.timePasswordChanged) {
try {
Services.logins.removeLogin(foundLogin);
} catch (ex) {
Cu.reportError("Failed to remove a login for " + foundLogins.hostname);
Cu.reportError(ex);
this._errorMap.logins++;
}
}
}
}
},
async _removeSomeVisits(visits) {
// It is necessary to recreate URL and date objects because the data
// can be serialized through JSON that destroys those objects.
for (let urlVisits of visits) {
let urlObj;
try {
urlObj = new URL(urlVisits.url);
} catch (ex) {
continue;
}
let visitData = {
url: urlObj,
beginDate: new Date(urlVisits.first),
endDate: new Date(urlVisits.last),
limit: urlVisits.visitCount,
};
try {
await PlacesUtils.history.removeVisitsByFilter(visitData);
} catch (ex) {
this._errorMap.visits++;
try {
visitData.url = visitData.url.href;
} catch (ignoredEx) {}
Cu.reportError("Failed to remove a visit: " + JSON.stringify(visitData));
Cu.reportError(ex);
}
}
},
/**
* Maybe open a new tab with a survey. The tab will only be opened if all of
* the following are true:
* - the 'browser.migrate.automigrate.undo-survey' pref is not empty.
* It should contain the URL of the survey to open.
* - the 'browser.migrate.automigrate.undo-survey-locales' pref, a
* comma-separated list of language codes, contains the language code
* that is currently in use for the 'global' chrome pacakge (ie the
* locale in which the user is currently using Firefox).
* The URL will be passed through nsIURLFormatter to allow including
* build ids etc. The special additional formatting variable
* "%IMPORTEDBROWSER" is also replaced with the name of the browser
* from which we imported data.
*
* @param {Window} chromeWindow A reference to the window in which to open a link.
*/
_maybeOpenUndoSurveyTab(chromeWindow) {
let canDoSurveyInLocale = false;
try {
let surveyLocales = Preferences.get(kAutoMigrateUndoSurveyLocalePref, "");
surveyLocales = surveyLocales.split(",").map(str => str.trim());
// Strip out any empty elements, so an empty pref doesn't
// lead to a an array with 1 empty string in it.
surveyLocales = new Set(surveyLocales.filter(str => !!str));
canDoSurveyInLocale =
surveyLocales.has(Services.locale.appLocaleAsLangTag);
} catch (ex) {
/* ignore exceptions and just don't do the survey. */
}
let migrationBrowser = this.getBrowserUsedForMigration();
let rawURL = Preferences.get(kAutoMigrateUndoSurveyPref, "");
if (!canDoSurveyInLocale || !migrationBrowser || !rawURL) {
return;
}
let url = Services.urlFormatter.formatURL(rawURL);
url = url.replace("%IMPORTEDBROWSER%", encodeURIComponent(migrationBrowser));
chromeWindow.openTrustedLinkIn(url, "tab");
},
QueryInterface: ChromeUtils.generateQI(
[Ci.nsIObserver, Ci.nsINavBookmarkObserver, Ci.nsISupportsWeakReference]
),
/**
* Undo action called by the UndoNotification or by the newtab
* @param chromeWindow A reference to the window in which to open a link.
*/
undoAutoMigration(chromeWindow) {
this._maybeOpenUndoSurveyTab(chromeWindow);
this.undo();
},
/**
* Keep the automigration result and not prompt anymore
*/
keepAutoMigration() {
this._purgeUndoState(this.UNDO_REMOVED_REASON_OFFER_REJECTED);
},
};
AutoMigrate.init();

Просмотреть файл

@ -16,8 +16,6 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
ChromeUtils.defineModuleGetter(this, "AutoMigrate",
"resource:///modules/AutoMigrate.jsm");
ChromeUtils.defineModuleGetter(this, "BookmarkHTMLUtils",
"resource://gre/modules/BookmarkHTMLUtils.jsm");
ChromeUtils.defineModuleGetter(this, "LoginHelper",
@ -923,15 +921,9 @@ var MigrationUtils = Object.freeze({
*/
startupMigration:
function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
if (Services.prefs.getBoolPref("browser.migrate.automigrate.enabled", false)) {
this.asyncStartupMigration(aProfileStartup,
aMigratorKey,
aProfileToMigrate);
} else {
this.spinResolve(this.asyncStartupMigration(aProfileStartup,
aMigratorKey,
aProfileToMigrate));
}
this.spinResolve(this.asyncStartupMigration(aProfileStartup,
aMigratorKey,
aProfileToMigrate));
},
asyncStartupMigration:
@ -978,16 +970,6 @@ var MigrationUtils = Object.freeze({
let isRefresh = migrator && skipSourcePage &&
migratorKey == AppConstants.MOZ_APP_NAME;
if (!isRefresh && AutoMigrate.enabled) {
try {
await AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
return;
} catch (ex) {
// If automigration failed, continue and show the dialog.
Cu.reportError(ex);
}
}
let migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FIRSTRUN;
if (isRefresh) {
migrationEntryPoint = this.MIGRATION_ENTRYPOINT_FXREFRESH;

Просмотреть файл

@ -8,8 +8,6 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
MARIONETTE_UNIT_MANIFESTS += ['tests/marionette/manifest.ini']
BROWSER_CHROME_MANIFESTS += [ 'tests/browser/browser.ini']
JAR_MANIFESTS += ['jar.mn']
XPIDL_SOURCES += [
@ -29,7 +27,6 @@ EXTRA_PP_COMPONENTS += [
]
EXTRA_JS_MODULES += [
'AutoMigrate.jsm',
'ChromeMigrationUtils.jsm',
'MigrationUtils.jsm',
]

Просмотреть файл

@ -1,9 +0,0 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/browser-test",
"plugin:mozilla/mochitest-test",
]
};

Просмотреть файл

@ -1,10 +0,0 @@
[DEFAULT]
prefs =
browser.newtabpage.preload=false
support-files =
head.js
[browser_undo_notification.js]
[browser_undo_notification_wording.js]
[browser_undo_notification_multiple_dismissal.js]
skip-if = os == "linux" && debug # Bug 1433324

Просмотреть файл

@ -1,46 +0,0 @@
"use strict";
add_task(async function autoMigrationUndoNotificationShows() {
scope.AutoMigrate.canUndo = () => true;
let undoCalled;
scope.AutoMigrate.undo = () => { undoCalled = true; };
for (let url of ["about:newtab", "about:home"]) {
undoCalled = false;
// Can't use pushPrefEnv because of bug 1323779
Services.prefs.setCharPref("browser.migrate.automigrate.browser", "someunknownbrowser");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, false);
let browser = tab.linkedBrowser;
let notification = await getOrWaitForNotification(browser, url);
ok(true, `Got notification for ${url}`);
let notificationBox = notification.parentNode;
notification.querySelector("button.notification-button-default").click();
ok(!undoCalled, "Undo should not be called when clicking the default button");
is(notification, notificationBox._closedNotification, "Notification should be closing");
let sessionUpdatePromise = BrowserTestUtils.waitForSessionStoreUpdate(tab);
BrowserTestUtils.removeTab(tab);
await sessionUpdatePromise;
undoCalled = false;
Services.prefs.setCharPref("browser.migrate.automigrate.browser", "chrome");
tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, false);
browser = tab.linkedBrowser;
notification = await getOrWaitForNotification(browser, url);
ok(true, `Got notification for ${url}`);
notificationBox = notification.parentNode;
// Set up the survey:
await SpecialPowers.pushPrefEnv({set: [
["browser.migrate.automigrate.undo-survey", "https://example.com/?browser=%IMPORTEDBROWSER%"],
["browser.migrate.automigrate.undo-survey-locales", "en-US"],
]});
let tabOpenedPromise = BrowserTestUtils.waitForNewTab(gBrowser, "https://example.com/?browser=Google%20Chrome");
notification.querySelector("button:not(.notification-button-default)").click();
ok(undoCalled, "Undo should be called when clicking the non-default (Don't Keep) button");
is(notification, notificationBox._closedNotification, "Notification should be closing");
let surveyTab = await tabOpenedPromise;
ok(surveyTab, "Should have opened a tab with a survey");
BrowserTestUtils.removeTab(surveyTab);
BrowserTestUtils.removeTab(tab);
}
});

Просмотреть файл

@ -1,111 +0,0 @@
"use strict";
/**
* Pretend we can undo something, trigger a notification, pick the undo option,
* and verify that the notifications are all dismissed immediately.
*/
add_task(async function checkNotificationsDismissed() {
await SpecialPowers.pushPrefEnv({set: [
["browser.migrate.automigrate.enabled", true],
["browser.migrate.automigrate.ui.enabled", true],
]});
Services.prefs.setCharPref("browser.migrate.automigrate.browser", "someunknownbrowser");
let {guid, lastModified} = await PlacesUtils.bookmarks.insert(
{title: "Some imported bookmark", parentGuid: PlacesUtils.bookmarks.toolbarGuid, url: "http://www.example.com"}
);
let testUndoData = {
visits: [],
bookmarks: [{guid, lastModified: lastModified.getTime()}],
logins: [],
};
let path = OS.Path.join(OS.Constants.Path.profileDir, "initialMigrationMetadata.jsonlz4");
registerCleanupFunction(() => {
return OS.File.remove(path, {ignoreAbsent: true});
});
await OS.File.writeAtomic(path, JSON.stringify(testUndoData), {
encoding: "utf-8",
compression: "lz4",
tmpPath: path + ".tmp",
});
let firstTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", false);
let secondTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:home", false);
let firstNotification = await getOrWaitForNotification(firstTab.linkedBrowser, "first tab");
let secondNotification = await getOrWaitForNotification(secondTab.linkedBrowser, "second tab");
// Create a listener for the removal in the first tab, and a listener for bookmarks removal,
// then click 'Don't keep' in the second tab, and verify that the notification is removed
// before we start removing bookmarks.
let haveRemovedBookmark = false;
let bmObserver;
let bookmarkRemovedPromise = new Promise(resolve => {
bmObserver = {
onItemRemoved(itemId, parentId, index, itemType, uri, removedGuid) {
if (guid == removedGuid) {
haveRemovedBookmark = true;
resolve();
}
},
};
PlacesUtils.bookmarks.addObserver(bmObserver);
registerCleanupFunction(() => PlacesUtils.bookmarks.removeObserver(bmObserver));
});
let firstTabNotificationRemovedPromise = new Promise(resolve => {
let notification = firstNotification;
// Save this reference because notification.parentNode will be null once it's removed.
let notificationBox = notification.parentNode;
let mut = new MutationObserver(mutations => {
// Yucky, but we have to detect either the removal via animation (with marginTop)
// or when the element is removed. We can't just detect the element being removed
// because this happens asynchronously (after the animation) and so it'd race
// with the rest of the undo happening.
for (let mutation of mutations) {
if (mutation.target == notification && mutation.attributeName == "style" &&
parseInt(notification.style.marginTop, 10) < 0) {
ok(!haveRemovedBookmark, "Should not have removed bookmark yet");
mut.disconnect();
resolve();
return;
}
if (mutation.target == notificationBox && mutation.removedNodes.length &&
mutation.removedNodes[0] == notification) {
ok(!haveRemovedBookmark, "Should not have removed bookmark yet");
mut.disconnect();
resolve();
return;
}
}
});
mut.observe(notification.parentNode, {childList: true});
mut.observe(notification, {attributes: true});
});
let prefResetPromise = new Promise(resolve => {
const kObservedPref = "browser.migrate.automigrate.browser";
let obs = () => {
Services.prefs.removeObserver(kObservedPref, obs);
ok(!Services.prefs.prefHasUserValue(kObservedPref),
"Pref should have been reset");
resolve();
};
Services.prefs.addObserver(kObservedPref, obs);
});
// Click "Don't keep" button:
let notificationToActivate = secondNotification;
notificationToActivate.querySelector("button:not(.notification-button-default)").click();
info("Waiting for notification to be removed in first (background) tab");
await firstTabNotificationRemovedPromise;
info("Waiting for bookmark to be removed");
await bookmarkRemovedPromise;
info("Waiting for prefs to be reset");
await prefResetPromise;
info("Removing spare tabs");
BrowserTestUtils.removeTab(firstTab);
BrowserTestUtils.removeTab(secondTab);
});

Просмотреть файл

@ -1,51 +0,0 @@
"use strict";
add_task(async function autoMigrationUndoNotificationShows() {
let localizedVersionOf = str => {
if (str == "logins") {
return "passwords";
}
if (str == "visits") {
return "history";
}
return str;
};
scope.AutoMigrate.canUndo = () => true;
let url = "about:home";
Services.prefs.setCharPref("browser.migrate.automigrate.browser", "someunknownbrowser");
const kSubsets = [
["bookmarks", "logins", "visits"],
["bookmarks", "logins"],
["bookmarks", "visits"],
["logins", "visits"],
["bookmarks"],
["logins"],
["visits"],
];
const kAllItems = ["bookmarks", "logins", "visits"];
for (let subset of kSubsets) {
let state = new Map(subset.map(item => [item, [{}]]));
scope.AutoMigrate._setImportedItemPrefFromState(state);
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, false);
let browser = tab.linkedBrowser;
let notification = await getOrWaitForNotification(browser, url);
ok(true, `Got notification for ${url}`);
let notificationText = document.getAnonymousElementByAttribute(notification, "class", "messageText");
notificationText = notificationText.textContent;
for (let potentiallyImported of kAllItems) {
let localizedImportItem = localizedVersionOf(potentiallyImported);
if (subset.includes(potentiallyImported)) {
ok(notificationText.includes(localizedImportItem),
"Expected notification to contain " + localizedImportItem);
} else {
ok(!notificationText.includes(localizedImportItem),
"Expected notification not to contain " + localizedImportItem);
}
}
BrowserTestUtils.removeTab(tab);
}
});

Просмотреть файл

@ -1,42 +0,0 @@
"use strict";
/* exported getOrWaitForNotification */
// Import AutoMigrate and save/restore methods to allow stubbing
const scope = {};
ChromeUtils.import("resource:///modules/AutoMigrate.jsm", scope);
const oldCanUndo = scope.AutoMigrate.canUndo;
const oldUndo = scope.AutoMigrate.undo;
registerCleanupFunction(function() {
scope.AutoMigrate.canUndo = oldCanUndo;
scope.AutoMigrate.undo = oldUndo;
});
const kExpectedNotificationId = "automigration-undo";
/**
* Helper to get the undo notification bar.
*/
function getNotification(browser) {
const box = gBrowser.getNotificationBox(browser).getNotificationWithValue(kExpectedNotificationId);
// Before activity stream, the page itself would trigger the notification bar.
// Until bug 1438305 to decide on how to handle migration, keep integration
// tests running by triggering the notification bar directly via tests.
if (!box) {
executeSoon(() => scope.AutoMigrate.showUndoNotificationBar(browser));
}
return box;
}
/**
* Helper to get or wait for the undo notification bar.
*/
function getOrWaitForNotification(browser, description) {
const notification = getNotification(browser);
if (notification) {
return notification;
}
info(`Notification for ${description} not immediately present, waiting for it.`);
return BrowserTestUtils.waitForNotificationBar(gBrowser, browser, kExpectedNotificationId);
}

Просмотреть файл

@ -1,705 +0,0 @@
"use strict";
ChromeUtils.import("resource:///modules/AutoMigrate.jsm", this);
let gShimmedMigratorKeyPicker = null;
let gShimmedMigrator = null;
const kMsecPerMin = 60 * 1000;
// 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.
let AutoMigrateBackstage = ChromeUtils.import("resource:///modules/AutoMigrate.jsm", {});
AutoMigrateBackstage.MigrationUtils = new Proxy({}, {
get(target, name) {
if (name == "getMigratorKeyForDefaultBrowser" && gShimmedMigratorKeyPicker) {
return gShimmedMigratorKeyPicker;
}
if (name == "getMigrator" && gShimmedMigrator) {
return function() { return gShimmedMigrator; };
}
return MigrationUtils[name];
},
});
registerCleanupFunction(function() {
AutoMigrateBackstage.MigrationUtils = MigrationUtils;
});
// This should be replaced by using History.fetch with a fetchVisits option,
// once that becomes available
async function visitsForURL(url) {
let visitCount = 0;
let db = await PlacesUtils.promiseDBConnection();
visitCount = await db.execute(
`SELECT count(*) FROM moz_historyvisits v
JOIN moz_places h ON h.id = v.place_id
WHERE url_hash = hash(:url) AND url = :url`,
{url});
visitCount = visitCount[0].getInt64(0);
return visitCount;
}
async function promiseThrows(fn) {
let failed = false;
try {
await fn();
} catch (e) {
failed = true;
}
Assert.ok(failed);
}
/**
* Test automatically picking a browser to migrate from
*/
add_task(async function checkMigratorPicking() {
await promiseThrows(() => AutoMigrate.pickMigrator("firefox"),
/Can't automatically migrate from Firefox/,
"Should throw when explicitly picking Firefox.");
await promiseThrows(() => AutoMigrate.pickMigrator("gobbledygook"),
/migrator object is not available/,
"Should throw when passing unknown migrator key");
gShimmedMigratorKeyPicker = function() {
return "firefox";
};
await promiseThrows(() => AutoMigrate.pickMigrator(),
/Can't automatically migrate from Firefox/,
"Should throw when implicitly picking Firefox.");
gShimmedMigratorKeyPicker = function() {
return "gobbledygook";
};
await promiseThrows(() => AutoMigrate.pickMigrator(),
/migrator object is not available/,
"Should throw when an unknown migrator is the default");
gShimmedMigratorKeyPicker = function() {
return "";
};
await promiseThrows(() => 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(async function checkProfilePicking() {
let fakeMigrator = {
_sourceProfiles: [{id: "a"}, {id: "b"}],
getSourceProfiles() {
return this._sourceProfiles;
},
};
let profB = fakeMigrator._sourceProfiles[1];
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator),
/Don't know how to pick a profile when more/,
"Should throw when there are multiple profiles.");
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
/Profile specified was not found/,
"Should throw when the profile supplied doesn't exist.");
let profileToMigrate = await AutoMigrate.pickProfile(fakeMigrator, "b");
Assert.equal(profileToMigrate, profB, "Should return profile supplied");
fakeMigrator._sourceProfiles = null;
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
/Profile specified but only a default profile found./,
"Should throw when the profile supplied doesn't exist.");
profileToMigrate = await AutoMigrate.pickProfile(fakeMigrator);
Assert.equal(profileToMigrate, null, "Should return default profile when that's the only one.");
fakeMigrator._sourceProfiles = [];
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator),
/No profile data found/,
"Should throw when no profile data is present.");
fakeMigrator._sourceProfiles = [{id: "a"}];
let profA = fakeMigrator._sourceProfiles[0];
profileToMigrate = await AutoMigrate.pickProfile(fakeMigrator);
Assert.equal(profileToMigrate, profA, "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(async function checkIntegration() {
gShimmedMigrator = {
getSourceProfiles() {
info("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";
};
await AutoMigrate.migrate("startup");
Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
"getMigrateData called with 'null' as a profile");
let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
"migrate called with 'null' as a profile");
});
/**
* Test the undo preconditions and a no-op undo in the automigrator.
*/
add_task(async function checkUndoPreconditions() {
let shouldAddData = false;
gShimmedMigrator = {
getSourceProfiles() {
info("Read sourceProfiles");
return null;
},
getMigrateData(profileToMigrate) {
this._getMigrateDataArgs = profileToMigrate;
return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
},
async migrate(types, startup, profileToMigrate) {
this._migrateArgs = [types, startup, profileToMigrate];
if (shouldAddData) {
// Insert a login and check that that worked.
await MigrationUtils.insertLoginsWrapper([{
hostname: "www.mozilla.org",
formSubmitURL: "http://www.mozilla.org",
username: "user",
password: "pass",
}]);
}
TestUtils.executeSoon(function() {
Services.obs.notifyObservers(null, "Migration:Ended", undefined);
});
},
};
gShimmedMigratorKeyPicker = function() {
return "gobbledygook";
};
await AutoMigrate.migrate("startup");
let migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
"getMigrateData called with 'null' as a profile");
let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
"migrate called with 'null' as a profile");
await migrationFinishedPromise;
Assert.ok(Preferences.has("browser.migrate.automigrate.browser"),
"Should have set browser pref");
Assert.ok(!(await AutoMigrate.canUndo()), "Should not be able to undo migration, as there's no data");
gShimmedMigrator._migrateArgs = null;
gShimmedMigrator._getMigrateDataArgs = null;
Preferences.reset("browser.migrate.automigrate.browser");
shouldAddData = true;
await AutoMigrate.migrate("startup");
migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
"getMigrateData called with 'null' as a profile");
Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
"migrate called with 'null' as a profile");
await migrationFinishedPromise;
let storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
"http://www.mozilla.org", null);
Assert.equal(storedLogins.length, 1, "Should have 1 login");
Assert.ok(Preferences.has("browser.migrate.automigrate.browser"),
"Should have set browser pref");
Assert.ok((await AutoMigrate.canUndo()), "Should be able to undo migration, as now there's data");
await AutoMigrate.undo();
Assert.ok(true, "Should be able to finish an undo cycle.");
// Check that the undo removed the passwords:
storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
"http://www.mozilla.org", null);
Assert.equal(storedLogins.length, 0, "Should have no logins");
});
/**
* Fake a migration and then try to undo it to verify all data gets removed.
*/
add_task(async function checkUndoRemoval() {
MigrationUtils.initializeUndoData();
Preferences.set("browser.migrate.automigrate.browser", "automationbrowser");
// Insert a login and check that that worked.
await MigrationUtils.insertLoginsWrapper([{
hostname: "www.mozilla.org",
formSubmitURL: "http://www.mozilla.org",
username: "user",
password: "pass",
}]);
let storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
"http://www.mozilla.org", null);
Assert.equal(storedLogins.length, 1, "Should have 1 login");
// Insert a bookmark and check that we have exactly 1 bookmark for that URI.
await MigrationUtils.insertBookmarkWrapper({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: "http://www.example.org/",
title: "Some example bookmark",
});
let bookmark = await PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
Assert.ok(bookmark, "Should have a bookmark before undo");
Assert.equal(bookmark.title, "Some example bookmark", "Should have correct bookmark before undo.");
// Insert 2 history visits
let now = new Date();
let visitedURI = Services.io.newURI("http://www.example.com/");
let frecencyUpdatePromise = new Promise(resolve => {
let observer = {
onManyFrecenciesChanged() {
PlacesUtils.history.removeObserver(observer);
resolve();
},
};
PlacesUtils.history.addObserver(observer);
});
await MigrationUtils.insertVisitsWrapper([{
url: visitedURI,
visits: [
{
transition: PlacesUtils.history.TRANSITION_LINK,
date: now,
},
{
transition: PlacesUtils.history.TRANSITION_LINK,
date: new Date(now - 100 * kMsecPerMin),
},
],
}]);
await frecencyUpdatePromise;
// Verify that both visits get reported.
let opts = PlacesUtils.history.getNewQueryOptions();
opts.resultType = opts.RESULTS_AS_VISIT;
let query = PlacesUtils.history.getNewQuery();
query.uri = visitedURI;
let visits = PlacesUtils.history.executeQuery(query, opts);
visits.root.containerOpen = true;
Assert.equal(visits.root.childCount, 2, "Should have 2 visits");
// Clean up:
visits.root.containerOpen = false;
await AutoMigrate.saveUndoState();
// Verify that we can undo, then undo:
Assert.ok(AutoMigrate.canUndo(), "Should be possible to undo migration");
await AutoMigrate.undo();
let histograms = [
"FX_STARTUP_MIGRATION_UNDO_BOOKMARKS_ERRORCOUNT",
"FX_STARTUP_MIGRATION_UNDO_LOGINS_ERRORCOUNT",
"FX_STARTUP_MIGRATION_UNDO_VISITS_ERRORCOUNT",
];
for (let histogramId of histograms) {
let keyedHistogram = Services.telemetry.getKeyedHistogramById(histogramId);
let histogramData = keyedHistogram.snapshot().automationbrowser;
Assert.equal(histogramData.sum, 0, `Should have reported 0 errors to ${histogramId}.`);
Assert.greaterOrEqual(histogramData.counts[0], 1, `Should have reported value of 0 one time to ${histogramId}.`);
}
histograms = [
"FX_STARTUP_MIGRATION_UNDO_BOOKMARKS_MS",
"FX_STARTUP_MIGRATION_UNDO_LOGINS_MS",
"FX_STARTUP_MIGRATION_UNDO_VISITS_MS",
"FX_STARTUP_MIGRATION_UNDO_TOTAL_MS",
];
for (let histogramId of histograms) {
Assert.greater(Services.telemetry.getKeyedHistogramById(histogramId).snapshot().automationbrowser.sum, 0,
`Should have reported non-zero time spent using undo for ${histogramId}`);
}
// Check that the undo removed the history visits:
visits = PlacesUtils.history.executeQuery(query, opts);
visits.root.containerOpen = true;
Assert.equal(visits.root.childCount, 0, "Should have no more visits");
visits.root.containerOpen = false;
// Check that the undo removed the bookmarks:
bookmark = await PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
Assert.ok(!bookmark, "Should have no bookmarks after undo");
// Check that the undo removed the passwords:
storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
"http://www.mozilla.org", null);
Assert.equal(storedLogins.length, 0, "Should have no logins");
});
add_task(async function checkUndoBookmarksState() {
MigrationUtils.initializeUndoData();
const {TYPE_FOLDER, TYPE_BOOKMARK} = PlacesUtils.bookmarks;
let title = "Some example bookmark";
let url = "http://www.example.com";
let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
let {guid, lastModified} = await MigrationUtils.insertBookmarkWrapper({
title, url, parentGuid,
});
Assert.deepEqual((await MigrationUtils.stopAndRetrieveUndoData()).get("bookmarks"),
[{lastModified, parentGuid, guid, type: TYPE_BOOKMARK}]);
MigrationUtils.initializeUndoData();
({guid, lastModified} = await MigrationUtils.insertBookmarkWrapper({
title, parentGuid, type: TYPE_FOLDER,
}));
let folder = {guid, lastModified, parentGuid, type: TYPE_FOLDER};
let folderGuid = folder.guid;
({guid, lastModified} = await MigrationUtils.insertBookmarkWrapper({
title, url, parentGuid: folderGuid,
}));
let kid1 = {guid, lastModified, parentGuid: folderGuid, type: TYPE_BOOKMARK};
({guid, lastModified} = await MigrationUtils.insertBookmarkWrapper({
title, url, parentGuid: folderGuid,
}));
let kid2 = {guid, lastModified, parentGuid: folderGuid, type: TYPE_BOOKMARK};
let bookmarksUndo = (await MigrationUtils.stopAndRetrieveUndoData()).get("bookmarks");
Assert.equal(bookmarksUndo.length, 3);
// We expect that the last modified time from first kid #1 and then kid #2
// has been propagated to the folder:
folder.lastModified = kid2.lastModified;
// Not just using deepEqual on the entire array (which should work) because
// the failure messages get truncated by xpcshell which is unhelpful.
Assert.deepEqual(bookmarksUndo[0], folder);
Assert.deepEqual(bookmarksUndo[1], kid1);
Assert.deepEqual(bookmarksUndo[2], kid2);
await PlacesUtils.bookmarks.eraseEverything();
});
add_task(async function testBookmarkRemovalByUndo() {
const {TYPE_FOLDER} = PlacesUtils.bookmarks;
MigrationUtils.initializeUndoData();
let title = "Some example bookmark";
let url = "http://www.mymagicaluniqueurl.com";
let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
let {guid} = await MigrationUtils.insertBookmarkWrapper({
title: "Some folder", parentGuid, type: TYPE_FOLDER,
});
let folderGuid = guid;
let itemsToRemove = [];
({guid} = await MigrationUtils.insertBookmarkWrapper({
title: "Inner folder", parentGuid: folderGuid, type: TYPE_FOLDER,
}));
let innerFolderGuid = guid;
itemsToRemove.push(innerFolderGuid);
({guid} = await MigrationUtils.insertBookmarkWrapper({
title: "Inner inner folder", parentGuid: innerFolderGuid, type: TYPE_FOLDER,
}));
itemsToRemove.push(guid);
({guid} = await MigrationUtils.insertBookmarkWrapper({
title: "Inner nested item", url: "http://inner-nested-example.com", parentGuid: guid,
}));
itemsToRemove.push(guid);
({guid} = await MigrationUtils.insertBookmarkWrapper({
title, url, parentGuid: folderGuid,
}));
itemsToRemove.push(guid);
for (let toBeRemovedGuid of itemsToRemove) {
let dbResultForGuid = await PlacesUtils.bookmarks.fetch(toBeRemovedGuid);
Assert.ok(dbResultForGuid, "Should be able to find items that will be removed.");
}
let bookmarkUndoState = (await MigrationUtils.stopAndRetrieveUndoData()).get("bookmarks");
// Now insert a separate item into this folder, not related to the migration.
let newItem = await PlacesUtils.bookmarks.insert(
{title: "Not imported", parentGuid: folderGuid, url: "http://www.example.com"}
);
await AutoMigrate._removeUnchangedBookmarks(bookmarkUndoState);
Assert.ok(true, "Successfully removed imported items.");
let itemFromDB = await PlacesUtils.bookmarks.fetch(newItem.guid);
Assert.ok(itemFromDB, "Item we inserted outside of migration is still there.");
itemFromDB = await PlacesUtils.bookmarks.fetch(folderGuid);
Assert.ok(itemFromDB, "Folder we inserted in migration is still there because of new kids.");
for (let removedGuid of itemsToRemove) {
let dbResultForGuid = await PlacesUtils.bookmarks.fetch(removedGuid);
let dbgStr = dbResultForGuid && dbResultForGuid.title;
Assert.equal(null, dbResultForGuid, "Should not be able to find items that should have been removed, but found " + dbgStr);
}
await PlacesUtils.bookmarks.eraseEverything();
});
add_task(async function checkUndoLoginsState() {
MigrationUtils.initializeUndoData();
await MigrationUtils.insertLoginsWrapper([{
username: "foo",
password: "bar",
hostname: "https://example.com",
formSubmitURL: "https://example.com/",
timeCreated: new Date(),
}]);
let storedLogins = Services.logins.findLogins({}, "https://example.com", "", "");
let storedLogin = storedLogins[0];
storedLogin.QueryInterface(Ci.nsILoginMetaInfo);
let {guid, timePasswordChanged} = storedLogin;
let undoLoginData = (await MigrationUtils.stopAndRetrieveUndoData()).get("logins");
Assert.deepEqual([{guid, timePasswordChanged}], undoLoginData);
Services.logins.removeAllLogins();
});
add_task(async function testLoginsRemovalByUndo() {
MigrationUtils.initializeUndoData();
await MigrationUtils.insertLoginsWrapper([{
username: "foo",
password: "bar",
hostname: "https://example.com",
formSubmitURL: "https://example.com/",
timeCreated: new Date(),
}]);
await MigrationUtils.insertLoginsWrapper([{
username: "foo",
password: "bar",
hostname: "https://example.org",
formSubmitURL: "https://example.org/",
timeCreated: new Date(new Date().getTime() - 10000),
}]);
// This should update the existing login
await LoginHelper.maybeImportLogins([{
username: "foo",
password: "bazzy",
hostname: "https://example.org",
formSubmitURL: "https://example.org/",
timePasswordChanged: new Date(),
}]);
Assert.equal(1, LoginHelper.searchLoginsWithObject({hostname: "https://example.org", formSubmitURL: "https://example.org/"}).length,
"Should be only 1 login for example.org (that was updated)");
let undoLoginData = (await MigrationUtils.stopAndRetrieveUndoData()).get("logins");
await AutoMigrate._removeUnchangedLogins(undoLoginData);
Assert.equal(0, LoginHelper.searchLoginsWithObject({hostname: "https://example.com", formSubmitURL: "https://example.com/"}).length,
"unchanged example.com entry should have been removed.");
Assert.equal(1, LoginHelper.searchLoginsWithObject({hostname: "https://example.org", formSubmitURL: "https://example.org/"}).length,
"changed example.org entry should have persisted.");
Services.logins.removeAllLogins();
});
add_task(async function checkUndoVisitsState() {
MigrationUtils.initializeUndoData();
await MigrationUtils.insertVisitsWrapper([{
url: NetUtil.newURI("http://www.example.com/"),
title: "Example",
visits: [{
date: new Date("2015-07-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}, {
date: new Date("2015-09-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}, {
date: new Date("2015-08-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
}, {
url: Services.io.newURI("http://www.example.org/"),
title: "Example",
visits: [{
date: new Date("2016-04-03"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}, {
date: new Date("2015-08-03"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
}, {
url: "http://www.example.com/",
title: "Example",
visits: [{
date: new Date("2015-10-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
}]);
let undoVisitData = (await MigrationUtils.stopAndRetrieveUndoData()).get("visits");
Assert.deepEqual(Array.from(undoVisitData.map(v => v.url)).sort(),
["http://www.example.com/", "http://www.example.org/"]);
Assert.deepEqual(undoVisitData.find(v => v.url == "http://www.example.com/"), {
url: "http://www.example.com/",
visitCount: 4,
first: new Date("2015-07-10").getTime(),
last: new Date("2015-10-10").getTime(),
});
Assert.deepEqual(undoVisitData.find(v => v.url == "http://www.example.org/"), {
url: "http://www.example.org/",
visitCount: 2,
first: new Date("2015-08-03").getTime(),
last: new Date("2016-04-03").getTime(),
});
await PlacesUtils.history.clear();
});
add_task(async function checkUndoVisitsState() {
MigrationUtils.initializeUndoData();
await MigrationUtils.insertVisitsWrapper([{
url: NetUtil.newURI("http://www.example.com/"),
title: "Example",
visits: [{
date: new Date("2015-07-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}, {
date: new Date("2015-09-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}, {
date: new Date("2015-08-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
}, {
url: NetUtil.newURI("http://www.example.org/"),
title: "Example",
visits: [{
date: new Date("2016-04-03"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}, {
date: new Date("2015-08-03"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
}, {
url: NetUtil.newURI("http://www.example.com/"),
title: "Example",
visits: [{
date: new Date("2015-10-10"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
}, {
url: NetUtil.newURI("http://www.mozilla.org/"),
title: "Example",
visits: [{
date: new Date("2015-01-01"),
transition: Ci.nsINavHistoryService.TRANSITION_LINK,
}],
}]);
// We have to wait until frecency updates have been handled in order
// to accurately determine whether we're doing the right thing.
let frecencyUpdatesHandled = new Promise(resolve => {
PlacesUtils.history.addObserver({
onManyFrecenciesChanged() {
PlacesUtils.history.removeObserver(this);
resolve();
},
});
});
await PlacesUtils.history.insertMany([{
url: "http://www.example.com/",
title: "Example",
visits: [{
date: new Date("2015-08-16"),
}],
}, {
url: "http://www.example.org/",
title: "Example",
visits: [{
date: new Date("2016-01-03"),
}, {
date: new Date("2015-05-03"),
}],
}, {
url: "http://www.unrelated.org/",
title: "Unrelated",
visits: [{
date: new Date("2015-09-01"),
}],
}]);
await frecencyUpdatesHandled;
let undoVisitData = (await MigrationUtils.stopAndRetrieveUndoData()).get("visits");
let frecencyChangesExpected = new Map([
["http://www.example.com/", PromiseUtils.defer()],
["http://www.example.org/", PromiseUtils.defer()],
]);
let uriDeletedExpected = new Map([
["http://www.mozilla.org/", PromiseUtils.defer()],
]);
let wrongMethodDeferred = PromiseUtils.defer();
let observer = {
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onTitleChanged(uri) {
wrongMethodDeferred.reject(new Error("Unexpected call to onTitleChanged " + uri.spec));
},
onClearHistory() {
wrongMethodDeferred.reject("Unexpected call to onClearHistory");
},
onPageChanged(uri) {
wrongMethodDeferred.reject(new Error("Unexpected call to onPageChanged " + uri.spec));
},
onFrecencyChanged(aURI) {
info("frecency change");
Assert.ok(frecencyChangesExpected.has(aURI.spec),
"Should be expecting frecency change for " + aURI.spec);
frecencyChangesExpected.get(aURI.spec).resolve();
},
onManyFrecenciesChanged() {
info("Many frecencies changed");
wrongMethodDeferred.reject(new Error("This test can't deal with onManyFrecenciesChanged to be called"));
},
onDeleteURI(aURI) {
info("delete uri");
Assert.ok(uriDeletedExpected.has(aURI.spec),
"Should be expecting uri deletion for " + aURI.spec);
uriDeletedExpected.get(aURI.spec).resolve();
},
};
PlacesUtils.history.addObserver(observer);
await AutoMigrate._removeSomeVisits(undoVisitData);
PlacesUtils.history.removeObserver(observer);
await Promise.all(uriDeletedExpected.values());
await Promise.all(frecencyChangesExpected.values());
Assert.equal(await visitsForURL("http://www.example.com/"), 1,
"1 example.com visit (out of 5) should have persisted despite being within the range, due to limiting");
Assert.equal(await visitsForURL("http://www.mozilla.org/"), 0,
"0 mozilla.org visits should have persisted (out of 1).");
Assert.equal(await visitsForURL("http://www.example.org/"), 2,
"2 example.org visits should have persisted (out of 4).");
Assert.equal(await visitsForURL("http://www.unrelated.org/"), 1,
"1 unrelated.org visits should have persisted as it's not involved in the import.");
await PlacesUtils.history.clear();
});
add_task(async function checkHistoryRemovalCompletion() {
AutoMigrate._errorMap = {bookmarks: 0, visits: 0, logins: 0};
await AutoMigrate._removeSomeVisits([{url: "http://www.example.com/",
first: 0,
last: PlacesUtils.toPRTime(new Date()),
limit: -1}]);
ok(true, "Removing visits should complete even if removing some visits failed.");
Assert.equal(AutoMigrate._errorMap.visits, 1, "Should have logged the error for visits.");
// Unfortunately there's not a reliable way to make removing bookmarks be
// unhappy unless the DB is messed up (e.g. contains children but has
// parents removed already).
await AutoMigrate._removeUnchangedBookmarks([
{guid: PlacesUtils.bookmarks, lastModified: new Date(0), parentGuid: 0},
{guid: "gobbledygook", lastModified: new Date(0), parentGuid: 0},
]);
ok(true, "Removing bookmarks should complete even if some items are gone or bogus.");
Assert.equal(AutoMigrate._errorMap.bookmarks, 0,
"Should have ignored removing non-existing (or builtin) bookmark.");
await AutoMigrate._removeUnchangedLogins([
{guid: "gobbledygook", timePasswordChanged: new Date(0)},
]);
ok(true, "Removing logins should complete even if logins don't exist.");
Assert.equal(AutoMigrate._errorMap.logins, 0,
"Should have ignored removing non-existing logins.");
});

Просмотреть файл

@ -8,7 +8,6 @@ support-files =
[test_360se_bookmarks.js]
skip-if = os != "win"
[test_automigration.js]
[test_Chrome_bookmarks.js]
[test_Chrome_cookies.js]
skip-if = os != "mac" # Relies on ULibDir

Просмотреть файл

@ -73,17 +73,3 @@ importedEdgeReadingList=Reading List (From Edge)
128_firefox=Windows and Tabs
# Automigration undo notification.
# %1$S will be replaced with brandShortName, %2$S will be replaced with the name of the browser we imported from
automigration.undo.message2.all = Dive right into %1$S! Import your favorite sites, bookmarks, history and passwords from %2$S.
automigration.undo.message2.bookmarks = Dive right into %1$S! Import your favorite sites and bookmarks from %2$S.
automigration.undo.message2.bookmarks.logins = Dive right into %1$S! Import your favorite sites, bookmarks and passwords from %2$S.
automigration.undo.message2.bookmarks.visits = Dive right into %1$S! Import your favorite sites, bookmarks and history from %2$S.
automigration.undo.message2.logins = Dive right into %1$S! Import your passwords from %2$S.
automigration.undo.message2.logins.visits = Dive right into %1$S! Import your favorite sites, history and passwords from %2$S.
automigration.undo.message2.visits = Dive right into %1$S! Import your favorite sites and history from %2$S.
automigration.undo.keep2.label = OK, Got it
automigration.undo.keep2.accesskey = O
automigration.undo.dontkeep2.label = No Thanks
automigration.undo.dontkeep2.accesskey = N
automigration.undo.unknownbrowser = Unknown Browser

Просмотреть файл

@ -6299,131 +6299,6 @@
"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_PROCESS_SUCCESS": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1271775],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"kind": "enumerated",
"n_values": 27,
"releaseChannelCollection": "opt-out",
"description": "Where automatic migration was attempted, indicates to what degree we succeeded. Values 0-25 indicate progress through the automatic migration sequence, with 25 indicating the migration finished. 26 is only used when the migration produced errors before it finished."
},
"FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_UNDO": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1283565],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"kind": "enumerated",
"n_values": 31,
"releaseChannelCollection": "opt-out",
"description": "Where undo of the automatic migration was attempted, indicates to what degree we succeeded to undo. 0 means we started to undo, 5 means we bailed out from the undo because it was not possible to complete it (there was nothing to undo or the user was signed in to sync). All higher values indicate progression through the undo sequence, with 30 indicating we finished the undo without exceptions in the middle."
},
"FX_STARTUP_MIGRATION_UNDO_REASON": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1289906],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "enumerated",
"n_values": 10,
"releaseChannelCollection": "opt-out",
"description": "Why the undo functionality of an automatic migration was disabled: 0 means we used undo, 1 means the user signed in to sync, 2 means the user created/modified a password, 3 means the user created/modified a bookmark (item or folder), 4 means we showed an undo option repeatedly and the user did not use it, 5 means we showed an undo option and the user actively elected to keep the data. The whole thing is keyed to the identifiers of different browsers (so 'chrome', 'ie', 'edge', 'safari', etc.)."
},
"FX_STARTUP_MIGRATION_UNDO_OFFERED": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1309617],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"kind": "enumerated",
"n_values": 5,
"releaseChannelCollection": "opt-out",
"description": "Indicates we showed a 'would you like to undo this automatic migration?' notification bar. The bucket indicates which nth day we're on (1st/2nd/3rd, by default - 0 would be indicative the pref didn't get set which shouldn't happen). After 3 days on which the notification gets shown, it will get disabled and never shown again."
},
"FX_STARTUP_MIGRATION_UNDO_BOOKMARKS_ERRORCOUNT": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1333233],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "exponential",
"n_buckets": 20,
"high": 100,
"releaseChannelCollection": "opt-out",
"description": "Indicates how many errors we find when trying to 'undo' bookmarks import. Keys are internal ids of browsers we import from, e.g. 'chrome' or 'ie', etc."
},
"FX_STARTUP_MIGRATION_UNDO_LOGINS_ERRORCOUNT": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1333233],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "exponential",
"n_buckets": 20,
"high": 100,
"releaseChannelCollection": "opt-out",
"description": "Indicates how many errors we find when trying to 'undo' login (password) import. Keys are internal ids of browsers we import from, e.g. 'chrome' or 'ie', etc."
},
"FX_STARTUP_MIGRATION_UNDO_VISITS_ERRORCOUNT": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1333233],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "exponential",
"n_buckets": 20,
"high": 100,
"releaseChannelCollection": "opt-out",
"description": "Indicates how many errors we find when trying to 'undo' history import. Keys are internal ids of browsers we import from, e.g. 'chrome' or 'ie', etc."
},
"FX_STARTUP_MIGRATION_UNDO_BOOKMARKS_MS": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1333233],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "exponential",
"n_buckets": 20,
"high": 60000,
"releaseChannelCollection": "opt-out",
"description": "Indicates how long it took to undo the startup import of bookmarks, in ms. Keys are internal ids of browsers we import from, e.g. 'chrome' or 'ie', etc."
},
"FX_STARTUP_MIGRATION_UNDO_LOGINS_MS": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1333233],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "exponential",
"n_buckets": 20,
"high": 60000,
"releaseChannelCollection": "opt-out",
"description": "Indicates how long it took to undo the startup import of logins, in ms. Keys are internal ids of browsers we import from, e.g. 'chrome' or 'ie', etc."
},
"FX_STARTUP_MIGRATION_UNDO_VISITS_MS": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1333233],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "exponential",
"n_buckets": 20,
"high": 60000,
"releaseChannelCollection": "opt-out",
"description": "Indicates how long it took to undo the startup import of visits (history), in ms. Keys are internal ids of browsers we import from, e.g. 'chrome' or 'ie', etc."
},
"FX_STARTUP_MIGRATION_UNDO_TOTAL_MS": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1333233],
"alert_emails": ["gijs@mozilla.com"],
"expires_in_version": "65",
"keyed": true,
"kind": "exponential",
"n_buckets": 20,
"high": 60000,
"releaseChannelCollection": "opt-out",
"description": "Indicates how long it took to undo the entirety of the startup undo, in ms. Keys are internal ids of browsers we import from, e.g. 'chrome' or 'ie', etc."
},
"FX_STARTUP_MIGRATION_DATA_RECENCY": {
"record_in_processes": ["main", "content"],
"bug_numbers": [1276694],

Просмотреть файл

@ -12,7 +12,6 @@
"AppInfo.jsm": ["newAppInfo", "getAppInfo", "updateAppInfo"],
"async.js": ["Async"],
"AsyncSpellCheckTestHelper.jsm": ["onSpellCheck"],
"AutoMigrate.jsm": ["AutoMigrate"],
"Battery.jsm": ["GetBattery", "Battery"],
"blocklist-clients.js": ["AddonBlocklistClient", "GfxBlocklistClient", "OneCRLBlocklistClient", "PluginBlocklistClient"],
"blocklist-updater.js": ["checkVersions", "addTestBlocklistClient"],