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:
Mike Conley
2022-12-06 17:50:39 +00:00
parent 6d71851894
commit 56e95c55b0
9 changed files with 1600 additions and 1481 deletions

View File

@@ -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,16 +68,32 @@ 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() {
get _chromeUserDataPathSuffix() {
return "Chrome";
}
_keychainServiceName = "Chrome Safe Storage";
_keychainAccountName = "Chrome";
async _getChromeUserDataPathIfExists() {
if (this._chromeUserDataPath) {
return this._chromeUserDataPath;
}
@@ -95,11 +107,9 @@ ChromeProfileMigrator.prototype._getChromeUserDataPathIfExists = async function(
this._chromeUserDataPath = null;
}
return this._chromeUserDataPath;
};
}
ChromeProfileMigrator.prototype.getResources = async function Chrome_getResources(
aProfile
) {
async getResources(aProfile) {
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
if (chromeUserDataPath) {
let profileFolder = chromeUserDataPath;
@@ -122,9 +132,9 @@ ChromeProfileMigrator.prototype.getResources = async function Chrome_getResource
}
}
return [];
};
}
ChromeProfileMigrator.prototype.getLastUsedDate = async function Chrome_getLastUsedDate() {
async getLastUsedDate() {
let sourceProfiles = await this.getSourceProfiles();
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
if (!chromeUserDataPath) {
@@ -145,9 +155,9 @@ ChromeProfileMigrator.prototype.getLastUsedDate = async function Chrome_getLastU
let datesOuter = await Promise.all(datePromises);
datesOuter.push(0);
return new Date(Math.max(...datesOuter));
};
}
ChromeProfileMigrator.prototype.getSourceProfiles = async function Chrome_getSourceProfiles() {
async getSourceProfiles() {
if ("__sourceProfiles" in this) {
return this.__sourceProfiles;
}
@@ -201,8 +211,155 @@ ChromeProfileMigrator.prototype.getSourceProfiles = async function Chrome_getSou
}, 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;
}
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);
},
};
};
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
* Chromium migrator
*/
export function ChromiumProfileMigrator() {
this._chromeUserDataPathSuffix = "Chromium";
this._keychainServiceName = "Chromium Safe Storage";
this._keychainAccountName = "Chromium";
export class ChromiumProfileMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chromium Profile Migrator";
}
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}"
);
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
}
get classID() {
return Components.ID("{8cece922-9720-42de-b7db-7cef88cb07ca}");
}
_chromeUserDataPathSuffix = "Chromium";
_keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
}
/**
* 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 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}"
);
export function BraveProfileMigrator() {
this._chromeUserDataPathSuffix = "Brave";
this._keychainServiceName = "Brave Browser Safe Storage";
this._keychainAccountName = "Brave Browser";
export class ChromeDevMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chrome Dev Profile Migrator";
}
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}"
);
export function ChromiumEdgeMigrator() {
this._chromeUserDataPathSuffix = "Edge";
this._keychainServiceName = "Microsoft Edge Safe Storage";
this._keychainAccountName = "Microsoft Edge";
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-dev";
}
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}"
);
export function ChromiumEdgeBetaMigrator() {
this._chromeUserDataPathSuffix = "Edge Beta";
this._keychainServiceName = "Microsoft Edge Safe Storage";
this._keychainAccountName = "Microsoft Edge";
get classID() {
return Components.ID("{7370a02a-4886-42c3-a4ec-d48c726ec30a}");
}
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}"
);
export function Chromium360seMigrator() {
this._chromeUserDataPathSuffix = "360 SE";
_chromeUserDataPathSuffix = "Chrome Dev";
_keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
}
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";
/**
* Chrome Beta migrator
*/
export class ChromeBetaMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chrome Beta Profile Migrator";
}
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() {
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-beta";
}
get classID() {
return Components.ID("{47f75963-840b-4950-a1f0-d9c1864f8b8e}");
}
_chromeUserDataPathSuffix = "Chrome Beta";
_keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
}
/**
* 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;
};
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() {
}
/**
* 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;
};
export function VivaldiProfileMigrator() {
this._chromeUserDataPathSuffix = "Vivaldi";
this._keychainServiceName = "Vivaldi Safe Storage";
this._keychainAccountName = "Vivaldi";
}
}
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}"
);
/**
* 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";
}

View File

@@ -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,25 +464,36 @@ EdgeBookmarksMigrator.prototype = {
},
};
export function EdgeProfileMigrator() {
/**
* Edge (EdgeHTML) profile migrator
*/
export class EdgeProfileMigrator extends MigratorBase {
constructor() {
super();
this.wrappedJSObject = this;
}
EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
get classDescription() {
return "Edge Profile Migrator";
}
EdgeProfileMigrator.prototype.getBookmarksMigratorForTesting = function(
dbOverride
) {
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);
};
}
EdgeProfileMigrator.prototype.getReadingListMigratorForTesting = function(
dbOverride
) {
getReadingListMigratorForTesting(dbOverride) {
return new EdgeReadingListMigrator(dbOverride);
};
}
EdgeProfileMigrator.prototype.getResources = function() {
getResources() {
let resources = [
new EdgeBookmarksMigrator(),
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
@@ -496,9 +505,9 @@ EdgeProfileMigrator.prototype.getResources = function() {
windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
resources.push(windowsVaultFormPasswordsMigrator);
return resources.filter(r => r.exists);
};
}
EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
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();
@@ -534,21 +543,18 @@ EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
return Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, dates));
});
};
}
/* Somewhat counterintuitively, this returns:
/**
* @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.jsm for slightly more info on how sourceProfiles is used.
* - |[]| 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.
*/
EdgeProfileMigrator.prototype.getSourceProfiles = function() {
getSourceProfiles() {
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
return isWin10OrHigher ? null : [];
};
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}"
);
}
}

View File

@@ -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,17 +24,37 @@ ChromeUtils.defineESModuleGetters(lazy, {
SessionMigration: "resource:///modules/sessionstore/SessionMigration.sys.mjs",
});
export function FirefoxProfileMigrator() {
/**
* 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...
}
FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
get classDescription() {
return "Firefox Profile Migrator";
}
FirefoxProfileMigrator.prototype._getAllProfiles = function() {
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=firefox";
}
get classID() {
return Components.ID("{91185366-ba97-4438-acba-48deaca63386}");
}
_getAllProfiles() {
let allProfiles = new Map();
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
Ci.nsIToolkitProfileService
);
let profileService = Cc[
"@mozilla.org/toolkit/profile-service;1"
].getService(Ci.nsIToolkitProfileService);
for (let profile of profileService.profiles) {
let rootDir = profile.rootDir;
@@ -48,19 +67,19 @@ FirefoxProfileMigrator.prototype._getAllProfiles = function() {
}
}
return allProfiles;
};
function sorter(a, b) {
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
}
FirefoxProfileMigrator.prototype.getSourceProfiles = function() {
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);
};
}
FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) {
_getFileObject(dir, fileName) {
let file = dir.clone();
file.append(fileName);
@@ -68,9 +87,9 @@ FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) {
// 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) {
getResources(aProfile) {
let sourceProfileDir = aProfile
? this._getAllProfiles().get(aProfile.id)
: Cc["@mozilla.org/toolkit/profile-service;1"].getService(
@@ -94,19 +113,16 @@ FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
}
return this._getResourcesInternal(sourceProfileDir, currentProfileDir);
};
}
FirefoxProfileMigrator.prototype.getLastUsedDate = function() {
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));
};
}
FirefoxProfileMigrator.prototype._getResourcesInternal = function(
sourceProfileDir,
currentProfileDir
) {
_getResourcesInternal(sourceProfileDir, currentProfileDir) {
let getFileResource = (aMigrationType, aFileNames) => {
let files = [];
for (let fileName of aFileNames) {
@@ -351,15 +367,9 @@ FirefoxProfileMigrator.prototype._getResourcesInternal = function(
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;
}
}

View File

@@ -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,13 +349,28 @@ IE7FormPasswords.prototype = {
},
};
export function IEProfileMigrator() {
/**
* 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.
}
IEProfileMigrator.prototype = Object.create(MigratorPrototype);
get classDescription() {
return "IE Profile Migrator";
}
IEProfileMigrator.prototype.getResources = function IE_getResources() {
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(),
@@ -371,9 +384,9 @@ IEProfileMigrator.prototype.getResources = function IE_getResources() {
windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
resources.push(windowsVaultFormPasswordsMigrator);
return resources.filter(r => r.exists);
};
}
IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
getLastUsedDate() {
let datePromises = ["Favs", "CookD"].map(dirId => {
let { path } = Services.dirsvc.get(dirId, Ci.nsIFile);
return OS.File.stat(path)
@@ -398,11 +411,5 @@ IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
return Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, dates));
});
};
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}"
);
}
}

View File

@@ -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 };

View 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];
}
}

View File

@@ -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,11 +342,23 @@ SearchStrings.prototype = {
},
};
export function SafariProfileMigrator() {}
/**
* Safari migrator
*/
export class SafariProfileMigrator extends MigratorBase {
get classDescription() {
return "Safari Profile Migrator";
}
SafariProfileMigrator.prototype = Object.create(MigratorPrototype);
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=safari";
}
SafariProfileMigrator.prototype.getResources = function SM_getResources() {
get classID() {
return Components.ID("{4b609ecf-60b2-4655-9df4-dc149e474da1}");
}
getResources() {
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
if (!profileDir.exists()) {
return null;
@@ -379,9 +389,9 @@ SafariProfileMigrator.prototype.getResources = function SM_getResources() {
}
return resources;
};
}
SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() {
getLastUsedDate() {
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
let path = OS.Path.join(profileDir.path, file);
@@ -394,9 +404,9 @@ SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate()
return Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, dates));
});
};
}
SafariProfileMigrator.prototype.hasPermissions = async function SM_hasPermissions() {
async hasPermissions() {
if (this._hasPermissions) {
return true;
}
@@ -415,11 +425,9 @@ SafariProfileMigrator.prototype.hasPermissions = async function SM_hasPermission
} catch (ex) {
return false;
}
};
}
SafariProfileMigrator.prototype.getPermissions = async function SM_getPermissions(
win
) {
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())) {
@@ -444,13 +452,10 @@ SafariProfileMigrator.prototype.getPermissions = async function SM_getPermission
return false;
}
}
};
return true;
}
Object.defineProperty(
SafariProfileMigrator.prototype,
"mainPreferencesPropertyList",
{
get: function get_mainPreferencesPropertyList() {
get mainPreferencesPropertyList() {
if (this._mainPreferencesPropertyList === undefined) {
let file = FileUtils.getDir("UsrPrfs", [], false);
if (file.exists()) {
@@ -466,13 +471,5 @@ Object.defineProperty(
return this._mainPreferencesPropertyList;
}
return this._mainPreferencesPropertyList;
},
}
);
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}"
);
}

View File

@@ -24,6 +24,7 @@ EXTRA_JS_MODULES += [
"ChromeProfileMigrator.sys.mjs",
"FirefoxProfileMigrator.sys.mjs",
"MigrationUtils.sys.mjs",
"MigratorBase.sys.mjs",
"ProfileMigrator.sys.mjs",
]

View File

@@ -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