Bug 1802961 - Convert MigratorPrototype into an ES6 class and move into its own ESM as MigratorBase to be subclassed. r=NeilDeakin
Differential Revision: https://phabricator.services.mozilla.com/D163257
This commit is contained in:
@@ -11,13 +11,9 @@ const AUTH_TYPE = {
|
||||
};
|
||||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
|
||||
import {
|
||||
MigratorPrototype,
|
||||
MigrationUtils,
|
||||
} from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
@@ -72,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";
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,13 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const TOPIC_WILL_IMPORT_BOOKMARKS =
|
||||
"initial-migration-will-import-default-bookmarks";
|
||||
const TOPIC_DID_IMPORT_BOOKMARKS =
|
||||
"initial-migration-did-import-default-bookmarks";
|
||||
const TOPIC_PLACES_DEFAULTS_FINISHED = "places-browser-init-complete";
|
||||
|
||||
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.sys.mjs",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
|
||||
PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
|
||||
PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
|
||||
ResponsivenessMonitor: "resource://gre/modules/ResponsivenessMonitor.sys.mjs",
|
||||
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
|
||||
@@ -44,475 +35,12 @@ function getL10n() {
|
||||
return gL10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} MigratorResource
|
||||
* A resource returned by a subclass of MigratorPrototype that can migrate
|
||||
* data to this browser.
|
||||
* @property {number} type
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what this resource represents. A resource can represent one or more types
|
||||
* of data, for example HISTORY and FORMDATA.
|
||||
* @property {Function} migrate
|
||||
* A function that will actually perform the migration of this resource's
|
||||
* data into this browser.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
|
||||
*
|
||||
* To implement a migrator:
|
||||
* 1. Import this module.
|
||||
* 2. Create the prototype for the migrator, extending MigratorPrototype.
|
||||
* Namely: MosaicMigrator.prototype = Object.create(MigratorPrototype);
|
||||
* 3. Set classDescription, contractID and classID for your migrator, and set
|
||||
* NSGetFactory appropriately.
|
||||
* 4. If the migrator supports multiple profiles, override the sourceProfiles
|
||||
* Here we default for single-profile migrator.
|
||||
* 5. Implement getResources(aProfile) (see below).
|
||||
* 6. For startup-only migrators, override |startupOnlyMigrator|.
|
||||
*/
|
||||
export var MigratorPrototype = {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIBrowserProfileMigrator"]),
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the source supports multiple profiles.
|
||||
*
|
||||
* Returns array of profile objects from which data may be imported. The object
|
||||
* should have the following keys:
|
||||
* id - a unique string identifier for the profile
|
||||
* name - a pretty name to display to the user in the UI
|
||||
*
|
||||
* Only profiles from which data can be imported should be listed. Otherwise
|
||||
* the behavior of the migration wizard isn't well-defined.
|
||||
*
|
||||
* For a single-profile source (e.g. safari, ie), this returns null,
|
||||
* and not an empty array. That is the default implementation.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {object[]|null}
|
||||
*/
|
||||
getSourceProfiles() {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* MUST BE OVERRIDDEN.
|
||||
*
|
||||
* Returns an array of "migration resources" objects for the given profile,
|
||||
* or for the "default" profile, if the migrator does not support multiple
|
||||
* profiles.
|
||||
*
|
||||
* Each migration resource should provide:
|
||||
* - a |type| getter, returning any of the migration types (see
|
||||
* nsIBrowserProfileMigrator).
|
||||
*
|
||||
* - a |migrate| method, taking a single argument, aCallback(bool success),
|
||||
* for migrating the data for this resource. It may do its job
|
||||
* synchronously or asynchronously. Either way, it must call
|
||||
* aCallback(bool aSuccess) when it's done. In the case of an exception
|
||||
* thrown from |migrate|, it's taken as if aCallback(false) is called.
|
||||
*
|
||||
* Note: In the case of a simple asynchronous implementation, you may find
|
||||
* MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
|
||||
*
|
||||
* For each migration type listed in nsIBrowserProfileMigrator, multiple
|
||||
* migration resources may be provided. This practice is useful when the
|
||||
* data for a certain migration type is independently stored in few
|
||||
* locations. For example, the mac version of Safari stores its "reading list"
|
||||
* bookmarks in a separate property list.
|
||||
*
|
||||
* Note that the importation of a particular migration type is reported as
|
||||
* successful if _any_ of its resources succeeded to import (that is, called,
|
||||
* |aCallback(true)|). However, completion-status for a particular migration
|
||||
* type is reported to the UI only once all of its migrators have called
|
||||
* aCallback.
|
||||
*
|
||||
* NOTE: The returned array should only include resources from which data
|
||||
* can be imported. So, for example, before adding a resource for the
|
||||
* BOOKMARKS migration type, you should check if you should check that the
|
||||
* bookmarks file exists.
|
||||
*
|
||||
* @abstract
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* In the case of multiple-profiles migrator, it is guaranteed that
|
||||
* aProfile is a value returned by the sourceProfiles getter (see
|
||||
* above).
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getResources: function MP_getResources(aProfile) {
|
||||
throw new Error("getResources must be overridden");
|
||||
},
|
||||
|
||||
/**
|
||||
* OVERRIDE in order to provide an estimate of when the last time was
|
||||
* that somebody used the browser. It is OK that this is somewhat fuzzy -
|
||||
* history may not be available (or be wiped or not present due to e.g.
|
||||
* incognito mode).
|
||||
*
|
||||
* If not overridden, the promise will resolve to the Unix epoch.
|
||||
*
|
||||
* @returns {Promise<Date>}
|
||||
* A Promise that resolves to the last used date.
|
||||
*/
|
||||
getLastUsedDate() {
|
||||
return Promise.resolve(new Date(0));
|
||||
},
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
|
||||
* that is just the Firefox migrator, see bug 737381). Default: false.
|
||||
*
|
||||
* Startup-only migrators are different in two ways:
|
||||
* - they may only be used during startup.
|
||||
* - the user-profile is half baked during migration. The folder exists,
|
||||
* but it's only accessible through MigrationUtils.profileStartup.
|
||||
* The migrator can call MigrationUtils.profileStartup.doStartup
|
||||
* at any point in order to initialize the profile.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator is start-up only.
|
||||
*/
|
||||
get startupOnlyMigrator() {
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the migrator is configured to be enabled. This is
|
||||
* controlled by the `browser.migrate.<BROWSER_KEY>.enabled` boolean
|
||||
* preference.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator should be shown in the migration wizard.
|
||||
*/
|
||||
get enabled() {
|
||||
let key = this.getBrowserKey();
|
||||
return Services.prefs.getBoolPref(`browser.migrate.${key}.enabled`, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* getResources.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* @returns {MigratorResource[]}
|
||||
*/
|
||||
getMigrateData: async function MP_getMigrateData(aProfile) {
|
||||
let resources = await this._getMaybeCachedResources(aProfile);
|
||||
if (!resources) {
|
||||
return 0;
|
||||
}
|
||||
let types = resources.map(r => r.type);
|
||||
return types.reduce((a, b) => {
|
||||
a |= b;
|
||||
return a;
|
||||
}, 0);
|
||||
},
|
||||
|
||||
getBrowserKey: function MP_getBrowserKey() {
|
||||
return this.contractID.match(/\=([^\=]+)$/)[1];
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* migrate for each resource.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
* @param {number} aItems
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what types of resources should be migrated.
|
||||
* @param {boolean} aStartup
|
||||
* True if this migration is occurring during startup.
|
||||
* @param {object|string} aProfile
|
||||
* The other browser profile that is being migrated from.
|
||||
*/
|
||||
migrate: async function MP_migrate(aItems, aStartup, aProfile) {
|
||||
let resources = await this._getMaybeCachedResources(aProfile);
|
||||
if (!resources.length) {
|
||||
throw new Error("migrate called for a non-existent source");
|
||||
}
|
||||
|
||||
if (aItems != Ci.nsIBrowserProfileMigrator.ALL) {
|
||||
resources = resources.filter(r => aItems & r.type);
|
||||
}
|
||||
|
||||
// Used to periodically give back control to the main-thread loop.
|
||||
let unblockMainThread = function() {
|
||||
return new Promise(resolve => {
|
||||
Services.tm.dispatchToMainThread(resolve);
|
||||
});
|
||||
};
|
||||
|
||||
let getHistogramIdForResourceType = (resourceType, template) => {
|
||||
if (resourceType == MigrationUtils.resourceTypes.HISTORY) {
|
||||
return template.replace("*", "HISTORY");
|
||||
}
|
||||
if (resourceType == MigrationUtils.resourceTypes.BOOKMARKS) {
|
||||
return template.replace("*", "BOOKMARKS");
|
||||
}
|
||||
if (resourceType == MigrationUtils.resourceTypes.PASSWORDS) {
|
||||
return template.replace("*", "LOGINS");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let browserKey = this.getBrowserKey();
|
||||
|
||||
let maybeStartTelemetryStopwatch = resourceType => {
|
||||
let histogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_IMPORT_MS"
|
||||
);
|
||||
if (histogramId) {
|
||||
TelemetryStopwatch.startKeyed(histogramId, browserKey);
|
||||
}
|
||||
return histogramId;
|
||||
};
|
||||
|
||||
let maybeStartResponsivenessMonitor = resourceType => {
|
||||
let responsivenessMonitor;
|
||||
let responsivenessHistogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_JANK_MS"
|
||||
);
|
||||
if (responsivenessHistogramId) {
|
||||
responsivenessMonitor = new lazy.ResponsivenessMonitor();
|
||||
}
|
||||
return { responsivenessMonitor, responsivenessHistogramId };
|
||||
};
|
||||
|
||||
let maybeFinishResponsivenessMonitor = (
|
||||
responsivenessMonitor,
|
||||
histogramId
|
||||
) => {
|
||||
if (responsivenessMonitor) {
|
||||
let accumulatedDelay = responsivenessMonitor.finish();
|
||||
if (histogramId) {
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(browserKey, accumulatedDelay);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let collectQuantityTelemetry = () => {
|
||||
for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
|
||||
let histogramId =
|
||||
"FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(browserKey, MigrationUtils._importQuantities[resourceType]);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Called either directly or through the bookmarks import callback.
|
||||
let doMigrate = async function() {
|
||||
let resourcesGroupedByItems = new Map();
|
||||
resources.forEach(function(resource) {
|
||||
if (!resourcesGroupedByItems.has(resource.type)) {
|
||||
resourcesGroupedByItems.set(resource.type, new Set());
|
||||
}
|
||||
resourcesGroupedByItems.get(resource.type).add(resource);
|
||||
});
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
throw new Error("No items to import");
|
||||
}
|
||||
|
||||
let notify = function(aMsg, aItemType) {
|
||||
Services.obs.notifyObservers(null, aMsg, aItemType);
|
||||
};
|
||||
|
||||
for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
|
||||
MigrationUtils._importQuantities[resourceType] = 0;
|
||||
}
|
||||
notify("Migration:Started");
|
||||
for (let [migrationType, itemResources] of resourcesGroupedByItems) {
|
||||
notify("Migration:ItemBeforeMigrate", migrationType);
|
||||
|
||||
let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType);
|
||||
|
||||
let {
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId,
|
||||
} = maybeStartResponsivenessMonitor(migrationType);
|
||||
|
||||
let itemSuccess = false;
|
||||
for (let res of itemResources) {
|
||||
let completeDeferred = lazy.PromiseUtils.defer();
|
||||
let resourceDone = function(aSuccess) {
|
||||
itemResources.delete(res);
|
||||
itemSuccess |= aSuccess;
|
||||
if (itemResources.size == 0) {
|
||||
notify(
|
||||
itemSuccess
|
||||
? "Migration:ItemAfterMigrate"
|
||||
: "Migration:ItemError",
|
||||
migrationType
|
||||
);
|
||||
resourcesGroupedByItems.delete(migrationType);
|
||||
|
||||
if (stopwatchHistogramId) {
|
||||
TelemetryStopwatch.finishKeyed(
|
||||
stopwatchHistogramId,
|
||||
browserKey
|
||||
);
|
||||
}
|
||||
|
||||
maybeFinishResponsivenessMonitor(
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId
|
||||
);
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
collectQuantityTelemetry();
|
||||
notify("Migration:Ended");
|
||||
}
|
||||
}
|
||||
completeDeferred.resolve();
|
||||
};
|
||||
|
||||
// If migrate throws, an error occurred, and the callback
|
||||
// (itemMayBeDone) might haven't been called.
|
||||
try {
|
||||
res.migrate(resourceDone);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
resourceDone(false);
|
||||
}
|
||||
|
||||
await completeDeferred.promise;
|
||||
await unblockMainThread();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
MigrationUtils.isStartupMigration &&
|
||||
!this.startupOnlyMigrator &&
|
||||
Services.policies.isAllowed("defaultBookmarks")
|
||||
) {
|
||||
MigrationUtils.profileStartup.doStartup();
|
||||
// First import the default bookmarks.
|
||||
// Note: We do not need to do so for the Firefox migrator
|
||||
// (=startupOnlyMigrator), as it just copies over the places database
|
||||
// from another profile.
|
||||
(async function() {
|
||||
// Tell nsBrowserGlue we're importing default bookmarks.
|
||||
let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
|
||||
Ci.nsIObserver
|
||||
);
|
||||
browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
|
||||
|
||||
// Import the default bookmarks. We ignore whether or not we succeed.
|
||||
await lazy.BookmarkHTMLUtils.importFromURL(
|
||||
"chrome://browser/content/default-bookmarks.html",
|
||||
{
|
||||
replace: true,
|
||||
source: lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
|
||||
}
|
||||
).catch(Cu.reportError);
|
||||
|
||||
// We'll tell nsBrowserGlue we've imported bookmarks, but before that
|
||||
// we need to make sure we're going to know when it's finished
|
||||
// initializing places:
|
||||
let placesInitedPromise = new Promise(resolve => {
|
||||
let onPlacesInited = function() {
|
||||
Services.obs.removeObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
resolve();
|
||||
};
|
||||
Services.obs.addObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
});
|
||||
browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
|
||||
await placesInitedPromise;
|
||||
doMigrate();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
doMigrate();
|
||||
},
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, this code
|
||||
* won't be part of the migrator itself.
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
async isSourceAvailable() {
|
||||
if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a single-profile source, check if any data is available.
|
||||
// For multiple-profiles source, make sure that at least one
|
||||
// profile is available.
|
||||
let exists = false;
|
||||
try {
|
||||
let profiles = await this.getSourceProfiles();
|
||||
if (!profiles) {
|
||||
let resources = await this._getMaybeCachedResources("");
|
||||
if (resources && resources.length) {
|
||||
exists = true;
|
||||
}
|
||||
} else {
|
||||
exists = !!profiles.length;
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
return exists;
|
||||
},
|
||||
|
||||
/*** PRIVATE STUFF - DO NOT OVERRIDE ***/
|
||||
|
||||
/**
|
||||
* Returns resources for a particular profile and then caches them for later
|
||||
* lookups.
|
||||
*
|
||||
* @param {object|string} aProfile
|
||||
* The profile that resources are being imported from.
|
||||
* @returns {Promise<MigrationResource[]>}
|
||||
*/
|
||||
_getMaybeCachedResources: async function PMB__getMaybeCachedResources(
|
||||
aProfile
|
||||
) {
|
||||
let profileKey = aProfile ? aProfile.id : "";
|
||||
if (this._resourcesByProfile) {
|
||||
if (profileKey in this._resourcesByProfile) {
|
||||
return this._resourcesByProfile[profileKey];
|
||||
}
|
||||
} else {
|
||||
this._resourcesByProfile = {};
|
||||
}
|
||||
this._resourcesByProfile[profileKey] = await this.getResources(aProfile);
|
||||
return this._resourcesByProfile[profileKey];
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The singleton MigrationUtils service. This service is the primary mechanism
|
||||
* by which migrations from other browsers to this browser occur. The singleton
|
||||
* instance of this class is exported from this module as `MigrationUtils`.
|
||||
*/
|
||||
class MigrationUtilsSingleton {
|
||||
class MigrationUtils {
|
||||
resourceTypes = Object.freeze({
|
||||
COOKIES: Ci.nsIBrowserProfileMigrator.COOKIES,
|
||||
HISTORY: Ci.nsIBrowserProfileMigrator.HISTORY,
|
||||
@@ -525,7 +53,7 @@ class MigrationUtilsSingleton {
|
||||
|
||||
/**
|
||||
* Helper for implementing simple asynchronous cases of migration resources'
|
||||
* |migrate(aCallback)| (see MigratorPrototype). If your |migrate| method
|
||||
* |migrate(aCallback)| (see MigratorBase). If your |migrate| method
|
||||
* just waits for some file to be read, for example, and then migrates
|
||||
* everything right away, you can wrap the async-function with this helper
|
||||
* and not worry about notifying the callback.
|
||||
@@ -711,7 +239,7 @@ class MigrationUtilsSingleton {
|
||||
* Internal name of the migration source. See `availableMigratorKeys`
|
||||
* for supported values by OS.
|
||||
*
|
||||
* @returns {MigratorPrototype}
|
||||
* @returns {MigratorBase}
|
||||
* A profile migrator implementing nsIBrowserProfileMigrator, if it can
|
||||
* import any data, null otherwise.
|
||||
*/
|
||||
@@ -1420,4 +948,6 @@ class MigrationUtilsSingleton {
|
||||
}
|
||||
}
|
||||
|
||||
export const MigrationUtils = new MigrationUtilsSingleton();
|
||||
const MigrationUtilsSingleton = new MigrationUtils();
|
||||
|
||||
export { MigrationUtilsSingleton as MigrationUtils };
|
||||
|
||||
489
browser/components/migration/MigratorBase.sys.mjs
Normal file
489
browser/components/migration/MigratorBase.sys.mjs
Normal file
@@ -0,0 +1,489 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const TOPIC_WILL_IMPORT_BOOKMARKS =
|
||||
"initial-migration-will-import-default-bookmarks";
|
||||
const TOPIC_DID_IMPORT_BOOKMARKS =
|
||||
"initial-migration-did-import-default-bookmarks";
|
||||
const TOPIC_PLACES_DEFAULTS_FINISHED = "places-browser-init-complete";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.sys.mjs",
|
||||
MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
|
||||
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
|
||||
PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
|
||||
ResponsivenessMonitor: "resource://gre/modules/ResponsivenessMonitor.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* @typedef {object} MigratorResource
|
||||
* A resource returned by a subclass of MigratorBase that can migrate
|
||||
* data to this browser.
|
||||
* @property {number} type
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what this resource represents. A resource can represent one or more types
|
||||
* of data, for example HISTORY and FORMDATA.
|
||||
* @property {Function} migrate
|
||||
* A function that will actually perform the migration of this resource's
|
||||
* data into this browser.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Shared prototype for migrators, implementing nsIBrowserProfileMigrator.
|
||||
*
|
||||
* To implement a migrator:
|
||||
* 1. Import this module.
|
||||
* 2. Create the prototype for the migrator, extending MigratorBase.
|
||||
* 3. Set classDescription, contractID and classID for your migrator, and update
|
||||
* components.conf to register the migrator as an XPCOM component.
|
||||
* 4. If the migrator supports multiple profiles, override the sourceProfiles
|
||||
* Here we default for single-profile migrator.
|
||||
* 5. Implement getResources(aProfile) (see below).
|
||||
* 6. For startup-only migrators, override |startupOnlyMigrator|.
|
||||
*/
|
||||
export class MigratorBase {
|
||||
QueryInterface = ChromeUtils.generateQI(["nsIBrowserProfileMigrator"]);
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the source supports multiple profiles.
|
||||
*
|
||||
* Returns array of profile objects from which data may be imported. The object
|
||||
* should have the following keys:
|
||||
* id - a unique string identifier for the profile
|
||||
* name - a pretty name to display to the user in the UI
|
||||
*
|
||||
* Only profiles from which data can be imported should be listed. Otherwise
|
||||
* the behavior of the migration wizard isn't well-defined.
|
||||
*
|
||||
* For a single-profile source (e.g. safari, ie), this returns null,
|
||||
* and not an empty array. That is the default implementation.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {object[]|null}
|
||||
*/
|
||||
getSourceProfiles() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* MUST BE OVERRIDDEN.
|
||||
*
|
||||
* Returns an array of "migration resources" objects for the given profile,
|
||||
* or for the "default" profile, if the migrator does not support multiple
|
||||
* profiles.
|
||||
*
|
||||
* Each migration resource should provide:
|
||||
* - a |type| getter, returning any of the migration types (see
|
||||
* nsIBrowserProfileMigrator).
|
||||
*
|
||||
* - a |migrate| method, taking a single argument, aCallback(bool success),
|
||||
* for migrating the data for this resource. It may do its job
|
||||
* synchronously or asynchronously. Either way, it must call
|
||||
* aCallback(bool aSuccess) when it's done. In the case of an exception
|
||||
* thrown from |migrate|, it's taken as if aCallback(false) is called.
|
||||
*
|
||||
* Note: In the case of a simple asynchronous implementation, you may find
|
||||
* MigrationUtils.wrapMigrateFunction handy for handling aCallback easily.
|
||||
*
|
||||
* For each migration type listed in nsIBrowserProfileMigrator, multiple
|
||||
* migration resources may be provided. This practice is useful when the
|
||||
* data for a certain migration type is independently stored in few
|
||||
* locations. For example, the mac version of Safari stores its "reading list"
|
||||
* bookmarks in a separate property list.
|
||||
*
|
||||
* Note that the importation of a particular migration type is reported as
|
||||
* successful if _any_ of its resources succeeded to import (that is, called,
|
||||
* |aCallback(true)|). However, completion-status for a particular migration
|
||||
* type is reported to the UI only once all of its migrators have called
|
||||
* aCallback.
|
||||
*
|
||||
* NOTE: The returned array should only include resources from which data
|
||||
* can be imported. So, for example, before adding a resource for the
|
||||
* BOOKMARKS migration type, you should check if you should check that the
|
||||
* bookmarks file exists.
|
||||
*
|
||||
* @abstract
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* In the case of multiple-profiles migrator, it is guaranteed that
|
||||
* aProfile is a value returned by the sourceProfiles getter (see
|
||||
* above).
|
||||
* @returns {Promise<MigratorResource[]>|MigratorResource[]}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getResources(aProfile) {
|
||||
throw new Error("getResources must be overridden");
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDE in order to provide an estimate of when the last time was
|
||||
* that somebody used the browser. It is OK that this is somewhat fuzzy -
|
||||
* history may not be available (or be wiped or not present due to e.g.
|
||||
* incognito mode).
|
||||
*
|
||||
* If not overridden, the promise will resolve to the Unix epoch.
|
||||
*
|
||||
* @returns {Promise<Date>}
|
||||
* A Promise that resolves to the last used date.
|
||||
*/
|
||||
getLastUsedDate() {
|
||||
return Promise.resolve(new Date(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
|
||||
* that is just the Firefox migrator, see bug 737381). Default: false.
|
||||
*
|
||||
* Startup-only migrators are different in two ways:
|
||||
* - they may only be used during startup.
|
||||
* - the user-profile is half baked during migration. The folder exists,
|
||||
* but it's only accessible through MigrationUtils.profileStartup.
|
||||
* The migrator can call MigrationUtils.profileStartup.doStartup
|
||||
* at any point in order to initialize the profile.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator is start-up only.
|
||||
*/
|
||||
get startupOnlyMigrator() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the migrator is configured to be enabled. This is
|
||||
* controlled by the `browser.migrate.<BROWSER_KEY>.enabled` boolean
|
||||
* preference.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* true if the migrator should be shown in the migration wizard.
|
||||
*/
|
||||
get enabled() {
|
||||
let key = this.getBrowserKey();
|
||||
return Services.prefs.getBoolPref(`browser.migrate.${key}.enabled`, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* getResources.
|
||||
*
|
||||
* See nsIBrowserProfileMigrator.
|
||||
*
|
||||
* @param {object|string} aProfile
|
||||
* The profile from which data may be imported, or an empty string
|
||||
* in the case of a single-profile migrator.
|
||||
* @returns {MigratorResource[]}
|
||||
*/
|
||||
async getMigrateData(aProfile) {
|
||||
let resources = await this.#getMaybeCachedResources(aProfile);
|
||||
if (!resources) {
|
||||
return 0;
|
||||
}
|
||||
let types = resources.map(r => r.type);
|
||||
return types.reduce((a, b) => {
|
||||
a |= b;
|
||||
return a;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
getBrowserKey() {
|
||||
return this.contractID.match(/\=([^\=]+)$/)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, the UI will just call
|
||||
* migrate for each resource.
|
||||
*
|
||||
* See nsIBrowserProfileMigrator.
|
||||
*
|
||||
* @param {number} aItems
|
||||
* A bitfield with bits from nsIBrowserProfileMigrator flipped to indicate
|
||||
* what types of resources should be migrated.
|
||||
* @param {boolean} aStartup
|
||||
* True if this migration is occurring during startup.
|
||||
* @param {object|string} aProfile
|
||||
* The other browser profile that is being migrated from.
|
||||
*/
|
||||
async migrate(aItems, aStartup, aProfile) {
|
||||
let resources = await this.#getMaybeCachedResources(aProfile);
|
||||
if (!resources.length) {
|
||||
throw new Error("migrate called for a non-existent source");
|
||||
}
|
||||
|
||||
if (aItems != Ci.nsIBrowserProfileMigrator.ALL) {
|
||||
resources = resources.filter(r => aItems & r.type);
|
||||
}
|
||||
|
||||
// Used to periodically give back control to the main-thread loop.
|
||||
let unblockMainThread = function() {
|
||||
return new Promise(resolve => {
|
||||
Services.tm.dispatchToMainThread(resolve);
|
||||
});
|
||||
};
|
||||
|
||||
let getHistogramIdForResourceType = (resourceType, template) => {
|
||||
if (resourceType == lazy.MigrationUtils.resourceTypes.HISTORY) {
|
||||
return template.replace("*", "HISTORY");
|
||||
}
|
||||
if (resourceType == lazy.MigrationUtils.resourceTypes.BOOKMARKS) {
|
||||
return template.replace("*", "BOOKMARKS");
|
||||
}
|
||||
if (resourceType == lazy.MigrationUtils.resourceTypes.PASSWORDS) {
|
||||
return template.replace("*", "LOGINS");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let browserKey = this.getBrowserKey();
|
||||
|
||||
let maybeStartTelemetryStopwatch = resourceType => {
|
||||
let histogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_IMPORT_MS"
|
||||
);
|
||||
if (histogramId) {
|
||||
TelemetryStopwatch.startKeyed(histogramId, browserKey);
|
||||
}
|
||||
return histogramId;
|
||||
};
|
||||
|
||||
let maybeStartResponsivenessMonitor = resourceType => {
|
||||
let responsivenessMonitor;
|
||||
let responsivenessHistogramId = getHistogramIdForResourceType(
|
||||
resourceType,
|
||||
"FX_MIGRATION_*_JANK_MS"
|
||||
);
|
||||
if (responsivenessHistogramId) {
|
||||
responsivenessMonitor = new lazy.ResponsivenessMonitor();
|
||||
}
|
||||
return { responsivenessMonitor, responsivenessHistogramId };
|
||||
};
|
||||
|
||||
let maybeFinishResponsivenessMonitor = (
|
||||
responsivenessMonitor,
|
||||
histogramId
|
||||
) => {
|
||||
if (responsivenessMonitor) {
|
||||
let accumulatedDelay = responsivenessMonitor.finish();
|
||||
if (histogramId) {
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(browserKey, accumulatedDelay);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let collectQuantityTelemetry = () => {
|
||||
for (let resourceType of Object.keys(
|
||||
lazy.MigrationUtils._importQuantities
|
||||
)) {
|
||||
let histogramId =
|
||||
"FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
|
||||
try {
|
||||
Services.telemetry
|
||||
.getKeyedHistogramById(histogramId)
|
||||
.add(
|
||||
browserKey,
|
||||
lazy.MigrationUtils._importQuantities[resourceType]
|
||||
);
|
||||
} catch (ex) {
|
||||
Cu.reportError(histogramId + ": " + ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Called either directly or through the bookmarks import callback.
|
||||
let doMigrate = async function() {
|
||||
let resourcesGroupedByItems = new Map();
|
||||
resources.forEach(function(resource) {
|
||||
if (!resourcesGroupedByItems.has(resource.type)) {
|
||||
resourcesGroupedByItems.set(resource.type, new Set());
|
||||
}
|
||||
resourcesGroupedByItems.get(resource.type).add(resource);
|
||||
});
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
throw new Error("No items to import");
|
||||
}
|
||||
|
||||
let notify = function(aMsg, aItemType) {
|
||||
Services.obs.notifyObservers(null, aMsg, aItemType);
|
||||
};
|
||||
|
||||
for (let resourceType of Object.keys(
|
||||
lazy.MigrationUtils._importQuantities
|
||||
)) {
|
||||
lazy.MigrationUtils._importQuantities[resourceType] = 0;
|
||||
}
|
||||
notify("Migration:Started");
|
||||
for (let [migrationType, itemResources] of resourcesGroupedByItems) {
|
||||
notify("Migration:ItemBeforeMigrate", migrationType);
|
||||
|
||||
let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType);
|
||||
|
||||
let {
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId,
|
||||
} = maybeStartResponsivenessMonitor(migrationType);
|
||||
|
||||
let itemSuccess = false;
|
||||
for (let res of itemResources) {
|
||||
let completeDeferred = lazy.PromiseUtils.defer();
|
||||
let resourceDone = function(aSuccess) {
|
||||
itemResources.delete(res);
|
||||
itemSuccess |= aSuccess;
|
||||
if (itemResources.size == 0) {
|
||||
notify(
|
||||
itemSuccess
|
||||
? "Migration:ItemAfterMigrate"
|
||||
: "Migration:ItemError",
|
||||
migrationType
|
||||
);
|
||||
resourcesGroupedByItems.delete(migrationType);
|
||||
|
||||
if (stopwatchHistogramId) {
|
||||
TelemetryStopwatch.finishKeyed(
|
||||
stopwatchHistogramId,
|
||||
browserKey
|
||||
);
|
||||
}
|
||||
|
||||
maybeFinishResponsivenessMonitor(
|
||||
responsivenessMonitor,
|
||||
responsivenessHistogramId
|
||||
);
|
||||
|
||||
if (resourcesGroupedByItems.size == 0) {
|
||||
collectQuantityTelemetry();
|
||||
notify("Migration:Ended");
|
||||
}
|
||||
}
|
||||
completeDeferred.resolve();
|
||||
};
|
||||
|
||||
// If migrate throws, an error occurred, and the callback
|
||||
// (itemMayBeDone) might haven't been called.
|
||||
try {
|
||||
res.migrate(resourceDone);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
resourceDone(false);
|
||||
}
|
||||
|
||||
await completeDeferred.promise;
|
||||
await unblockMainThread();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
lazy.MigrationUtils.isStartupMigration &&
|
||||
!this.startupOnlyMigrator &&
|
||||
Services.policies.isAllowed("defaultBookmarks")
|
||||
) {
|
||||
lazy.MigrationUtils.profileStartup.doStartup();
|
||||
// First import the default bookmarks.
|
||||
// Note: We do not need to do so for the Firefox migrator
|
||||
// (=startupOnlyMigrator), as it just copies over the places database
|
||||
// from another profile.
|
||||
(async function() {
|
||||
// Tell nsBrowserGlue we're importing default bookmarks.
|
||||
let browserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
|
||||
Ci.nsIObserver
|
||||
);
|
||||
browserGlue.observe(null, TOPIC_WILL_IMPORT_BOOKMARKS, "");
|
||||
|
||||
// Import the default bookmarks. We ignore whether or not we succeed.
|
||||
await lazy.BookmarkHTMLUtils.importFromURL(
|
||||
"chrome://browser/content/default-bookmarks.html",
|
||||
{
|
||||
replace: true,
|
||||
source: lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
|
||||
}
|
||||
).catch(Cu.reportError);
|
||||
|
||||
// We'll tell nsBrowserGlue we've imported bookmarks, but before that
|
||||
// we need to make sure we're going to know when it's finished
|
||||
// initializing places:
|
||||
let placesInitedPromise = new Promise(resolve => {
|
||||
let onPlacesInited = function() {
|
||||
Services.obs.removeObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
resolve();
|
||||
};
|
||||
Services.obs.addObserver(
|
||||
onPlacesInited,
|
||||
TOPIC_PLACES_DEFAULTS_FINISHED
|
||||
);
|
||||
});
|
||||
browserGlue.observe(null, TOPIC_DID_IMPORT_BOOKMARKS, "");
|
||||
await placesInitedPromise;
|
||||
doMigrate();
|
||||
})();
|
||||
return;
|
||||
}
|
||||
doMigrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* DO NOT OVERRIDE - After deCOMing migration, this code
|
||||
* won't be part of the migrator itself.
|
||||
*
|
||||
* See nsIBrowserProfileMigrator.
|
||||
*/
|
||||
async isSourceAvailable() {
|
||||
if (this.startupOnlyMigrator && !lazy.MigrationUtils.isStartupMigration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For a single-profile source, check if any data is available.
|
||||
// For multiple-profiles source, make sure that at least one
|
||||
// profile is available.
|
||||
let exists = false;
|
||||
try {
|
||||
let profiles = await this.getSourceProfiles();
|
||||
if (!profiles) {
|
||||
let resources = await this.#getMaybeCachedResources("");
|
||||
if (resources && resources.length) {
|
||||
exists = true;
|
||||
}
|
||||
} else {
|
||||
exists = !!profiles.length;
|
||||
}
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
return exists;
|
||||
}
|
||||
|
||||
/*** PRIVATE STUFF - DO NOT OVERRIDE ***/
|
||||
|
||||
/**
|
||||
* Returns resources for a particular profile and then caches them for later
|
||||
* lookups.
|
||||
*
|
||||
* @param {object|string} aProfile
|
||||
* The profile that resources are being imported from.
|
||||
* @returns {Promise<MigrationResource[]>}
|
||||
*/
|
||||
async #getMaybeCachedResources(aProfile) {
|
||||
let profileKey = aProfile ? aProfile.id : "";
|
||||
if (this._resourcesByProfile) {
|
||||
if (profileKey in this._resourcesByProfile) {
|
||||
return this._resourcesByProfile[profileKey];
|
||||
}
|
||||
} else {
|
||||
this._resourcesByProfile = {};
|
||||
}
|
||||
this._resourcesByProfile[profileKey] = await this.getResources(aProfile);
|
||||
return this._resourcesByProfile[profileKey];
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,8 @@
|
||||
import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
|
||||
|
||||
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
import {
|
||||
MigrationUtils,
|
||||
MigratorPrototype,
|
||||
} from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
|
||||
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
@@ -344,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}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ EXTRA_JS_MODULES += [
|
||||
"ChromeProfileMigrator.sys.mjs",
|
||||
"FirefoxProfileMigrator.sys.mjs",
|
||||
"MigrationUtils.sys.mjs",
|
||||
"MigratorBase.sys.mjs",
|
||||
"ProfileMigrator.sys.mjs",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
var { MigrationUtils, MigratorPrototype } = ChromeUtils.importESModule(
|
||||
var { MigrationUtils } = ChromeUtils.importESModule(
|
||||
"resource:///modules/MigrationUtils.sys.mjs"
|
||||
);
|
||||
var { LoginHelper } = ChromeUtils.import(
|
||||
@@ -42,7 +42,7 @@ updateAppInfo();
|
||||
/**
|
||||
* Migrates the requested resource and waits for the migration to be complete.
|
||||
*
|
||||
* @param {MigratorPrototype} migrator
|
||||
* @param {MigratorBase} migrator
|
||||
* The migrator being used to migrate the data.
|
||||
* @param {number} resourceType
|
||||
* This is a bitfield with bits from nsIBrowserProfileMigrator flipped to indicate what
|
||||
|
||||
Reference in New Issue
Block a user