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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
import { import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
MigratorPrototype,
MigrationUtils,
} from "resource:///modules/MigrationUtils.sys.mjs";
const lazy = {}; const lazy = {};
@@ -72,16 +68,32 @@ function convertBookmarks(items, errorAccumulator) {
return itemsToInsert; 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"; get classID() {
ChromeProfileMigrator.prototype._keychainAccountName = "Chrome"; 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) { if (this._chromeUserDataPath) {
return this._chromeUserDataPath; return this._chromeUserDataPath;
} }
@@ -95,11 +107,9 @@ ChromeProfileMigrator.prototype._getChromeUserDataPathIfExists = async function(
this._chromeUserDataPath = null; this._chromeUserDataPath = null;
} }
return this._chromeUserDataPath; return this._chromeUserDataPath;
}; }
ChromeProfileMigrator.prototype.getResources = async function Chrome_getResources( async getResources(aProfile) {
aProfile
) {
let chromeUserDataPath = await this._getChromeUserDataPathIfExists(); let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
if (chromeUserDataPath) { if (chromeUserDataPath) {
let profileFolder = chromeUserDataPath; let profileFolder = chromeUserDataPath;
@@ -122,9 +132,9 @@ ChromeProfileMigrator.prototype.getResources = async function Chrome_getResource
} }
} }
return []; return [];
}; }
ChromeProfileMigrator.prototype.getLastUsedDate = async function Chrome_getLastUsedDate() { async getLastUsedDate() {
let sourceProfiles = await this.getSourceProfiles(); let sourceProfiles = await this.getSourceProfiles();
let chromeUserDataPath = await this._getChromeUserDataPathIfExists(); let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
if (!chromeUserDataPath) { if (!chromeUserDataPath) {
@@ -145,9 +155,9 @@ ChromeProfileMigrator.prototype.getLastUsedDate = async function Chrome_getLastU
let datesOuter = await Promise.all(datePromises); let datesOuter = await Promise.all(datePromises);
datesOuter.push(0); datesOuter.push(0);
return new Date(Math.max(...datesOuter)); return new Date(Math.max(...datesOuter));
}; }
ChromeProfileMigrator.prototype.getSourceProfiles = async function Chrome_getSourceProfiles() { async getSourceProfiles() {
if ("__sourceProfiles" in this) { if ("__sourceProfiles" in this) {
return this.__sourceProfiles; return this.__sourceProfiles;
} }
@@ -201,7 +211,154 @@ ChromeProfileMigrator.prototype.getSourceProfiles = async function Chrome_getSou
}, this) }, this)
.map(({ profile }) => profile); .map(({ profile }) => profile);
return this.__sourceProfiles; 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) { async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
let bookmarksPath = PathUtils.join(aProfileFolder, "Bookmarks"); 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() { export class ChromiumProfileMigrator extends ChromeProfileMigrator {
this._chromeUserDataPathSuffix = "Chromium"; get classDescription() {
this._keychainServiceName = "Chromium Safe Storage"; return "Chromium Profile Migrator";
this._keychainAccountName = "Chromium"; }
}
ChromiumProfileMigrator.prototype = Object.create( get contractID() {
ChromeProfileMigrator.prototype return "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
); }
ChromiumProfileMigrator.prototype.classDescription =
"Chromium Profile Migrator"; get classID() {
ChromiumProfileMigrator.prototype.contractID = return Components.ID("{8cece922-9720-42de-b7db-7cef88cb07ca}");
"@mozilla.org/profile/migrator;1?app=browser&type=chromium"; }
ChromiumProfileMigrator.prototype.classID = Components.ID(
"{8cece922-9720-42de-b7db-7cef88cb07ca}" _chromeUserDataPathSuffix = "Chromium";
); _keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
}
/** /**
* Chrome Canary * Chrome Canary
* Not available on Linux * Not available on Linux
*/ */
export function CanaryProfileMigrator() { export class CanaryProfileMigrator extends ChromeProfileMigrator {
this._chromeUserDataPathSuffix = "Canary"; 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) * Chrome Dev - Linux only (not available in Mac and Windows)
*/ */
export function ChromeDevMigrator() { export class ChromeDevMigrator extends ChromeProfileMigrator {
this._chromeUserDataPathSuffix = "Chrome Dev"; get classDescription() {
} return "Chrome Dev Profile Migrator";
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() { get contractID() {
this._chromeUserDataPathSuffix = "Chrome Beta"; return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-dev";
} }
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() { get classID() {
this._chromeUserDataPathSuffix = "Brave"; return Components.ID("{7370a02a-4886-42c3-a4ec-d48c726ec30a}");
this._keychainServiceName = "Brave Browser Safe Storage"; }
this._keychainAccountName = "Brave Browser";
_chromeUserDataPathSuffix = "Chrome Dev";
_keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
} }
BraveProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype); /**
BraveProfileMigrator.prototype.classDescription = "Brave Browser Migrator"; * Chrome Beta migrator
BraveProfileMigrator.prototype.contractID = */
"@mozilla.org/profile/migrator;1?app=browser&type=brave"; export class ChromeBetaMigrator extends ChromeProfileMigrator {
BraveProfileMigrator.prototype.classID = Components.ID( get classDescription() {
"{4071880a-69e4-4c83-88b4-6c589a62801d}" return "Chrome Beta Profile Migrator";
); }
export function ChromiumEdgeMigrator() { get contractID() {
this._chromeUserDataPathSuffix = "Edge"; return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-beta";
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}"
);
export function ChromiumEdgeBetaMigrator() { get classID() {
this._chromeUserDataPathSuffix = "Edge Beta"; return Components.ID("{47f75963-840b-4950-a1f0-d9c1864f8b8e}");
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}"
);
export function Chromium360seMigrator() { _chromeUserDataPathSuffix = "Chrome Beta";
this._chromeUserDataPathSuffix = "360 SE"; _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"; * Brave migrator
this._keychainServiceName = "Opera Browser Safe Storage"; */
this._keychainAccountName = "Opera Browser"; 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";
} }
OperaProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
OperaProfileMigrator.prototype.classDescription = "Opera Browser Migrator"; /**
OperaProfileMigrator.prototype.contractID = * Edge (Chromium-based) migrator
"@mozilla.org/profile/migrator;1?app=browser&type=opera"; */
OperaProfileMigrator.prototype.classID = Components.ID( export class ChromiumEdgeMigrator extends ChromeProfileMigrator {
"{16c5d501-e411-41eb-93f2-af6c9ba64dee}" get classDescription() {
); return "Chromium Edge Profile Migrator";
OperaProfileMigrator.prototype.getSourceProfiles = function() { }
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; 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 /**
); * Opera GX migrator
OperaGXProfileMigrator.prototype.classDescription = "Opera GX Browser Migrator"; */
OperaGXProfileMigrator.prototype.contractID = export class OperaGXProfileMigrator extends ChromeProfileMigrator {
"@mozilla.org/profile/migrator;1?app=browser&type=opera-gx"; get classDescription() {
OperaGXProfileMigrator.prototype.classID = Components.ID( return "Opera GX Browser Migrator";
"{26F4E0A0-B533-4FDA-B344-6FF5DA45D6DC}" }
);
OperaGXProfileMigrator.prototype.getSourceProfiles = function() { 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; return null;
}; }
export function VivaldiProfileMigrator() {
this._chromeUserDataPathSuffix = "Vivaldi";
this._keychainServiceName = "Vivaldi Safe Storage";
this._keychainAccountName = "Vivaldi";
} }
VivaldiProfileMigrator.prototype = Object.create( /**
ChromeProfileMigrator.prototype * Vivaldi migrator
); */
VivaldiProfileMigrator.prototype.classDescription = "Vivaldi Migrator"; export class VivaldiProfileMigrator extends ChromeProfileMigrator {
VivaldiProfileMigrator.prototype.contractID = get classDescription() {
"@mozilla.org/profile/migrator;1?app=browser&type=vivaldi"; return "Vivaldi Migrator";
VivaldiProfileMigrator.prototype.classID = Components.ID( }
"{54a6a025-e70d-49dd-ba95-0f7e45d728d3}"
); 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
MigrationUtils, import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
MigratorPrototype,
} from "resource:///modules/MigrationUtils.sys.mjs";
import { MSMigrationUtils } from "resource:///modules/MSMigrationUtils.sys.mjs"; import { MSMigrationUtils } from "resource:///modules/MSMigrationUtils.sys.mjs";
const lazy = {}; 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; this.wrappedJSObject = this;
} }
EdgeProfileMigrator.prototype = Object.create(MigratorPrototype); get classDescription() {
return "Edge Profile Migrator";
}
EdgeProfileMigrator.prototype.getBookmarksMigratorForTesting = function( get contractID() {
dbOverride 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); return new EdgeBookmarksMigrator(dbOverride);
}; }
EdgeProfileMigrator.prototype.getReadingListMigratorForTesting = function( getReadingListMigratorForTesting(dbOverride) {
dbOverride
) {
return new EdgeReadingListMigrator(dbOverride); return new EdgeReadingListMigrator(dbOverride);
}; }
EdgeProfileMigrator.prototype.getResources = function() { getResources() {
let resources = [ let resources = [
new EdgeBookmarksMigrator(), new EdgeBookmarksMigrator(),
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE), MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
@@ -496,9 +505,9 @@ EdgeProfileMigrator.prototype.getResources = function() {
windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords"; windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
resources.push(windowsVaultFormPasswordsMigrator); resources.push(windowsVaultFormPasswordsMigrator);
return resources.filter(r => r.exists); 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 // 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: // sourceProfiles) or if we can't find the database file:
let sourceProfiles = await this.getSourceProfiles(); let sourceProfiles = await this.getSourceProfiles();
@@ -534,21 +543,18 @@ EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
return Promise.all(datePromises).then(dates => { return Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, 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+) * - |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. * - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid
* See MigrationUtils.jsm for slightly more info on how sourceProfiles is used. * 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"); let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
return isWin10OrHigher ? null : []; 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. * from the source profile.
*/ */
import { import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
MigrationUtils,
MigratorPrototype, import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
} from "resource:///modules/MigrationUtils.sys.mjs";
const lazy = {}; const lazy = {};
@@ -25,17 +24,37 @@ ChromeUtils.defineESModuleGetters(lazy, {
SessionMigration: "resource:///modules/sessionstore/SessionMigration.sys.mjs", 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... 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 allProfiles = new Map();
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService( let profileService = Cc[
Ci.nsIToolkitProfileService "@mozilla.org/toolkit/profile-service;1"
); ].getService(Ci.nsIToolkitProfileService);
for (let profile of profileService.profiles) { for (let profile of profileService.profiles) {
let rootDir = profile.rootDir; let rootDir = profile.rootDir;
@@ -48,19 +67,19 @@ FirefoxProfileMigrator.prototype._getAllProfiles = function() {
} }
} }
return allProfiles; return allProfiles;
}; }
function sorter(a, b) { getSourceProfiles() {
let sorter = (a, b) => {
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase()); return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
} };
FirefoxProfileMigrator.prototype.getSourceProfiles = function() {
return [...this._getAllProfiles().keys()] return [...this._getAllProfiles().keys()]
.map(x => ({ id: x, name: x })) .map(x => ({ id: x, name: x }))
.sort(sorter); .sort(sorter);
}; }
FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) { _getFileObject(dir, fileName) {
let file = dir.clone(); let file = dir.clone();
file.append(fileName); 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 // they are not expected to work alone. Return null to avoid trying to
// copy non-existing files. // copy non-existing files.
return file.exists() ? file : null; return file.exists() ? file : null;
}; }
FirefoxProfileMigrator.prototype.getResources = function(aProfile) { getResources(aProfile) {
let sourceProfileDir = aProfile let sourceProfileDir = aProfile
? this._getAllProfiles().get(aProfile.id) ? this._getAllProfiles().get(aProfile.id)
: Cc["@mozilla.org/toolkit/profile-service;1"].getService( : Cc["@mozilla.org/toolkit/profile-service;1"].getService(
@@ -94,19 +113,16 @@ FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
} }
return this._getResourcesInternal(sourceProfileDir, currentProfileDir); return this._getResourcesInternal(sourceProfileDir, currentProfileDir);
}; }
FirefoxProfileMigrator.prototype.getLastUsedDate = function() { getLastUsedDate() {
// We always pretend we're really old, so that we don't mess // We always pretend we're really old, so that we don't mess
// up the determination of which browser is the most 'recent' // up the determination of which browser is the most 'recent'
// to import from. // to import from.
return Promise.resolve(new Date(0)); return Promise.resolve(new Date(0));
}; }
FirefoxProfileMigrator.prototype._getResourcesInternal = function( _getResourcesInternal(sourceProfileDir, currentProfileDir) {
sourceProfileDir,
currentProfileDir
) {
let getFileResource = (aMigrationType, aFileNames) => { let getFileResource = (aMigrationType, aFileNames) => {
let files = []; let files = [];
for (let fileName of aFileNames) { for (let fileName of aFileNames) {
@@ -351,15 +367,9 @@ FirefoxProfileMigrator.prototype._getResourcesInternal = function(
telemetry, telemetry,
favicons, favicons,
].filter(r => r); ].filter(r => r);
}; }
Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", { get startupOnlyMigrator() {
get: () => true, return 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}"
);

View File

@@ -8,10 +8,8 @@ const kLoginsKey =
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
import { import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
MigrationUtils, import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
MigratorPrototype,
} from "resource:///modules/MigrationUtils.sys.mjs";
import { MSMigrationUtils } from "resource:///modules/MSMigrationUtils.sys.mjs"; import { MSMigrationUtils } from "resource:///modules/MSMigrationUtils.sys.mjs";
const lazy = {}; 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. 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 = [ let resources = [
MSMigrationUtils.getBookmarksMigrator(), MSMigrationUtils.getBookmarksMigrator(),
new History(), new History(),
@@ -371,9 +384,9 @@ IEProfileMigrator.prototype.getResources = function IE_getResources() {
windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords"; windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
resources.push(windowsVaultFormPasswordsMigrator); resources.push(windowsVaultFormPasswordsMigrator);
return resources.filter(r => r.exists); return resources.filter(r => r.exists);
}; }
IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() { getLastUsedDate() {
let datePromises = ["Favs", "CookD"].map(dirId => { let datePromises = ["Favs", "CookD"].map(dirId => {
let { path } = Services.dirsvc.get(dirId, Ci.nsIFile); let { path } = Services.dirsvc.get(dirId, Ci.nsIFile);
return OS.File.stat(path) return OS.File.stat(path)
@@ -398,11 +411,5 @@ IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
return Promise.all(datePromises).then(dates => { return Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, 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 * 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/. */ * 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"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {}; const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
PlacesUIUtils: "resource:///modules/PlacesUIUtils.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", Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs", setTimeout: "resource://gre/modules/Timer.sys.mjs",
WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs", WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
@@ -44,475 +35,12 @@ function getL10n() {
return gL10n; 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 * The singleton MigrationUtils service. This service is the primary mechanism
* by which migrations from other browsers to this browser occur. The singleton * by which migrations from other browsers to this browser occur. The singleton
* instance of this class is exported from this module as `MigrationUtils`. * instance of this class is exported from this module as `MigrationUtils`.
*/ */
class MigrationUtilsSingleton { class MigrationUtils {
resourceTypes = Object.freeze({ resourceTypes = Object.freeze({
COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES, COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY, HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,
@@ -525,7 +53,7 @@ class MigrationUtilsSingleton {
/** /**
* Helper for implementing simple asynchronous cases of migration resources' * 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 * 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 * everything right away, you can wrap the async-function with this helper
* and not worry about notifying the callback. * and not worry about notifying the callback.
@@ -711,7 +239,7 @@ class MigrationUtilsSingleton {
* Internal name of the migration source. See `availableMigratorKeys` * Internal name of the migration source. See `availableMigratorKeys`
* for supported values by OS. * for supported values by OS.
* *
* @returns {MigratorPrototype} * @returns {MigratorBase}
* A profile migrator implementing nsIBrowserProfileMigrator, if it can * A profile migrator implementing nsIBrowserProfileMigrator, if it can
* import any data, null otherwise. * 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"; import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm"); const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
import { import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
MigrationUtils, import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
MigratorPrototype,
} from "resource:///modules/MigrationUtils.sys.mjs";
const lazy = {}; 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); let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
if (!profileDir.exists()) { if (!profileDir.exists()) {
return null; return null;
@@ -379,9 +389,9 @@ SafariProfileMigrator.prototype.getResources = function SM_getResources() {
} }
return resources; return resources;
}; }
SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() { getLastUsedDate() {
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false); let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
let datePromises = ["Bookmarks.plist", "History.plist"].map(file => { let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
let path = OS.Path.join(profileDir.path, 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 Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, dates)); return new Date(Math.max.apply(Math, dates));
}); });
}; }
SafariProfileMigrator.prototype.hasPermissions = async function SM_hasPermissions() { async hasPermissions() {
if (this._hasPermissions) { if (this._hasPermissions) {
return true; return true;
} }
@@ -415,11 +425,9 @@ SafariProfileMigrator.prototype.hasPermissions = async function SM_hasPermission
} catch (ex) { } catch (ex) {
return false; return false;
} }
}; }
SafariProfileMigrator.prototype.getPermissions = async function SM_getPermissions( async getPermissions(win) {
win
) {
// Keep prompting the user until they pick a file that grants us access, // Keep prompting the user until they pick a file that grants us access,
// or they cancel out of the file open panel. // or they cancel out of the file open panel.
while (!(await this.hasPermissions())) { while (!(await this.hasPermissions())) {
@@ -444,13 +452,10 @@ SafariProfileMigrator.prototype.getPermissions = async function SM_getPermission
return false; return false;
} }
} }
}; return true;
}
Object.defineProperty( get mainPreferencesPropertyList() {
SafariProfileMigrator.prototype,
"mainPreferencesPropertyList",
{
get: function get_mainPreferencesPropertyList() {
if (this._mainPreferencesPropertyList === undefined) { if (this._mainPreferencesPropertyList === undefined) {
let file = FileUtils.getDir("UsrPrfs", [], false); let file = FileUtils.getDir("UsrPrfs", [], false);
if (file.exists()) { if (file.exists()) {
@@ -466,13 +471,5 @@ Object.defineProperty(
return this._mainPreferencesPropertyList; return this._mainPreferencesPropertyList;
} }
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", "ChromeProfileMigrator.sys.mjs",
"FirefoxProfileMigrator.sys.mjs", "FirefoxProfileMigrator.sys.mjs",
"MigrationUtils.sys.mjs", "MigrationUtils.sys.mjs",
"MigratorBase.sys.mjs",
"ProfileMigrator.sys.mjs", "ProfileMigrator.sys.mjs",
] ]

View File

@@ -1,6 +1,6 @@
"use strict"; "use strict";
var { MigrationUtils, MigratorPrototype } = ChromeUtils.importESModule( var { MigrationUtils } = ChromeUtils.importESModule(
"resource:///modules/MigrationUtils.sys.mjs" "resource:///modules/MigrationUtils.sys.mjs"
); );
var { LoginHelper } = ChromeUtils.import( var { LoginHelper } = ChromeUtils.import(
@@ -42,7 +42,7 @@ updateAppInfo();
/** /**
* Migrates the requested resource and waits for the migration to be complete. * 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. * The migrator being used to migrate the data.
* @param {number} resourceType * @param {number} resourceType
* This is a bitfield with bits from nsIBrowserProfileMigrator flipped to indicate what * This is a bitfield with bits from nsIBrowserProfileMigrator flipped to indicate what