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 { 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,136 +68,297 @@ 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() {
|
||||||
if (this._chromeUserDataPath) {
|
return "Chrome";
|
||||||
|
}
|
||||||
|
|
||||||
|
_keychainServiceName = "Chrome Safe Storage";
|
||||||
|
|
||||||
|
_keychainAccountName = "Chrome";
|
||||||
|
|
||||||
|
async _getChromeUserDataPathIfExists() {
|
||||||
|
if (this._chromeUserDataPath) {
|
||||||
|
return this._chromeUserDataPath;
|
||||||
|
}
|
||||||
|
let path = lazy.ChromeMigrationUtils.getDataPath(
|
||||||
|
this._chromeUserDataPathSuffix
|
||||||
|
);
|
||||||
|
let exists = await IOUtils.exists(path);
|
||||||
|
if (exists) {
|
||||||
|
this._chromeUserDataPath = path;
|
||||||
|
} else {
|
||||||
|
this._chromeUserDataPath = null;
|
||||||
|
}
|
||||||
return this._chromeUserDataPath;
|
return this._chromeUserDataPath;
|
||||||
}
|
}
|
||||||
let path = lazy.ChromeMigrationUtils.getDataPath(
|
|
||||||
this._chromeUserDataPathSuffix
|
|
||||||
);
|
|
||||||
let exists = await IOUtils.exists(path);
|
|
||||||
if (exists) {
|
|
||||||
this._chromeUserDataPath = path;
|
|
||||||
} else {
|
|
||||||
this._chromeUserDataPath = null;
|
|
||||||
}
|
|
||||||
return this._chromeUserDataPath;
|
|
||||||
};
|
|
||||||
|
|
||||||
ChromeProfileMigrator.prototype.getResources = async function Chrome_getResources(
|
async getResources(aProfile) {
|
||||||
aProfile
|
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||||
) {
|
if (chromeUserDataPath) {
|
||||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
let profileFolder = chromeUserDataPath;
|
||||||
if (chromeUserDataPath) {
|
if (aProfile) {
|
||||||
let profileFolder = chromeUserDataPath;
|
profileFolder = PathUtils.join(chromeUserDataPath, aProfile.id);
|
||||||
if (aProfile) {
|
|
||||||
profileFolder = PathUtils.join(chromeUserDataPath, aProfile.id);
|
|
||||||
}
|
|
||||||
if (await IOUtils.exists(profileFolder)) {
|
|
||||||
let possibleResourcePromises = [
|
|
||||||
GetBookmarksResource(profileFolder, this.getBrowserKey()),
|
|
||||||
GetHistoryResource(profileFolder),
|
|
||||||
GetCookiesResource(profileFolder),
|
|
||||||
];
|
|
||||||
if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
|
|
||||||
possibleResourcePromises.push(
|
|
||||||
this._GetPasswordsResource(profileFolder)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
let possibleResources = await Promise.all(possibleResourcePromises);
|
if (await IOUtils.exists(profileFolder)) {
|
||||||
return possibleResources.filter(r => r != null);
|
let possibleResourcePromises = [
|
||||||
}
|
GetBookmarksResource(profileFolder, this.getBrowserKey()),
|
||||||
}
|
GetHistoryResource(profileFolder),
|
||||||
return [];
|
GetCookiesResource(profileFolder),
|
||||||
};
|
];
|
||||||
|
if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
|
||||||
ChromeProfileMigrator.prototype.getLastUsedDate = async function Chrome_getLastUsedDate() {
|
possibleResourcePromises.push(
|
||||||
let sourceProfiles = await this.getSourceProfiles();
|
this._GetPasswordsResource(profileFolder)
|
||||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
);
|
||||||
if (!chromeUserDataPath) {
|
}
|
||||||
return new Date(0);
|
let possibleResources = await Promise.all(possibleResourcePromises);
|
||||||
}
|
return possibleResources.filter(r => r != null);
|
||||||
let datePromises = sourceProfiles.map(async profile => {
|
|
||||||
let basePath = PathUtils.join(chromeUserDataPath, profile.id);
|
|
||||||
let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
|
|
||||||
async leafName => {
|
|
||||||
let path = PathUtils.join(basePath, leafName);
|
|
||||||
let info = await IOUtils.stat(path).catch(() => null);
|
|
||||||
return info ? info.lastModificationDate : 0;
|
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
let dates = await Promise.all(fileDatePromises);
|
|
||||||
return Math.max(...dates);
|
|
||||||
});
|
|
||||||
let datesOuter = await Promise.all(datePromises);
|
|
||||||
datesOuter.push(0);
|
|
||||||
return new Date(Math.max(...datesOuter));
|
|
||||||
};
|
|
||||||
|
|
||||||
ChromeProfileMigrator.prototype.getSourceProfiles = async function Chrome_getSourceProfiles() {
|
|
||||||
if ("__sourceProfiles" in this) {
|
|
||||||
return this.__sourceProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
|
||||||
if (!chromeUserDataPath) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
let localState;
|
async getLastUsedDate() {
|
||||||
let profiles = [];
|
let sourceProfiles = await this.getSourceProfiles();
|
||||||
try {
|
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||||
localState = await lazy.ChromeMigrationUtils.getLocalState(
|
if (!chromeUserDataPath) {
|
||||||
this._chromeUserDataPathSuffix
|
return new Date(0);
|
||||||
);
|
|
||||||
let info_cache = localState.profile.info_cache;
|
|
||||||
for (let profileFolderName in info_cache) {
|
|
||||||
profiles.push({
|
|
||||||
id: profileFolderName,
|
|
||||||
name: info_cache[profileFolderName].name || profileFolderName,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Avoid reporting NotFoundErrors from trying to get local state.
|
|
||||||
if (localState || e.name != "NotFoundError") {
|
|
||||||
Cu.reportError("Error detecting Chrome profiles: " + e);
|
|
||||||
}
|
|
||||||
// If we weren't able to detect any profiles above, fallback to the Default profile.
|
|
||||||
let defaultProfilePath = PathUtils.join(chromeUserDataPath, "Default");
|
|
||||||
if (await IOUtils.exists(defaultProfilePath)) {
|
|
||||||
profiles = [
|
|
||||||
{
|
|
||||||
id: "Default",
|
|
||||||
name: "Default",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
let datePromises = sourceProfiles.map(async profile => {
|
||||||
|
let basePath = PathUtils.join(chromeUserDataPath, profile.id);
|
||||||
|
let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
|
||||||
|
async leafName => {
|
||||||
|
let path = PathUtils.join(basePath, leafName);
|
||||||
|
let info = await IOUtils.stat(path).catch(() => null);
|
||||||
|
return info ? info.lastModificationDate : 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let dates = await Promise.all(fileDatePromises);
|
||||||
|
return Math.max(...dates);
|
||||||
|
});
|
||||||
|
let datesOuter = await Promise.all(datePromises);
|
||||||
|
datesOuter.push(0);
|
||||||
|
return new Date(Math.max(...datesOuter));
|
||||||
}
|
}
|
||||||
|
|
||||||
let profileResources = await Promise.all(
|
async getSourceProfiles() {
|
||||||
profiles.map(async profile => ({
|
if ("__sourceProfiles" in this) {
|
||||||
profile,
|
return this.__sourceProfiles;
|
||||||
resources: await this.getResources(profile),
|
}
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Only list profiles from which any data can be imported
|
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||||
this.__sourceProfiles = profileResources
|
if (!chromeUserDataPath) {
|
||||||
.filter(({ resources }) => {
|
return [];
|
||||||
return resources && !!resources.length;
|
}
|
||||||
}, this)
|
|
||||||
.map(({ profile }) => profile);
|
let localState;
|
||||||
return this.__sourceProfiles;
|
let profiles = [];
|
||||||
};
|
try {
|
||||||
|
localState = await lazy.ChromeMigrationUtils.getLocalState(
|
||||||
|
this._chromeUserDataPathSuffix
|
||||||
|
);
|
||||||
|
let info_cache = localState.profile.info_cache;
|
||||||
|
for (let profileFolderName in info_cache) {
|
||||||
|
profiles.push({
|
||||||
|
id: profileFolderName,
|
||||||
|
name: info_cache[profileFolderName].name || profileFolderName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Avoid reporting NotFoundErrors from trying to get local state.
|
||||||
|
if (localState || e.name != "NotFoundError") {
|
||||||
|
Cu.reportError("Error detecting Chrome profiles: " + e);
|
||||||
|
}
|
||||||
|
// If we weren't able to detect any profiles above, fallback to the Default profile.
|
||||||
|
let defaultProfilePath = PathUtils.join(chromeUserDataPath, "Default");
|
||||||
|
if (await IOUtils.exists(defaultProfilePath)) {
|
||||||
|
profiles = [
|
||||||
|
{
|
||||||
|
id: "Default",
|
||||||
|
name: "Default",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let profileResources = await Promise.all(
|
||||||
|
profiles.map(async profile => ({
|
||||||
|
profile,
|
||||||
|
resources: await this.getResources(profile),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only list profiles from which any data can be imported
|
||||||
|
this.__sourceProfiles = profileResources
|
||||||
|
.filter(({ resources }) => {
|
||||||
|
return resources && !!resources.length;
|
||||||
|
}, this)
|
||||||
|
.map(({ profile }) => profile);
|
||||||
|
return this.__sourceProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _GetPasswordsResource(aProfileFolder) {
|
||||||
|
let loginPath = PathUtils.join(aProfileFolder, "Login Data");
|
||||||
|
if (!(await IOUtils.exists(loginPath))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
_chromeUserDataPathSuffix,
|
||||||
|
_keychainServiceName,
|
||||||
|
_keychainAccountName,
|
||||||
|
_keychainMockPassphrase = null,
|
||||||
|
} = this;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: MigrationUtils.resourceTypes.PASSWORDS,
|
||||||
|
|
||||||
|
async migrate(aCallback) {
|
||||||
|
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
|
||||||
|
loginPath,
|
||||||
|
"Chrome passwords",
|
||||||
|
`SELECT origin_url, action_url, username_element, username_value,
|
||||||
|
password_element, password_value, signon_realm, scheme, date_created,
|
||||||
|
times_used FROM logins WHERE blacklisted_by_user = 0`
|
||||||
|
).catch(ex => {
|
||||||
|
Cu.reportError(ex);
|
||||||
|
aCallback(false);
|
||||||
|
});
|
||||||
|
// If the promise was rejected we will have already called aCallback,
|
||||||
|
// so we can just return here.
|
||||||
|
if (!rows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no relevant rows, return before initializing crypto and
|
||||||
|
// thus prompting for Keychain access on macOS.
|
||||||
|
if (!rows.length) {
|
||||||
|
aCallback(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let crypto;
|
||||||
|
try {
|
||||||
|
if (AppConstants.platform == "win") {
|
||||||
|
let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
|
||||||
|
"resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
|
||||||
|
);
|
||||||
|
crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
|
||||||
|
} else if (AppConstants.platform == "macosx") {
|
||||||
|
let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
|
||||||
|
"resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
|
||||||
|
);
|
||||||
|
crypto = new ChromeMacOSLoginCrypto(
|
||||||
|
_keychainServiceName,
|
||||||
|
_keychainAccountName,
|
||||||
|
_keychainMockPassphrase
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
aCallback(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
// Handle the user canceling Keychain access or other OSCrypto errors.
|
||||||
|
Cu.reportError(ex);
|
||||||
|
aCallback(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let logins = [];
|
||||||
|
let fallbackCreationDate = new Date();
|
||||||
|
for (let row of rows) {
|
||||||
|
try {
|
||||||
|
let origin_url = lazy.NetUtil.newURI(
|
||||||
|
row.getResultByName("origin_url")
|
||||||
|
);
|
||||||
|
// Ignore entries for non-http(s)/ftp URLs because we likely can't
|
||||||
|
// use them anyway.
|
||||||
|
const kValidSchemes = new Set(["https", "http", "ftp"]);
|
||||||
|
if (!kValidSchemes.has(origin_url.scheme)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let loginInfo = {
|
||||||
|
username: row.getResultByName("username_value"),
|
||||||
|
password: await crypto.decryptData(
|
||||||
|
row.getResultByName("password_value"),
|
||||||
|
null
|
||||||
|
),
|
||||||
|
origin: origin_url.prePath,
|
||||||
|
formActionOrigin: null,
|
||||||
|
httpRealm: null,
|
||||||
|
usernameElement: row.getResultByName("username_element"),
|
||||||
|
passwordElement: row.getResultByName("password_element"),
|
||||||
|
timeCreated: lazy.ChromeMigrationUtils.chromeTimeToDate(
|
||||||
|
row.getResultByName("date_created") + 0,
|
||||||
|
fallbackCreationDate
|
||||||
|
).getTime(),
|
||||||
|
timesUsed: row.getResultByName("times_used") + 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (row.getResultByName("scheme")) {
|
||||||
|
case AUTH_TYPE.SCHEME_HTML:
|
||||||
|
let action_url = row.getResultByName("action_url");
|
||||||
|
if (!action_url) {
|
||||||
|
// If there is no action_url, store the wildcard "" value.
|
||||||
|
// See the `formActionOrigin` IDL comments.
|
||||||
|
loginInfo.formActionOrigin = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let action_uri = lazy.NetUtil.newURI(action_url);
|
||||||
|
if (!kValidSchemes.has(action_uri.scheme)) {
|
||||||
|
continue; // This continues the outer for loop.
|
||||||
|
}
|
||||||
|
loginInfo.formActionOrigin = action_uri.prePath;
|
||||||
|
break;
|
||||||
|
case AUTH_TYPE.SCHEME_BASIC:
|
||||||
|
case AUTH_TYPE.SCHEME_DIGEST:
|
||||||
|
// signon_realm format is URIrealm, so we need remove URI
|
||||||
|
loginInfo.httpRealm = row
|
||||||
|
.getResultByName("signon_realm")
|
||||||
|
.substring(loginInfo.origin.length + 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
"Login data scheme type not supported: " +
|
||||||
|
row.getResultByName("scheme")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
logins.push(loginInfo);
|
||||||
|
} catch (e) {
|
||||||
|
Cu.reportError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (logins.length) {
|
||||||
|
await MigrationUtils.insertLoginsWrapper(logins);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Cu.reportError(e);
|
||||||
|
}
|
||||||
|
if (crypto.finalize) {
|
||||||
|
crypto.finalize();
|
||||||
|
}
|
||||||
|
aCallback(true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
|
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
|
* Chromium migrator
|
||||||
) {
|
*/
|
||||||
let loginPath = PathUtils.join(aProfileFolder, "Login Data");
|
export class ChromiumProfileMigrator extends ChromeProfileMigrator {
|
||||||
if (!(await IOUtils.exists(loginPath))) {
|
get classDescription() {
|
||||||
return null;
|
return "Chromium Profile Migrator";
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
get contractID() {
|
||||||
_chromeUserDataPathSuffix,
|
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
|
||||||
_keychainServiceName,
|
}
|
||||||
_keychainAccountName,
|
|
||||||
_keychainMockPassphrase = null,
|
|
||||||
} = this;
|
|
||||||
|
|
||||||
return {
|
get classID() {
|
||||||
type: MigrationUtils.resourceTypes.PASSWORDS,
|
return Components.ID("{8cece922-9720-42de-b7db-7cef88cb07ca}");
|
||||||
|
}
|
||||||
|
|
||||||
async migrate(aCallback) {
|
_chromeUserDataPathSuffix = "Chromium";
|
||||||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
|
_keychainServiceName = "Chromium Safe Storage";
|
||||||
loginPath,
|
_keychainAccountName = "Chromium";
|
||||||
"Chrome passwords",
|
|
||||||
`SELECT origin_url, action_url, username_element, username_value,
|
|
||||||
password_element, password_value, signon_realm, scheme, date_created,
|
|
||||||
times_used FROM logins WHERE blacklisted_by_user = 0`
|
|
||||||
).catch(ex => {
|
|
||||||
Cu.reportError(ex);
|
|
||||||
aCallback(false);
|
|
||||||
});
|
|
||||||
// If the promise was rejected we will have already called aCallback,
|
|
||||||
// so we can just return here.
|
|
||||||
if (!rows) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there are no relevant rows, return before initializing crypto and
|
|
||||||
// thus prompting for Keychain access on macOS.
|
|
||||||
if (!rows.length) {
|
|
||||||
aCallback(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let crypto;
|
|
||||||
try {
|
|
||||||
if (AppConstants.platform == "win") {
|
|
||||||
let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
|
|
||||||
"resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
|
|
||||||
);
|
|
||||||
crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
|
|
||||||
} else if (AppConstants.platform == "macosx") {
|
|
||||||
let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
|
|
||||||
"resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
|
|
||||||
);
|
|
||||||
crypto = new ChromeMacOSLoginCrypto(
|
|
||||||
_keychainServiceName,
|
|
||||||
_keychainAccountName,
|
|
||||||
_keychainMockPassphrase
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
aCallback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
// Handle the user canceling Keychain access or other OSCrypto errors.
|
|
||||||
Cu.reportError(ex);
|
|
||||||
aCallback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let logins = [];
|
|
||||||
let fallbackCreationDate = new Date();
|
|
||||||
for (let row of rows) {
|
|
||||||
try {
|
|
||||||
let origin_url = lazy.NetUtil.newURI(
|
|
||||||
row.getResultByName("origin_url")
|
|
||||||
);
|
|
||||||
// Ignore entries for non-http(s)/ftp URLs because we likely can't
|
|
||||||
// use them anyway.
|
|
||||||
const kValidSchemes = new Set(["https", "http", "ftp"]);
|
|
||||||
if (!kValidSchemes.has(origin_url.scheme)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let loginInfo = {
|
|
||||||
username: row.getResultByName("username_value"),
|
|
||||||
password: await crypto.decryptData(
|
|
||||||
row.getResultByName("password_value"),
|
|
||||||
null
|
|
||||||
),
|
|
||||||
origin: origin_url.prePath,
|
|
||||||
formActionOrigin: null,
|
|
||||||
httpRealm: null,
|
|
||||||
usernameElement: row.getResultByName("username_element"),
|
|
||||||
passwordElement: row.getResultByName("password_element"),
|
|
||||||
timeCreated: lazy.ChromeMigrationUtils.chromeTimeToDate(
|
|
||||||
row.getResultByName("date_created") + 0,
|
|
||||||
fallbackCreationDate
|
|
||||||
).getTime(),
|
|
||||||
timesUsed: row.getResultByName("times_used") + 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (row.getResultByName("scheme")) {
|
|
||||||
case AUTH_TYPE.SCHEME_HTML:
|
|
||||||
let action_url = row.getResultByName("action_url");
|
|
||||||
if (!action_url) {
|
|
||||||
// If there is no action_url, store the wildcard "" value.
|
|
||||||
// See the `formActionOrigin` IDL comments.
|
|
||||||
loginInfo.formActionOrigin = "";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let action_uri = lazy.NetUtil.newURI(action_url);
|
|
||||||
if (!kValidSchemes.has(action_uri.scheme)) {
|
|
||||||
continue; // This continues the outer for loop.
|
|
||||||
}
|
|
||||||
loginInfo.formActionOrigin = action_uri.prePath;
|
|
||||||
break;
|
|
||||||
case AUTH_TYPE.SCHEME_BASIC:
|
|
||||||
case AUTH_TYPE.SCHEME_DIGEST:
|
|
||||||
// signon_realm format is URIrealm, so we need remove URI
|
|
||||||
loginInfo.httpRealm = row
|
|
||||||
.getResultByName("signon_realm")
|
|
||||||
.substring(loginInfo.origin.length + 1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
"Login data scheme type not supported: " +
|
|
||||||
row.getResultByName("scheme")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
logins.push(loginInfo);
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (logins.length) {
|
|
||||||
await MigrationUtils.insertLoginsWrapper(logins);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(e);
|
|
||||||
}
|
|
||||||
if (crypto.finalize) {
|
|
||||||
crypto.finalize();
|
|
||||||
}
|
|
||||||
aCallback(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
|
|
||||||
ChromeProfileMigrator.prototype.contractID =
|
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=chrome";
|
|
||||||
ChromeProfileMigrator.prototype.classID = Components.ID(
|
|
||||||
"{4cec1de4-1671-4fc3-a53e-6c539dc77a26}"
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chromium migration
|
|
||||||
*/
|
|
||||||
export function ChromiumProfileMigrator() {
|
|
||||||
this._chromeUserDataPathSuffix = "Chromium";
|
|
||||||
this._keychainServiceName = "Chromium Safe Storage";
|
|
||||||
this._keychainAccountName = "Chromium";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ChromiumProfileMigrator.prototype = Object.create(
|
|
||||||
ChromeProfileMigrator.prototype
|
|
||||||
);
|
|
||||||
ChromiumProfileMigrator.prototype.classDescription =
|
|
||||||
"Chromium Profile Migrator";
|
|
||||||
ChromiumProfileMigrator.prototype.contractID =
|
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=chromium";
|
|
||||||
ChromiumProfileMigrator.prototype.classID = Components.ID(
|
|
||||||
"{8cece922-9720-42de-b7db-7cef88cb07ca}"
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chrome Canary
|
* 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";
|
|
||||||
this._keychainServiceName = "Opera Browser Safe Storage";
|
|
||||||
this._keychainAccountName = "Opera Browser";
|
|
||||||
}
|
|
||||||
OperaProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
|
||||||
OperaProfileMigrator.prototype.classDescription = "Opera Browser Migrator";
|
|
||||||
OperaProfileMigrator.prototype.contractID =
|
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=opera";
|
|
||||||
OperaProfileMigrator.prototype.classID = Components.ID(
|
|
||||||
"{16c5d501-e411-41eb-93f2-af6c9ba64dee}"
|
|
||||||
);
|
|
||||||
OperaProfileMigrator.prototype.getSourceProfiles = function() {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function OperaGXProfileMigrator() {
|
|
||||||
this._chromeUserDataPathSuffix = "Opera GX";
|
|
||||||
this._keychainServiceName = "Opera Browser Safe Storage";
|
|
||||||
this._keychainAccountName = "Opera Browser";
|
|
||||||
}
|
|
||||||
OperaGXProfileMigrator.prototype = Object.create(
|
|
||||||
ChromeProfileMigrator.prototype
|
|
||||||
);
|
|
||||||
OperaGXProfileMigrator.prototype.classDescription = "Opera GX Browser Migrator";
|
|
||||||
OperaGXProfileMigrator.prototype.contractID =
|
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=opera-gx";
|
|
||||||
OperaGXProfileMigrator.prototype.classID = Components.ID(
|
|
||||||
"{26F4E0A0-B533-4FDA-B344-6FF5DA45D6DC}"
|
|
||||||
);
|
|
||||||
OperaGXProfileMigrator.prototype.getSourceProfiles = function() {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function VivaldiProfileMigrator() {
|
|
||||||
this._chromeUserDataPathSuffix = "Vivaldi";
|
|
||||||
this._keychainServiceName = "Vivaldi Safe Storage";
|
|
||||||
this._keychainAccountName = "Vivaldi";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VivaldiProfileMigrator.prototype = Object.create(
|
/**
|
||||||
ChromeProfileMigrator.prototype
|
* Brave migrator
|
||||||
);
|
*/
|
||||||
VivaldiProfileMigrator.prototype.classDescription = "Vivaldi Migrator";
|
export class BraveProfileMigrator extends ChromeProfileMigrator {
|
||||||
VivaldiProfileMigrator.prototype.contractID =
|
get classDescription() {
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=vivaldi";
|
return "Brave Browser Migrator";
|
||||||
VivaldiProfileMigrator.prototype.classID = Components.ID(
|
}
|
||||||
"{54a6a025-e70d-49dd-ba95-0f7e45d728d3}"
|
|
||||||
);
|
get contractID() {
|
||||||
|
return "@mozilla.org/profile/migrator;1?app=browser&type=brave";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{4071880a-69e4-4c83-88b4-6c589a62801d}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chromeUserDataPathSuffix = "Brave";
|
||||||
|
_keychainServiceName = "Brave Browser Safe Storage";
|
||||||
|
_keychainAccountName = "Brave Browser";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edge (Chromium-based) migrator
|
||||||
|
*/
|
||||||
|
export class ChromiumEdgeMigrator extends ChromeProfileMigrator {
|
||||||
|
get classDescription() {
|
||||||
|
return "Chromium Edge Profile Migrator";
|
||||||
|
}
|
||||||
|
|
||||||
|
get contractID() {
|
||||||
|
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{3c7f6b7c-baa9-4338-acfa-04bf79f1dcf1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chromeUserDataPathSuffix = "Edge";
|
||||||
|
_keychainServiceName = "Microsoft Edge Safe Storage";
|
||||||
|
_keychainAccountName = "Microsoft Edge";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edge Beta (Chromium-based) migrator
|
||||||
|
*/
|
||||||
|
export class ChromiumEdgeBetaMigrator extends ChromeProfileMigrator {
|
||||||
|
get classDescription() {
|
||||||
|
return "Chromium Edge Beta Profile Migrator";
|
||||||
|
}
|
||||||
|
|
||||||
|
get contractID() {
|
||||||
|
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge-beta";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{0fc3d48a-c1c3-4871-b58f-a8b47d1555fb}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chromeUserDataPathSuffix = "Edge Beta";
|
||||||
|
_keychainServiceName = "Microsoft Edge Safe Storage";
|
||||||
|
_keychainAccountName = "Microsoft Edge";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chromium 360 migrator
|
||||||
|
*/
|
||||||
|
export class Chromium360seMigrator extends ChromeProfileMigrator {
|
||||||
|
get classDescription() {
|
||||||
|
return "Chromium 360 Secure Browser Profile Migrator";
|
||||||
|
}
|
||||||
|
|
||||||
|
get contractID() {
|
||||||
|
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-360se";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{2e1a182e-ce4f-4dc9-a22c-d4125b931552}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chromeUserDataPathSuffix = "360 SE";
|
||||||
|
_keychainServiceName = "Microsoft Edge Safe Storage";
|
||||||
|
_keychainAccountName = "Microsoft Edge";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opera migrator
|
||||||
|
*/
|
||||||
|
export class OperaProfileMigrator extends ChromeProfileMigrator {
|
||||||
|
get classDescription() {
|
||||||
|
return "Opera Browser Migrator";
|
||||||
|
}
|
||||||
|
|
||||||
|
get contractID() {
|
||||||
|
return "@mozilla.org/profile/migrator;1?app=browser&type=opera";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{16c5d501-e411-41eb-93f2-af6c9ba64dee}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chromeUserDataPathSuffix = "Opera";
|
||||||
|
_keychainServiceName = "Opera Browser Safe Storage";
|
||||||
|
_keychainAccountName = "Opera Browser";
|
||||||
|
|
||||||
|
getSourceProfiles() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opera GX migrator
|
||||||
|
*/
|
||||||
|
export class OperaGXProfileMigrator extends ChromeProfileMigrator {
|
||||||
|
get classDescription() {
|
||||||
|
return "Opera GX Browser Migrator";
|
||||||
|
}
|
||||||
|
|
||||||
|
get contractID() {
|
||||||
|
return "@mozilla.org/profile/migrator;1?app=browser&type=opera-gx";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{26F4E0A0-B533-4FDA-B344-6FF5DA45D6DC}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chromeUserDataPathSuffix = "Opera GX";
|
||||||
|
_keychainServiceName = "Opera Browser Safe Storage";
|
||||||
|
_keychainAccountName = "Opera Browser";
|
||||||
|
|
||||||
|
getSourceProfiles() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vivaldi migrator
|
||||||
|
*/
|
||||||
|
export class VivaldiProfileMigrator extends ChromeProfileMigrator {
|
||||||
|
get classDescription() {
|
||||||
|
return "Vivaldi Migrator";
|
||||||
|
}
|
||||||
|
|
||||||
|
get contractID() {
|
||||||
|
return "@mozilla.org/profile/migrator;1?app=browser&type=vivaldi";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{54a6a025-e70d-49dd-ba95-0f7e45d728d3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_chromeUserDataPathSuffix = "Vivaldi";
|
||||||
|
_keychainServiceName = "Vivaldi Safe Storage";
|
||||||
|
_keychainAccountName = "Vivaldi";
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
|||||||
|
|
||||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
import { 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,89 +464,97 @@ EdgeBookmarksMigrator.prototype = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EdgeProfileMigrator() {
|
/**
|
||||||
this.wrappedJSObject = this;
|
* Edge (EdgeHTML) profile migrator
|
||||||
}
|
|
||||||
|
|
||||||
EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
|
|
||||||
|
|
||||||
EdgeProfileMigrator.prototype.getBookmarksMigratorForTesting = function(
|
|
||||||
dbOverride
|
|
||||||
) {
|
|
||||||
return new EdgeBookmarksMigrator(dbOverride);
|
|
||||||
};
|
|
||||||
|
|
||||||
EdgeProfileMigrator.prototype.getReadingListMigratorForTesting = function(
|
|
||||||
dbOverride
|
|
||||||
) {
|
|
||||||
return new EdgeReadingListMigrator(dbOverride);
|
|
||||||
};
|
|
||||||
|
|
||||||
EdgeProfileMigrator.prototype.getResources = function() {
|
|
||||||
let resources = [
|
|
||||||
new EdgeBookmarksMigrator(),
|
|
||||||
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
|
|
||||||
new EdgeTypedURLMigrator(),
|
|
||||||
new EdgeTypedURLDBMigrator(),
|
|
||||||
new EdgeReadingListMigrator(),
|
|
||||||
];
|
|
||||||
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
|
||||||
windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
|
|
||||||
resources.push(windowsVaultFormPasswordsMigrator);
|
|
||||||
return resources.filter(r => r.exists);
|
|
||||||
};
|
|
||||||
|
|
||||||
EdgeProfileMigrator.prototype.getLastUsedDate = async function() {
|
|
||||||
// Don't do this if we don't have a single profile (see the comment for
|
|
||||||
// sourceProfiles) or if we can't find the database file:
|
|
||||||
let sourceProfiles = await this.getSourceProfiles();
|
|
||||||
if (sourceProfiles !== null || !lazy.gEdgeDatabase) {
|
|
||||||
return Promise.resolve(new Date(0));
|
|
||||||
}
|
|
||||||
let logFilePath = PathUtils.join(
|
|
||||||
lazy.gEdgeDatabase.parent.path,
|
|
||||||
"LogFiles",
|
|
||||||
"edb.log"
|
|
||||||
);
|
|
||||||
let dbPath = lazy.gEdgeDatabase.path;
|
|
||||||
let cookieMigrator = MSMigrationUtils.getCookiesMigrator(
|
|
||||||
MSMigrationUtils.MIGRATION_TYPE_EDGE
|
|
||||||
);
|
|
||||||
let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
|
|
||||||
let datePromises = [logFilePath, dbPath, ...cookiePaths].map(path => {
|
|
||||||
return IOUtils.stat(path)
|
|
||||||
.then(info => info.lastModified)
|
|
||||||
.catch(() => 0);
|
|
||||||
});
|
|
||||||
datePromises.push(
|
|
||||||
new Promise(resolve => {
|
|
||||||
let typedURLs = new Map();
|
|
||||||
try {
|
|
||||||
typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
|
|
||||||
} catch (ex) {}
|
|
||||||
let times = [0, ...typedURLs.values()];
|
|
||||||
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
|
||||||
resolve(Math.max.apply(Math, times) / 1000);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return Promise.all(datePromises).then(dates => {
|
|
||||||
return new Date(Math.max.apply(Math, dates));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Somewhat counterintuitively, this returns:
|
|
||||||
* - |null| to indicate "There is only 1 (default) profile" (on win10+)
|
|
||||||
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
|
|
||||||
* See MigrationUtils.jsm for slightly more info on how sourceProfiles is used.
|
|
||||||
*/
|
*/
|
||||||
EdgeProfileMigrator.prototype.getSourceProfiles = function() {
|
export class EdgeProfileMigrator extends MigratorBase {
|
||||||
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
|
constructor() {
|
||||||
return isWin10OrHigher ? null : [];
|
super();
|
||||||
};
|
this.wrappedJSObject = this;
|
||||||
|
}
|
||||||
|
|
||||||
EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
|
get classDescription() {
|
||||||
EdgeProfileMigrator.prototype.contractID =
|
return "Edge Profile Migrator";
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=edge";
|
}
|
||||||
EdgeProfileMigrator.prototype.classID = Components.ID(
|
|
||||||
"{62e8834b-2d17-49f5-96ff-56344903a2ae}"
|
get contractID() {
|
||||||
);
|
return "@mozilla.org/profile/migrator;1?app=browser&type=edge";
|
||||||
|
}
|
||||||
|
|
||||||
|
get classID() {
|
||||||
|
return Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
|
||||||
|
}
|
||||||
|
|
||||||
|
getBookmarksMigratorForTesting(dbOverride) {
|
||||||
|
return new EdgeBookmarksMigrator(dbOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
getReadingListMigratorForTesting(dbOverride) {
|
||||||
|
return new EdgeReadingListMigrator(dbOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
getResources() {
|
||||||
|
let resources = [
|
||||||
|
new EdgeBookmarksMigrator(),
|
||||||
|
MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
|
||||||
|
new EdgeTypedURLMigrator(),
|
||||||
|
new EdgeTypedURLDBMigrator(),
|
||||||
|
new EdgeReadingListMigrator(),
|
||||||
|
];
|
||||||
|
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
||||||
|
windowsVaultFormPasswordsMigrator.name = "EdgeVaultFormPasswords";
|
||||||
|
resources.push(windowsVaultFormPasswordsMigrator);
|
||||||
|
return resources.filter(r => r.exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLastUsedDate() {
|
||||||
|
// Don't do this if we don't have a single profile (see the comment for
|
||||||
|
// sourceProfiles) or if we can't find the database file:
|
||||||
|
let sourceProfiles = await this.getSourceProfiles();
|
||||||
|
if (sourceProfiles !== null || !lazy.gEdgeDatabase) {
|
||||||
|
return Promise.resolve(new Date(0));
|
||||||
|
}
|
||||||
|
let logFilePath = PathUtils.join(
|
||||||
|
lazy.gEdgeDatabase.parent.path,
|
||||||
|
"LogFiles",
|
||||||
|
"edb.log"
|
||||||
|
);
|
||||||
|
let dbPath = lazy.gEdgeDatabase.path;
|
||||||
|
let cookieMigrator = MSMigrationUtils.getCookiesMigrator(
|
||||||
|
MSMigrationUtils.MIGRATION_TYPE_EDGE
|
||||||
|
);
|
||||||
|
let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
|
||||||
|
let datePromises = [logFilePath, dbPath, ...cookiePaths].map(path => {
|
||||||
|
return IOUtils.stat(path)
|
||||||
|
.then(info => info.lastModified)
|
||||||
|
.catch(() => 0);
|
||||||
|
});
|
||||||
|
datePromises.push(
|
||||||
|
new Promise(resolve => {
|
||||||
|
let typedURLs = new Map();
|
||||||
|
try {
|
||||||
|
typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
|
||||||
|
} catch (ex) {}
|
||||||
|
let times = [0, ...typedURLs.values()];
|
||||||
|
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
||||||
|
resolve(Math.max.apply(Math, times) / 1000);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return Promise.all(datePromises).then(dates => {
|
||||||
|
return new Date(Math.max.apply(Math, dates));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Array|null}
|
||||||
|
* Somewhat counterintuitively, this returns:
|
||||||
|
* - |null| to indicate "There is only 1 (default) profile" (on win10+)
|
||||||
|
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid
|
||||||
|
* using this migrator.
|
||||||
|
* See MigrationUtils.sys.mjs for slightly more info on how sourceProfiles is used.
|
||||||
|
*/
|
||||||
|
getSourceProfiles() {
|
||||||
|
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
|
||||||
|
return isWin10OrHigher ? null : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,10 +11,9 @@
|
|||||||
* from the source profile.
|
* 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,341 +24,352 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||||||
SessionMigration: "resource:///modules/sessionstore/SessionMigration.sys.mjs",
|
SessionMigration: "resource:///modules/sessionstore/SessionMigration.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
export function FirefoxProfileMigrator() {
|
/**
|
||||||
this.wrappedJSObject = this; // for testing...
|
* 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
|
||||||
FirefoxProfileMigrator.prototype = Object.create(MigratorPrototype);
|
* old profile data into the existing profile.
|
||||||
|
*
|
||||||
FirefoxProfileMigrator.prototype._getAllProfiles = function() {
|
* This migrator is what powers the "Profile Refresh" mechanism.
|
||||||
let allProfiles = new Map();
|
*/
|
||||||
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
export class FirefoxProfileMigrator extends MigratorBase {
|
||||||
Ci.nsIToolkitProfileService
|
constructor() {
|
||||||
);
|
super();
|
||||||
for (let profile of profileService.profiles) {
|
this.wrappedJSObject = this; // for testing...
|
||||||
let rootDir = profile.rootDir;
|
|
||||||
|
|
||||||
if (
|
|
||||||
rootDir.exists() &&
|
|
||||||
rootDir.isReadable() &&
|
|
||||||
!rootDir.equals(MigrationUtils.profileStartup.directory)
|
|
||||||
) {
|
|
||||||
allProfiles.set(profile.name, rootDir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allProfiles;
|
|
||||||
};
|
|
||||||
|
|
||||||
function sorter(a, b) {
|
|
||||||
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
FirefoxProfileMigrator.prototype.getSourceProfiles = function() {
|
|
||||||
return [...this._getAllProfiles().keys()]
|
|
||||||
.map(x => ({ id: x, name: x }))
|
|
||||||
.sort(sorter);
|
|
||||||
};
|
|
||||||
|
|
||||||
FirefoxProfileMigrator.prototype._getFileObject = function(dir, fileName) {
|
|
||||||
let file = dir.clone();
|
|
||||||
file.append(fileName);
|
|
||||||
|
|
||||||
// File resources are monolithic. We don't make partial copies since
|
|
||||||
// they are not expected to work alone. Return null to avoid trying to
|
|
||||||
// copy non-existing files.
|
|
||||||
return file.exists() ? file : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
|
|
||||||
let sourceProfileDir = aProfile
|
|
||||||
? this._getAllProfiles().get(aProfile.id)
|
|
||||||
: Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
|
||||||
Ci.nsIToolkitProfileService
|
|
||||||
).defaultProfile.rootDir;
|
|
||||||
if (
|
|
||||||
!sourceProfileDir ||
|
|
||||||
!sourceProfileDir.exists() ||
|
|
||||||
!sourceProfileDir.isReadable()
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Being a startup-only migrator, we can rely on
|
get classDescription() {
|
||||||
// MigrationUtils.profileStartup being set.
|
return "Firefox Profile Migrator";
|
||||||
let currentProfileDir = MigrationUtils.profileStartup.directory;
|
|
||||||
|
|
||||||
// Surely data cannot be imported from the current profile.
|
|
||||||
if (sourceProfileDir.equals(currentProfileDir)) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._getResourcesInternal(sourceProfileDir, currentProfileDir);
|
get contractID() {
|
||||||
};
|
return "@mozilla.org/profile/migrator;1?app=browser&type=firefox";
|
||||||
|
}
|
||||||
|
|
||||||
FirefoxProfileMigrator.prototype.getLastUsedDate = function() {
|
get classID() {
|
||||||
// We always pretend we're really old, so that we don't mess
|
return Components.ID("{91185366-ba97-4438-acba-48deaca63386}");
|
||||||
// up the determination of which browser is the most 'recent'
|
}
|
||||||
// to import from.
|
|
||||||
return Promise.resolve(new Date(0));
|
|
||||||
};
|
|
||||||
|
|
||||||
FirefoxProfileMigrator.prototype._getResourcesInternal = function(
|
_getAllProfiles() {
|
||||||
sourceProfileDir,
|
let allProfiles = new Map();
|
||||||
currentProfileDir
|
let profileService = Cc[
|
||||||
) {
|
"@mozilla.org/toolkit/profile-service;1"
|
||||||
let getFileResource = (aMigrationType, aFileNames) => {
|
].getService(Ci.nsIToolkitProfileService);
|
||||||
let files = [];
|
for (let profile of profileService.profiles) {
|
||||||
for (let fileName of aFileNames) {
|
let rootDir = profile.rootDir;
|
||||||
let file = this._getFileObject(sourceProfileDir, fileName);
|
|
||||||
if (file) {
|
if (
|
||||||
files.push(file);
|
rootDir.exists() &&
|
||||||
|
rootDir.isReadable() &&
|
||||||
|
!rootDir.equals(MigrationUtils.profileStartup.directory)
|
||||||
|
) {
|
||||||
|
allProfiles.set(profile.name, rootDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!files.length) {
|
return allProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSourceProfiles() {
|
||||||
|
let sorter = (a, b) => {
|
||||||
|
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
return [...this._getAllProfiles().keys()]
|
||||||
|
.map(x => ({ id: x, name: x }))
|
||||||
|
.sort(sorter);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getFileObject(dir, fileName) {
|
||||||
|
let file = dir.clone();
|
||||||
|
file.append(fileName);
|
||||||
|
|
||||||
|
// File resources are monolithic. We don't make partial copies since
|
||||||
|
// they are not expected to work alone. Return null to avoid trying to
|
||||||
|
// copy non-existing files.
|
||||||
|
return file.exists() ? file : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getResources(aProfile) {
|
||||||
|
let sourceProfileDir = aProfile
|
||||||
|
? this._getAllProfiles().get(aProfile.id)
|
||||||
|
: Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
||||||
|
Ci.nsIToolkitProfileService
|
||||||
|
).defaultProfile.rootDir;
|
||||||
|
if (
|
||||||
|
!sourceProfileDir ||
|
||||||
|
!sourceProfileDir.exists() ||
|
||||||
|
!sourceProfileDir.isReadable()
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
type: aMigrationType,
|
// Being a startup-only migrator, we can rely on
|
||||||
migrate(aCallback) {
|
// MigrationUtils.profileStartup being set.
|
||||||
for (let file of files) {
|
let currentProfileDir = MigrationUtils.profileStartup.directory;
|
||||||
file.copyTo(currentProfileDir, "");
|
|
||||||
|
// Surely data cannot be imported from the current profile.
|
||||||
|
if (sourceProfileDir.equals(currentProfileDir)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._getResourcesInternal(sourceProfileDir, currentProfileDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastUsedDate() {
|
||||||
|
// We always pretend we're really old, so that we don't mess
|
||||||
|
// up the determination of which browser is the most 'recent'
|
||||||
|
// to import from.
|
||||||
|
return Promise.resolve(new Date(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
_getResourcesInternal(sourceProfileDir, currentProfileDir) {
|
||||||
|
let getFileResource = (aMigrationType, aFileNames) => {
|
||||||
|
let files = [];
|
||||||
|
for (let fileName of aFileNames) {
|
||||||
|
let file = this._getFileObject(sourceProfileDir, fileName);
|
||||||
|
if (file) {
|
||||||
|
files.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!files.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: aMigrationType,
|
||||||
|
migrate(aCallback) {
|
||||||
|
for (let file of files) {
|
||||||
|
file.copyTo(currentProfileDir, "");
|
||||||
|
}
|
||||||
|
aCallback(true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function savePrefs() {
|
||||||
|
// If we've used the pref service to write prefs for the new profile, it's too
|
||||||
|
// early in startup for the service to have a profile directory, so we have to
|
||||||
|
// manually tell it where to save the prefs file.
|
||||||
|
let newPrefsFile = currentProfileDir.clone();
|
||||||
|
newPrefsFile.append("prefs.js");
|
||||||
|
Services.prefs.savePrefFile(newPrefsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
let types = MigrationUtils.resourceTypes;
|
||||||
|
let places = getFileResource(types.HISTORY, [
|
||||||
|
"places.sqlite",
|
||||||
|
"places.sqlite-wal",
|
||||||
|
]);
|
||||||
|
let favicons = getFileResource(types.HISTORY, [
|
||||||
|
"favicons.sqlite",
|
||||||
|
"favicons.sqlite-wal",
|
||||||
|
]);
|
||||||
|
let cookies = getFileResource(types.COOKIES, [
|
||||||
|
"cookies.sqlite",
|
||||||
|
"cookies.sqlite-wal",
|
||||||
|
]);
|
||||||
|
let passwords = getFileResource(types.PASSWORDS, [
|
||||||
|
"signons.sqlite",
|
||||||
|
"logins.json",
|
||||||
|
"key3.db",
|
||||||
|
"key4.db",
|
||||||
|
]);
|
||||||
|
let formData = getFileResource(types.FORMDATA, [
|
||||||
|
"formhistory.sqlite",
|
||||||
|
"autofill-profiles.json",
|
||||||
|
]);
|
||||||
|
let bookmarksBackups = getFileResource(types.OTHERDATA, [
|
||||||
|
lazy.PlacesBackups.profileRelativeFolderPath,
|
||||||
|
]);
|
||||||
|
let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
|
||||||
|
|
||||||
|
let session;
|
||||||
|
if (Services.env.get("MOZ_RESET_PROFILE_MIGRATE_SESSION")) {
|
||||||
|
// We only want to restore the previous firefox session if the profile refresh was
|
||||||
|
// triggered by user. The MOZ_RESET_PROFILE_MIGRATE_SESSION would be set when a user-triggered
|
||||||
|
// profile refresh happened in nsAppRunner.cpp. Hence, we detect the MOZ_RESET_PROFILE_MIGRATE_SESSION
|
||||||
|
// to see if session data migration is required.
|
||||||
|
Services.env.set("MOZ_RESET_PROFILE_MIGRATE_SESSION", "");
|
||||||
|
let sessionCheckpoints = this._getFileObject(
|
||||||
|
sourceProfileDir,
|
||||||
|
"sessionCheckpoints.json"
|
||||||
|
);
|
||||||
|
let sessionFile = this._getFileObject(
|
||||||
|
sourceProfileDir,
|
||||||
|
"sessionstore.jsonlz4"
|
||||||
|
);
|
||||||
|
if (sessionFile) {
|
||||||
|
session = {
|
||||||
|
type: types.SESSION,
|
||||||
|
migrate(aCallback) {
|
||||||
|
sessionCheckpoints.copyTo(
|
||||||
|
currentProfileDir,
|
||||||
|
"sessionCheckpoints.json"
|
||||||
|
);
|
||||||
|
let newSessionFile = currentProfileDir.clone();
|
||||||
|
newSessionFile.append("sessionstore.jsonlz4");
|
||||||
|
let migrationPromise = lazy.SessionMigration.migrate(
|
||||||
|
sessionFile.path,
|
||||||
|
newSessionFile.path
|
||||||
|
);
|
||||||
|
migrationPromise.then(
|
||||||
|
function() {
|
||||||
|
let buildID = Services.appinfo.platformBuildID;
|
||||||
|
let mstone = Services.appinfo.platformVersion;
|
||||||
|
// Force the browser to one-off resume the session that we give it:
|
||||||
|
Services.prefs.setBoolPref(
|
||||||
|
"browser.sessionstore.resume_session_once",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
// Reset the homepage_override prefs so that the browser doesn't override our
|
||||||
|
// session with the "what's new" page:
|
||||||
|
Services.prefs.setCharPref(
|
||||||
|
"browser.startup.homepage_override.mstone",
|
||||||
|
mstone
|
||||||
|
);
|
||||||
|
Services.prefs.setCharPref(
|
||||||
|
"browser.startup.homepage_override.buildID",
|
||||||
|
buildID
|
||||||
|
);
|
||||||
|
savePrefs();
|
||||||
|
aCallback(true);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
aCallback(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync/FxA related data
|
||||||
|
let sync = {
|
||||||
|
name: "sync", // name is used only by tests.
|
||||||
|
type: types.OTHERDATA,
|
||||||
|
migrate: async aCallback => {
|
||||||
|
// Try and parse a signedInUser.json file from the source directory and
|
||||||
|
// if we can, copy it to the new profile and set sync's username pref
|
||||||
|
// (which acts as a de-facto flag to indicate if sync is configured)
|
||||||
|
try {
|
||||||
|
let oldPath = PathUtils.join(
|
||||||
|
sourceProfileDir.path,
|
||||||
|
"signedInUser.json"
|
||||||
|
);
|
||||||
|
let exists = await IOUtils.exists(oldPath);
|
||||||
|
if (exists) {
|
||||||
|
let data = await IOUtils.readJSON(oldPath);
|
||||||
|
if (data && data.accountData && data.accountData.email) {
|
||||||
|
let username = data.accountData.email;
|
||||||
|
// copy the file itself.
|
||||||
|
await IOUtils.copy(
|
||||||
|
oldPath,
|
||||||
|
PathUtils.join(currentProfileDir.path, "signedInUser.json")
|
||||||
|
);
|
||||||
|
// Now we need to know whether Sync is actually configured for this
|
||||||
|
// user. The only way we know is by looking at the prefs file from
|
||||||
|
// the old profile. We avoid trying to do a full parse of the prefs
|
||||||
|
// file and even avoid parsing the single string value we care
|
||||||
|
// about.
|
||||||
|
let prefsPath = PathUtils.join(sourceProfileDir.path, "prefs.js");
|
||||||
|
if (await IOUtils.exists(oldPath)) {
|
||||||
|
let rawPrefs = await IOUtils.readUTF8(prefsPath, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
if (/^user_pref\("services\.sync\.username"/m.test(rawPrefs)) {
|
||||||
|
// sync's configured in the source profile - ensure it is in the
|
||||||
|
// new profile too.
|
||||||
|
// Write it to prefs.js and flush the file.
|
||||||
|
Services.prefs.setStringPref(
|
||||||
|
"services.sync.username",
|
||||||
|
username
|
||||||
|
);
|
||||||
|
savePrefs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
aCallback(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
aCallback(true);
|
aCallback(true);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
function savePrefs() {
|
// Telemetry related migrations.
|
||||||
// If we've used the pref service to write prefs for the new profile, it's too
|
let times = {
|
||||||
// early in startup for the service to have a profile directory, so we have to
|
name: "times", // name is used only by tests.
|
||||||
// manually tell it where to save the prefs file.
|
type: types.OTHERDATA,
|
||||||
let newPrefsFile = currentProfileDir.clone();
|
migrate: aCallback => {
|
||||||
newPrefsFile.append("prefs.js");
|
let file = this._getFileObject(sourceProfileDir, "times.json");
|
||||||
Services.prefs.savePrefFile(newPrefsFile);
|
if (file) {
|
||||||
}
|
file.copyTo(currentProfileDir, "");
|
||||||
|
}
|
||||||
|
// And record the fact a migration (ie, a reset) happened.
|
||||||
|
let recordMigration = async () => {
|
||||||
|
try {
|
||||||
|
let profileTimes = await lazy.ProfileAge(currentProfileDir.path);
|
||||||
|
await profileTimes.recordProfileReset();
|
||||||
|
aCallback(true);
|
||||||
|
} catch (e) {
|
||||||
|
aCallback(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let types = MigrationUtils.resourceTypes;
|
recordMigration();
|
||||||
let places = getFileResource(types.HISTORY, [
|
},
|
||||||
"places.sqlite",
|
};
|
||||||
"places.sqlite-wal",
|
let telemetry = {
|
||||||
]);
|
name: "telemetry", // name is used only by tests...
|
||||||
let favicons = getFileResource(types.HISTORY, [
|
type: types.OTHERDATA,
|
||||||
"favicons.sqlite",
|
migrate: aCallback => {
|
||||||
"favicons.sqlite-wal",
|
let createSubDir = name => {
|
||||||
]);
|
let dir = currentProfileDir.clone();
|
||||||
let cookies = getFileResource(types.COOKIES, [
|
dir.append(name);
|
||||||
"cookies.sqlite",
|
dir.create(Ci.nsIFile.DIRECTORY_TYPE, lazy.FileUtils.PERMS_DIRECTORY);
|
||||||
"cookies.sqlite-wal",
|
return dir;
|
||||||
]);
|
};
|
||||||
let passwords = getFileResource(types.PASSWORDS, [
|
|
||||||
"signons.sqlite",
|
|
||||||
"logins.json",
|
|
||||||
"key3.db",
|
|
||||||
"key4.db",
|
|
||||||
]);
|
|
||||||
let formData = getFileResource(types.FORMDATA, [
|
|
||||||
"formhistory.sqlite",
|
|
||||||
"autofill-profiles.json",
|
|
||||||
]);
|
|
||||||
let bookmarksBackups = getFileResource(types.OTHERDATA, [
|
|
||||||
lazy.PlacesBackups.profileRelativeFolderPath,
|
|
||||||
]);
|
|
||||||
let dictionary = getFileResource(types.OTHERDATA, ["persdict.dat"]);
|
|
||||||
|
|
||||||
let session;
|
// If the 'datareporting' directory exists we migrate files from it.
|
||||||
if (Services.env.get("MOZ_RESET_PROFILE_MIGRATE_SESSION")) {
|
let dataReportingDir = this._getFileObject(
|
||||||
// We only want to restore the previous firefox session if the profile refresh was
|
sourceProfileDir,
|
||||||
// triggered by user. The MOZ_RESET_PROFILE_MIGRATE_SESSION would be set when a user-triggered
|
"datareporting"
|
||||||
// profile refresh happened in nsAppRunner.cpp. Hence, we detect the MOZ_RESET_PROFILE_MIGRATE_SESSION
|
|
||||||
// to see if session data migration is required.
|
|
||||||
Services.env.set("MOZ_RESET_PROFILE_MIGRATE_SESSION", "");
|
|
||||||
let sessionCheckpoints = this._getFileObject(
|
|
||||||
sourceProfileDir,
|
|
||||||
"sessionCheckpoints.json"
|
|
||||||
);
|
|
||||||
let sessionFile = this._getFileObject(
|
|
||||||
sourceProfileDir,
|
|
||||||
"sessionstore.jsonlz4"
|
|
||||||
);
|
|
||||||
if (sessionFile) {
|
|
||||||
session = {
|
|
||||||
type: types.SESSION,
|
|
||||||
migrate(aCallback) {
|
|
||||||
sessionCheckpoints.copyTo(
|
|
||||||
currentProfileDir,
|
|
||||||
"sessionCheckpoints.json"
|
|
||||||
);
|
|
||||||
let newSessionFile = currentProfileDir.clone();
|
|
||||||
newSessionFile.append("sessionstore.jsonlz4");
|
|
||||||
let migrationPromise = lazy.SessionMigration.migrate(
|
|
||||||
sessionFile.path,
|
|
||||||
newSessionFile.path
|
|
||||||
);
|
|
||||||
migrationPromise.then(
|
|
||||||
function() {
|
|
||||||
let buildID = Services.appinfo.platformBuildID;
|
|
||||||
let mstone = Services.appinfo.platformVersion;
|
|
||||||
// Force the browser to one-off resume the session that we give it:
|
|
||||||
Services.prefs.setBoolPref(
|
|
||||||
"browser.sessionstore.resume_session_once",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
// Reset the homepage_override prefs so that the browser doesn't override our
|
|
||||||
// session with the "what's new" page:
|
|
||||||
Services.prefs.setCharPref(
|
|
||||||
"browser.startup.homepage_override.mstone",
|
|
||||||
mstone
|
|
||||||
);
|
|
||||||
Services.prefs.setCharPref(
|
|
||||||
"browser.startup.homepage_override.buildID",
|
|
||||||
buildID
|
|
||||||
);
|
|
||||||
savePrefs();
|
|
||||||
aCallback(true);
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
aCallback(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync/FxA related data
|
|
||||||
let sync = {
|
|
||||||
name: "sync", // name is used only by tests.
|
|
||||||
type: types.OTHERDATA,
|
|
||||||
migrate: async aCallback => {
|
|
||||||
// Try and parse a signedInUser.json file from the source directory and
|
|
||||||
// if we can, copy it to the new profile and set sync's username pref
|
|
||||||
// (which acts as a de-facto flag to indicate if sync is configured)
|
|
||||||
try {
|
|
||||||
let oldPath = PathUtils.join(
|
|
||||||
sourceProfileDir.path,
|
|
||||||
"signedInUser.json"
|
|
||||||
);
|
);
|
||||||
let exists = await IOUtils.exists(oldPath);
|
if (dataReportingDir && dataReportingDir.isDirectory()) {
|
||||||
if (exists) {
|
// Copy only specific files.
|
||||||
let data = await IOUtils.readJSON(oldPath);
|
let toCopy = ["state.json", "session-state.json"];
|
||||||
if (data && data.accountData && data.accountData.email) {
|
|
||||||
let username = data.accountData.email;
|
let dest = createSubDir("datareporting");
|
||||||
// copy the file itself.
|
let enumerator = dataReportingDir.directoryEntries;
|
||||||
await IOUtils.copy(
|
while (enumerator.hasMoreElements()) {
|
||||||
oldPath,
|
let file = enumerator.nextFile;
|
||||||
PathUtils.join(currentProfileDir.path, "signedInUser.json")
|
if (file.isDirectory() || !toCopy.includes(file.leafName)) {
|
||||||
);
|
continue;
|
||||||
// Now we need to know whether Sync is actually configured for this
|
|
||||||
// user. The only way we know is by looking at the prefs file from
|
|
||||||
// the old profile. We avoid trying to do a full parse of the prefs
|
|
||||||
// file and even avoid parsing the single string value we care
|
|
||||||
// about.
|
|
||||||
let prefsPath = PathUtils.join(sourceProfileDir.path, "prefs.js");
|
|
||||||
if (await IOUtils.exists(oldPath)) {
|
|
||||||
let rawPrefs = await IOUtils.readUTF8(prefsPath, {
|
|
||||||
encoding: "utf-8",
|
|
||||||
});
|
|
||||||
if (/^user_pref\("services\.sync\.username"/m.test(rawPrefs)) {
|
|
||||||
// sync's configured in the source profile - ensure it is in the
|
|
||||||
// new profile too.
|
|
||||||
// Write it to prefs.js and flush the file.
|
|
||||||
Services.prefs.setStringPref(
|
|
||||||
"services.sync.username",
|
|
||||||
username
|
|
||||||
);
|
|
||||||
savePrefs();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
file.copyTo(dest, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex) {
|
|
||||||
aCallback(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
aCallback(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Telemetry related migrations.
|
aCallback(true);
|
||||||
let times = {
|
},
|
||||||
name: "times", // name is used only by tests.
|
};
|
||||||
type: types.OTHERDATA,
|
|
||||||
migrate: aCallback => {
|
|
||||||
let file = this._getFileObject(sourceProfileDir, "times.json");
|
|
||||||
if (file) {
|
|
||||||
file.copyTo(currentProfileDir, "");
|
|
||||||
}
|
|
||||||
// And record the fact a migration (ie, a reset) happened.
|
|
||||||
let recordMigration = async () => {
|
|
||||||
try {
|
|
||||||
let profileTimes = await lazy.ProfileAge(currentProfileDir.path);
|
|
||||||
await profileTimes.recordProfileReset();
|
|
||||||
aCallback(true);
|
|
||||||
} catch (e) {
|
|
||||||
aCallback(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
recordMigration();
|
return [
|
||||||
},
|
places,
|
||||||
};
|
cookies,
|
||||||
let telemetry = {
|
passwords,
|
||||||
name: "telemetry", // name is used only by tests...
|
formData,
|
||||||
type: types.OTHERDATA,
|
dictionary,
|
||||||
migrate: aCallback => {
|
bookmarksBackups,
|
||||||
let createSubDir = name => {
|
session,
|
||||||
let dir = currentProfileDir.clone();
|
sync,
|
||||||
dir.append(name);
|
times,
|
||||||
dir.create(Ci.nsIFile.DIRECTORY_TYPE, lazy.FileUtils.PERMS_DIRECTORY);
|
telemetry,
|
||||||
return dir;
|
favicons,
|
||||||
};
|
].filter(r => r);
|
||||||
|
}
|
||||||
|
|
||||||
// If the 'datareporting' directory exists we migrate files from it.
|
get startupOnlyMigrator() {
|
||||||
let dataReportingDir = this._getFileObject(
|
return true;
|
||||||
sourceProfileDir,
|
}
|
||||||
"datareporting"
|
}
|
||||||
);
|
|
||||||
if (dataReportingDir && dataReportingDir.isDirectory()) {
|
|
||||||
// Copy only specific files.
|
|
||||||
let toCopy = ["state.json", "session-state.json"];
|
|
||||||
|
|
||||||
let dest = createSubDir("datareporting");
|
|
||||||
let enumerator = dataReportingDir.directoryEntries;
|
|
||||||
while (enumerator.hasMoreElements()) {
|
|
||||||
let file = enumerator.nextFile;
|
|
||||||
if (file.isDirectory() || !toCopy.includes(file.leafName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
file.copyTo(dest, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
aCallback(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return [
|
|
||||||
places,
|
|
||||||
cookies,
|
|
||||||
passwords,
|
|
||||||
formData,
|
|
||||||
dictionary,
|
|
||||||
bookmarksBackups,
|
|
||||||
session,
|
|
||||||
sync,
|
|
||||||
times,
|
|
||||||
telemetry,
|
|
||||||
favicons,
|
|
||||||
].filter(r => r);
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperty(FirefoxProfileMigrator.prototype, "startupOnlyMigrator", {
|
|
||||||
get: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
FirefoxProfileMigrator.prototype.classDescription = "Firefox Profile Migrator";
|
|
||||||
FirefoxProfileMigrator.prototype.contractID =
|
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=firefox";
|
|
||||||
FirefoxProfileMigrator.prototype.classID = Components.ID(
|
|
||||||
"{91185366-ba97-4438-acba-48deaca63386}"
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -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,58 +349,67 @@ IE7FormPasswords.prototype = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function IEProfileMigrator() {
|
/**
|
||||||
this.wrappedJSObject = this; // export this to be able to use it in the unittest.
|
* Internet Explorer profile migrator
|
||||||
}
|
*/
|
||||||
|
export class IEProfileMigrator extends MigratorBase {
|
||||||
IEProfileMigrator.prototype = Object.create(MigratorPrototype);
|
constructor() {
|
||||||
|
super();
|
||||||
IEProfileMigrator.prototype.getResources = function IE_getResources() {
|
this.wrappedJSObject = this; // export this to be able to use it in the unittest.
|
||||||
let resources = [
|
|
||||||
MSMigrationUtils.getBookmarksMigrator(),
|
|
||||||
new History(),
|
|
||||||
MSMigrationUtils.getCookiesMigrator(),
|
|
||||||
];
|
|
||||||
// Only support the form password migrator for Windows XP to 7.
|
|
||||||
if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
|
|
||||||
resources.push(new IE7FormPasswords());
|
|
||||||
}
|
}
|
||||||
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
|
||||||
windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
|
|
||||||
resources.push(windowsVaultFormPasswordsMigrator);
|
|
||||||
return resources.filter(r => r.exists);
|
|
||||||
};
|
|
||||||
|
|
||||||
IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
|
get classDescription() {
|
||||||
let datePromises = ["Favs", "CookD"].map(dirId => {
|
return "IE Profile Migrator";
|
||||||
let { path } = Services.dirsvc.get(dirId, Ci.nsIFile);
|
}
|
||||||
return OS.File.stat(path)
|
|
||||||
.catch(() => null)
|
|
||||||
.then(info => {
|
|
||||||
return info ? info.lastModificationDate : 0;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
datePromises.push(
|
|
||||||
new Promise(resolve => {
|
|
||||||
let typedURLs = new Map();
|
|
||||||
try {
|
|
||||||
typedURLs = MSMigrationUtils.getTypedURLs(
|
|
||||||
"Software\\Microsoft\\Internet Explorer"
|
|
||||||
);
|
|
||||||
} catch (ex) {}
|
|
||||||
let dates = [0, ...typedURLs.values()];
|
|
||||||
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
|
||||||
resolve(Math.max.apply(Math, dates) / 1000);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return Promise.all(datePromises).then(dates => {
|
|
||||||
return new Date(Math.max.apply(Math, dates));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
|
get contractID() {
|
||||||
IEProfileMigrator.prototype.contractID =
|
return "@mozilla.org/profile/migrator;1?app=browser&type=ie";
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=ie";
|
}
|
||||||
IEProfileMigrator.prototype.classID = Components.ID(
|
|
||||||
"{3d2532e3-4932-4774-b7ba-968f5899d3a4}"
|
get classID() {
|
||||||
);
|
return Components.ID("{3d2532e3-4932-4774-b7ba-968f5899d3a4}");
|
||||||
|
}
|
||||||
|
|
||||||
|
getResources() {
|
||||||
|
let resources = [
|
||||||
|
MSMigrationUtils.getBookmarksMigrator(),
|
||||||
|
new History(),
|
||||||
|
MSMigrationUtils.getCookiesMigrator(),
|
||||||
|
];
|
||||||
|
// Only support the form password migrator for Windows XP to 7.
|
||||||
|
if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
|
||||||
|
resources.push(new IE7FormPasswords());
|
||||||
|
}
|
||||||
|
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
|
||||||
|
windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
|
||||||
|
resources.push(windowsVaultFormPasswordsMigrator);
|
||||||
|
return resources.filter(r => r.exists);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastUsedDate() {
|
||||||
|
let datePromises = ["Favs", "CookD"].map(dirId => {
|
||||||
|
let { path } = Services.dirsvc.get(dirId, Ci.nsIFile);
|
||||||
|
return OS.File.stat(path)
|
||||||
|
.catch(() => null)
|
||||||
|
.then(info => {
|
||||||
|
return info ? info.lastModificationDate : 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
datePromises.push(
|
||||||
|
new Promise(resolve => {
|
||||||
|
let typedURLs = new Map();
|
||||||
|
try {
|
||||||
|
typedURLs = MSMigrationUtils.getTypedURLs(
|
||||||
|
"Software\\Microsoft\\Internet Explorer"
|
||||||
|
);
|
||||||
|
} catch (ex) {}
|
||||||
|
let dates = [0, ...typedURLs.values()];
|
||||||
|
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
|
||||||
|
resolve(Math.max.apply(Math, dates) / 1000);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return Promise.all(datePromises).then(dates => {
|
||||||
|
return new Date(Math.max.apply(Math, dates));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,22 +2,13 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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 };
|
||||||
|
|||||||
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";
|
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,135 +342,134 @@ SearchStrings.prototype = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SafariProfileMigrator() {}
|
/**
|
||||||
|
* Safari migrator
|
||||||
SafariProfileMigrator.prototype = Object.create(MigratorPrototype);
|
*/
|
||||||
|
export class SafariProfileMigrator extends MigratorBase {
|
||||||
SafariProfileMigrator.prototype.getResources = function SM_getResources() {
|
get classDescription() {
|
||||||
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
return "Safari Profile Migrator";
|
||||||
if (!profileDir.exists()) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let resources = [];
|
get contractID() {
|
||||||
let pushProfileFileResource = function(aFileName, aConstructor) {
|
return "@mozilla.org/profile/migrator;1?app=browser&type=safari";
|
||||||
let file = profileDir.clone();
|
}
|
||||||
file.append(aFileName);
|
|
||||||
if (file.exists()) {
|
get classID() {
|
||||||
resources.push(new aConstructor(file));
|
return Components.ID("{4b609ecf-60b2-4655-9df4-dc149e474da1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
getResources() {
|
||||||
|
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||||
|
if (!profileDir.exists()) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
pushProfileFileResource("History.plist", History);
|
let resources = [];
|
||||||
pushProfileFileResource("Bookmarks.plist", Bookmarks);
|
let pushProfileFileResource = function(aFileName, aConstructor) {
|
||||||
|
let file = profileDir.clone();
|
||||||
|
file.append(aFileName);
|
||||||
|
if (file.exists()) {
|
||||||
|
resources.push(new aConstructor(file));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// The Reading List feature was introduced at the same time in Windows and
|
pushProfileFileResource("History.plist", History);
|
||||||
// Mac versions of Safari. Not surprisingly, they are stored in the same
|
pushProfileFileResource("Bookmarks.plist", Bookmarks);
|
||||||
// format in both versions. Surpsingly, only on Windows there is a
|
|
||||||
// separate property list for it. This code is used on mac too, because
|
|
||||||
// Apple may fix this at some point.
|
|
||||||
pushProfileFileResource("ReadingList.plist", Bookmarks);
|
|
||||||
|
|
||||||
let prefs = this.mainPreferencesPropertyList;
|
// The Reading List feature was introduced at the same time in Windows and
|
||||||
if (prefs) {
|
// Mac versions of Safari. Not surprisingly, they are stored in the same
|
||||||
resources.push(new SearchStrings(prefs));
|
// format in both versions. Surpsingly, only on Windows there is a
|
||||||
|
// separate property list for it. This code is used on mac too, because
|
||||||
|
// Apple may fix this at some point.
|
||||||
|
pushProfileFileResource("ReadingList.plist", Bookmarks);
|
||||||
|
|
||||||
|
let prefs = this.mainPreferencesPropertyList;
|
||||||
|
if (prefs) {
|
||||||
|
resources.push(new SearchStrings(prefs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
return resources;
|
getLastUsedDate() {
|
||||||
};
|
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||||
|
let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
|
||||||
SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() {
|
let path = OS.Path.join(profileDir.path, file);
|
||||||
let profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
|
return OS.File.stat(path)
|
||||||
let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
|
.catch(() => null)
|
||||||
let path = OS.Path.join(profileDir.path, file);
|
.then(info => {
|
||||||
return OS.File.stat(path)
|
return info ? info.lastModificationDate : 0;
|
||||||
.catch(() => null)
|
});
|
||||||
.then(info => {
|
});
|
||||||
return info ? info.lastModificationDate : 0;
|
return Promise.all(datePromises).then(dates => {
|
||||||
});
|
return new Date(Math.max.apply(Math, dates));
|
||||||
});
|
});
|
||||||
return Promise.all(datePromises).then(dates => {
|
|
||||||
return new Date(Math.max.apply(Math, dates));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
SafariProfileMigrator.prototype.hasPermissions = async function SM_hasPermissions() {
|
|
||||||
if (this._hasPermissions) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// Check if we have access:
|
|
||||||
let target = FileUtils.getDir(
|
|
||||||
"ULibDir",
|
|
||||||
["Safari", "Bookmarks.plist"],
|
|
||||||
false
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
// 'stat' is always allowed, but reading is somehow not, if the user hasn't
|
|
||||||
// allowed it:
|
|
||||||
await IOUtils.read(target.path, { maxBytes: 1 });
|
|
||||||
this._hasPermissions = true;
|
|
||||||
return true;
|
|
||||||
} catch (ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SafariProfileMigrator.prototype.getPermissions = async function SM_getPermissions(
|
async hasPermissions() {
|
||||||
win
|
if (this._hasPermissions) {
|
||||||
) {
|
return true;
|
||||||
// Keep prompting the user until they pick a file that grants us access,
|
}
|
||||||
// or they cancel out of the file open panel.
|
// Check if we have access:
|
||||||
while (!(await this.hasPermissions())) {
|
let target = FileUtils.getDir(
|
||||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
"ULibDir",
|
||||||
// The title (second arg) is not displayed on macOS, so leave it blank.
|
["Safari", "Bookmarks.plist"],
|
||||||
fp.init(win, "", Ci.nsIFilePicker.modeOpen);
|
false
|
||||||
// This is a little weird. You'd expect that it matters which file
|
);
|
||||||
// the user picks, but it doesn't really, as long as it's in this
|
try {
|
||||||
// directory. Anyway, let's not confuse the user: the sensible idea
|
// 'stat' is always allowed, but reading is somehow not, if the user hasn't
|
||||||
// here is to ask for permissions for Bookmarks.plist, and we'll
|
// allowed it:
|
||||||
// silently accept whatever input as long as we can then read the plist.
|
await IOUtils.read(target.path, { maxBytes: 1 });
|
||||||
fp.appendFilter("plist", "*.plist");
|
this._hasPermissions = true;
|
||||||
fp.filterIndex = 1;
|
return true;
|
||||||
fp.displayDirectory = FileUtils.getDir("ULibDir", ["Safari"], false);
|
} catch (ex) {
|
||||||
// Now wait for the filepicker to open and close. If the user picks
|
|
||||||
// any file in this directory, macOS will grant us read access, so
|
|
||||||
// we don't need to check or do anything else with the file returned
|
|
||||||
// by the filepicker.
|
|
||||||
let result = await new Promise(resolve => fp.open(resolve));
|
|
||||||
// Bail if the user cancels the dialog:
|
|
||||||
if (result == Ci.nsIFilePicker.returnCancel) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Object.defineProperty(
|
async getPermissions(win) {
|
||||||
SafariProfileMigrator.prototype,
|
// Keep prompting the user until they pick a file that grants us access,
|
||||||
"mainPreferencesPropertyList",
|
// or they cancel out of the file open panel.
|
||||||
{
|
while (!(await this.hasPermissions())) {
|
||||||
get: function get_mainPreferencesPropertyList() {
|
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||||
if (this._mainPreferencesPropertyList === undefined) {
|
// The title (second arg) is not displayed on macOS, so leave it blank.
|
||||||
let file = FileUtils.getDir("UsrPrfs", [], false);
|
fp.init(win, "", Ci.nsIFilePicker.modeOpen);
|
||||||
if (file.exists()) {
|
// This is a little weird. You'd expect that it matters which file
|
||||||
file.append("com.apple.Safari.plist");
|
// the user picks, but it doesn't really, as long as it's in this
|
||||||
if (file.exists()) {
|
// directory. Anyway, let's not confuse the user: the sensible idea
|
||||||
this._mainPreferencesPropertyList = new MainPreferencesPropertyList(
|
// here is to ask for permissions for Bookmarks.plist, and we'll
|
||||||
file
|
// silently accept whatever input as long as we can then read the plist.
|
||||||
);
|
fp.appendFilter("plist", "*.plist");
|
||||||
return this._mainPreferencesPropertyList;
|
fp.filterIndex = 1;
|
||||||
}
|
fp.displayDirectory = FileUtils.getDir("ULibDir", ["Safari"], false);
|
||||||
}
|
// Now wait for the filepicker to open and close. If the user picks
|
||||||
this._mainPreferencesPropertyList = null;
|
// any file in this directory, macOS will grant us read access, so
|
||||||
return this._mainPreferencesPropertyList;
|
// we don't need to check or do anything else with the file returned
|
||||||
|
// by the filepicker.
|
||||||
|
let result = await new Promise(resolve => fp.open(resolve));
|
||||||
|
// Bail if the user cancels the dialog:
|
||||||
|
if (result == Ci.nsIFilePicker.returnCancel) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return this._mainPreferencesPropertyList;
|
}
|
||||||
},
|
return true;
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
|
get mainPreferencesPropertyList() {
|
||||||
SafariProfileMigrator.prototype.contractID =
|
if (this._mainPreferencesPropertyList === undefined) {
|
||||||
"@mozilla.org/profile/migrator;1?app=browser&type=safari";
|
let file = FileUtils.getDir("UsrPrfs", [], false);
|
||||||
SafariProfileMigrator.prototype.classID = Components.ID(
|
if (file.exists()) {
|
||||||
"{4b609ecf-60b2-4655-9df4-dc149e474da1}"
|
file.append("com.apple.Safari.plist");
|
||||||
);
|
if (file.exists()) {
|
||||||
|
this._mainPreferencesPropertyList = new MainPreferencesPropertyList(
|
||||||
|
file
|
||||||
|
);
|
||||||
|
return this._mainPreferencesPropertyList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._mainPreferencesPropertyList = null;
|
||||||
|
return this._mainPreferencesPropertyList;
|
||||||
|
}
|
||||||
|
return this._mainPreferencesPropertyList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ EXTRA_JS_MODULES += [
|
|||||||
"ChromeProfileMigrator.sys.mjs",
|
"ChromeProfileMigrator.sys.mjs",
|
||||||
"FirefoxProfileMigrator.sys.mjs",
|
"FirefoxProfileMigrator.sys.mjs",
|
||||||
"MigrationUtils.sys.mjs",
|
"MigrationUtils.sys.mjs",
|
||||||
|
"MigratorBase.sys.mjs",
|
||||||
"ProfileMigrator.sys.mjs",
|
"ProfileMigrator.sys.mjs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user