Bug 1802961 - Convert MigratorPrototype into an ES6 class and move into its own ESM as MigratorBase to be subclassed. r=NeilDeakin
Differential Revision: https://phabricator.services.mozilla.com/D163257
This commit is contained in:
@@ -11,13 +11,9 @@ const AUTH_TYPE = {
|
||||
};
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
|
||||
import {
|
||||
MigratorPrototype,
|
||||
MigrationUtils,
|
||||
} from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
@@ -72,136 +68,297 @@ function convertBookmarks(items, errorAccumulator) {
|
||||
return itemsToInsert;
|
||||
}
|
||||
|
||||
export function ChromeProfileMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Chrome";
|
||||
}
|
||||
/**
|
||||
* Chrome profile migrator. This can also be used as a parent class for
|
||||
* migrators for browsers that are variants of Chrome.
|
||||
*/
|
||||
export class ChromeProfileMigrator extends MigratorBase {
|
||||
get classDescription() {
|
||||
return "Chrome Profile Migrator";
|
||||
}
|
||||
|
||||
ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
|
||||
}
|
||||
|
||||
ChromeProfileMigrator.prototype._keychainServiceName = "Chrome Safe Storage";
|
||||
ChromeProfileMigrator.prototype._keychainAccountName = "Chrome";
|
||||
get classID() {
|
||||
return Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
|
||||
}
|
||||
|
||||
ChromeProfileMigrator.prototype._getChromeUserDataPathIfExists = async function() {
|
||||
if (this._chromeUserDataPath) {
|
||||
get _chromeUserDataPathSuffix() {
|
||||
return "Chrome";
|
||||
}
|
||||
|
||||
_keychainServiceName = "Chrome Safe Storage";
|
||||
|
||||
_keychainAccountName = "Chrome";
|
||||
|
||||
async _getChromeUserDataPathIfExists() {
|
||||
if (this._chromeUserDataPath) {
|
||||
return this._chromeUserDataPath;
|
||||
}
|
||||
let path = lazy.ChromeMigrationUtils.getDataPath(
|
||||
this._chromeUserDataPathSuffix
|
||||
);
|
||||
let exists = await IOUtils.exists(path);
|
||||
if (exists) {
|
||||
this._chromeUserDataPath = path;
|
||||
} else {
|
||||
this._chromeUserDataPath = null;
|
||||
}
|
||||
return this._chromeUserDataPath;
|
||||
}
|
||||
let path = lazy.ChromeMigrationUtils.getDataPath(
|
||||
this._chromeUserDataPathSuffix
|
||||
);
|
||||
let exists = await IOUtils.exists(path);
|
||||
if (exists) {
|
||||
this._chromeUserDataPath = path;
|
||||
} else {
|
||||
this._chromeUserDataPath = null;
|
||||
}
|
||||
return this._chromeUserDataPath;
|
||||
};
|
||||
|
||||
ChromeProfileMigrator.prototype.getResources = async function Chrome_getResources(
|
||||
aProfile
|
||||
) {
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (chromeUserDataPath) {
|
||||
let profileFolder = chromeUserDataPath;
|
||||
if (aProfile) {
|
||||
profileFolder = PathUtils.join(chromeUserDataPath, aProfile.id);
|
||||
}
|
||||
if (await IOUtils.exists(profileFolder)) {
|
||||
let possibleResourcePromises = [
|
||||
GetBookmarksResource(profileFolder, this.getBrowserKey()),
|
||||
GetHistoryResource(profileFolder),
|
||||
GetCookiesResource(profileFolder),
|
||||
];
|
||||
if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
|
||||
possibleResourcePromises.push(
|
||||
this._GetPasswordsResource(profileFolder)
|
||||
);
|
||||
async getResources(aProfile) {
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (chromeUserDataPath) {
|
||||
let profileFolder = chromeUserDataPath;
|
||||
if (aProfile) {
|
||||
profileFolder = PathUtils.join(chromeUserDataPath, aProfile.id);
|
||||
}
|
||||
let possibleResources = await Promise.all(possibleResourcePromises);
|
||||
return possibleResources.filter(r => r != null);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
ChromeProfileMigrator.prototype.getLastUsedDate = async function Chrome_getLastUsedDate() {
|
||||
let sourceProfiles = await this.getSourceProfiles();
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (!chromeUserDataPath) {
|
||||
return new Date(0);
|
||||
}
|
||||
let datePromises = sourceProfiles.map(async profile => {
|
||||
let basePath = PathUtils.join(chromeUserDataPath, profile.id);
|
||||
let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
|
||||
async leafName => {
|
||||
let path = PathUtils.join(basePath, leafName);
|
||||
let info = await IOUtils.stat(path).catch(() => null);
|
||||
return info ? info.lastModificationDate : 0;
|
||||
if (await IOUtils.exists(profileFolder)) {
|
||||
let possibleResourcePromises = [
|
||||
GetBookmarksResource(profileFolder, this.getBrowserKey()),
|
||||
GetHistoryResource(profileFolder),
|
||||
GetCookiesResource(profileFolder),
|
||||
];
|
||||
if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
|
||||
possibleResourcePromises.push(
|
||||
this._GetPasswordsResource(profileFolder)
|
||||
);
|
||||
}
|
||||
let possibleResources = await Promise.all(possibleResourcePromises);
|
||||
return possibleResources.filter(r => r != null);
|
||||
}
|
||||
);
|
||||
let dates = await Promise.all(fileDatePromises);
|
||||
return Math.max(...dates);
|
||||
});
|
||||
let datesOuter = await Promise.all(datePromises);
|
||||
datesOuter.push(0);
|
||||
return new Date(Math.max(...datesOuter));
|
||||
};
|
||||
|
||||
ChromeProfileMigrator.prototype.getSourceProfiles = async function Chrome_getSourceProfiles() {
|
||||
if ("__sourceProfiles" in this) {
|
||||
return this.__sourceProfiles;
|
||||
}
|
||||
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (!chromeUserDataPath) {
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
let localState;
|
||||
let profiles = [];
|
||||
try {
|
||||
localState = await lazy.ChromeMigrationUtils.getLocalState(
|
||||
this._chromeUserDataPathSuffix
|
||||
);
|
||||
let info_cache = localState.profile.info_cache;
|
||||
for (let profileFolderName in info_cache) {
|
||||
profiles.push({
|
||||
id: profileFolderName,
|
||||
name: info_cache[profileFolderName].name || profileFolderName,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Avoid reporting NotFoundErrors from trying to get local state.
|
||||
if (localState || e.name != "NotFoundError") {
|
||||
Cu.reportError("Error detecting Chrome profiles: " + e);
|
||||
}
|
||||
// If we weren't able to detect any profiles above, fallback to the Default profile.
|
||||
let defaultProfilePath = PathUtils.join(chromeUserDataPath, "Default");
|
||||
if (await IOUtils.exists(defaultProfilePath)) {
|
||||
profiles = [
|
||||
{
|
||||
id: "Default",
|
||||
name: "Default",
|
||||
},
|
||||
];
|
||||
async getLastUsedDate() {
|
||||
let sourceProfiles = await this.getSourceProfiles();
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (!chromeUserDataPath) {
|
||||
return new Date(0);
|
||||
}
|
||||
let datePromises = sourceProfiles.map(async profile => {
|
||||
let basePath = PathUtils.join(chromeUserDataPath, profile.id);
|
||||
let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
|
||||
async leafName => {
|
||||
let path = PathUtils.join(basePath, leafName);
|
||||
let info = await IOUtils.stat(path).catch(() => null);
|
||||
return info ? info.lastModificationDate : 0;
|
||||
}
|
||||
);
|
||||
let dates = await Promise.all(fileDatePromises);
|
||||
return Math.max(...dates);
|
||||
});
|
||||
let datesOuter = await Promise.all(datePromises);
|
||||
datesOuter.push(0);
|
||||
return new Date(Math.max(...datesOuter));
|
||||
}
|
||||
|
||||
let profileResources = await Promise.all(
|
||||
profiles.map(async profile => ({
|
||||
profile,
|
||||
resources: await this.getResources(profile),
|
||||
}))
|
||||
);
|
||||
async getSourceProfiles() {
|
||||
if ("__sourceProfiles" in this) {
|
||||
return this.__sourceProfiles;
|
||||
}
|
||||
|
||||
// Only list profiles from which any data can be imported
|
||||
this.__sourceProfiles = profileResources
|
||||
.filter(({ resources }) => {
|
||||
return resources && !!resources.length;
|
||||
}, this)
|
||||
.map(({ profile }) => profile);
|
||||
return this.__sourceProfiles;
|
||||
};
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (!chromeUserDataPath) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let localState;
|
||||
let profiles = [];
|
||||
try {
|
||||
localState = await lazy.ChromeMigrationUtils.getLocalState(
|
||||
this._chromeUserDataPathSuffix
|
||||
);
|
||||
let info_cache = localState.profile.info_cache;
|
||||
for (let profileFolderName in info_cache) {
|
||||
profiles.push({
|
||||
id: profileFolderName,
|
||||
name: info_cache[profileFolderName].name || profileFolderName,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Avoid reporting NotFoundErrors from trying to get local state.
|
||||
if (localState || e.name != "NotFoundError") {
|
||||
Cu.reportError("Error detecting Chrome profiles: " + e);
|
||||
}
|
||||
// If we weren't able to detect any profiles above, fallback to the Default profile.
|
||||
let defaultProfilePath = PathUtils.join(chromeUserDataPath, "Default");
|
||||
if (await IOUtils.exists(defaultProfilePath)) {
|
||||
profiles = [
|
||||
{
|
||||
id: "Default",
|
||||
name: "Default",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
let profileResources = await Promise.all(
|
||||
profiles.map(async profile => ({
|
||||
profile,
|
||||
resources: await this.getResources(profile),
|
||||
}))
|
||||
);
|
||||
|
||||
// Only list profiles from which any data can be imported
|
||||
this.__sourceProfiles = profileResources
|
||||
.filter(({ resources }) => {
|
||||
return resources && !!resources.length;
|
||||
}, this)
|
||||
.map(({ profile }) => profile);
|
||||
return this.__sourceProfiles;
|
||||
}
|
||||
|
||||
async _GetPasswordsResource(aProfileFolder) {
|
||||
let loginPath = PathUtils.join(aProfileFolder, "Login Data");
|
||||
if (!(await IOUtils.exists(loginPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let {
|
||||
_chromeUserDataPathSuffix,
|
||||
_keychainServiceName,
|
||||
_keychainAccountName,
|
||||
_keychainMockPassphrase = null,
|
||||
} = this;
|
||||
|
||||
return {
|
||||
type: MigrationUtils.resourceTypes.PASSWORDS,
|
||||
|
||||
async migrate(aCallback) {
|
||||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
|
||||
loginPath,
|
||||
"Chrome passwords",
|
||||
`SELECT origin_url, action_url, username_element, username_value,
|
||||
password_element, password_value, signon_realm, scheme, date_created,
|
||||
times_used FROM logins WHERE blacklisted_by_user = 0`
|
||||
).catch(ex => {
|
||||
Cu.reportError(ex);
|
||||
aCallback(false);
|
||||
});
|
||||
// If the promise was rejected we will have already called aCallback,
|
||||
// so we can just return here.
|
||||
if (!rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are no relevant rows, return before initializing crypto and
|
||||
// thus prompting for Keychain access on macOS.
|
||||
if (!rows.length) {
|
||||
aCallback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let crypto;
|
||||
try {
|
||||
if (AppConstants.platform == "win") {
|
||||
let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
|
||||
);
|
||||
crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
|
||||
} else if (AppConstants.platform == "macosx") {
|
||||
let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
|
||||
);
|
||||
crypto = new ChromeMacOSLoginCrypto(
|
||||
_keychainServiceName,
|
||||
_keychainAccountName,
|
||||
_keychainMockPassphrase
|
||||
);
|
||||
} else {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
} catch (ex) {
|
||||
// Handle the user canceling Keychain access or other OSCrypto errors.
|
||||
Cu.reportError(ex);
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let logins = [];
|
||||
let fallbackCreationDate = new Date();
|
||||
for (let row of rows) {
|
||||
try {
|
||||
let origin_url = lazy.NetUtil.newURI(
|
||||
row.getResultByName("origin_url")
|
||||
);
|
||||
// Ignore entries for non-http(s)/ftp URLs because we likely can't
|
||||
// use them anyway.
|
||||
const kValidSchemes = new Set(["https", "http", "ftp"]);
|
||||
if (!kValidSchemes.has(origin_url.scheme)) {
|
||||
continue;
|
||||
}
|
||||
let loginInfo = {
|
||||
username: row.getResultByName("username_value"),
|
||||
password: await crypto.decryptData(
|
||||
row.getResultByName("password_value"),
|
||||
null
|
||||
),
|
||||
origin: origin_url.prePath,
|
||||
formActionOrigin: null,
|
||||
httpRealm: null,
|
||||
usernameElement: row.getResultByName("username_element"),
|
||||
passwordElement: row.getResultByName("password_element"),
|
||||
timeCreated: lazy.ChromeMigrationUtils.chromeTimeToDate(
|
||||
row.getResultByName("date_created") + 0,
|
||||
fallbackCreationDate
|
||||
).getTime(),
|
||||
timesUsed: row.getResultByName("times_used") + 0,
|
||||
};
|
||||
|
||||
switch (row.getResultByName("scheme")) {
|
||||
case AUTH_TYPE.SCHEME_HTML:
|
||||
let action_url = row.getResultByName("action_url");
|
||||
if (!action_url) {
|
||||
// If there is no action_url, store the wildcard "" value.
|
||||
// See the `formActionOrigin` IDL comments.
|
||||
loginInfo.formActionOrigin = "";
|
||||
break;
|
||||
}
|
||||
let action_uri = lazy.NetUtil.newURI(action_url);
|
||||
if (!kValidSchemes.has(action_uri.scheme)) {
|
||||
continue; // This continues the outer for loop.
|
||||
}
|
||||
loginInfo.formActionOrigin = action_uri.prePath;
|
||||
break;
|
||||
case AUTH_TYPE.SCHEME_BASIC:
|
||||
case AUTH_TYPE.SCHEME_DIGEST:
|
||||
// signon_realm format is URIrealm, so we need remove URI
|
||||
loginInfo.httpRealm = row
|
||||
.getResultByName("signon_realm")
|
||||
.substring(loginInfo.origin.length + 1);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
"Login data scheme type not supported: " +
|
||||
row.getResultByName("scheme")
|
||||
);
|
||||
}
|
||||
logins.push(loginInfo);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (logins.length) {
|
||||
await MigrationUtils.insertLoginsWrapper(logins);
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
if (crypto.finalize) {
|
||||
crypto.finalize();
|
||||
}
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
|
||||
let bookmarksPath = PathUtils.join(aProfileFolder, "Bookmarks");
|
||||
@@ -458,328 +615,250 @@ async function GetCookiesResource(aProfileFolder) {
|
||||
};
|
||||
}
|
||||
|
||||
ChromeProfileMigrator.prototype._GetPasswordsResource = async function(
|
||||
aProfileFolder
|
||||
) {
|
||||
let loginPath = PathUtils.join(aProfileFolder, "Login Data");
|
||||
if (!(await IOUtils.exists(loginPath))) {
|
||||
return null;
|
||||
/**
|
||||
* Chromium migrator
|
||||
*/
|
||||
export class ChromiumProfileMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Chromium Profile Migrator";
|
||||
}
|
||||
|
||||
let {
|
||||
_chromeUserDataPathSuffix,
|
||||
_keychainServiceName,
|
||||
_keychainAccountName,
|
||||
_keychainMockPassphrase = null,
|
||||
} = this;
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
|
||||
}
|
||||
|
||||
return {
|
||||
type: MigrationUtils.resourceTypes.PASSWORDS,
|
||||
get classID() {
|
||||
return Components.ID("{8cece922-9720-42de-b7db-7cef88cb07ca}");
|
||||
}
|
||||
|
||||
async migrate(aCallback) {
|
||||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
|
||||
loginPath,
|
||||
"Chrome passwords",
|
||||
`SELECT origin_url, action_url, username_element, username_value,
|
||||
password_element, password_value, signon_realm, scheme, date_created,
|
||||
times_used FROM logins WHERE blacklisted_by_user = 0`
|
||||
).catch(ex => {
|
||||
Cu.reportError(ex);
|
||||
aCallback(false);
|
||||
});
|
||||
// If the promise was rejected we will have already called aCallback,
|
||||
// so we can just return here.
|
||||
if (!rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are no relevant rows, return before initializing crypto and
|
||||
// thus prompting for Keychain access on macOS.
|
||||
if (!rows.length) {
|
||||
aCallback(true);
|
||||
return;
|
||||
}
|
||||
|
||||
let crypto;
|
||||
try {
|
||||
if (AppConstants.platform == "win") {
|
||||
let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
|
||||
);
|
||||
crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
|
||||
} else if (AppConstants.platform == "macosx") {
|
||||
let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
|
||||
);
|
||||
crypto = new ChromeMacOSLoginCrypto(
|
||||
_keychainServiceName,
|
||||
_keychainAccountName,
|
||||
_keychainMockPassphrase
|
||||
);
|
||||
} else {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
} catch (ex) {
|
||||
// Handle the user canceling Keychain access or other OSCrypto errors.
|
||||
Cu.reportError(ex);
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let logins = [];
|
||||
let fallbackCreationDate = new Date();
|
||||
for (let row of rows) {
|
||||
try {
|
||||
let origin_url = lazy.NetUtil.newURI(
|
||||
row.getResultByName("origin_url")
|
||||
);
|
||||
// Ignore entries for non-http(s)/ftp URLs because we likely can't
|
||||
// use them anyway.
|
||||
const kValidSchemes = new Set(["https", "http", "ftp"]);
|
||||
if (!kValidSchemes.has(origin_url.scheme)) {
|
||||
continue;
|
||||
}
|
||||
let loginInfo = {
|
||||
username: row.getResultByName("username_value"),
|
||||
password: await crypto.decryptData(
|
||||
row.getResultByName("password_value"),
|
||||
null
|
||||
),
|
||||
origin: origin_url.prePath,
|
||||
formActionOrigin: null,
|
||||
httpRealm: null,
|
||||
usernameElement: row.getResultByName("username_element"),
|
||||
passwordElement: row.getResultByName("password_element"),
|
||||
timeCreated: lazy.ChromeMigrationUtils.chromeTimeToDate(
|
||||
row.getResultByName("date_created") + 0,
|
||||
fallbackCreationDate
|
||||
).getTime(),
|
||||
timesUsed: row.getResultByName("times_used") + 0,
|
||||
};
|
||||
|
||||
switch (row.getResultByName("scheme")) {
|
||||
case AUTH_TYPE.SCHEME_HTML:
|
||||
let action_url = row.getResultByName("action_url");
|
||||
if (!action_url) {
|
||||
// If there is no action_url, store the wildcard "" value.
|
||||
// See the `formActionOrigin` IDL comments.
|
||||
loginInfo.formActionOrigin = "";
|
||||
break;
|
||||
}
|
||||
let action_uri = lazy.NetUtil.newURI(action_url);
|
||||
if (!kValidSchemes.has(action_uri.scheme)) {
|
||||
continue; // This continues the outer for loop.
|
||||
}
|
||||
loginInfo.formActionOrigin = action_uri.prePath;
|
||||
break;
|
||||
case AUTH_TYPE.SCHEME_BASIC:
|
||||
case AUTH_TYPE.SCHEME_DIGEST:
|
||||
// signon_realm format is URIrealm, so we need remove URI
|
||||
loginInfo.httpRealm = row
|
||||
.getResultByName("signon_realm")
|
||||
.substring(loginInfo.origin.length + 1);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
"Login data scheme type not supported: " +
|
||||
row.getResultByName("scheme")
|
||||
);
|
||||
}
|
||||
logins.push(loginInfo);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (logins.length) {
|
||||
await MigrationUtils.insertLoginsWrapper(logins);
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
if (crypto.finalize) {
|
||||
crypto.finalize();
|
||||
}
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
|
||||
ChromeProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=chrome";
|
||||
ChromeProfileMigrator.prototype.classID = Components.ID(
|
||||
"{4cec1de4-1671-4fc3-a53e-6c539dc77a26}"
|
||||
);
|
||||
|
||||
/**
|
||||
* Chromium migration
|
||||
*/
|
||||
export function ChromiumProfileMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Chromium";
|
||||
this._keychainServiceName = "Chromium Safe Storage";
|
||||
this._keychainAccountName = "Chromium";
|
||||
_chromeUserDataPathSuffix = "Chromium";
|
||||
_keychainServiceName = "Chromium Safe Storage";
|
||||
_keychainAccountName = "Chromium";
|
||||
}
|
||||
|
||||
ChromiumProfileMigrator.prototype = Object.create(
|
||||
ChromeProfileMigrator.prototype
|
||||
);
|
||||
ChromiumProfileMigrator.prototype.classDescription =
|
||||
"Chromium Profile Migrator";
|
||||
ChromiumProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=chromium";
|
||||
ChromiumProfileMigrator.prototype.classID = Components.ID(
|
||||
"{8cece922-9720-42de-b7db-7cef88cb07ca}"
|
||||
);
|
||||
|
||||
/**
|
||||
* Chrome Canary
|
||||
* Not available on Linux
|
||||
*/
|
||||
export function CanaryProfileMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Canary";
|
||||
export class CanaryProfileMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Chrome Canary Profile Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=canary";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{4bf85aa5-4e21-46ca-825f-f9c51a5e8c76}");
|
||||
}
|
||||
|
||||
get _chromeUserDataPathSuffix() {
|
||||
return "Canary";
|
||||
}
|
||||
|
||||
get _keychainServiceName() {
|
||||
return "Chromium Safe Storage";
|
||||
}
|
||||
|
||||
get _keychainAccountName() {
|
||||
return "Chromium";
|
||||
}
|
||||
}
|
||||
CanaryProfileMigrator.prototype = Object.create(
|
||||
ChromeProfileMigrator.prototype
|
||||
);
|
||||
CanaryProfileMigrator.prototype.classDescription =
|
||||
"Chrome Canary Profile Migrator";
|
||||
CanaryProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=canary";
|
||||
CanaryProfileMigrator.prototype.classID = Components.ID(
|
||||
"{4bf85aa5-4e21-46ca-825f-f9c51a5e8c76}"
|
||||
);
|
||||
|
||||
/**
|
||||
* Chrome Dev - Linux only (not available in Mac and Windows)
|
||||
*/
|
||||
export function ChromeDevMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Chrome Dev";
|
||||
}
|
||||
ChromeDevMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
||||
ChromeDevMigrator.prototype.classDescription = "Chrome Dev Profile Migrator";
|
||||
ChromeDevMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=chrome-dev";
|
||||
ChromeDevMigrator.prototype.classID = Components.ID(
|
||||
"{7370a02a-4886-42c3-a4ec-d48c726ec30a}"
|
||||
);
|
||||
export class ChromeDevMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Chrome Dev Profile Migrator";
|
||||
}
|
||||
|
||||
export function ChromeBetaMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Chrome Beta";
|
||||
}
|
||||
ChromeBetaMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
||||
ChromeBetaMigrator.prototype.classDescription = "Chrome Beta Profile Migrator";
|
||||
ChromeBetaMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=chrome-beta";
|
||||
ChromeBetaMigrator.prototype.classID = Components.ID(
|
||||
"{47f75963-840b-4950-a1f0-d9c1864f8b8e}"
|
||||
);
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-dev";
|
||||
}
|
||||
|
||||
export function BraveProfileMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Brave";
|
||||
this._keychainServiceName = "Brave Browser Safe Storage";
|
||||
this._keychainAccountName = "Brave Browser";
|
||||
get classID() {
|
||||
return Components.ID("{7370a02a-4886-42c3-a4ec-d48c726ec30a}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "Chrome Dev";
|
||||
_keychainServiceName = "Chromium Safe Storage";
|
||||
_keychainAccountName = "Chromium";
|
||||
}
|
||||
|
||||
BraveProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
||||
BraveProfileMigrator.prototype.classDescription = "Brave Browser Migrator";
|
||||
BraveProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=brave";
|
||||
BraveProfileMigrator.prototype.classID = Components.ID(
|
||||
"{4071880a-69e4-4c83-88b4-6c589a62801d}"
|
||||
);
|
||||
/**
|
||||
* Chrome Beta migrator
|
||||
*/
|
||||
export class ChromeBetaMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Chrome Beta Profile Migrator";
|
||||
}
|
||||
|
||||
export function ChromiumEdgeMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Edge";
|
||||
this._keychainServiceName = "Microsoft Edge Safe Storage";
|
||||
this._keychainAccountName = "Microsoft Edge";
|
||||
}
|
||||
ChromiumEdgeMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
||||
ChromiumEdgeMigrator.prototype.classDescription =
|
||||
"Chromium Edge Profile Migrator";
|
||||
ChromiumEdgeMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge";
|
||||
ChromiumEdgeMigrator.prototype.classID = Components.ID(
|
||||
"{3c7f6b7c-baa9-4338-acfa-04bf79f1dcf1}"
|
||||
);
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-beta";
|
||||
}
|
||||
|
||||
export function ChromiumEdgeBetaMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Edge Beta";
|
||||
this._keychainServiceName = "Microsoft Edge Safe Storage";
|
||||
this._keychainAccountName = "Microsoft Edge";
|
||||
}
|
||||
ChromiumEdgeBetaMigrator.prototype = Object.create(
|
||||
ChromiumEdgeMigrator.prototype
|
||||
);
|
||||
ChromiumEdgeBetaMigrator.prototype.classDescription =
|
||||
"Chromium Edge Beta Profile Migrator";
|
||||
ChromiumEdgeBetaMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge-beta";
|
||||
ChromiumEdgeBetaMigrator.prototype.classID = Components.ID(
|
||||
"{0fc3d48a-c1c3-4871-b58f-a8b47d1555fb}"
|
||||
);
|
||||
get classID() {
|
||||
return Components.ID("{47f75963-840b-4950-a1f0-d9c1864f8b8e}");
|
||||
}
|
||||
|
||||
export function Chromium360seMigrator() {
|
||||
this._chromeUserDataPathSuffix = "360 SE";
|
||||
}
|
||||
Chromium360seMigrator.prototype = Object.create(
|
||||
ChromeProfileMigrator.prototype
|
||||
);
|
||||
Chromium360seMigrator.prototype.classDescription =
|
||||
"Chromium 360 Secure Browser Profile Migrator";
|
||||
Chromium360seMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=chromium-360se";
|
||||
Chromium360seMigrator.prototype.classID = Components.ID(
|
||||
"{2e1a182e-ce4f-4dc9-a22c-d4125b931552}"
|
||||
);
|
||||
|
||||
export function OperaProfileMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Opera";
|
||||
this._keychainServiceName = "Opera Browser Safe Storage";
|
||||
this._keychainAccountName = "Opera Browser";
|
||||
}
|
||||
OperaProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
||||
OperaProfileMigrator.prototype.classDescription = "Opera Browser Migrator";
|
||||
OperaProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=opera";
|
||||
OperaProfileMigrator.prototype.classID = Components.ID(
|
||||
"{16c5d501-e411-41eb-93f2-af6c9ba64dee}"
|
||||
);
|
||||
OperaProfileMigrator.prototype.getSourceProfiles = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
export function OperaGXProfileMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Opera GX";
|
||||
this._keychainServiceName = "Opera Browser Safe Storage";
|
||||
this._keychainAccountName = "Opera Browser";
|
||||
}
|
||||
OperaGXProfileMigrator.prototype = Object.create(
|
||||
ChromeProfileMigrator.prototype
|
||||
);
|
||||
OperaGXProfileMigrator.prototype.classDescription = "Opera GX Browser Migrator";
|
||||
OperaGXProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=opera-gx";
|
||||
OperaGXProfileMigrator.prototype.classID = Components.ID(
|
||||
"{26F4E0A0-B533-4FDA-B344-6FF5DA45D6DC}"
|
||||
);
|
||||
OperaGXProfileMigrator.prototype.getSourceProfiles = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
export function VivaldiProfileMigrator() {
|
||||
this._chromeUserDataPathSuffix = "Vivaldi";
|
||||
this._keychainServiceName = "Vivaldi Safe Storage";
|
||||
this._keychainAccountName = "Vivaldi";
|
||||
_chromeUserDataPathSuffix = "Chrome Beta";
|
||||
_keychainServiceName = "Chromium Safe Storage";
|
||||
_keychainAccountName = "Chromium";
|
||||
}
|
||||
|
||||
VivaldiProfileMigrator.prototype = Object.create(
|
||||
ChromeProfileMigrator.prototype
|
||||
);
|
||||
VivaldiProfileMigrator.prototype.classDescription = "Vivaldi Migrator";
|
||||
VivaldiProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=vivaldi";
|
||||
VivaldiProfileMigrator.prototype.classID = Components.ID(
|
||||
"{54a6a025-e70d-49dd-ba95-0f7e45d728d3}"
|
||||
);
|
||||
/**
|
||||
* Brave migrator
|
||||
*/
|
||||
export class BraveProfileMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Brave Browser Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=brave";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{4071880a-69e4-4c83-88b4-6c589a62801d}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "Brave";
|
||||
_keychainServiceName = "Brave Browser Safe Storage";
|
||||
_keychainAccountName = "Brave Browser";
|
||||
}
|
||||
|
||||
/**
|
||||
* Edge (Chromium-based) migrator
|
||||
*/
|
||||
export class ChromiumEdgeMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Chromium Edge Profile Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{3c7f6b7c-baa9-4338-acfa-04bf79f1dcf1}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "Edge";
|
||||
_keychainServiceName = "Microsoft Edge Safe Storage";
|
||||
_keychainAccountName = "Microsoft Edge";
|
||||
}
|
||||
|
||||
/**
|
||||
* Edge Beta (Chromium-based) migrator
|
||||
*/
|
||||
export class ChromiumEdgeBetaMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Chromium Edge Beta Profile Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge-beta";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{0fc3d48a-c1c3-4871-b58f-a8b47d1555fb}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "Edge Beta";
|
||||
_keychainServiceName = "Microsoft Edge Safe Storage";
|
||||
_keychainAccountName = "Microsoft Edge";
|
||||
}
|
||||
|
||||
/**
|
||||
* Chromium 360 migrator
|
||||
*/
|
||||
export class Chromium360seMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Chromium 360 Secure Browser Profile Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-360se";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{2e1a182e-ce4f-4dc9-a22c-d4125b931552}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "360 SE";
|
||||
_keychainServiceName = "Microsoft Edge Safe Storage";
|
||||
_keychainAccountName = "Microsoft Edge";
|
||||
}
|
||||
|
||||
/**
|
||||
* Opera migrator
|
||||
*/
|
||||
export class OperaProfileMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Opera Browser Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=opera";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{16c5d501-e411-41eb-93f2-af6c9ba64dee}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "Opera";
|
||||
_keychainServiceName = "Opera Browser Safe Storage";
|
||||
_keychainAccountName = "Opera Browser";
|
||||
|
||||
getSourceProfiles() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opera GX migrator
|
||||
*/
|
||||
export class OperaGXProfileMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Opera GX Browser Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=opera-gx";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{26F4E0A0-B533-4FDA-B344-6FF5DA45D6DC}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "Opera GX";
|
||||
_keychainServiceName = "Opera Browser Safe Storage";
|
||||
_keychainAccountName = "Opera Browser";
|
||||
|
||||
getSourceProfiles() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vivaldi migrator
|
||||
*/
|
||||
export class VivaldiProfileMigrator extends ChromeProfileMigrator {
|
||||
get classDescription() {
|
||||
return "Vivaldi Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=vivaldi";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{54a6a025-e70d-49dd-ba95-0f7e45d728d3}");
|
||||
}
|
||||
|
||||
_chromeUserDataPathSuffix = "Vivaldi";
|
||||
_keychainServiceName = "Vivaldi Safe Storage";
|
||||
_keychainAccountName = "Vivaldi";
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
import {
|
||||
MigrationUtils,
|
||||
MigratorPrototype,
|
||||
} from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
|
||||
import { MSMigrationUtils } from "resource:///modules/MSMigrationUtils.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
@@ -466,89 +464,97 @@ EdgeBookmarksMigrator.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
export function EdgeProfileMigrator() {
|
||||
this.wrappedJSObject = this;
|
||||
}
|
||||
|
||||
EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
|
||||
EdgeProfileMigrator.prototype.getBookmarksMigratorForTesting = function(
|
||||
dbOverride
|
||||
) {
|
||||
return new EdgeBookmarksMigrator(dbOverride);
|
||||
};
|
||||
|
||||
EdgeProfileMigrator.prototype.getReadingListMigratorForTesting = function(
|
||||
dbOverride
|
||||
) {
|
||||
return new EdgeReadingListMigrator(dbOverride);
|
||||
};
|
||||
|
||||
EdgeProfileMigrator.prototype.getResources = function() {
|
||||
let resources = [
|
||||
new EdgeBookmarksMigrator(),
|
||||
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
|
||||
new EdgeTypedURLMigrator(),
|
||||
new EdgeTypedURLDBMigrator(),
|
||||
new EdgeReadingListMigrator(),
|
||||
];
|
||||
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
||||
windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
|
||||
resources.push(windowsVaultFormPasswordsMigrator);
|
||||
return resources.filter(r => r.exists);
|
||||
};
|
||||
|
||||
EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
|
||||
// Don't do this if we don't have a single profile (see the comment for
|
||||
// sourceProfiles) or if we can't find the database file:
|
||||
let sourceProfiles = await this.getSourceProfiles();
|
||||
if (sourceProfiles !== null || !lazy.gEdgeDatabase) {
|
||||
return Promise.resolve(new Date(0));
|
||||
}
|
||||
let logFilePath = PathUtils.join(
|
||||
lazy.gEdgeDatabase.parent.path,
|
||||
"LogFiles",
|
||||
"edb.log"
|
||||
);
|
||||
let dbPath = lazy.gEdgeDatabase.path;
|
||||
let cookieMigrator = MSMigrationUtils.getCookiesMigrator(
|
||||
MSMigrationUtils.MIGRATION_TYPE_EDGE
|
||||
);
|
||||
let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
|
||||
let datePromises = [logFilePath, dbPath, ...cookiePaths].map(path => {
|
||||
return IOUtils.stat(path)
|
||||
.then(info => info.lastModified)
|
||||
.catch(() => 0);
|
||||
});
|
||||
datePromises.push(
|
||||
new Promise(resolve => {
|
||||
let typedURLs = new Map();
|
||||
try {
|
||||
typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
|
||||
} catch (ex) {}
|
||||
let times = [0, ...typedURLs.values()];
|
||||
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
||||
resolve(Math.max.apply(Math, times) / 1000);
|
||||
})
|
||||
);
|
||||
return Promise.all(datePromises).then(dates => {
|
||||
return new Date(Math.max.apply(Math, dates));
|
||||
});
|
||||
};
|
||||
|
||||
/* Somewhat counterintuitively, this returns:
|
||||
* - |null| to indicate "There is only 1 (default) profile" (on win10+)
|
||||
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
|
||||
* See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
|
||||
/**
|
||||
* Edge (EdgeHTML) profile migrator
|
||||
*/
|
||||
EdgeProfileMigrator.prototype.getSourceProfiles = function() {
|
||||
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
|
||||
return isWin10OrHigher ? null : [];
|
||||
};
|
||||
export class EdgeProfileMigrator extends MigratorBase {
|
||||
constructor() {
|
||||
super();
|
||||
this.wrappedJSObject = this;
|
||||
}
|
||||
|
||||
EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
|
||||
EdgeProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=edge";
|
||||
EdgeProfileMigrator.prototype.classID = Components.ID(
|
||||
"{62e8834b-2d17-49f5-96ff-56344903a2ae}"
|
||||
);
|
||||
get classDescription() {
|
||||
return "Edge Profile Migrator";
|
||||
}
|
||||
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=edge";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
|
||||
}
|
||||
|
||||
getBookmarksMigratorForTesting(dbOverride) {
|
||||
return new EdgeBookmarksMigrator(dbOverride);
|
||||
}
|
||||
|
||||
getReadingListMigratorForTesting(dbOverride) {
|
||||
return new EdgeReadingListMigrator(dbOverride);
|
||||
}
|
||||
|
||||
getResources() {
|
||||
let resources = [
|
||||
new EdgeBookmarksMigrator(),
|
||||
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
|
||||
new EdgeTypedURLMigrator(),
|
||||
new EdgeTypedURLDBMigrator(),
|
||||
new EdgeReadingListMigrator(),
|
||||
];
|
||||
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
||||
windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
|
||||
resources.push(windowsVaultFormPasswordsMigrator);
|
||||
return resources.filter(r => r.exists);
|
||||
}
|
||||
|
||||
async getLastUsedDate() {
|
||||
// Don't do this if we don't have a single profile (see the comment for
|
||||
// sourceProfiles) or if we can't find the database file:
|
||||
let sourceProfiles = await this.getSourceProfiles();
|
||||
if (sourceProfiles !== null || !lazy.gEdgeDatabase) {
|
||||
return Promise.resolve(new Date(0));
|
||||
}
|
||||
let logFilePath = PathUtils.join(
|
||||
lazy.gEdgeDatabase.parent.path,
|
||||
"LogFiles",
|
||||
"edb.log"
|
||||
);
|
||||
let dbPath = lazy.gEdgeDatabase.path;
|
||||
let cookieMigrator = MSMigrationUtils.getCookiesMigrator(
|
||||
MSMigrationUtils.MIGRATION_TYPE_EDGE
|
||||
);
|
||||
let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
|
||||
let datePromises = [logFilePath, dbPath, ...cookiePaths].map(path => {
|
||||
return IOUtils.stat(path)
|
||||
.then(info => info.lastModified)
|
||||
.catch(() => 0);
|
||||
});
|
||||
datePromises.push(
|
||||
new Promise(resolve => {
|
||||
let typedURLs = new Map();
|
||||
try {
|
||||
typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
|
||||
} catch (ex) {}
|
||||
let times = [0, ...typedURLs.values()];
|
||||
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
||||
resolve(Math.max.apply(Math, times) / 1000);
|
||||
})
|
||||
);
|
||||
return Promise.all(datePromises).then(dates => {
|
||||
return new Date(Math.max.apply(Math, dates));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Array|null}
|
||||
* Somewhat counterintuitively, this returns:
|
||||
* - |null| to indicate "There is only 1 (default) profile" (on win10+)
|
||||
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid
|
||||
* using this migrator.
|
||||
* See MigrationUtils.sys.mjs for slightly more info on how sourceProfiles is used.
|
||||
*/
|
||||
getSourceProfiles() {
|
||||
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
|
||||
return isWin10OrHigher ? null : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
* from the source profile.
|
||||
*/
|
||||
|
||||
import {
|
||||
MigrationUtils,
|
||||
MigratorPrototype,
|
||||
} from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
|
||||
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
@@ -25,341 +24,352 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
SessionMigration: "resource:///modules/sessionstore/SessionMigration.sys.mjs",
|
||||
});
|
||||
|
||||
export function FirefoxProfileMigrator() {
|
||||
this.wrappedJSObject = this; // for testing...
|
||||
}
|
||||
|
||||
FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
|
||||
FirefoxProfileMigrator.prototype._getAllProfiles = function() {
|
||||
let allProfiles = new Map();
|
||||
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
||||
Ci.nsIToolkitProfileService
|
||||
);
|
||||
for (let profile of profileService.profiles) {
|
||||
let rootDir = profile.rootDir;
|
||||
|
||||
if (
|
||||
rootDir.exists() &&
|
||||
rootDir.isReadable() &&
|
||||
!rootDir.equals(MigrationUtils.profileStartup.directory)
|
||||
) {
|
||||
allProfiles.set(profile.name, rootDir);
|
||||
}
|
||||
}
|
||||
return allProfiles;
|
||||
};
|
||||
|
||||
function sorter(a, b) {
|
||||
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
FirefoxProfileMigrator.prototype.getSourceProfiles = function() {
|
||||
return [...this._getAllProfiles().keys()]
|
||||
.map(x => ({ id: x, name: x }))
|
||||
.sort(sorter);
|
||||
};
|
||||
|
||||
FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) {
|
||||
let file = dir.clone();
|
||||
file.append(fileName);
|
||||
|
||||
// File resources are monolithic. We don't make partial copies since
|
||||
// they are not expected to work alone. Return null to avoid trying to
|
||||
// copy non-existing files.
|
||||
return file.exists() ? file : null;
|
||||
};
|
||||
|
||||
FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
|
||||
let sourceProfileDir = aProfile
|
||||
? this._getAllProfiles().get(aProfile.id)
|
||||
: Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
||||
Ci.nsIToolkitProfileService
|
||||
).defaultProfile.rootDir;
|
||||
if (
|
||||
!sourceProfileDir ||
|
||||
!sourceProfileDir.exists() ||
|
||||
!sourceProfileDir.isReadable()
|
||||
) {
|
||||
return null;
|
||||
/**
|
||||
* Firefox profile migrator. Currently, this class only does "pave over"
|
||||
* migrations, where various parts of an old profile overwrite a new
|
||||
* profile. This is distinct from other migrators which attempt to import
|
||||
* old profile data into the existing profile.
|
||||
*
|
||||
* This migrator is what powers the "Profile Refresh" mechanism.
|
||||
*/
|
||||
export class FirefoxProfileMigrator extends MigratorBase {
|
||||
constructor() {
|
||||
super();
|
||||
this.wrappedJSObject = this; // for testing...
|
||||
}
|
||||
|
||||
// Being a startup-only migrator, we can rely on
|
||||
// MigrationUtils.profileStartup being set.
|
||||
let currentProfileDir = MigrationUtils.profileStartup.directory;
|
||||
|
||||
// Surely data cannot be imported from the current profile.
|
||||
if (sourceProfileDir.equals(currentProfileDir)) {
|
||||
return null;
|
||||
get classDescription() {
|
||||
return "Firefox Profile Migrator";
|
||||
}
|
||||
|
||||
return this._getResourcesInternal(sourceProfileDir, currentProfileDir);
|
||||
};
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=firefox";
|
||||
}
|
||||
|
||||
FirefoxProfileMigrator.prototype.getLastUsedDate = function() {
|
||||
// We always pretend we're really old, so that we don't mess
|
||||
// up the determination of which browser is the most 'recent'
|
||||
// to import from.
|
||||
return Promise.resolve(new Date(0));
|
||||
};
|
||||
get classID() {
|
||||
return Components.ID("{91185366-ba97-4438-acba-48deaca63386}");
|
||||
}
|
||||
|
||||
FirefoxProfileMigrator.prototype._getResourcesInternal = function(
|
||||
sourceProfileDir,
|
||||
currentProfileDir
|
||||
) {
|
||||
let getFileResource = (aMigrationType, aFileNames) => {
|
||||
let files = [];
|
||||
for (let fileName of aFileNames) {
|
||||
let file = this._getFileObject(sourceProfileDir, fileName);
|
||||
if (file) {
|
||||
files.push(file);
|
||||
_getAllProfiles() {
|
||||
let allProfiles = new Map();
|
||||
let profileService = Cc[
|
||||
"@mozilla.org/toolkit/profile-service;1"
|
||||
].getService(Ci.nsIToolkitProfileService);
|
||||
for (let profile of profileService.profiles) {
|
||||
let rootDir = profile.rootDir;
|
||||
|
||||
if (
|
||||
rootDir.exists() &&
|
||||
rootDir.isReadable() &&
|
||||
!rootDir.equals(MigrationUtils.profileStartup.directory)
|
||||
) {
|
||||
allProfiles.set(profile.name, rootDir);
|
||||
}
|
||||
}
|
||||
if (!files.length) {
|
||||
return allProfiles;
|
||||
}
|
||||
|
||||
getSourceProfiles() {
|
||||
let sorter = (a, b) => {
|
||||
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
|
||||
};
|
||||
|
||||
return [...this._getAllProfiles().keys()]
|
||||
.map(x => ({ id: x, name: x }))
|
||||
.sort(sorter);
|
||||
}
|
||||
|
||||
_getFileObject(dir, fileName) {
|
||||
let file = dir.clone();
|
||||
file.append(fileName);
|
||||
|
||||
// File resources are monolithic. We don't make partial copies since
|
||||
// they are not expected to work alone. Return null to avoid trying to
|
||||
// copy non-existing files.
|
||||
return file.exists() ? file : null;
|
||||
}
|
||||
|
||||
getResources(aProfile) {
|
||||
let sourceProfileDir = aProfile
|
||||
? this._getAllProfiles().get(aProfile.id)
|
||||
: Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
||||
Ci.nsIToolkitProfileService
|
||||
).defaultProfile.rootDir;
|
||||
if (
|
||||
!sourceProfileDir ||
|
||||
!sourceProfileDir.exists() ||
|
||||
!sourceProfileDir.isReadable()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: aMigrationType,
|
||||
migrate(aCallback) {
|
||||
for (let file of files) {
|
||||
file.copyTo(currentProfileDir, "");
|
||||
|
||||
// Being a startup-only migrator, we can rely on
|
||||
// MigrationUtils.profileStartup being set.
|
||||
let currentProfileDir = MigrationUtils.profileStartup.directory;
|
||||
|
||||
// Surely data cannot be imported from the current profile.
|
||||
if (sourceProfileDir.equals(currentProfileDir)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._getResourcesInternal(sourceProfileDir, currentProfileDir);
|
||||
}
|
||||
|
||||
getLastUsedDate() {
|
||||
// We always pretend we're really old, so that we don't mess
|
||||
// up the determination of which browser is the most 'recent'
|
||||
// to import from.
|
||||
return Promise.resolve(new Date(0));
|
||||
}
|
||||
|
||||
_getResourcesInternal(sourceProfileDir, currentProfileDir) {
|
||||
let getFileResource = (aMigrationType, aFileNames) => {
|
||||
let files = [];
|
||||
for (let fileName of aFileNames) {
|
||||
let file = this._getFileObject(sourceProfileDir, fileName);
|
||||
if (file) {
|
||||
files.push(file);
|
||||
}
|
||||
}
|
||||
if (!files.length) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: aMigrationType,
|
||||
migrate(aCallback) {
|
||||
for (let file of files) {
|
||||
file.copyTo(currentProfileDir, "");
|
||||
}
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
function savePrefs() {
|
||||
// If we've used the pref service to write prefs for the new profile, it's too
|
||||
// early in startup for the service to have a profile directory, so we have to
|
||||
// manually tell it where to save the prefs file.
|
||||
let newPrefsFile = currentProfileDir.clone();
|
||||
newPrefsFile.append("prefs.js");
|
||||
Services.prefs.savePrefFile(newPrefsFile);
|
||||
}
|
||||
|
||||
let types = MigrationUtils.resourceTypes;
|
||||
let places = getFileResource(types.HISTORY, [
|
||||
"places.sqlite",
|
||||
"places.sqlite-wal",
|
||||
]);
|
||||
let favicons = getFileResource(types.HISTORY, [
|
||||
"favicons.sqlite",
|
||||
"favicons.sqlite-wal",
|
||||
]);
|
||||
let cookies = getFileResource(types.COOKIES, [
|
||||
"cookies.sqlite",
|
||||
"cookies.sqlite-wal",
|
||||
]);
|
||||
let passwords = getFileResource(types.PASSWORDS, [
|
||||
"signons.sqlite",
|
||||
"logins.json",
|
||||
"key3.db",
|
||||
"key4.db",
|
||||
]);
|
||||
let formData = getFileResource(types.FORMDATA, [
|
||||
"formhistory.sqlite",
|
||||
"autofill-profiles.json",
|
||||
]);
|
||||
let bookmarksBackups = getFileResource(types.OTHERDATA, [
|
||||
lazy.PlacesBackups.profileRelativeFolderPath,
|
||||
]);
|
||||
let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
|
||||
|
||||
let session;
|
||||
if (Services.env.get("MOZ_RESET_PROFILE_MIGRATE_SESSION")) {
|
||||
// We only want to restore the previous firefox session if the profile refresh was
|
||||
// triggered by user. The MOZ_RESET_PROFILE_MIGRATE_SESSION would be set when a user-triggered
|
||||
// profile refresh happened in nsAppRunner.cpp. Hence, we detect the MOZ_RESET_PROFILE_MIGRATE_SESSION
|
||||
// to see if session data migration is required.
|
||||
Services.env.set("MOZ_RESET_PROFILE_MIGRATE_SESSION", "");
|
||||
let sessionCheckpoints = this._getFileObject(
|
||||
sourceProfileDir,
|
||||
"sessionCheckpoints.json"
|
||||
);
|
||||
let sessionFile = this._getFileObject(
|
||||
sourceProfileDir,
|
||||
"sessionstore.jsonlz4"
|
||||
);
|
||||
if (sessionFile) {
|
||||
session = {
|
||||
type: types.SESSION,
|
||||
migrate(aCallback) {
|
||||
sessionCheckpoints.copyTo(
|
||||
currentProfileDir,
|
||||
"sessionCheckpoints.json"
|
||||
);
|
||||
let newSessionFile = currentProfileDir.clone();
|
||||
newSessionFile.append("sessionstore.jsonlz4");
|
||||
let migrationPromise = lazy.SessionMigration.migrate(
|
||||
sessionFile.path,
|
||||
newSessionFile.path
|
||||
);
|
||||
migrationPromise.then(
|
||||
function() {
|
||||
let buildID = Services.appinfo.platformBuildID;
|
||||
let mstone = Services.appinfo.platformVersion;
|
||||
// Force the browser to one-off resume the session that we give it:
|
||||
Services.prefs.setBoolPref(
|
||||
"browser.sessionstore.resume_session_once",
|
||||
true
|
||||
);
|
||||
// Reset the homepage_override prefs so that the browser doesn't override our
|
||||
// session with the "what's new" page:
|
||||
Services.prefs.setCharPref(
|
||||
"browser.startup.homepage_override.mstone",
|
||||
mstone
|
||||
);
|
||||
Services.prefs.setCharPref(
|
||||
"browser.startup.homepage_override.buildID",
|
||||
buildID
|
||||
);
|
||||
savePrefs();
|
||||
aCallback(true);
|
||||
},
|
||||
function() {
|
||||
aCallback(false);
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Sync/FxA related data
|
||||
let sync = {
|
||||
name: "sync", // name is used only by tests.
|
||||
type: types.OTHERDATA,
|
||||
migrate: async aCallback => {
|
||||
// Try and parse a signedInUser.json file from the source directory and
|
||||
// if we can, copy it to the new profile and set sync's username pref
|
||||
// (which acts as a de-facto flag to indicate if sync is configured)
|
||||
try {
|
||||
let oldPath = PathUtils.join(
|
||||
sourceProfileDir.path,
|
||||
"signedInUser.json"
|
||||
);
|
||||
let exists = await IOUtils.exists(oldPath);
|
||||
if (exists) {
|
||||
let data = await IOUtils.readJSON(oldPath);
|
||||
if (data && data.accountData && data.accountData.email) {
|
||||
let username = data.accountData.email;
|
||||
// copy the file itself.
|
||||
await IOUtils.copy(
|
||||
oldPath,
|
||||
PathUtils.join(currentProfileDir.path, "signedInUser.json")
|
||||
);
|
||||
// Now we need to know whether Sync is actually configured for this
|
||||
// user. The only way we know is by looking at the prefs file from
|
||||
// the old profile. We avoid trying to do a full parse of the prefs
|
||||
// file and even avoid parsing the single string value we care
|
||||
// about.
|
||||
let prefsPath = PathUtils.join(sourceProfileDir.path, "prefs.js");
|
||||
if (await IOUtils.exists(oldPath)) {
|
||||
let rawPrefs = await IOUtils.readUTF8(prefsPath, {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
if (/^user_pref\("services\.sync\.username"/m.test(rawPrefs)) {
|
||||
// sync's configured in the source profile - ensure it is in the
|
||||
// new profile too.
|
||||
// Write it to prefs.js and flush the file.
|
||||
Services.prefs.setStringPref(
|
||||
"services.sync.username",
|
||||
username
|
||||
);
|
||||
savePrefs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
function savePrefs() {
|
||||
// If we've used the pref service to write prefs for the new profile, it's too
|
||||
// early in startup for the service to have a profile directory, so we have to
|
||||
// manually tell it where to save the prefs file.
|
||||
let newPrefsFile = currentProfileDir.clone();
|
||||
newPrefsFile.append("prefs.js");
|
||||
Services.prefs.savePrefFile(newPrefsFile);
|
||||
}
|
||||
// Telemetry related migrations.
|
||||
let times = {
|
||||
name: "times", // name is used only by tests.
|
||||
type: types.OTHERDATA,
|
||||
migrate: aCallback => {
|
||||
let file = this._getFileObject(sourceProfileDir, "times.json");
|
||||
if (file) {
|
||||
file.copyTo(currentProfileDir, "");
|
||||
}
|
||||
// And record the fact a migration (ie, a reset) happened.
|
||||
let recordMigration = async () => {
|
||||
try {
|
||||
let profileTimes = await lazy.ProfileAge(currentProfileDir.path);
|
||||
await profileTimes.recordProfileReset();
|
||||
aCallback(true);
|
||||
} catch (e) {
|
||||
aCallback(false);
|
||||
}
|
||||
};
|
||||
|
||||
let types = MigrationUtils.resourceTypes;
|
||||
let places = getFileResource(types.HISTORY, [
|
||||
"places.sqlite",
|
||||
"places.sqlite-wal",
|
||||
]);
|
||||
let favicons = getFileResource(types.HISTORY, [
|
||||
"favicons.sqlite",
|
||||
"favicons.sqlite-wal",
|
||||
]);
|
||||
let cookies = getFileResource(types.COOKIES, [
|
||||
"cookies.sqlite",
|
||||
"cookies.sqlite-wal",
|
||||
]);
|
||||
let passwords = getFileResource(types.PASSWORDS, [
|
||||
"signons.sqlite",
|
||||
"logins.json",
|
||||
"key3.db",
|
||||
"key4.db",
|
||||
]);
|
||||
let formData = getFileResource(types.FORMDATA, [
|
||||
"formhistory.sqlite",
|
||||
"autofill-profiles.json",
|
||||
]);
|
||||
let bookmarksBackups = getFileResource(types.OTHERDATA, [
|
||||
lazy.PlacesBackups.profileRelativeFolderPath,
|
||||
]);
|
||||
let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
|
||||
recordMigration();
|
||||
},
|
||||
};
|
||||
let telemetry = {
|
||||
name: "telemetry", // name is used only by tests...
|
||||
type: types.OTHERDATA,
|
||||
migrate: aCallback => {
|
||||
let createSubDir = name => {
|
||||
let dir = currentProfileDir.clone();
|
||||
dir.append(name);
|
||||
dir.create(Ci.nsIFile.DIRECTORY_TYPE, lazy.FileUtils.PERMS_DIRECTORY);
|
||||
return dir;
|
||||
};
|
||||
|
||||
let session;
|
||||
if (Services.env.get("MOZ_RESET_PROFILE_MIGRATE_SESSION")) {
|
||||
// We only want to restore the previous firefox session if the profile refresh was
|
||||
// triggered by user. The MOZ_RESET_PROFILE_MIGRATE_SESSION would be set when a user-triggered
|
||||
// profile refresh happened in nsAppRunner.cpp. Hence, we detect the MOZ_RESET_PROFILE_MIGRATE_SESSION
|
||||
// to see if session data migration is required.
|
||||
Services.env.set("MOZ_RESET_PROFILE_MIGRATE_SESSION", "");
|
||||
let sessionCheckpoints = this._getFileObject(
|
||||
sourceProfileDir,
|
||||
"sessionCheckpoints.json"
|
||||
);
|
||||
let sessionFile = this._getFileObject(
|
||||
sourceProfileDir,
|
||||
"sessionstore.jsonlz4"
|
||||
);
|
||||
if (sessionFile) {
|
||||
session = {
|
||||
type: types.SESSION,
|
||||
migrate(aCallback) {
|
||||
sessionCheckpoints.copyTo(
|
||||
currentProfileDir,
|
||||
"sessionCheckpoints.json"
|
||||
);
|
||||
let newSessionFile = currentProfileDir.clone();
|
||||
newSessionFile.append("sessionstore.jsonlz4");
|
||||
let migrationPromise = lazy.SessionMigration.migrate(
|
||||
sessionFile.path,
|
||||
newSessionFile.path
|
||||
);
|
||||
migrationPromise.then(
|
||||
function() {
|
||||
let buildID = Services.appinfo.platformBuildID;
|
||||
let mstone = Services.appinfo.platformVersion;
|
||||
// Force the browser to one-off resume the session that we give it:
|
||||
Services.prefs.setBoolPref(
|
||||
"browser.sessionstore.resume_session_once",
|
||||
true
|
||||
);
|
||||
// Reset the homepage_override prefs so that the browser doesn't override our
|
||||
// session with the "what's new" page:
|
||||
Services.prefs.setCharPref(
|
||||
"browser.startup.homepage_override.mstone",
|
||||
mstone
|
||||
);
|
||||
Services.prefs.setCharPref(
|
||||
"browser.startup.homepage_override.buildID",
|
||||
buildID
|
||||
);
|
||||
savePrefs();
|
||||
aCallback(true);
|
||||
},
|
||||
function() {
|
||||
aCallback(false);
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Sync/FxA related data
|
||||
let sync = {
|
||||
name: "sync", // name is used only by tests.
|
||||
type: types.OTHERDATA,
|
||||
migrate: async aCallback => {
|
||||
// Try and parse a signedInUser.json file from the source directory and
|
||||
// if we can, copy it to the new profile and set sync's username pref
|
||||
// (which acts as a de-facto flag to indicate if sync is configured)
|
||||
try {
|
||||
let oldPath = PathUtils.join(
|
||||
sourceProfileDir.path,
|
||||
"signedInUser.json"
|
||||
// If the 'datareporting' directory exists we migrate files from it.
|
||||
let dataReportingDir = this._getFileObject(
|
||||
sourceProfileDir,
|
||||
"datareporting"
|
||||
);
|
||||
let exists = await IOUtils.exists(oldPath);
|
||||
if (exists) {
|
||||
let data = await IOUtils.readJSON(oldPath);
|
||||
if (data && data.accountData && data.accountData.email) {
|
||||
let username = data.accountData.email;
|
||||
// copy the file itself.
|
||||
await IOUtils.copy(
|
||||
oldPath,
|
||||
PathUtils.join(currentProfileDir.path, "signedInUser.json")
|
||||
);
|
||||
// Now we need to know whether Sync is actually configured for this
|
||||
// user. The only way we know is by looking at the prefs file from
|
||||
// the old profile. We avoid trying to do a full parse of the prefs
|
||||
// file and even avoid parsing the single string value we care
|
||||
// about.
|
||||
let prefsPath = PathUtils.join(sourceProfileDir.path, "prefs.js");
|
||||
if (await IOUtils.exists(oldPath)) {
|
||||
let rawPrefs = await IOUtils.readUTF8(prefsPath, {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
if (/^user_pref\("services\.sync\.username"/m.test(rawPrefs)) {
|
||||
// sync's configured in the source profile - ensure it is in the
|
||||
// new profile too.
|
||||
// Write it to prefs.js and flush the file.
|
||||
Services.prefs.setStringPref(
|
||||
"services.sync.username",
|
||||
username
|
||||
);
|
||||
savePrefs();
|
||||
}
|
||||
if (dataReportingDir && dataReportingDir.isDirectory()) {
|
||||
// Copy only specific files.
|
||||
let toCopy = ["state.json", "session-state.json"];
|
||||
|
||||
let dest = createSubDir("datareporting");
|
||||
let enumerator = dataReportingDir.directoryEntries;
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let file = enumerator.nextFile;
|
||||
if (file.isDirectory() || !toCopy.includes(file.leafName)) {
|
||||
continue;
|
||||
}
|
||||
file.copyTo(dest, "");
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
|
||||
// Telemetry related migrations.
|
||||
let times = {
|
||||
name: "times", // name is used only by tests.
|
||||
type: types.OTHERDATA,
|
||||
migrate: aCallback => {
|
||||
let file = this._getFileObject(sourceProfileDir, "times.json");
|
||||
if (file) {
|
||||
file.copyTo(currentProfileDir, "");
|
||||
}
|
||||
// And record the fact a migration (ie, a reset) happened.
|
||||
let recordMigration = async () => {
|
||||
try {
|
||||
let profileTimes = await lazy.ProfileAge(currentProfileDir.path);
|
||||
await profileTimes.recordProfileReset();
|
||||
aCallback(true);
|
||||
} catch (e) {
|
||||
aCallback(false);
|
||||
}
|
||||
};
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
|
||||
recordMigration();
|
||||
},
|
||||
};
|
||||
let telemetry = {
|
||||
name: "telemetry", // name is used only by tests...
|
||||
type: types.OTHERDATA,
|
||||
migrate: aCallback => {
|
||||
let createSubDir = name => {
|
||||
let dir = currentProfileDir.clone();
|
||||
dir.append(name);
|
||||
dir.create(Ci.nsIFile.DIRECTORY_TYPE, lazy.FileUtils.PERMS_DIRECTORY);
|
||||
return dir;
|
||||
};
|
||||
return [
|
||||
places,
|
||||
cookies,
|
||||
passwords,
|
||||
formData,
|
||||
dictionary,
|
||||
bookmarksBackups,
|
||||
session,
|
||||
sync,
|
||||
times,
|
||||
telemetry,
|
||||
favicons,
|
||||
].filter(r => r);
|
||||
}
|
||||
|
||||
// If the 'datareporting' directory exists we migrate files from it.
|
||||
let dataReportingDir = this._getFileObject(
|
||||
sourceProfileDir,
|
||||
"datareporting"
|
||||
);
|
||||
if (dataReportingDir && dataReportingDir.isDirectory()) {
|
||||
// Copy only specific files.
|
||||
let toCopy = ["state.json", "session-state.json"];
|
||||
|
||||
let dest = createSubDir("datareporting");
|
||||
let enumerator = dataReportingDir.directoryEntries;
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let file = enumerator.nextFile;
|
||||
if (file.isDirectory() || !toCopy.includes(file.leafName)) {
|
||||
continue;
|
||||
}
|
||||
file.copyTo(dest, "");
|
||||
}
|
||||
}
|
||||
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
places,
|
||||
cookies,
|
||||
passwords,
|
||||
formData,
|
||||
dictionary,
|
||||
bookmarksBackups,
|
||||
session,
|
||||
sync,
|
||||
times,
|
||||
telemetry,
|
||||
favicons,
|
||||
].filter(r => r);
|
||||
};
|
||||
|
||||
Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
|
||||
get: () => true,
|
||||
});
|
||||
|
||||
FirefoxProfileMigrator.prototype.classDescription = "Firefox Profile Migrator";
|
||||
FirefoxProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=firefox";
|
||||
FirefoxProfileMigrator.prototype.classID = Components.ID(
|
||||
"{91185366-ba97-4438-acba-48deaca63386}"
|
||||
);
|
||||
get startupOnlyMigrator() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,8 @@ const kLoginsKey =
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
|
||||
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
import {
|
||||
MigrationUtils,
|
||||
MigratorPrototype,
|
||||
} from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
|
||||
import { MSMigrationUtils } from "resource:///modules/MSMigrationUtils.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
@@ -351,58 +349,67 @@ IE7FormPasswords.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
export function IEProfileMigrator() {
|
||||
this.wrappedJSObject = this; // export this to be able to use it in the unittest.
|
||||
}
|
||||
|
||||
IEProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
|
||||
IEProfileMigrator.prototype.getResources = function IE_getResources() {
|
||||
let resources = [
|
||||
MSMigrationUtils.getBookmarksMigrator(),
|
||||
new History(),
|
||||
MSMigrationUtils.getCookiesMigrator(),
|
||||
];
|
||||
// Only support the form password migrator for Windows XP to 7.
|
||||
if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
|
||||
resources.push(new IE7FormPasswords());
|
||||
/**
|
||||
* Internet Explorer profile migrator
|
||||
*/
|
||||
export class IEProfileMigrator extends MigratorBase {
|
||||
constructor() {
|
||||
super();
|
||||
this.wrappedJSObject = this; // export this to be able to use it in the unittest.
|
||||
}
|
||||
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
||||
windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
|
||||
resources.push(windowsVaultFormPasswordsMigrator);
|
||||
return resources.filter(r => r.exists);
|
||||
};
|
||||
|
||||
IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
|
||||
let datePromises = ["Favs", "CookD"].map(dirId => {
|
||||
let { path } = Services.dirsvc.get(dirId, Ci.nsIFile);
|
||||
return OS.File.stat(path)
|
||||
.catch(() => null)
|
||||
.then(info => {
|
||||
return info ? info.lastModificationDate : 0;
|
||||
});
|
||||
});
|
||||
datePromises.push(
|
||||
new Promise(resolve => {
|
||||
let typedURLs = new Map();
|
||||
try {
|
||||
typedURLs = MSMigrationUtils.getTypedURLs(
|
||||
"Software\\Microsoft\\Internet Explorer"
|
||||
);
|
||||
} catch (ex) {}
|
||||
let dates = [0, ...typedURLs.values()];
|
||||
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
||||
resolve(Math.max.apply(Math, dates) / 1000);
|
||||
})
|
||||
);
|
||||
return Promise.all(datePromises).then(dates => {
|
||||
return new Date(Math.max.apply(Math, dates));
|
||||
});
|
||||
};
|
||||
get classDescription() {
|
||||
return "IE Profile Migrator";
|
||||
}
|
||||
|
||||
IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
|
||||
IEProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=ie";
|
||||
IEProfileMigrator.prototype.classID = Components.ID(
|
||||
"{3d2532e3-4932-4774-b7ba-968f5899d3a4}"
|
||||
);
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=ie";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
|
||||
}
|
||||
|
||||
getResources() {
|
||||
let resources = [
|
||||
MSMigrationUtils.getBookmarksMigrator(),
|
||||
new History(),
|
||||
MSMigrationUtils.getCookiesMigrator(),
|
||||
];
|
||||
// Only support the form password migrator for Windows XP to 7.
|
||||
if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
|
||||
resources.push(new IE7FormPasswords());
|
||||
}
|
||||
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
||||
windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
|
||||
resources.push(windowsVaultFormPasswordsMigrator);
|
||||
return resources.filter(r => r.exists);
|
||||
}
|
||||
|
||||
getLastUsedDate() {
|
||||
let datePromises = ["Favs", "CookD"].map(dirId => {
|
||||
let { path } = Services.dirsvc.get(dirId, Ci.nsIFile);
|
||||
return OS.File.stat(path)
|
||||
.catch(() => null)
|
||||
.then(info => {
|
||||
return info ? info.lastModificationDate : 0;
|
||||
});
|
||||
});
|
||||
datePromises.push(
|
||||
new Promise(resolve => {
|
||||
let typedURLs = new Map();
|
||||
try {
|
||||
typedURLs = MSMigrationUtils.getTypedURLs(
|
||||
"Software\\Microsoft\\Internet Explorer"
|
||||
);
|
||||
} catch (ex) {}
|
||||
let dates = [0, ...typedURLs.values()];
|
||||
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
||||
resolve(Math.max.apply(Math, dates) / 1000);
|
||||
})
|
||||
);
|
||||
return Promise.all(datePromises).then(dates => {
|
||||
return new Date(Math.max.apply(Math, dates));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,13 @@
|
||||
* 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/. */
|
||||
|
||||
const TOPIC_WILL_IMPORT_BOOKMARKS =
|
||||
"initial-migration-will-import-default-bookmarks";
|
||||
const TOPIC_DID_IMPORT_BOOKMARKS =
|
||||
"initial-migration-did-import-default-bookmarks";
|
||||
const TOPIC_PLACES_DEFAULTS_FINISHED = "places-browser-init-complete";
|
||||
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.sys.mjs",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
|
||||
PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
|
||||
PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
|
||||
ResponsivenessMonitor: "resource://gre/modules/ResponsivenessMonitor.sys.mjs",
|
||||
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
|
||||
@@ -44,475 +35,12 @@ function getL10n() {
|
||||
return gL10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} MigratorResource
|
||||
* A resource returned by a subclass of MigratorPrototype that can migrate
|
||||
* data to this browser.
|
||||
* @property {number} type
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what this resource represents. A resource can represent one or more types
|
||||
* of data, for example HISTORY and FORMDATA.
|
||||
* @property {Function} migrate
|
||||
* A function that will actually perform the migration of this resource's
|
||||
* data into this browser.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
|
||||
*
|
||||
* To implement a migrator:
|
||||
* 1. Import this module.
|
||||
* 2. Create the prototype for the migrator, extending MigratorPrototype.
|
||||
* Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
|
||||
* 3. Set classDescription, contractID and classID for your migrator, and set
|
||||
* NSGetFactory appropriately.
|
||||
* 4. If the migrator supports multiple profiles, override the sourceProfiles
|
||||
* Here we default for single-profile migrator.
|
||||
* 5. Implement getResources(aProfile) (see below).
|
||||
* 6. For startup-only migrators, override |startupOnlyMigrator|.
|
||||
*/
|
||||
export var MigratorPrototype = {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIBrowserProfileMigrator"]),
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the source supports multiple profiles.
|
||||
*
|
||||
* Returns array of profile objects from which data may be imported. The object
|
||||
* should have the following keys:
|
||||
* id - a unique string identifier for the profile
|
||||
* name - a pretty name to display to the user in the UI
|
||||
*
|
||||
* Only profiles from which data can be imported should be listed. Otherwise
|
||||
* the behavior of the migration wizard isn't well-defined.
|
||||
*
|
||||
* For a single-profile source (e.g. safari, ie), this returns null,
|
||||
* and not an empty array. That is the default implementation.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {object[]|null}
|
||||
*/
|
||||
getSourceProfiles() {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* MUST BE OVERRIDDEN.
|
||||
*
|
||||
* Returns an array of "migration resources" objects for the given profile,
|
||||
* or for the "default" profile, if the migrator does not support multiple
|
||||
* profiles.
|
||||
*
|
||||
* Each migration resource should provide:
|
||||
* - a |type| getter, returning any of the migration types (see
|
||||
* nsIBrowserProfileMigrator).
|
||||
*
|
||||
* - a |migrate| method, taking a single argument, aCallback(bool success),
|
||||
* for migrating the data for this resource. It may do its job
|
||||
* synchronously or asynchronously. Either way, it must call
|
||||
* aCallback(bool aSuccess) when it's done. In the case of an exception
|
||||
* thrown from |migrate|, it's taken as if aCallback(false) is called.
|
||||
*
|
||||
* Note: In the case of a simple asynchronous implementation, you may find
|
||||
* MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
|
||||
*
|
||||
* For each migration type listed in nsIBrowserProfileMigrator, multiple
|
||||
* migration resources may be provided. This practice is useful when the
|
||||
* data for a certain migration type is independently stored in few
|
||||
* locations. For example, the mac version of Safari stores its "reading list"
|
||||
* bookmarks in a separate property list.
|
||||
*
|
||||
* Note that the importation of a particular migration type is reported as
|
||||
* successful if _any_ of its resources succeeded to import (that is, called,
|
||||
* |aCallback(true)|). However, completion-status for a particular migration
|
||||
* type is reported to the UI only once all of its migrators have called
|
||||
* aCallback.
|
||||
*
|
||||
* NOTE: The returned array should only include resources from which data
|
||||
* can be imported. So, for example, before adding a resource for the
|
||||
* BOOKMARKS migration type, you should check if you should check that the
|
||||
* bookmarks file exists.
|
||||
*
|
||||
* @abstract
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* In the case of multiple-profiles migrator, it is guaranteed that
|
||||
* aProfile is a value returned by the sourceProfiles getter (see
|
||||
* above).
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getResources: function MP_getResources(aProfile) {
|
||||
throw new Error("getResources must be overridden");
|
||||
},
|
||||
|
||||
/**
|
||||
* OVERRIDE in order to provide an estimate of when the last time was
|
||||
* that somebody used the browser. It is OK that this is somewhat fuzzy -
|
||||
* history may not be available (or be wiped or not present due to e.g.
|
||||
* incognito mode).
|
||||
*
|
||||
* If not overridden, the promise will resolve to the Unix epoch.
|
||||
*
|
||||
* @returns {Promise<Date>}
|
||||
* A Promise that resolves to the last used date.
|
||||
*/
|
||||
getLastUsedDate() {
|
||||
return Promise.resolve(new Date(0));
|
||||
},
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
|
||||
* that is just the Firefox migrator, see bug 737381). Default: false.
|
||||
*
|
||||
* Startup-only migrators are different in two ways:
|
||||
* - they may only be used during startup.
|
||||
* - the user-profile is half baked during migration. The folder exists,
|
||||
* but it's only accessible through MigrationUtils.profileStartup.
|
||||
* The migrator can call MigrationUtils.profileStartup.doStartup
|
||||
* at any point in order to initialize the profile.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator is start-up only.
|
||||
*/
|
||||
get startupOnlyMigrator() {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the migrator is configured to be enabled. This is
|
||||
* controlled by the `browser.migrate.<BROWSER_KEY>.enabled` boolean
|
||||
* preference.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator should be shown in the migration wizard.
|
||||
*/
|
||||
get enabled() {
|
||||
let key = this.getBrowserKey();
|
||||
return Services.prefs.getBoolPref(`browser.migrate.${key}.enabled`, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* getResources.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* @returns {MigratorResource[]}
|
||||
*/
|
||||
getMigrateData: async function MP_getMigrateData(aProfile) {
|
||||
let resources = await this._getMaybeCachedResources(aProfile);
|
||||
if (!resources) {
|
||||
return 0;
|
||||
}
|
||||
let types = resources.map(r => r.type);
|
||||
return types.reduce((a, b) => {
|
||||
a |= b;
|
||||
return a;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
getBrowserKey: function MP_getBrowserKey() {
|
||||
return this.contractID.match(/\=([^\=]+)$/)[1];
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* migrate for each resource.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
* @param {number} aItems
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what types of resources should be migrated.
|
||||
* @param {boolean} aStartup
|
||||
* True if this migration is occurring during startup.
|
||||
* @param {object|string} aProfile
|
||||
* The other browser profile that is being migrated from.
|
||||
*/
|
||||
migrate: async function MP_migrate(aItems, aStartup, aProfile) {
|
||||
let resources = await this._getMaybeCachedResources(aProfile);
|
||||
if (!resources.length) {
|
||||
throw new Error("migrate called for a non-existent source");
|
||||
}
|
||||
|
||||
if (aItems != Ci.nsIBrowserProfileMigrator.ALL) {
|
||||
resources = resources.filter(r => aItems & r.type);
|
||||
}
|
||||
|
||||
// Used to periodically give back control to the main-thread loop.
|
||||
let unblockMainThread = function() {
|
||||
return new Promise(resolve => {
|
||||
Services.tm.dispatchToMainThread(resolve);
|
||||
});
|
||||
};
|
||||
|
||||
let getHistogramIdForResourceType = (resourceType, template) => {
|
||||
if (resourceType == MigrationUtils.resourceTypes.HISTORY) {
|
||||
return template.replace("*", "HISTORY");
|
||||
}
|
||||
if (resourceType == MigrationUtils.resourceTypes.BOOKMARKS) {
|
||||
return template.replace("*", "BOOKMARKS");
|
||||
}
|
||||
if (resourceType == MigrationUtils.resourceTypes.PASSWORDS) {
|
||||
return template.replace("*", "LOGINS");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let browserKey = this.getBrowserKey();
|
||||
|
||||
let maybeStartTelemetryStopwatch = resourceType => {
|
||||
let histogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_IMPORT_MS"
|
||||
);
|
||||
if (histogramId) {
|
||||
TelemetryStopwatch.startKeyed(histogramId, browserKey);
|
||||
}
|
||||
return histogramId;
|
||||
};
|
||||
|
||||
let maybeStartResponsivenessMonitor = resourceType => {
|
||||
let responsivenessMonitor;
|
||||
let responsivenessHistogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_JANK_MS"
|
||||
);
|
||||
if (responsivenessHistogramId) {
|
||||
responsivenessMonitor = new lazy.ResponsivenessMonitor();
|
||||
}
|
||||
return { responsivenessMonitor, responsivenessHistogramId };
|
||||
};
|
||||
|
||||
let maybeFinishResponsivenessMonitor = (
|
||||
responsivenessMonitor,
|
||||
histogramId
|
||||
) => {
|
||||
if (responsivenessMonitor) {
|
||||
let accumulatedDelay = responsivenessMonitor.finish();
|
||||
if (histogramId) {
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(browserKey, accumulatedDelay);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let collectQuantityTelemetry = () => {
|
||||
for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
|
||||
let histogramId =
|
||||
"FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(browserKey, MigrationUtils._importQuantities[resourceType]);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Called either directly or through the bookmarks import callback.
|
||||
let doMigrate = async function() {
|
||||
let resourcesGroupedByItems = new Map();
|
||||
resources.forEach(function(resource) {
|
||||
if (!resourcesGroupedByItems.has(resource.type)) {
|
||||
resourcesGroupedByItems.set(resource.type, new Set());
|
||||
}
|
||||
resourcesGroupedByItems.get(resource.type).add(resource);
|
||||
});
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
throw new Error("No items to import");
|
||||
}
|
||||
|
||||
let notify = function(aMsg, aItemType) {
|
||||
Services.obs.notifyObservers(null, aMsg, aItemType);
|
||||
};
|
||||
|
||||
for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
|
||||
MigrationUtils._importQuantities[resourceType] = 0;
|
||||
}
|
||||
notify("Migration:Started");
|
||||
for (let [migrationType, itemResources] of resourcesGroupedByItems) {
|
||||
notify("Migration:ItemBeforeMigrate", migrationType);
|
||||
|
||||
let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType);
|
||||
|
||||
let {
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId,
|
||||
} = maybeStartResponsivenessMonitor(migrationType);
|
||||
|
||||
let itemSuccess = false;
|
||||
for (let res of itemResources) {
|
||||
let completeDeferred = lazy.PromiseUtils.defer();
|
||||
let resourceDone = function(aSuccess) {
|
||||
itemResources.delete(res);
|
||||
itemSuccess |= aSuccess;
|
||||
if (itemResources.size == 0) {
|
||||
notify(
|
||||
itemSuccess
|
||||
? "Migration:ItemAfterMigrate"
|
||||
: "Migration:ItemError",
|
||||
migrationType
|
||||
);
|
||||
resourcesGroupedByItems.delete(migrationType);
|
||||
|
||||
if (stopwatchHistogramId) {
|
||||
TelemetryStopwatch.finishKeyed(
|
||||
stopwatchHistogramId,
|
||||
browserKey
|
||||
);
|
||||
}
|
||||
|
||||
maybeFinishResponsivenessMonitor(
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId
|
||||
);
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
collectQuantityTelemetry();
|
||||
notify("Migration:Ended");
|
||||
}
|
||||
}
|
||||
completeDeferred.resolve();
|
||||
};
|
||||
|
||||
// If migrate throws, an error occurred, and the callback
|
||||
// (itemMayBeDone) might haven't been called.
|
||||
try {
|
||||
res.migrate(resourceDone);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
resourceDone(false);
|
||||
}
|
||||
|
||||
await completeDeferred.promise;
|
||||
await unblockMainThread();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
MigrationUtils.isStartupMigration &&
|
||||
!this.startupOnlyMigrator &&
|
||||
Services.policies.isAllowed("defaultBookmarks")
|
||||
) {
|
||||
MigrationUtils.profileStartup.doStartup();
|
||||
// First import the default bookmarks.
|
||||
// Note: We do not need to do so for the Firefox migrator
|
||||
// (=startupOnlyMigrator), as it just copies over the places database
|
||||
// from another profile.
|
||||
(async function() {
|
||||
// Tell nsBrowserGlue we're importing default bookmarks.
|
||||
let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
|
||||
Ci.nsIObserver
|
||||
);
|
||||
browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
|
||||
|
||||
// Import the default bookmarks. We ignore whether or not we succeed.
|
||||
await lazy.BookmarkHTMLUtils.importFromURL(
|
||||
"chrome://browser/content/default-bookmarks.html",
|
||||
{
|
||||
replace: true,
|
||||
source: lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
|
||||
}
|
||||
).catch(Cu.reportError);
|
||||
|
||||
// We'll tell nsBrowserGlue we've imported bookmarks, but before that
|
||||
// we need to make sure we're going to know when it's finished
|
||||
// initializing places:
|
||||
let placesInitedPromise = new Promise(resolve => {
|
||||
let onPlacesInited = function() {
|
||||
Services.obs.removeObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
resolve();
|
||||
};
|
||||
Services.obs.addObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
});
|
||||
browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
|
||||
await placesInitedPromise;
|
||||
doMigrate();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
doMigrate();
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, this code
|
||||
* won't be part of the migrator itself.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
async isSourceAvailable() {
|
||||
if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a single-profile source, check if any data is available.
|
||||
// For multiple-profiles source, make sure that at least one
|
||||
// profile is available.
|
||||
let exists = false;
|
||||
try {
|
||||
let profiles = await this.getSourceProfiles();
|
||||
if (!profiles) {
|
||||
let resources = await this._getMaybeCachedResources("");
|
||||
if (resources && resources.length) {
|
||||
exists = true;
|
||||
}
|
||||
} else {
|
||||
exists = !!profiles.length;
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
return exists;
|
||||
},
|
||||
|
||||
/*** PRIVATE STUFF - DO NOT OVERRIDE ***/
|
||||
|
||||
/**
|
||||
* Returns resources for a particular profile and then caches them for later
|
||||
* lookups.
|
||||
*
|
||||
* @param {object|string} aProfile
|
||||
* The profile that resources are being imported from.
|
||||
* @returns {Promise<MigrationResource[]>}
|
||||
*/
|
||||
_getMaybeCachedResources: async function PMB__getMaybeCachedResources(
|
||||
aProfile
|
||||
) {
|
||||
let profileKey = aProfile ? aProfile.id : "";
|
||||
if (this._resourcesByProfile) {
|
||||
if (profileKey in this._resourcesByProfile) {
|
||||
return this._resourcesByProfile[profileKey];
|
||||
}
|
||||
} else {
|
||||
this._resourcesByProfile = {};
|
||||
}
|
||||
this._resourcesByProfile[profileKey] = await this.getResources(aProfile);
|
||||
return this._resourcesByProfile[profileKey];
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The singleton MigrationUtils service. This service is the primary mechanism
|
||||
* by which migrations from other browsers to this browser occur. The singleton
|
||||
* instance of this class is exported from this module as `MigrationUtils`.
|
||||
*/
|
||||
class MigrationUtilsSingleton {
|
||||
class MigrationUtils {
|
||||
resourceTypes = Object.freeze({
|
||||
COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
|
||||
HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,
|
||||
@@ -525,7 +53,7 @@ class MigrationUtilsSingleton {
|
||||
|
||||
/**
|
||||
* Helper for implementing simple asynchronous cases of migration resources'
|
||||
* |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method
|
||||
* |migrate(aCallback)| (see MigratorBase). If your |migrate| method
|
||||
* just waits for some file to be read, for example, and then migrates
|
||||
* everything right away, you can wrap the async-function with this helper
|
||||
* and not worry about notifying the callback.
|
||||
@@ -711,7 +239,7 @@ class MigrationUtilsSingleton {
|
||||
* Internal name of the migration source. See `availableMigratorKeys`
|
||||
* for supported values by OS.
|
||||
*
|
||||
* @returns {MigratorPrototype}
|
||||
* @returns {MigratorBase}
|
||||
* A profile migrator implementing nsIBrowserProfileMigrator, if it can
|
||||
* import any data, null otherwise.
|
||||
*/
|
||||
@@ -1420,4 +948,6 @@ class MigrationUtilsSingleton {
|
||||
}
|
||||
}
|
||||
|
||||
export const MigrationUtils = new MigrationUtilsSingleton();
|
||||
const MigrationUtilsSingleton = new MigrationUtils();
|
||||
|
||||
export { MigrationUtilsSingleton as MigrationUtils };
|
||||
|
||||
489
browser/components/migration/MigratorBase.sys.mjs
Normal file
489
browser/components/migration/MigratorBase.sys.mjs
Normal file
@@ -0,0 +1,489 @@
|
||||
/* 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/. */
|
||||
|
||||
const TOPIC_WILL_IMPORT_BOOKMARKS =
|
||||
"initial-migration-will-import-default-bookmarks";
|
||||
const TOPIC_DID_IMPORT_BOOKMARKS =
|
||||
"initial-migration-did-import-default-bookmarks";
|
||||
const TOPIC_PLACES_DEFAULTS_FINISHED = "places-browser-init-complete";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.sys.mjs",
|
||||
MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
|
||||
PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
|
||||
ResponsivenessMonitor: "resource://gre/modules/ResponsivenessMonitor.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {object} MigratorResource
|
||||
* A resource returned by a subclass of MigratorBase that can migrate
|
||||
* data to this browser.
|
||||
* @property {number} type
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what this resource represents. A resource can represent one or more types
|
||||
* of data, for example HISTORY and FORMDATA.
|
||||
* @property {Function} migrate
|
||||
* A function that will actually perform the migration of this resource's
|
||||
* data into this browser.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
|
||||
*
|
||||
* To implement a migrator:
|
||||
* 1. Import this module.
|
||||
* 2. Create the prototype for the migrator, extending MigratorBase.
|
||||
* 3. Set classDescription, contractID and classID for your migrator, and update
|
||||
* components.conf to register the migrator as an XPCOM component.
|
||||
* 4. If the migrator supports multiple profiles, override the sourceProfiles
|
||||
* Here we default for single-profile migrator.
|
||||
* 5. Implement getResources(aProfile) (see below).
|
||||
* 6. For startup-only migrators, override |startupOnlyMigrator|.
|
||||
*/
|
||||
export class MigratorBase {
|
||||
QueryInterface = ChromeUtils.generateQI(["nsIBrowserProfileMigrator"]);
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the source supports multiple profiles.
|
||||
*
|
||||
* Returns array of profile objects from which data may be imported. The object
|
||||
* should have the following keys:
|
||||
* id - a unique string identifier for the profile
|
||||
* name - a pretty name to display to the user in the UI
|
||||
*
|
||||
* Only profiles from which data can be imported should be listed. Otherwise
|
||||
* the behavior of the migration wizard isn't well-defined.
|
||||
*
|
||||
* For a single-profile source (e.g. safari, ie), this returns null,
|
||||
* and not an empty array. That is the default implementation.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {object[]|null}
|
||||
*/
|
||||
getSourceProfiles() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* MUST BE OVERRIDDEN.
|
||||
*
|
||||
* Returns an array of "migration resources" objects for the given profile,
|
||||
* or for the "default" profile, if the migrator does not support multiple
|
||||
* profiles.
|
||||
*
|
||||
* Each migration resource should provide:
|
||||
* - a |type| getter, returning any of the migration types (see
|
||||
* nsIBrowserProfileMigrator).
|
||||
*
|
||||
* - a |migrate| method, taking a single argument, aCallback(bool success),
|
||||
* for migrating the data for this resource. It may do its job
|
||||
* synchronously or asynchronously. Either way, it must call
|
||||
* aCallback(bool aSuccess) when it's done. In the case of an exception
|
||||
* thrown from |migrate|, it's taken as if aCallback(false) is called.
|
||||
*
|
||||
* Note: In the case of a simple asynchronous implementation, you may find
|
||||
* MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
|
||||
*
|
||||
* For each migration type listed in nsIBrowserProfileMigrator, multiple
|
||||
* migration resources may be provided. This practice is useful when the
|
||||
* data for a certain migration type is independently stored in few
|
||||
* locations. For example, the mac version of Safari stores its "reading list"
|
||||
* bookmarks in a separate property list.
|
||||
*
|
||||
* Note that the importation of a particular migration type is reported as
|
||||
* successful if _any_ of its resources succeeded to import (that is, called,
|
||||
* |aCallback(true)|). However, completion-status for a particular migration
|
||||
* type is reported to the UI only once all of its migrators have called
|
||||
* aCallback.
|
||||
*
|
||||
* NOTE: The returned array should only include resources from which data
|
||||
* can be imported. So, for example, before adding a resource for the
|
||||
* BOOKMARKS migration type, you should check if you should check that the
|
||||
* bookmarks file exists.
|
||||
*
|
||||
* @abstract
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* In the case of multiple-profiles migrator, it is guaranteed that
|
||||
* aProfile is a value returned by the sourceProfiles getter (see
|
||||
* above).
|
||||
* @returns {Promise<MigratorResource[]>|MigratorResource[]}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getResources(aProfile) {
|
||||
throw new Error("getResources must be overridden");
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDE in order to provide an estimate of when the last time was
|
||||
* that somebody used the browser. It is OK that this is somewhat fuzzy -
|
||||
* history may not be available (or be wiped or not present due to e.g.
|
||||
* incognito mode).
|
||||
*
|
||||
* If not overridden, the promise will resolve to the Unix epoch.
|
||||
*
|
||||
* @returns {Promise<Date>}
|
||||
* A Promise that resolves to the last used date.
|
||||
*/
|
||||
getLastUsedDate() {
|
||||
return Promise.resolve(new Date(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
|
||||
* that is just the Firefox migrator, see bug 737381). Default: false.
|
||||
*
|
||||
* Startup-only migrators are different in two ways:
|
||||
* - they may only be used during startup.
|
||||
* - the user-profile is half baked during migration. The folder exists,
|
||||
* but it's only accessible through MigrationUtils.profileStartup.
|
||||
* The migrator can call MigrationUtils.profileStartup.doStartup
|
||||
* at any point in order to initialize the profile.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator is start-up only.
|
||||
*/
|
||||
get startupOnlyMigrator() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the migrator is configured to be enabled. This is
|
||||
* controlled by the `browser.migrate.<BROWSER_KEY>.enabled` boolean
|
||||
* preference.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator should be shown in the migration wizard.
|
||||
*/
|
||||
get enabled() {
|
||||
let key = this.getBrowserKey();
|
||||
return Services.prefs.getBoolPref(`browser.migrate.${key}.enabled`, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* getResources.
|
||||
*
|
||||
* See nsIBrowserProfileMigrator.
|
||||
*
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* @returns {MigratorResource[]}
|
||||
*/
|
||||
async getMigrateData(aProfile) {
|
||||
let resources = await this.#getMaybeCachedResources(aProfile);
|
||||
if (!resources) {
|
||||
return 0;
|
||||
}
|
||||
let types = resources.map(r => r.type);
|
||||
return types.reduce((a, b) => {
|
||||
a |= b;
|
||||
return a;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
getBrowserKey() {
|
||||
return this.contractID.match(/\=([^\=]+)$/)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* migrate for each resource.
|
||||
*
|
||||
* See nsIBrowserProfileMigrator.
|
||||
*
|
||||
* @param {number} aItems
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what types of resources should be migrated.
|
||||
* @param {boolean} aStartup
|
||||
* True if this migration is occurring during startup.
|
||||
* @param {object|string} aProfile
|
||||
* The other browser profile that is being migrated from.
|
||||
*/
|
||||
async migrate(aItems, aStartup, aProfile) {
|
||||
let resources = await this.#getMaybeCachedResources(aProfile);
|
||||
if (!resources.length) {
|
||||
throw new Error("migrate called for a non-existent source");
|
||||
}
|
||||
|
||||
if (aItems != Ci.nsIBrowserProfileMigrator.ALL) {
|
||||
resources = resources.filter(r => aItems & r.type);
|
||||
}
|
||||
|
||||
// Used to periodically give back control to the main-thread loop.
|
||||
let unblockMainThread = function() {
|
||||
return new Promise(resolve => {
|
||||
Services.tm.dispatchToMainThread(resolve);
|
||||
});
|
||||
};
|
||||
|
||||
let getHistogramIdForResourceType = (resourceType, template) => {
|
||||
if (resourceType == lazy.MigrationUtils.resourceTypes.HISTORY) {
|
||||
return template.replace("*", "HISTORY");
|
||||
}
|
||||
if (resourceType == lazy.MigrationUtils.resourceTypes.BOOKMARKS) {
|
||||
return template.replace("*", "BOOKMARKS");
|
||||
}
|
||||
if (resourceType == lazy.MigrationUtils.resourceTypes.PASSWORDS) {
|
||||
return template.replace("*", "LOGINS");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let browserKey = this.getBrowserKey();
|
||||
|
||||
let maybeStartTelemetryStopwatch = resourceType => {
|
||||
let histogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_IMPORT_MS"
|
||||
);
|
||||
if (histogramId) {
|
||||
TelemetryStopwatch.startKeyed(histogramId, browserKey);
|
||||
}
|
||||
return histogramId;
|
||||
};
|
||||
|
||||
let maybeStartResponsivenessMonitor = resourceType => {
|
||||
let responsivenessMonitor;
|
||||
let responsivenessHistogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_JANK_MS"
|
||||
);
|
||||
if (responsivenessHistogramId) {
|
||||
responsivenessMonitor = new lazy.ResponsivenessMonitor();
|
||||
}
|
||||
return { responsivenessMonitor, responsivenessHistogramId };
|
||||
};
|
||||
|
||||
let maybeFinishResponsivenessMonitor = (
|
||||
responsivenessMonitor,
|
||||
histogramId
|
||||
) => {
|
||||
if (responsivenessMonitor) {
|
||||
let accumulatedDelay = responsivenessMonitor.finish();
|
||||
if (histogramId) {
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(browserKey, accumulatedDelay);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let collectQuantityTelemetry = () => {
|
||||
for (let resourceType of Object.keys(
|
||||
lazy.MigrationUtils._importQuantities
|
||||
)) {
|
||||
let histogramId =
|
||||
"FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(
|
||||
browserKey,
|
||||
lazy.MigrationUtils._importQuantities[resourceType]
|
||||
);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Called either directly or through the bookmarks import callback.
|
||||
let doMigrate = async function() {
|
||||
let resourcesGroupedByItems = new Map();
|
||||
resources.forEach(function(resource) {
|
||||
if (!resourcesGroupedByItems.has(resource.type)) {
|
||||
resourcesGroupedByItems.set(resource.type, new Set());
|
||||
}
|
||||
resourcesGroupedByItems.get(resource.type).add(resource);
|
||||
});
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
throw new Error("No items to import");
|
||||
}
|
||||
|
||||
let notify = function(aMsg, aItemType) {
|
||||
Services.obs.notifyObservers(null, aMsg, aItemType);
|
||||
};
|
||||
|
||||
for (let resourceType of Object.keys(
|
||||
lazy.MigrationUtils._importQuantities
|
||||
)) {
|
||||
lazy.MigrationUtils._importQuantities[resourceType] = 0;
|
||||
}
|
||||
notify("Migration:Started");
|
||||
for (let [migrationType, itemResources] of resourcesGroupedByItems) {
|
||||
notify("Migration:ItemBeforeMigrate", migrationType);
|
||||
|
||||
let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType);
|
||||
|
||||
let {
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId,
|
||||
} = maybeStartResponsivenessMonitor(migrationType);
|
||||
|
||||
let itemSuccess = false;
|
||||
for (let res of itemResources) {
|
||||
let completeDeferred = lazy.PromiseUtils.defer();
|
||||
let resourceDone = function(aSuccess) {
|
||||
itemResources.delete(res);
|
||||
itemSuccess |= aSuccess;
|
||||
if (itemResources.size == 0) {
|
||||
notify(
|
||||
itemSuccess
|
||||
? "Migration:ItemAfterMigrate"
|
||||
: "Migration:ItemError",
|
||||
migrationType
|
||||
);
|
||||
resourcesGroupedByItems.delete(migrationType);
|
||||
|
||||
if (stopwatchHistogramId) {
|
||||
TelemetryStopwatch.finishKeyed(
|
||||
stopwatchHistogramId,
|
||||
browserKey
|
||||
);
|
||||
}
|
||||
|
||||
maybeFinishResponsivenessMonitor(
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId
|
||||
);
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
collectQuantityTelemetry();
|
||||
notify("Migration:Ended");
|
||||
}
|
||||
}
|
||||
completeDeferred.resolve();
|
||||
};
|
||||
|
||||
// If migrate throws, an error occurred, and the callback
|
||||
// (itemMayBeDone) might haven't been called.
|
||||
try {
|
||||
res.migrate(resourceDone);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
resourceDone(false);
|
||||
}
|
||||
|
||||
await completeDeferred.promise;
|
||||
await unblockMainThread();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
lazy.MigrationUtils.isStartupMigration &&
|
||||
!this.startupOnlyMigrator &&
|
||||
Services.policies.isAllowed("defaultBookmarks")
|
||||
) {
|
||||
lazy.MigrationUtils.profileStartup.doStartup();
|
||||
// First import the default bookmarks.
|
||||
// Note: We do not need to do so for the Firefox migrator
|
||||
// (=startupOnlyMigrator), as it just copies over the places database
|
||||
// from another profile.
|
||||
(async function() {
|
||||
// Tell nsBrowserGlue we're importing default bookmarks.
|
||||
let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
|
||||
Ci.nsIObserver
|
||||
);
|
||||
browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
|
||||
|
||||
// Import the default bookmarks. We ignore whether or not we succeed.
|
||||
await lazy.BookmarkHTMLUtils.importFromURL(
|
||||
"chrome://browser/content/default-bookmarks.html",
|
||||
{
|
||||
replace: true,
|
||||
source: lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
|
||||
}
|
||||
).catch(Cu.reportError);
|
||||
|
||||
// We'll tell nsBrowserGlue we've imported bookmarks, but before that
|
||||
// we need to make sure we're going to know when it's finished
|
||||
// initializing places:
|
||||
let placesInitedPromise = new Promise(resolve => {
|
||||
let onPlacesInited = function() {
|
||||
Services.obs.removeObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
resolve();
|
||||
};
|
||||
Services.obs.addObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
});
|
||||
browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
|
||||
await placesInitedPromise;
|
||||
doMigrate();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
doMigrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, this code
|
||||
* won't be part of the migrator itself.
|
||||
*
|
||||
* See nsIBrowserProfileMigrator.
|
||||
*/
|
||||
async isSourceAvailable() {
|
||||
if (this.startupOnlyMigrator && !lazy.MigrationUtils.isStartupMigration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a single-profile source, check if any data is available.
|
||||
// For multiple-profiles source, make sure that at least one
|
||||
// profile is available.
|
||||
let exists = false;
|
||||
try {
|
||||
let profiles = await this.getSourceProfiles();
|
||||
if (!profiles) {
|
||||
let resources = await this.#getMaybeCachedResources("");
|
||||
if (resources && resources.length) {
|
||||
exists = true;
|
||||
}
|
||||
} else {
|
||||
exists = !!profiles.length;
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/*** PRIVATE STUFF - DO NOT OVERRIDE ***/
|
||||
|
||||
/**
|
||||
* Returns resources for a particular profile and then caches them for later
|
||||
* lookups.
|
||||
*
|
||||
* @param {object|string} aProfile
|
||||
* The profile that resources are being imported from.
|
||||
* @returns {Promise<MigrationResource[]>}
|
||||
*/
|
||||
async #getMaybeCachedResources(aProfile) {
|
||||
let profileKey = aProfile ? aProfile.id : "";
|
||||
if (this._resourcesByProfile) {
|
||||
if (profileKey in this._resourcesByProfile) {
|
||||
return this._resourcesByProfile[profileKey];
|
||||
}
|
||||
} else {
|
||||
this._resourcesByProfile = {};
|
||||
}
|
||||
this._resourcesByProfile[profileKey] = await this.getResources(aProfile);
|
||||
return this._resourcesByProfile[profileKey];
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,8 @@
|
||||
import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
|
||||
|
||||
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
import {
|
||||
MigrationUtils,
|
||||
MigratorPrototype,
|
||||
} from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
@@ -344,135 +342,134 @@ SearchStrings.prototype = {
|
||||
},
|
||||
};
|
||||
|
||||
export function SafariProfileMigrator() {}
|
||||
|
||||
SafariProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
|
||||
SafariProfileMigrator.prototype.getResources = function SM_getResources() {
|
||||
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||
if (!profileDir.exists()) {
|
||||
return null;
|
||||
/**
|
||||
* Safari migrator
|
||||
*/
|
||||
export class SafariProfileMigrator extends MigratorBase {
|
||||
get classDescription() {
|
||||
return "Safari Profile Migrator";
|
||||
}
|
||||
|
||||
let resources = [];
|
||||
let pushProfileFileResource = function(aFileName, aConstructor) {
|
||||
let file = profileDir.clone();
|
||||
file.append(aFileName);
|
||||
if (file.exists()) {
|
||||
resources.push(new aConstructor(file));
|
||||
get contractID() {
|
||||
return "@mozilla.org/profile/migrator;1?app=browser&type=safari";
|
||||
}
|
||||
|
||||
get classID() {
|
||||
return Components.ID("{4b609ecf-60b2-4655-9df4-dc149e474da1}");
|
||||
}
|
||||
|
||||
getResources() {
|
||||
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||
if (!profileDir.exists()) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pushProfileFileResource("History.plist", History);
|
||||
pushProfileFileResource("Bookmarks.plist", Bookmarks);
|
||||
let resources = [];
|
||||
let pushProfileFileResource = function(aFileName, aConstructor) {
|
||||
let file = profileDir.clone();
|
||||
file.append(aFileName);
|
||||
if (file.exists()) {
|
||||
resources.push(new aConstructor(file));
|
||||
}
|
||||
};
|
||||
|
||||
// The Reading List feature was introduced at the same time in Windows and
|
||||
// Mac versions of Safari. Not surprisingly, they are stored in the same
|
||||
// format in both versions. Surpsingly, only on Windows there is a
|
||||
// separate property list for it. This code is used on mac too, because
|
||||
// Apple may fix this at some point.
|
||||
pushProfileFileResource("ReadingList.plist", Bookmarks);
|
||||
pushProfileFileResource("History.plist", History);
|
||||
pushProfileFileResource("Bookmarks.plist", Bookmarks);
|
||||
|
||||
let prefs = this.mainPreferencesPropertyList;
|
||||
if (prefs) {
|
||||
resources.push(new SearchStrings(prefs));
|
||||
// The Reading List feature was introduced at the same time in Windows and
|
||||
// Mac versions of Safari. Not surprisingly, they are stored in the same
|
||||
// format in both versions. Surpsingly, only on Windows there is a
|
||||
// separate property list for it. This code is used on mac too, because
|
||||
// Apple may fix this at some point.
|
||||
pushProfileFileResource("ReadingList.plist", Bookmarks);
|
||||
|
||||
let prefs = this.mainPreferencesPropertyList;
|
||||
if (prefs) {
|
||||
resources.push(new SearchStrings(prefs));
|
||||
}
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
return resources;
|
||||
};
|
||||
|
||||
SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() {
|
||||
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||
let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
|
||||
let path = OS.Path.join(profileDir.path, file);
|
||||
return OS.File.stat(path)
|
||||
.catch(() => null)
|
||||
.then(info => {
|
||||
return info ? info.lastModificationDate : 0;
|
||||
});
|
||||
});
|
||||
return Promise.all(datePromises).then(dates => {
|
||||
return new Date(Math.max.apply(Math, dates));
|
||||
});
|
||||
};
|
||||
|
||||
SafariProfileMigrator.prototype.hasPermissions = async function SM_hasPermissions() {
|
||||
if (this._hasPermissions) {
|
||||
return true;
|
||||
getLastUsedDate() {
|
||||
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||
let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
|
||||
let path = OS.Path.join(profileDir.path, file);
|
||||
return OS.File.stat(path)
|
||||
.catch(() => null)
|
||||
.then(info => {
|
||||
return info ? info.lastModificationDate : 0;
|
||||
});
|
||||
});
|
||||
return Promise.all(datePromises).then(dates => {
|
||||
return new Date(Math.max.apply(Math, dates));
|
||||
});
|
||||
}
|
||||
// Check if we have access:
|
||||
let target = FileUtils.getDir(
|
||||
"ULibDir",
|
||||
["Safari", "Bookmarks.plist"],
|
||||
false
|
||||
);
|
||||
try {
|
||||
// 'stat' is always allowed, but reading is somehow not, if the user hasn't
|
||||
// allowed it:
|
||||
await IOUtils.read(target.path, { maxBytes: 1 });
|
||||
this._hasPermissions = true;
|
||||
return true;
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
SafariProfileMigrator.prototype.getPermissions = async function SM_getPermissions(
|
||||
win
|
||||
) {
|
||||
// Keep prompting the user until they pick a file that grants us access,
|
||||
// or they cancel out of the file open panel.
|
||||
while (!(await this.hasPermissions())) {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
// The title (second arg) is not displayed on macOS, so leave it blank.
|
||||
fp.init(win, "", Ci.nsIFilePicker.modeOpen);
|
||||
// This is a little weird. You'd expect that it matters which file
|
||||
// the user picks, but it doesn't really, as long as it's in this
|
||||
// directory. Anyway, let's not confuse the user: the sensible idea
|
||||
// here is to ask for permissions for Bookmarks.plist, and we'll
|
||||
// silently accept whatever input as long as we can then read the plist.
|
||||
fp.appendFilter("plist", "*.plist");
|
||||
fp.filterIndex = 1;
|
||||
fp.displayDirectory = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||
// Now wait for the filepicker to open and close. If the user picks
|
||||
// any file in this directory, macOS will grant us read access, so
|
||||
// we don't need to check or do anything else with the file returned
|
||||
// by the filepicker.
|
||||
let result = await new Promise(resolve => fp.open(resolve));
|
||||
// Bail if the user cancels the dialog:
|
||||
if (result == Ci.nsIFilePicker.returnCancel) {
|
||||
async hasPermissions() {
|
||||
if (this._hasPermissions) {
|
||||
return true;
|
||||
}
|
||||
// Check if we have access:
|
||||
let target = FileUtils.getDir(
|
||||
"ULibDir",
|
||||
["Safari", "Bookmarks.plist"],
|
||||
false
|
||||
);
|
||||
try {
|
||||
// 'stat' is always allowed, but reading is somehow not, if the user hasn't
|
||||
// allowed it:
|
||||
await IOUtils.read(target.path, { maxBytes: 1 });
|
||||
this._hasPermissions = true;
|
||||
return true;
|
||||
} catch (ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperty(
|
||||
SafariProfileMigrator.prototype,
|
||||
"mainPreferencesPropertyList",
|
||||
{
|
||||
get: function get_mainPreferencesPropertyList() {
|
||||
if (this._mainPreferencesPropertyList === undefined) {
|
||||
let file = FileUtils.getDir("UsrPrfs", [], false);
|
||||
if (file.exists()) {
|
||||
file.append("com.apple.Safari.plist");
|
||||
if (file.exists()) {
|
||||
this._mainPreferencesPropertyList = new MainPreferencesPropertyList(
|
||||
file
|
||||
);
|
||||
return this._mainPreferencesPropertyList;
|
||||
}
|
||||
}
|
||||
this._mainPreferencesPropertyList = null;
|
||||
return this._mainPreferencesPropertyList;
|
||||
async getPermissions(win) {
|
||||
// Keep prompting the user until they pick a file that grants us access,
|
||||
// or they cancel out of the file open panel.
|
||||
while (!(await this.hasPermissions())) {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
// The title (second arg) is not displayed on macOS, so leave it blank.
|
||||
fp.init(win, "", Ci.nsIFilePicker.modeOpen);
|
||||
// This is a little weird. You'd expect that it matters which file
|
||||
// the user picks, but it doesn't really, as long as it's in this
|
||||
// directory. Anyway, let's not confuse the user: the sensible idea
|
||||
// here is to ask for permissions for Bookmarks.plist, and we'll
|
||||
// silently accept whatever input as long as we can then read the plist.
|
||||
fp.appendFilter("plist", "*.plist");
|
||||
fp.filterIndex = 1;
|
||||
fp.displayDirectory = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||
// Now wait for the filepicker to open and close. If the user picks
|
||||
// any file in this directory, macOS will grant us read access, so
|
||||
// we don't need to check or do anything else with the file returned
|
||||
// by the filepicker.
|
||||
let result = await new Promise(resolve => fp.open(resolve));
|
||||
// Bail if the user cancels the dialog:
|
||||
if (result == Ci.nsIFilePicker.returnCancel) {
|
||||
return false;
|
||||
}
|
||||
return this._mainPreferencesPropertyList;
|
||||
},
|
||||
}
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
|
||||
SafariProfileMigrator.prototype.contractID =
|
||||
"@mozilla.org/profile/migrator;1?app=browser&type=safari";
|
||||
SafariProfileMigrator.prototype.classID = Components.ID(
|
||||
"{4b609ecf-60b2-4655-9df4-dc149e474da1}"
|
||||
);
|
||||
get mainPreferencesPropertyList() {
|
||||
if (this._mainPreferencesPropertyList === undefined) {
|
||||
let file = FileUtils.getDir("UsrPrfs", [], false);
|
||||
if (file.exists()) {
|
||||
file.append("com.apple.Safari.plist");
|
||||
if (file.exists()) {
|
||||
this._mainPreferencesPropertyList = new MainPreferencesPropertyList(
|
||||
file
|
||||
);
|
||||
return this._mainPreferencesPropertyList;
|
||||
}
|
||||
}
|
||||
this._mainPreferencesPropertyList = null;
|
||||
return this._mainPreferencesPropertyList;
|
||||
}
|
||||
return this._mainPreferencesPropertyList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ EXTRA_JS_MODULES += [
|
||||
"ChromeProfileMigrator.sys.mjs",
|
||||
"FirefoxProfileMigrator.sys.mjs",
|
||||
"MigrationUtils.sys.mjs",
|
||||
"MigratorBase.sys.mjs",
|
||||
"ProfileMigrator.sys.mjs",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
var { MigrationUtils, MigratorPrototype } = ChromeUtils.importESModule(
|
||||
var { MigrationUtils } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MigrationUtils.sys.mjs"
|
||||
);
|
||||
var { LoginHelper } = ChromeUtils.import(
|
||||
@@ -42,7 +42,7 @@ updateAppInfo();
|
||||
/**
|
||||
* Migrates the requested resource and waits for the migration to be complete.
|
||||
*
|
||||
* @param {MigratorPrototype} migrator
|
||||
* @param {MigratorBase} migrator
|
||||
* The migrator being used to migrate the data.
|
||||
* @param {number} resourceType
|
||||
* This is a bitfield with bits from nsIBrowserProfileMigrator flipped to indicate what
|
||||
|
||||
Reference in New Issue
Block a user