mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Merge autoland to mozilla-central. a=merge
This commit is contained in:
commit
4611b95418
@ -212,83 +212,81 @@ function Qihoo360seProfileMigrator() {
|
||||
|
||||
Qihoo360seProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
|
||||
Object.defineProperty(Qihoo360seProfileMigrator.prototype, "sourceProfiles", {
|
||||
get() {
|
||||
if ("__sourceProfiles" in this)
|
||||
return this.__sourceProfiles;
|
||||
Qihoo360seProfileMigrator.prototype.getSourceProfiles = function() {
|
||||
if ("__sourceProfiles" in this)
|
||||
return this.__sourceProfiles;
|
||||
|
||||
if (!this._usersDir) {
|
||||
this.__sourceProfiles = [];
|
||||
return this.__sourceProfiles;
|
||||
if (!this._usersDir) {
|
||||
this.__sourceProfiles = [];
|
||||
return this.__sourceProfiles;
|
||||
}
|
||||
|
||||
let profiles = [];
|
||||
let noLoggedInUser = true;
|
||||
try {
|
||||
let loginIni = this._usersDir.clone();
|
||||
loginIni.append("login.ini");
|
||||
if (!loginIni.exists()) {
|
||||
throw new Error("360 Secure Browser's 'login.ini' does not exist.");
|
||||
}
|
||||
if (!loginIni.isReadable()) {
|
||||
throw new Error("360 Secure Browser's 'login.ini' file could not be read.");
|
||||
}
|
||||
|
||||
let profiles = [];
|
||||
let noLoggedInUser = true;
|
||||
let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
|
||||
let loginIniObj = parseINIStrings(loginIniInUtf8);
|
||||
try {
|
||||
let loginIni = this._usersDir.clone();
|
||||
loginIni.append("login.ini");
|
||||
if (!loginIni.exists()) {
|
||||
throw new Error("360 Secure Browser's 'login.ini' does not exist.");
|
||||
}
|
||||
if (!loginIni.isReadable()) {
|
||||
throw new Error("360 Secure Browser's 'login.ini' file could not be read.");
|
||||
loginIniInUtf8.remove(false);
|
||||
} catch (ex) {}
|
||||
|
||||
let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
|
||||
|
||||
/*
|
||||
* NowLogin section may:
|
||||
* 1. be missing or without email, before any user logs in.
|
||||
* 2. represents the current logged in user
|
||||
* 3. represents the most recent logged in user
|
||||
*
|
||||
* In the second case, user represented by NowLogin should be the first
|
||||
* profile; otherwise the default user should be selected by default.
|
||||
*/
|
||||
if (nowLoginEmail) {
|
||||
if (loginIniObj.NowLogin.IsLogined === "1") {
|
||||
noLoggedInUser = false;
|
||||
}
|
||||
|
||||
let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
|
||||
let loginIniObj = parseINIStrings(loginIniInUtf8);
|
||||
try {
|
||||
loginIniInUtf8.remove(false);
|
||||
} catch (ex) {}
|
||||
|
||||
let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
|
||||
|
||||
/*
|
||||
* NowLogin section may:
|
||||
* 1. be missing or without email, before any user logs in.
|
||||
* 2. represents the current logged in user
|
||||
* 3. represents the most recent logged in user
|
||||
*
|
||||
* In the second case, user represented by NowLogin should be the first
|
||||
* profile; otherwise the default user should be selected by default.
|
||||
*/
|
||||
if (nowLoginEmail) {
|
||||
if (loginIniObj.NowLogin.IsLogined === "1") {
|
||||
noLoggedInUser = false;
|
||||
}
|
||||
|
||||
profiles.push({
|
||||
id: this._getIdFromConfig(loginIniObj.NowLogin),
|
||||
name: nowLoginEmail,
|
||||
});
|
||||
}
|
||||
|
||||
for (let section in loginIniObj) {
|
||||
if (!loginIniObj[section].email ||
|
||||
(nowLoginEmail && loginIniObj[section].email == nowLoginEmail)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
profiles.push({
|
||||
id: this._getIdFromConfig(loginIniObj[section]),
|
||||
name: loginIniObj[section].email,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
|
||||
} finally {
|
||||
profiles[noLoggedInUser ? "unshift" : "push"]({
|
||||
id: this._defaultUserPath,
|
||||
name: "Default",
|
||||
profiles.push({
|
||||
id: this._getIdFromConfig(loginIniObj.NowLogin),
|
||||
name: nowLoginEmail,
|
||||
});
|
||||
}
|
||||
|
||||
this.__sourceProfiles = profiles.filter(profile => {
|
||||
let resources = this.getResources(profile);
|
||||
return resources && resources.length > 0;
|
||||
for (let section in loginIniObj) {
|
||||
if (!loginIniObj[section].email ||
|
||||
(nowLoginEmail && loginIniObj[section].email == nowLoginEmail)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
profiles.push({
|
||||
id: this._getIdFromConfig(loginIniObj[section]),
|
||||
name: loginIniObj[section].email,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
|
||||
} finally {
|
||||
profiles[noLoggedInUser ? "unshift" : "push"]({
|
||||
id: this._defaultUserPath,
|
||||
name: "Default",
|
||||
});
|
||||
return this.__sourceProfiles;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.__sourceProfiles = profiles.filter(profile => {
|
||||
let resources = this.getResources(profile);
|
||||
return resources && resources.length > 0;
|
||||
});
|
||||
return this.__sourceProfiles;
|
||||
};
|
||||
|
||||
Qihoo360seProfileMigrator.prototype._getIdFromConfig = function(aConfig) {
|
||||
return aConfig.UserMd5 || getHash(aConfig.email);
|
||||
@ -308,12 +306,13 @@ Qihoo360seProfileMigrator.prototype.getResources = function(aProfile) {
|
||||
return resources.filter(r => r.exists);
|
||||
};
|
||||
|
||||
Qihoo360seProfileMigrator.prototype.getLastUsedDate = function() {
|
||||
let bookmarksPaths = this.sourceProfiles.map(({id}) => {
|
||||
Qihoo360seProfileMigrator.prototype.getLastUsedDate = async function() {
|
||||
let sourceProfiles = await this.getSourceProfiles();
|
||||
let bookmarksPaths = sourceProfiles.map(({id}) => {
|
||||
return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
|
||||
});
|
||||
if (!bookmarksPaths.length) {
|
||||
return Promise.resolve(new Date(0));
|
||||
return new Date(0);
|
||||
}
|
||||
let datePromises = bookmarksPaths.map(path => {
|
||||
return OS.File.stat(path).catch(() => null).then(info => {
|
||||
|
@ -90,17 +90,17 @@ const AutoMigrate = {
|
||||
* @throws if automatically deciding on migrators/data
|
||||
* failed for some reason.
|
||||
*/
|
||||
migrate(profileStartup, migratorKey, profileToMigrate) {
|
||||
async migrate(profileStartup, migratorKey, profileToMigrate) {
|
||||
let histogram = Services.telemetry.getHistogramById(
|
||||
"FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
|
||||
histogram.add(0);
|
||||
let {migrator, pickedKey} = this.pickMigrator(migratorKey);
|
||||
let {migrator, pickedKey} = await this.pickMigrator(migratorKey);
|
||||
histogram.add(5);
|
||||
|
||||
profileToMigrate = this.pickProfile(migrator, profileToMigrate);
|
||||
profileToMigrate = await this.pickProfile(migrator, profileToMigrate);
|
||||
histogram.add(10);
|
||||
|
||||
let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
|
||||
let resourceTypes = await migrator.getMigrateData(profileToMigrate, profileStartup);
|
||||
if (!(resourceTypes & this.resourceTypesToUse)) {
|
||||
throw new Error("No usable resources were found for the selected browser!");
|
||||
}
|
||||
@ -129,7 +129,7 @@ const AutoMigrate = {
|
||||
MigrationUtils.initializeUndoData();
|
||||
Services.obs.addObserver(migrationObserver, "Migration:Ended");
|
||||
Services.obs.addObserver(migrationObserver, "Migration:ItemError");
|
||||
migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
|
||||
await migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
|
||||
histogram.add(20);
|
||||
},
|
||||
|
||||
@ -140,7 +140,7 @@ const AutoMigrate = {
|
||||
* @returns {Object} an object with the migrator to use for migrating, as
|
||||
* well as the key we eventually ended up using to obtain it.
|
||||
*/
|
||||
pickMigrator(migratorKey) {
|
||||
async pickMigrator(migratorKey) {
|
||||
if (!migratorKey) {
|
||||
let defaultKey = MigrationUtils.getMigratorKeyForDefaultBrowser();
|
||||
if (!defaultKey) {
|
||||
@ -152,7 +152,7 @@ const AutoMigrate = {
|
||||
throw new Error("Can't automatically migrate from Firefox.");
|
||||
}
|
||||
|
||||
let migrator = MigrationUtils.getMigrator(migratorKey);
|
||||
let migrator = await MigrationUtils.getMigrator(migratorKey);
|
||||
if (!migrator) {
|
||||
throw new Error("Migrator specified or a default was found, but the migrator object is not available (or has no data).");
|
||||
}
|
||||
@ -167,8 +167,8 @@ const AutoMigrate = {
|
||||
* @returns the profile to migrate, or null if migrating
|
||||
* from the default profile.
|
||||
*/
|
||||
pickProfile(migrator, suggestedId) {
|
||||
let profiles = migrator.sourceProfiles;
|
||||
async pickProfile(migrator, suggestedId) {
|
||||
let profiles = await migrator.getSourceProfiles();
|
||||
if (profiles && !profiles.length) {
|
||||
throw new Error("No profile data found to migrate.");
|
||||
}
|
||||
|
@ -6,10 +6,8 @@
|
||||
this.EXPORTED_SYMBOLS = ["ChromeMigrationUtils"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
|
||||
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@ -37,7 +35,10 @@ this.ChromeMigrationUtils = {
|
||||
* @param {String} profileId - A Chrome user profile ID. For example, "Profile 1".
|
||||
* @returns {Array} All installed Chrome extensions information.
|
||||
*/
|
||||
async getExtensionList(profileId = this.getLastUsedProfileId()) {
|
||||
async getExtensionList(profileId) {
|
||||
if (profileId === undefined) {
|
||||
profileId = await this.getLastUsedProfileId();
|
||||
}
|
||||
let path = this.getExtensionPath(profileId);
|
||||
let iterator = new OS.File.DirectoryIterator(path);
|
||||
let extensionList = [];
|
||||
@ -58,7 +59,10 @@ this.ChromeMigrationUtils = {
|
||||
* @param {String} profileId - The user profile's ID.
|
||||
* @retruns {Object} The Chrome extension information.
|
||||
*/
|
||||
async getExtensionInformation(extensionId, profileId = this.getLastUsedProfileId()) {
|
||||
async getExtensionInformation(extensionId, profileId) {
|
||||
if (profileId === undefined) {
|
||||
profileId = await this.getLastUsedProfileId();
|
||||
}
|
||||
let extensionInformation = null;
|
||||
try {
|
||||
let manifestPath = this.getExtensionPath(profileId);
|
||||
@ -151,7 +155,10 @@ this.ChromeMigrationUtils = {
|
||||
* @param {String} profileId - The user profile's ID.
|
||||
* @returns {Boolean} Return true if the extension is installed otherwise return false.
|
||||
*/
|
||||
async isExtensionInstalled(extensionId, profileId = this.getLastUsedProfileId()) {
|
||||
async isExtensionInstalled(extensionId, profileId) {
|
||||
if (profileId === undefined) {
|
||||
profileId = await this.getLastUsedProfileId();
|
||||
}
|
||||
let extensionPath = this.getExtensionPath(profileId);
|
||||
let isInstalled = await OS.File.exists(OS.Path.join(extensionPath, extensionId));
|
||||
return isInstalled;
|
||||
@ -161,8 +168,8 @@ this.ChromeMigrationUtils = {
|
||||
* Get the last used user profile's ID.
|
||||
* @returns {String} The last used user profile's ID.
|
||||
*/
|
||||
getLastUsedProfileId() {
|
||||
let localState = this.getLocalState();
|
||||
async getLastUsedProfileId() {
|
||||
let localState = await this.getLocalState();
|
||||
return localState ? localState.profile.last_used : "Default";
|
||||
},
|
||||
|
||||
@ -170,21 +177,12 @@ this.ChromeMigrationUtils = {
|
||||
* Get the local state file content.
|
||||
* @returns {Object} The JSON-based content.
|
||||
*/
|
||||
getLocalState() {
|
||||
let localStateFile = new FileUtils.File(this.getChromeUserDataPath());
|
||||
localStateFile.append("Local State");
|
||||
if (!localStateFile.exists())
|
||||
throw new Error("Chrome's 'Local State' file does not exist.");
|
||||
if (!localStateFile.isReadable())
|
||||
throw new Error("Chrome's 'Local State' file could not be read.");
|
||||
|
||||
async getLocalState() {
|
||||
let localState = null;
|
||||
try {
|
||||
let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
|
||||
fstream.init(localStateFile, -1, 0, 0);
|
||||
let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
|
||||
{ charset: "UTF-8" });
|
||||
localState = JSON.parse(inputStream);
|
||||
let localStatePath = OS.Path.join(this.getChromeUserDataPath(), "Local State");
|
||||
let localStateJson = await OS.File.read(localStatePath, { encoding: "utf-8" });
|
||||
localState = JSON.parse(localStateJson);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
throw ex;
|
||||
|
@ -8,8 +8,6 @@
|
||||
|
||||
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
|
||||
const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
|
||||
|
||||
const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
|
||||
const S100NS_PER_MS = 10;
|
||||
|
||||
@ -20,7 +18,6 @@ const AUTH_TYPE = {
|
||||
};
|
||||
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
@ -92,28 +89,40 @@ function convertBookmarks(items, errorAccumulator) {
|
||||
}
|
||||
|
||||
function ChromeProfileMigrator() {
|
||||
let path = ChromeMigrationUtils.getDataPath("Chrome");
|
||||
let chromeUserDataFolder = new FileUtils.File(path);
|
||||
this._chromeUserDataFolder = chromeUserDataFolder.exists() ?
|
||||
chromeUserDataFolder : null;
|
||||
this._chromeUserDataPathSuffix = "Chrome";
|
||||
}
|
||||
|
||||
ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
|
||||
ChromeProfileMigrator.prototype._getChromeUserDataPathIfExists = async function() {
|
||||
if (this._chromeUserDataPath) {
|
||||
return this._chromeUserDataPath;
|
||||
}
|
||||
let path = ChromeMigrationUtils.getDataPath(this._chromeUserDataPathSuffix);
|
||||
let exists = await OS.File.exists(path);
|
||||
if (exists) {
|
||||
this._chromeUserDataPath = path;
|
||||
} else {
|
||||
this._chromeUserDataPath = null;
|
||||
}
|
||||
return this._chromeUserDataPath;
|
||||
};
|
||||
|
||||
ChromeProfileMigrator.prototype.getResources =
|
||||
function Chrome_getResources(aProfile) {
|
||||
if (this._chromeUserDataFolder) {
|
||||
let profileFolder = this._chromeUserDataFolder.clone();
|
||||
profileFolder.append(aProfile.id);
|
||||
if (profileFolder.exists()) {
|
||||
let possibleResources = [
|
||||
async function Chrome_getResources(aProfile) {
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (chromeUserDataPath) {
|
||||
let profileFolder = OS.Path.join(chromeUserDataPath, aProfile.id);
|
||||
if (await OS.File.exists(profileFolder)) {
|
||||
let possibleResourcePromises = [
|
||||
GetBookmarksResource(profileFolder),
|
||||
GetHistoryResource(profileFolder),
|
||||
GetCookiesResource(profileFolder),
|
||||
];
|
||||
if (AppConstants.platform == "win") {
|
||||
possibleResources.push(GetWindowsPasswordsResource(profileFolder));
|
||||
possibleResourcePromises.push(GetWindowsPasswordsResource(profileFolder));
|
||||
}
|
||||
let possibleResources = await Promise.all(possibleResourcePromises);
|
||||
return possibleResources.filter(r => r != null);
|
||||
}
|
||||
}
|
||||
@ -121,39 +130,41 @@ ChromeProfileMigrator.prototype.getResources =
|
||||
};
|
||||
|
||||
ChromeProfileMigrator.prototype.getLastUsedDate =
|
||||
function Chrome_getLastUsedDate() {
|
||||
let datePromises = this.sourceProfiles.map(profile => {
|
||||
let basePath = OS.Path.join(this._chromeUserDataFolder.path, profile.id);
|
||||
let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(leafName => {
|
||||
async function Chrome_getLastUsedDate() {
|
||||
let sourceProfiles = await this.getSourceProfiles();
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (!chromeUserDataPath) {
|
||||
return new Date(0);
|
||||
}
|
||||
let datePromises = sourceProfiles.map(async profile => {
|
||||
let basePath = OS.Path.join(chromeUserDataPath, profile.id);
|
||||
let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(async leafName => {
|
||||
let path = OS.Path.join(basePath, leafName);
|
||||
return OS.File.stat(path).catch(() => null).then(info => {
|
||||
return info ? info.lastModificationDate : 0;
|
||||
});
|
||||
});
|
||||
return Promise.all(fileDatePromises).then(dates => {
|
||||
return Math.max.apply(Math, dates);
|
||||
let info = await OS.File.stat(path).catch(() => null);
|
||||
return info ? info.lastModificationDate : 0;
|
||||
});
|
||||
let dates = await Promise.all(fileDatePromises);
|
||||
return Math.max(...dates);
|
||||
});
|
||||
return Promise.all(datePromises).then(dates => {
|
||||
dates.push(0);
|
||||
return new Date(Math.max.apply(Math, dates));
|
||||
});
|
||||
let datesOuter = await Promise.all(datePromises);
|
||||
datesOuter.push(0);
|
||||
return new Date(Math.max(...datesOuter));
|
||||
};
|
||||
|
||||
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
|
||||
get: function Chrome_sourceProfiles() {
|
||||
ChromeProfileMigrator.prototype.getSourceProfiles =
|
||||
async function Chrome_getSourceProfiles() {
|
||||
if ("__sourceProfiles" in this)
|
||||
return this.__sourceProfiles;
|
||||
|
||||
if (!this._chromeUserDataFolder)
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (!chromeUserDataPath)
|
||||
return [];
|
||||
|
||||
let profiles = [];
|
||||
try {
|
||||
let info_cache = ChromeMigrationUtils.getLocalState().profile.info_cache;
|
||||
let localState = await ChromeMigrationUtils.getLocalState();
|
||||
let info_cache = localState.profile.info_cache;
|
||||
for (let profileFolderName in info_cache) {
|
||||
let profileFolder = this._chromeUserDataFolder.clone();
|
||||
profileFolder.append(profileFolderName);
|
||||
profiles.push({
|
||||
id: profileFolderName,
|
||||
name: info_cache[profileFolderName].name || profileFolderName,
|
||||
@ -162,9 +173,8 @@ Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
|
||||
} catch (e) {
|
||||
Cu.reportError("Error detecting Chrome profiles: " + e);
|
||||
// If we weren't able to detect any profiles above, fallback to the Default profile.
|
||||
let defaultProfileFolder = this._chromeUserDataFolder.clone();
|
||||
defaultProfileFolder.append("Default");
|
||||
if (defaultProfileFolder.exists()) {
|
||||
let defaultProfilePath = OS.Path.join(chromeUserDataPath, "Default");
|
||||
if (await OS.File.exists(defaultProfilePath)) {
|
||||
profiles = [{
|
||||
id: "Default",
|
||||
name: "Default",
|
||||
@ -172,36 +182,34 @@ Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
|
||||
}
|
||||
}
|
||||
|
||||
// Only list profiles from which any data can be imported
|
||||
this.__sourceProfiles = profiles.filter(function(profile) {
|
||||
let resources = this.getResources(profile);
|
||||
return resources && resources.length > 0;
|
||||
}, this);
|
||||
return this.__sourceProfiles;
|
||||
},
|
||||
});
|
||||
let profileResources = await Promise.all(profiles.map(async profile => ({
|
||||
profile,
|
||||
resources: await this.getResources(profile),
|
||||
})));
|
||||
|
||||
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
|
||||
get: function Chrome_sourceHomePageURL() {
|
||||
let prefsFile = this._chromeUserDataFolder.clone();
|
||||
prefsFile.append("Preferences");
|
||||
if (prefsFile.exists()) {
|
||||
// XXX reading and parsing JSON is synchronous.
|
||||
let fstream = Cc[FILE_INPUT_STREAM_CID].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
fstream.init(prefsFile, -1, 0, 0);
|
||||
// Only list profiles from which any data can be imported
|
||||
this.__sourceProfiles = profileResources.filter(({resources}) => {
|
||||
return resources && resources.length > 0;
|
||||
}, this).map(({profile}) => profile);
|
||||
return this.__sourceProfiles;
|
||||
};
|
||||
|
||||
ChromeProfileMigrator.prototype.getSourceHomePageURL =
|
||||
async function Chrome_getSourceHomePageURL() {
|
||||
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
|
||||
if (!chromeUserDataPath)
|
||||
return "";
|
||||
let prefsPath = OS.Path.join(chromeUserDataPath, "Preferences");
|
||||
if (await OS.File.exists(prefsPath)) {
|
||||
try {
|
||||
return JSON.parse(
|
||||
NetUtil.readInputStreamToString(fstream, fstream.available(),
|
||||
{ charset: "UTF-8" })
|
||||
).homepage;
|
||||
let json = await OS.File.read(prefsPath, {encoding: "UTF-8"});
|
||||
return JSON.parse(json).homepage;
|
||||
} catch (e) {
|
||||
Cu.reportError("Error parsing Chrome's preferences file: " + e);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceLocked", {
|
||||
get: function Chrome_sourceLocked() {
|
||||
@ -210,10 +218,9 @@ Object.defineProperty(ChromeProfileMigrator.prototype, "sourceLocked", {
|
||||
},
|
||||
});
|
||||
|
||||
function GetBookmarksResource(aProfileFolder) {
|
||||
let bookmarksFile = aProfileFolder.clone();
|
||||
bookmarksFile.append("Bookmarks");
|
||||
if (!bookmarksFile.exists())
|
||||
async function GetBookmarksResource(aProfileFolder) {
|
||||
let bookmarksPath = OS.Path.join(aProfileFolder, "Bookmarks");
|
||||
if (!(await OS.File.exists(bookmarksPath)))
|
||||
return null;
|
||||
|
||||
return {
|
||||
@ -224,7 +231,7 @@ function GetBookmarksResource(aProfileFolder) {
|
||||
let gotErrors = false;
|
||||
let errorGatherer = function() { gotErrors = true; };
|
||||
// Parse Chrome bookmark file that is JSON format
|
||||
let bookmarkJSON = await OS.File.read(bookmarksFile.path, {encoding: "UTF-8"});
|
||||
let bookmarkJSON = await OS.File.read(bookmarksPath, {encoding: "UTF-8"});
|
||||
let roots = JSON.parse(bookmarkJSON).roots;
|
||||
|
||||
// Importing bookmark bar items
|
||||
@ -261,10 +268,9 @@ function GetBookmarksResource(aProfileFolder) {
|
||||
};
|
||||
}
|
||||
|
||||
function GetHistoryResource(aProfileFolder) {
|
||||
let historyFile = aProfileFolder.clone();
|
||||
historyFile.append("History");
|
||||
if (!historyFile.exists())
|
||||
async function GetHistoryResource(aProfileFolder) {
|
||||
let historyPath = OS.Path.join(aProfileFolder, "History");
|
||||
if (!(await OS.File.exists(historyPath)))
|
||||
return null;
|
||||
|
||||
return {
|
||||
@ -285,7 +291,7 @@ function GetHistoryResource(aProfileFolder) {
|
||||
}
|
||||
|
||||
let rows =
|
||||
await MigrationUtils.getRowsFromDBWithoutLocks(historyFile.path, "Chrome history", query);
|
||||
await MigrationUtils.getRowsFromDBWithoutLocks(historyPath, "Chrome history", query);
|
||||
let places = [];
|
||||
for (let row of rows) {
|
||||
try {
|
||||
@ -333,10 +339,9 @@ function GetHistoryResource(aProfileFolder) {
|
||||
};
|
||||
}
|
||||
|
||||
function GetCookiesResource(aProfileFolder) {
|
||||
let cookiesFile = aProfileFolder.clone();
|
||||
cookiesFile.append("Cookies");
|
||||
if (!cookiesFile.exists())
|
||||
async function GetCookiesResource(aProfileFolder) {
|
||||
let cookiesPath = OS.Path.join(aProfileFolder, "Cookies");
|
||||
if (!(await OS.File.exists(cookiesPath)))
|
||||
return null;
|
||||
|
||||
return {
|
||||
@ -344,7 +349,7 @@ function GetCookiesResource(aProfileFolder) {
|
||||
|
||||
async migrate(aCallback) {
|
||||
// We don't support decrypting cookies yet so only import plaintext ones.
|
||||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(cookiesFile.path, "Chrome cookies",
|
||||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(cookiesPath, "Chrome cookies",
|
||||
`SELECT host_key, name, value, path, expires_utc, secure, httponly, encrypted_value
|
||||
FROM cookies
|
||||
WHERE length(encrypted_value) = 0`).catch(ex => {
|
||||
@ -385,17 +390,16 @@ function GetCookiesResource(aProfileFolder) {
|
||||
};
|
||||
}
|
||||
|
||||
function GetWindowsPasswordsResource(aProfileFolder) {
|
||||
let loginFile = aProfileFolder.clone();
|
||||
loginFile.append("Login Data");
|
||||
if (!loginFile.exists())
|
||||
async function GetWindowsPasswordsResource(aProfileFolder) {
|
||||
let loginPath = OS.Path.join(aProfileFolder, "Login Data");
|
||||
if (!(await OS.File.exists(loginPath)))
|
||||
return null;
|
||||
|
||||
return {
|
||||
type: MigrationUtils.resourceTypes.PASSWORDS,
|
||||
|
||||
async migrate(aCallback) {
|
||||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(loginFile.path, "Chrome passwords",
|
||||
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 => {
|
||||
@ -470,9 +474,7 @@ ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53
|
||||
* Chromium migration
|
||||
**/
|
||||
function ChromiumProfileMigrator() {
|
||||
let path = ChromeMigrationUtils.getDataPath("Chromium");
|
||||
let chromiumUserDataFolder = new FileUtils.File(path);
|
||||
this._chromeUserDataFolder = chromiumUserDataFolder.exists() ? chromiumUserDataFolder : null;
|
||||
this._chromeUserDataPathSuffix = "Chromium";
|
||||
}
|
||||
|
||||
ChromiumProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
||||
@ -487,9 +489,7 @@ var componentsArray = [ChromeProfileMigrator, ChromiumProfileMigrator];
|
||||
* Not available on Linux
|
||||
**/
|
||||
function CanaryProfileMigrator() {
|
||||
let path = ChromeMigrationUtils.getDataPath("Canary");
|
||||
let chromeUserDataFolder = new FileUtils.File(path);
|
||||
this._chromeUserDataFolder = chromeUserDataFolder.exists() ? chromeUserDataFolder : null;
|
||||
this._chromeUserDataPathSuffix = "Canary";
|
||||
}
|
||||
CanaryProfileMigrator.prototype = Object.create(ChromeProfileMigrator.prototype);
|
||||
CanaryProfileMigrator.prototype.classDescription = "Chrome Canary Profile Migrator";
|
||||
|
@ -374,10 +374,11 @@ EdgeProfileMigrator.prototype.getResources = function() {
|
||||
return resources.filter(r => r.exists);
|
||||
};
|
||||
|
||||
EdgeProfileMigrator.prototype.getLastUsedDate = function() {
|
||||
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:
|
||||
if (this.sourceProfiles !== null || !gEdgeDatabase) {
|
||||
let sourceProfiles = await this.getSourceProfiles();
|
||||
if (sourceProfiles !== null || !gEdgeDatabase) {
|
||||
return Promise.resolve(new Date(0));
|
||||
}
|
||||
let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
|
||||
@ -408,10 +409,10 @@ EdgeProfileMigrator.prototype.getLastUsedDate = function() {
|
||||
* - |[]| 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.__defineGetter__("sourceProfiles", function() {
|
||||
EdgeProfileMigrator.prototype.getSourceProfiles = function() {
|
||||
let isWin10OrHigher = AppConstants.isPlatformAndVersionAtLeast("win", "10");
|
||||
return isWin10OrHigher ? null : [];
|
||||
});
|
||||
};
|
||||
|
||||
EdgeProfileMigrator.prototype.__defineGetter__("sourceLocked", function() {
|
||||
// There is an exclusive lock on some databases. Assume they are locked for now.
|
||||
|
@ -61,11 +61,9 @@ function sorter(a, b) {
|
||||
return a.id.toLocaleLowerCase().localeCompare(b.id.toLocaleLowerCase());
|
||||
}
|
||||
|
||||
Object.defineProperty(FirefoxProfileMigrator.prototype, "sourceProfiles", {
|
||||
get() {
|
||||
return [...this._getAllProfiles().keys()].map(x => ({id: x, name: x})).sort(sorter);
|
||||
},
|
||||
});
|
||||
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();
|
||||
|
@ -378,32 +378,30 @@ IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
|
||||
});
|
||||
};
|
||||
|
||||
Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
|
||||
get: function IE_get_sourceHomePageURL() {
|
||||
let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
|
||||
kMainKey, "Default_Page_URL");
|
||||
let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||
kMainKey, "Start Page");
|
||||
// If the user didn't customize the Start Page, he is still on the default
|
||||
// page, that may be considered the equivalent of our about:home. There's
|
||||
// no reason to retain it, since it is heavily targeted to IE.
|
||||
let homepage = startPage != defaultStartPage ? startPage : "";
|
||||
IEProfileMigrator.prototype.getSourceHomePageURL = function IE_getSourceHomePageURL() {
|
||||
let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
|
||||
kMainKey, "Default_Page_URL");
|
||||
let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||
kMainKey, "Start Page");
|
||||
// If the user didn't customize the Start Page, he is still on the default
|
||||
// page, that may be considered the equivalent of our about:home. There's
|
||||
// no reason to retain it, since it is heavily targeted to IE.
|
||||
let homepage = startPage != defaultStartPage ? startPage : "";
|
||||
|
||||
// IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
|
||||
// are in addition to the Start Page, and no empty entries are possible,
|
||||
// thus a Start Page is always defined if any of these exists, though it
|
||||
// may be the default one.
|
||||
let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||
kMainKey, "Secondary Start Pages");
|
||||
if (secondaryPages) {
|
||||
if (homepage)
|
||||
secondaryPages.unshift(homepage);
|
||||
homepage = secondaryPages.join("|");
|
||||
}
|
||||
// IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
|
||||
// are in addition to the Start Page, and no empty entries are possible,
|
||||
// thus a Start Page is always defined if any of these exists, though it
|
||||
// may be the default one.
|
||||
let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
|
||||
kMainKey, "Secondary Start Pages");
|
||||
if (secondaryPages) {
|
||||
if (homepage)
|
||||
secondaryPages.unshift(homepage);
|
||||
homepage = secondaryPages.join("|");
|
||||
}
|
||||
|
||||
return homepage;
|
||||
},
|
||||
});
|
||||
return homepage;
|
||||
};
|
||||
|
||||
IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
|
||||
IEProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=ie";
|
||||
|
@ -83,7 +83,7 @@ function getMigrationBundle() {
|
||||
* Here we default for single-profile migrator.
|
||||
* 5. Implement getResources(aProfile) (see below).
|
||||
* 6. If the migrator supports reading the home page of the source browser,
|
||||
* override |sourceHomePageURL| getter.
|
||||
* override |getSourceHomePageURL| getter.
|
||||
* 7. For startup-only migrators, override |startupOnlyMigrator|.
|
||||
*/
|
||||
this.MigratorPrototype = {
|
||||
@ -103,7 +103,7 @@ this.MigratorPrototype = {
|
||||
* For a single-profile source (e.g. safari, ie), this returns null,
|
||||
* and not an empty array. That is the default implementation.
|
||||
*/
|
||||
get sourceProfiles() {
|
||||
getSourceProfiles() {
|
||||
return null;
|
||||
},
|
||||
|
||||
@ -206,8 +206,8 @@ this.MigratorPrototype = {
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
getMigrateData: function MP_getMigrateData(aProfile) {
|
||||
let resources = this._getMaybeCachedResources(aProfile);
|
||||
getMigrateData: async function MP_getMigrateData(aProfile) {
|
||||
let resources = await this._getMaybeCachedResources(aProfile);
|
||||
if (!resources) {
|
||||
return [];
|
||||
}
|
||||
@ -225,8 +225,8 @@ this.MigratorPrototype = {
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
migrate: function MP_migrate(aItems, aStartup, aProfile) {
|
||||
let resources = this._getMaybeCachedResources(aProfile);
|
||||
migrate: async function MP_migrate(aItems, aStartup, aProfile) {
|
||||
let resources = await this._getMaybeCachedResources(aProfile);
|
||||
if (resources.length == 0)
|
||||
throw new Error("migrate called for a non-existent source");
|
||||
|
||||
@ -411,7 +411,7 @@ this.MigratorPrototype = {
|
||||
*
|
||||
* @see nsIBrowserProfileMigrator
|
||||
*/
|
||||
get sourceExists() {
|
||||
async isSourceAvailable() {
|
||||
if (this.startupOnlyMigrator && !MigrationUtils.isStartupMigration)
|
||||
return false;
|
||||
|
||||
@ -420,9 +420,9 @@ this.MigratorPrototype = {
|
||||
// profile is available.
|
||||
let exists = false;
|
||||
try {
|
||||
let profiles = this.sourceProfiles;
|
||||
let profiles = await this.getSourceProfiles();
|
||||
if (!profiles) {
|
||||
let resources = this._getMaybeCachedResources("");
|
||||
let resources = await this._getMaybeCachedResources("");
|
||||
if (resources && resources.length > 0)
|
||||
exists = true;
|
||||
} else {
|
||||
@ -435,7 +435,7 @@ this.MigratorPrototype = {
|
||||
},
|
||||
|
||||
/** * PRIVATE STUFF - DO NOT OVERRIDE ***/
|
||||
_getMaybeCachedResources: function PMB__getMaybeCachedResources(aProfile) {
|
||||
_getMaybeCachedResources: async function PMB__getMaybeCachedResources(aProfile) {
|
||||
let profileKey = aProfile ? aProfile.id : "";
|
||||
if (this._resourcesByProfile) {
|
||||
if (profileKey in this._resourcesByProfile)
|
||||
@ -443,7 +443,7 @@ this.MigratorPrototype = {
|
||||
} else {
|
||||
this._resourcesByProfile = { };
|
||||
}
|
||||
this._resourcesByProfile[profileKey] = this.getResources(aProfile);
|
||||
this._resourcesByProfile[profileKey] = await this.getResources(aProfile);
|
||||
return this._resourcesByProfile[profileKey];
|
||||
},
|
||||
};
|
||||
@ -663,6 +663,28 @@ this.MigrationUtils = Object.freeze({
|
||||
return gMigrators;
|
||||
},
|
||||
|
||||
spinResolve: function MU_spinResolve(promise) {
|
||||
if (!(promise instanceof Promise)) {
|
||||
return promise;
|
||||
}
|
||||
let done = false;
|
||||
let result = null;
|
||||
let error = null;
|
||||
promise.catch(e => {
|
||||
error = e;
|
||||
}).then(r => {
|
||||
result = r;
|
||||
done = true;
|
||||
});
|
||||
|
||||
Services.tm.spinEventLoopUntil(() => done);
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns the migrator for the given source, if any data is available
|
||||
* for this source, or null otherwise.
|
||||
@ -685,7 +707,7 @@ this.MigrationUtils = Object.freeze({
|
||||
* @return profile migrator implementing nsIBrowserProfileMigrator, if it can
|
||||
* import any data, null otherwise.
|
||||
*/
|
||||
getMigrator: function MU_getMigrator(aKey) {
|
||||
getMigrator: async function MU_getMigrator(aKey) {
|
||||
let migrator = null;
|
||||
if (this._migrators.has(aKey)) {
|
||||
migrator = this._migrators.get(aKey);
|
||||
@ -698,7 +720,7 @@ this.MigrationUtils = Object.freeze({
|
||||
}
|
||||
|
||||
try {
|
||||
return migrator && migrator.sourceExists ? migrator : null;
|
||||
return migrator && (await migrator.isSourceAvailable()) ? migrator : null;
|
||||
} catch (ex) { Cu.reportError(ex); return null; }
|
||||
},
|
||||
|
||||
@ -876,7 +898,8 @@ this.MigrationUtils = Object.freeze({
|
||||
/**
|
||||
* Show the migration wizard for startup-migration. This should only be
|
||||
* called by ProfileMigrator (see ProfileMigrator.js), which implements
|
||||
* nsIProfileMigrator.
|
||||
* nsIProfileMigrator. This runs asynchronously if we are running an
|
||||
* automigration.
|
||||
*
|
||||
* @param aProfileStartup
|
||||
* the nsIProfileStartup instance provided to ProfileMigrator.migrate.
|
||||
@ -895,6 +918,19 @@ this.MigrationUtils = Object.freeze({
|
||||
*/
|
||||
startupMigration:
|
||||
function MU_startupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
|
||||
if (Services.prefs.getBoolPref("browser.migrate.automigrate.enabled", false)) {
|
||||
this.asyncStartupMigration(aProfileStartup,
|
||||
aMigratorKey,
|
||||
aProfileToMigrate);
|
||||
} else {
|
||||
this.spinResolve(this.asyncStartupMigration(aProfileStartup,
|
||||
aMigratorKey,
|
||||
aProfileToMigrate));
|
||||
}
|
||||
},
|
||||
|
||||
asyncStartupMigration:
|
||||
async function MU_asyncStartupMigrator(aProfileStartup, aMigratorKey, aProfileToMigrate) {
|
||||
if (!aProfileStartup) {
|
||||
throw new Error("an profile-startup instance is required for startup-migration");
|
||||
}
|
||||
@ -902,7 +938,7 @@ this.MigrationUtils = Object.freeze({
|
||||
|
||||
let skipSourcePage = false, migrator = null, migratorKey = "";
|
||||
if (aMigratorKey) {
|
||||
migrator = this.getMigrator(aMigratorKey);
|
||||
migrator = await this.getMigrator(aMigratorKey);
|
||||
if (!migrator) {
|
||||
// aMigratorKey must point to a valid source, so, if it doesn't
|
||||
// cleanup and throw.
|
||||
@ -915,18 +951,19 @@ this.MigrationUtils = Object.freeze({
|
||||
} else {
|
||||
let defaultBrowserKey = this.getMigratorKeyForDefaultBrowser();
|
||||
if (defaultBrowserKey) {
|
||||
migrator = this.getMigrator(defaultBrowserKey);
|
||||
migrator = await this.getMigrator(defaultBrowserKey);
|
||||
if (migrator)
|
||||
migratorKey = defaultBrowserKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!migrator) {
|
||||
let migrators = await Promise.all(gAvailableMigratorKeys.map(key => this.getMigrator(key)));
|
||||
// If there's no migrator set so far, ensure that there is at least one
|
||||
// migrator available before opening the wizard.
|
||||
// Note that we don't need to check the default browser first, because
|
||||
// if that one existed we would have used it in the block above this one.
|
||||
if (!gAvailableMigratorKeys.some(key => !!this.getMigrator(key))) {
|
||||
if (!migrators.some(m => m)) {
|
||||
// None of the keys produced a usable migrator, so finish up here:
|
||||
this.finishMigration();
|
||||
return;
|
||||
@ -938,7 +975,7 @@ this.MigrationUtils = Object.freeze({
|
||||
|
||||
if (!isRefresh && AutoMigrate.enabled) {
|
||||
try {
|
||||
AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
|
||||
await AutoMigrate.migrate(aProfileStartup, migratorKey, aProfileToMigrate);
|
||||
return;
|
||||
} catch (ex) {
|
||||
// If automigration failed, continue and show the dialog.
|
||||
|
@ -288,24 +288,6 @@ MainPreferencesPropertyList.prototype = {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Workaround for nsIBrowserProfileMigrator.sourceHomePageURL until
|
||||
// it's replaced with an async method.
|
||||
_readSync: function MPPL__readSync() {
|
||||
if ("_dict" in this)
|
||||
return this._dict;
|
||||
|
||||
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
inputStream.init(this._file, -1, -1, 0);
|
||||
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
|
||||
createInstance(Ci.nsIBinaryInputStream);
|
||||
binaryStream.setInputStream(inputStream);
|
||||
let bytes = binaryStream.readByteArray(inputStream.available());
|
||||
this._dict = PropertyListUtils._readFromArrayBufferSync(
|
||||
new Uint8Array(bytes).buffer);
|
||||
return this._dict;
|
||||
},
|
||||
};
|
||||
|
||||
function SearchStrings(aMainPreferencesPropertyListInstance) {
|
||||
@ -402,16 +384,14 @@ Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyL
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(SafariProfileMigrator.prototype, "sourceHomePageURL", {
|
||||
get: function get_sourceHomePageURL() {
|
||||
if (this.mainPreferencesPropertyList) {
|
||||
let dict = this.mainPreferencesPropertyList._readSync();
|
||||
if (dict.has("HomePage"))
|
||||
return dict.get("HomePage");
|
||||
}
|
||||
return "";
|
||||
},
|
||||
});
|
||||
SafariProfileMigrator.prototype.getSourceHomePageURL = async function SM_getSourceHomePageURL() {
|
||||
if (this.mainPreferencesPropertyList) {
|
||||
let dict = await new Promise(resolve => this.mainPreferencesPropertyList.read(resolve));
|
||||
if (dict.has("HomePage"))
|
||||
return dict.get("HomePage");
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
SafariProfileMigrator.prototype.classDescription = "Safari Profile Migrator";
|
||||
SafariProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=safari";
|
||||
|
@ -43,7 +43,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
this._autoMigrate = args[3].QueryInterface(kIPStartup);
|
||||
this._skipImportSourcePage = args[4];
|
||||
if (this._migrator && args[5]) {
|
||||
let sourceProfiles = this._migrator.sourceProfiles;
|
||||
let sourceProfiles = this.spinResolve(this._migrator.getSourceProfiles());
|
||||
this._selectedProfile = sourceProfiles.find(profile => profile.id == args[5]);
|
||||
}
|
||||
|
||||
@ -67,17 +67,35 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
MigrationUtils.finishMigration();
|
||||
},
|
||||
|
||||
spinResolve(promise) {
|
||||
let canAdvance = this._wiz.canAdvance;
|
||||
let canRewind = this._wiz.canRewind;
|
||||
let canCancel = this._canCancel;
|
||||
this._wiz.canAdvance = false;
|
||||
this._wiz.canRewind = false;
|
||||
this._canCancel = false;
|
||||
let result = MigrationUtils.spinResolve(promise);
|
||||
this._wiz.canAdvance = canAdvance;
|
||||
this._wiz.canRewind = canRewind;
|
||||
this._canCancel = canCancel;
|
||||
return result;
|
||||
},
|
||||
|
||||
onWizardCancel() {
|
||||
return this._canCancel;
|
||||
},
|
||||
|
||||
// 1 - Import Source
|
||||
onImportSourcePageShow() {
|
||||
// Show warning message to close the selected browser when needed
|
||||
function toggleCloseBrowserWarning() {
|
||||
let toggleCloseBrowserWarning = () => {
|
||||
let visibility = "hidden";
|
||||
if (group.selectedItem.id != "nothing") {
|
||||
let migrator = MigrationUtils.getMigrator(group.selectedItem.id);
|
||||
let migrator = this.spinResolve(MigrationUtils.getMigrator(group.selectedItem.id));
|
||||
visibility = migrator.sourceLocked ? "visible" : "hidden";
|
||||
}
|
||||
document.getElementById("closeSourceBrowser").style.visibility = visibility;
|
||||
}
|
||||
};
|
||||
this._wiz.canRewind = false;
|
||||
|
||||
var selectedMigrator = null;
|
||||
@ -88,7 +106,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
for (var i = 0; i < group.childNodes.length; ++i) {
|
||||
var migratorKey = group.childNodes[i].id;
|
||||
if (migratorKey != "nothing") {
|
||||
var migrator = MigrationUtils.getMigrator(migratorKey);
|
||||
var migrator = this.spinResolve(MigrationUtils.getMigrator(migratorKey));
|
||||
if (migrator) {
|
||||
// Save this as the first selectable item, if we don't already have
|
||||
// one, or if it is the migrator that was passed to us.
|
||||
@ -148,7 +166,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
|
||||
if (!this._migrator || (newSource != this._source)) {
|
||||
// Create the migrator for the selected source.
|
||||
this._migrator = MigrationUtils.getMigrator(newSource);
|
||||
this._migrator = this.spinResolve(MigrationUtils.getMigrator(newSource));
|
||||
|
||||
this._itemsFlags = kIMig.ALL;
|
||||
this._selectedProfile = null;
|
||||
@ -156,7 +174,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
this._source = newSource;
|
||||
|
||||
// check for more than one source profile
|
||||
var sourceProfiles = this._migrator.sourceProfiles;
|
||||
var sourceProfiles = this.spinResolve(this._migrator.getSourceProfiles());
|
||||
if (this._skipImportSourcePage) {
|
||||
this._wiz.currentPage.next = "homePageImport";
|
||||
} else if (sourceProfiles && sourceProfiles.length > 1) {
|
||||
@ -189,7 +207,7 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
// Note that this block is still reached even if the user chose 'From File'
|
||||
// and we canceled the dialog. When that happens, _migrator will be null.
|
||||
if (this._migrator) {
|
||||
var sourceProfiles = this._migrator.sourceProfiles;
|
||||
var sourceProfiles = this.spinResolve(this._migrator.getSourceProfiles());
|
||||
|
||||
for (let profile of sourceProfiles) {
|
||||
var item = document.createElement("radio");
|
||||
@ -204,14 +222,16 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
|
||||
onSelectProfilePageRewound() {
|
||||
var profiles = document.getElementById("profiles");
|
||||
this._selectedProfile = this._migrator.sourceProfiles.find(
|
||||
let sourceProfiles = this.spinResolve(this._migrator.getSourceProfiles());
|
||||
this._selectedProfile = sourceProfiles.find(
|
||||
profile => profile.id == profiles.selectedItem.id
|
||||
) || null;
|
||||
},
|
||||
|
||||
onSelectProfilePageAdvanced() {
|
||||
var profiles = document.getElementById("profiles");
|
||||
this._selectedProfile = this._migrator.sourceProfiles.find(
|
||||
let sourceProfiles = this.spinResolve(this._migrator.getSourceProfiles());
|
||||
this._selectedProfile = sourceProfiles.find(
|
||||
profile => profile.id == profiles.selectedItem.id
|
||||
) || null;
|
||||
|
||||
@ -226,7 +246,8 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
while (dataSources.hasChildNodes())
|
||||
dataSources.firstChild.remove();
|
||||
|
||||
var items = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
|
||||
var items = this.spinResolve(this._migrator.getMigrateData(this._selectedProfile,
|
||||
this._autoMigrate));
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
var itemID = (items >> i) & 0x1 ? Math.pow(2, i) : 0;
|
||||
if (itemID > 0) {
|
||||
@ -304,9 +325,9 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
var appName = MigrationUtils.getBrowserName(this._source);
|
||||
|
||||
// semi-wallpaper for crash when multiple profiles exist, since we haven't initialized mSourceProfile in places
|
||||
this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
|
||||
this.spinResolve(this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate));
|
||||
|
||||
var oldHomePageURL = this._migrator.sourceHomePageURL;
|
||||
var oldHomePageURL = this.spinResolve(this._migrator.getSourceHomePageURL());
|
||||
|
||||
if (oldHomePageURL && appName) {
|
||||
var oldHomePageLabel =
|
||||
@ -337,15 +358,18 @@ var MigrationWizard = { /* exported MigrationWizard */
|
||||
this._wiz.canAdvance = false;
|
||||
|
||||
// When automigrating, show all of the data that can be received from this source.
|
||||
if (this._autoMigrate)
|
||||
this._itemsFlags = this._migrator.getMigrateData(this._selectedProfile, this._autoMigrate);
|
||||
if (this._autoMigrate) {
|
||||
this._itemsFlags =
|
||||
this.spinResolve(this._migrator.getMigrateData(this._selectedProfile,
|
||||
this._autoMigrate));
|
||||
}
|
||||
|
||||
this._listItems("migratingItems");
|
||||
setTimeout(() => this.onMigratingMigrate(), 0);
|
||||
},
|
||||
|
||||
onMigratingMigrate() {
|
||||
this._migrator.migrate(this._itemsFlags, this._autoMigrate, this._selectedProfile);
|
||||
async onMigratingMigrate() {
|
||||
await this._migrator.migrate(this._itemsFlags, this._autoMigrate, this._selectedProfile);
|
||||
|
||||
Services.telemetry.getHistogramById("FX_MIGRATION_SOURCE_BROWSER")
|
||||
.add(MigrationUtils.getSourceIdForTelemetry(this._source));
|
||||
|
@ -38,10 +38,10 @@ interface nsIBrowserProfileMigrator : nsISupports
|
||||
* @param aProfile the profile that we are looking for available data
|
||||
* to import
|
||||
* @param aDoingStartup "true" if the profile is not currently being used.
|
||||
* @return bit field containing profile items (see above)
|
||||
* @return Promise containing a bit field containing profile items (see above)
|
||||
* @note a return value of 0 represents no items rather than ALL.
|
||||
*/
|
||||
unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
|
||||
jsval getMigrateData(in jsval aProfile, in boolean aDoingStartup);
|
||||
|
||||
/**
|
||||
* Get the last time data from this browser was modified
|
||||
@ -50,23 +50,26 @@ interface nsIBrowserProfileMigrator : nsISupports
|
||||
jsval getLastUsedDate();
|
||||
|
||||
/**
|
||||
* Whether or not there is any data that can be imported from this
|
||||
* Get whether or not there is any data that can be imported from this
|
||||
* browser (i.e. whether or not it is installed, and there exists
|
||||
* a user profile)
|
||||
* @return a promise that resolves with a boolean.
|
||||
*/
|
||||
readonly attribute boolean sourceExists;
|
||||
jsval isSourceAvailable();
|
||||
|
||||
|
||||
/**
|
||||
* An enumeration of available profiles. If the import source does
|
||||
* not support profiles, this attribute is null.
|
||||
* @return a promise that resolves with an array of profiles or null.
|
||||
*/
|
||||
readonly attribute jsval sourceProfiles;
|
||||
jsval getSourceProfiles();
|
||||
|
||||
/**
|
||||
* The import source homepage. Returns null if not present/available
|
||||
* @return a promise that resolves with a string or null.
|
||||
*/
|
||||
readonly attribute AUTF8String sourceHomePageURL;
|
||||
jsval getSourceHomePageURL();
|
||||
|
||||
|
||||
/**
|
||||
|
@ -30,9 +30,9 @@ updateAppInfo();
|
||||
/**
|
||||
* Migrates the requested resource and waits for the migration to be complete.
|
||||
*/
|
||||
function promiseMigration(migrator, resourceType, aProfile = null) {
|
||||
async function promiseMigration(migrator, resourceType, aProfile = null) {
|
||||
// Ensure resource migration is available.
|
||||
let availableSources = migrator.getMigrateData(aProfile, false);
|
||||
let availableSources = await migrator.getMigrateData(aProfile, false);
|
||||
Assert.ok((availableSources & resourceType) > 0, "Resource supported by migrator");
|
||||
|
||||
return new Promise(resolve => {
|
||||
|
@ -3,11 +3,11 @@
|
||||
add_task(async function() {
|
||||
registerFakePath("AppData", do_get_file("AppData/Roaming/"));
|
||||
|
||||
let migrator = MigrationUtils.getMigrator("360se");
|
||||
let migrator = await MigrationUtils.getMigrator("360se");
|
||||
// Sanity check for the source.
|
||||
Assert.ok(migrator.sourceExists);
|
||||
Assert.ok(await migrator.isSourceAvailable());
|
||||
|
||||
let profiles = migrator.sourceProfiles;
|
||||
let profiles = await migrator.getSourceProfiles();
|
||||
Assert.equal(profiles.length, 2, "Should present two profiles");
|
||||
Assert.equal(profiles[0].name, "test@firefox.com.cn", "Current logged in user should be the first");
|
||||
Assert.equal(profiles[profiles.length - 1].name, "Default", "Default user should be the last");
|
||||
|
@ -44,6 +44,6 @@ add_task(async function test_isExtensionInstalled_function() {
|
||||
});
|
||||
|
||||
add_task(async function test_getLastUsedProfileId_function() {
|
||||
let profileId = ChromeMigrationUtils.getLastUsedProfileId();
|
||||
let profileId = await ChromeMigrationUtils.getLastUsedProfileId();
|
||||
Assert.equal(profileId, "Default", "The last used profile ID should be Default.");
|
||||
});
|
||||
|
@ -72,9 +72,9 @@ add_task(async function() {
|
||||
|
||||
await OS.File.writeAtomic(target.path, JSON.stringify(bookmarksData), {encoding: "utf-8"});
|
||||
|
||||
let migrator = MigrationUtils.getMigrator("chrome");
|
||||
let migrator = await MigrationUtils.getMigrator("chrome");
|
||||
// Sanity check for the source.
|
||||
Assert.ok(migrator.sourceExists);
|
||||
Assert.ok(await migrator.isSourceAvailable());
|
||||
|
||||
let itemsSeen = {bookmarks: 0, folders: 0};
|
||||
let bmObserver = {
|
||||
|
@ -4,9 +4,9 @@ Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
|
||||
|
||||
add_task(async function() {
|
||||
registerFakePath("ULibDir", do_get_file("Library/"));
|
||||
let migrator = MigrationUtils.getMigrator("chrome");
|
||||
let migrator = await MigrationUtils.getMigrator("chrome");
|
||||
|
||||
Assert.ok(migrator.sourceExists, "Sanity check the source exists");
|
||||
Assert.ok(await migrator.isSourceAvailable(), "Sanity check the source exists");
|
||||
|
||||
const COOKIE = {
|
||||
expiry: 2145934800,
|
||||
|
@ -143,8 +143,8 @@ add_task(async function test_importIntoEmptyDB() {
|
||||
await promiseSetPassword(login);
|
||||
}
|
||||
|
||||
let migrator = MigrationUtils.getMigrator("chrome");
|
||||
Assert.ok(migrator.sourceExists, "Sanity check the source exists");
|
||||
let migrator = await MigrationUtils.getMigrator("chrome");
|
||||
Assert.ok(await migrator.isSourceAvailable(), "Sanity check the source exists");
|
||||
|
||||
let logins = Services.logins.getAllLogins({});
|
||||
Assert.equal(logins.length, 0, "There are no logins initially");
|
||||
@ -164,8 +164,8 @@ add_task(async function test_importIntoEmptyDB() {
|
||||
|
||||
// Test that existing logins for the same primary key don't get overwritten
|
||||
add_task(async function test_importExistingLogins() {
|
||||
let migrator = MigrationUtils.getMigrator("chrome");
|
||||
Assert.ok(migrator.sourceExists, "Sanity check the source exists");
|
||||
let migrator = await MigrationUtils.getMigrator("chrome");
|
||||
Assert.ok(await migrator.isSourceAvailable(), "Sanity check the source exists");
|
||||
|
||||
Services.logins.removeAllLogins();
|
||||
let logins = Services.logins.getAllLogins({});
|
||||
|
@ -1,9 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
add_task(async function() {
|
||||
let migrator = MigrationUtils.getMigrator("ie");
|
||||
let migrator = await MigrationUtils.getMigrator("ie");
|
||||
// Sanity check for the source.
|
||||
Assert.ok(migrator.sourceExists);
|
||||
Assert.ok(await migrator.isSourceAvailable());
|
||||
|
||||
// Wait for the imported bookmarks. Check that "From Internet Explorer"
|
||||
// folders are created in the menu and on the toolbar.
|
||||
|
@ -4,9 +4,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
|
||||
"resource://gre/modules/ctypes.jsm");
|
||||
|
||||
add_task(async function() {
|
||||
let migrator = MigrationUtils.getMigrator("ie");
|
||||
let migrator = await MigrationUtils.getMigrator("ie");
|
||||
// Sanity check for the source.
|
||||
Assert.ok(migrator.sourceExists);
|
||||
Assert.ok(await migrator.isSourceAvailable());
|
||||
|
||||
const BOOL = ctypes.bool;
|
||||
const LPCTSTR = ctypes.char16_t.ptr;
|
||||
|
@ -3,9 +3,9 @@
|
||||
add_task(async function() {
|
||||
registerFakePath("ULibDir", do_get_file("Library/"));
|
||||
|
||||
let migrator = MigrationUtils.getMigrator("safari");
|
||||
let migrator = await MigrationUtils.getMigrator("safari");
|
||||
// Sanity check for the source.
|
||||
Assert.ok(migrator.sourceExists);
|
||||
Assert.ok(await migrator.isSourceAvailable());
|
||||
|
||||
// Wait for the imported bookmarks. Check that "From Safari"
|
||||
// folders are created on the toolbar.
|
||||
|
@ -43,36 +43,45 @@ async function visitsForURL(url) {
|
||||
return visitCount;
|
||||
}
|
||||
|
||||
async function promiseThrows(fn) {
|
||||
let failed = false;
|
||||
try {
|
||||
await fn();
|
||||
} catch (e) {
|
||||
failed = true;
|
||||
}
|
||||
Assert.ok(failed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test automatically picking a browser to migrate from
|
||||
*/
|
||||
add_task(async function checkMigratorPicking() {
|
||||
Assert.throws(() => AutoMigrate.pickMigrator("firefox"),
|
||||
/Can't automatically migrate from Firefox/,
|
||||
"Should throw when explicitly picking Firefox.");
|
||||
await promiseThrows(() => AutoMigrate.pickMigrator("firefox"),
|
||||
/Can't automatically migrate from Firefox/,
|
||||
"Should throw when explicitly picking Firefox.");
|
||||
|
||||
Assert.throws(() => AutoMigrate.pickMigrator("gobbledygook"),
|
||||
/migrator object is not available/,
|
||||
"Should throw when passing unknown migrator key");
|
||||
await promiseThrows(() => AutoMigrate.pickMigrator("gobbledygook"),
|
||||
/migrator object is not available/,
|
||||
"Should throw when passing unknown migrator key");
|
||||
gShimmedMigratorKeyPicker = function() {
|
||||
return "firefox";
|
||||
};
|
||||
Assert.throws(() => AutoMigrate.pickMigrator(),
|
||||
/Can't automatically migrate from Firefox/,
|
||||
"Should throw when implicitly picking Firefox.");
|
||||
await promiseThrows(() => AutoMigrate.pickMigrator(),
|
||||
/Can't automatically migrate from Firefox/,
|
||||
"Should throw when implicitly picking Firefox.");
|
||||
gShimmedMigratorKeyPicker = function() {
|
||||
return "gobbledygook";
|
||||
};
|
||||
Assert.throws(() => AutoMigrate.pickMigrator(),
|
||||
/migrator object is not available/,
|
||||
"Should throw when an unknown migrator is the default");
|
||||
await promiseThrows(() => AutoMigrate.pickMigrator(),
|
||||
/migrator object is not available/,
|
||||
"Should throw when an unknown migrator is the default");
|
||||
gShimmedMigratorKeyPicker = function() {
|
||||
return "";
|
||||
};
|
||||
Assert.throws(() => AutoMigrate.pickMigrator(),
|
||||
/Could not determine default browser key/,
|
||||
"Should throw when an unknown migrator is the default");
|
||||
await promiseThrows(() => AutoMigrate.pickMigrator(),
|
||||
/Could not determine default browser key/,
|
||||
"Should throw when an unknown migrator is the default");
|
||||
});
|
||||
|
||||
|
||||
@ -80,32 +89,37 @@ add_task(async function checkMigratorPicking() {
|
||||
* Test automatically picking a profile to migrate from
|
||||
*/
|
||||
add_task(async function checkProfilePicking() {
|
||||
let fakeMigrator = {sourceProfiles: [{id: "a"}, {id: "b"}]};
|
||||
let profB = fakeMigrator.sourceProfiles[1];
|
||||
Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
|
||||
/Don't know how to pick a profile when more/,
|
||||
"Should throw when there are multiple profiles.");
|
||||
Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
|
||||
/Profile specified was not found/,
|
||||
"Should throw when the profile supplied doesn't exist.");
|
||||
let profileToMigrate = AutoMigrate.pickProfile(fakeMigrator, "b");
|
||||
let fakeMigrator = {
|
||||
_sourceProfiles: [{id: "a"}, {id: "b"}],
|
||||
getSourceProfiles() {
|
||||
return this._sourceProfiles;
|
||||
},
|
||||
};
|
||||
let profB = fakeMigrator._sourceProfiles[1];
|
||||
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator),
|
||||
/Don't know how to pick a profile when more/,
|
||||
"Should throw when there are multiple profiles.");
|
||||
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
|
||||
/Profile specified was not found/,
|
||||
"Should throw when the profile supplied doesn't exist.");
|
||||
let profileToMigrate = await AutoMigrate.pickProfile(fakeMigrator, "b");
|
||||
Assert.equal(profileToMigrate, profB, "Should return profile supplied");
|
||||
|
||||
fakeMigrator.sourceProfiles = null;
|
||||
Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
|
||||
/Profile specified but only a default profile found./,
|
||||
"Should throw when the profile supplied doesn't exist.");
|
||||
profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
|
||||
fakeMigrator._sourceProfiles = null;
|
||||
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator, "c"),
|
||||
/Profile specified but only a default profile found./,
|
||||
"Should throw when the profile supplied doesn't exist.");
|
||||
profileToMigrate = await AutoMigrate.pickProfile(fakeMigrator);
|
||||
Assert.equal(profileToMigrate, null, "Should return default profile when that's the only one.");
|
||||
|
||||
fakeMigrator.sourceProfiles = [];
|
||||
Assert.throws(() => AutoMigrate.pickProfile(fakeMigrator),
|
||||
/No profile data found/,
|
||||
"Should throw when no profile data is present.");
|
||||
fakeMigrator._sourceProfiles = [];
|
||||
await promiseThrows(() => AutoMigrate.pickProfile(fakeMigrator),
|
||||
/No profile data found/,
|
||||
"Should throw when no profile data is present.");
|
||||
|
||||
fakeMigrator.sourceProfiles = [{id: "a"}];
|
||||
let profA = fakeMigrator.sourceProfiles[0];
|
||||
profileToMigrate = AutoMigrate.pickProfile(fakeMigrator);
|
||||
fakeMigrator._sourceProfiles = [{id: "a"}];
|
||||
let profA = fakeMigrator._sourceProfiles[0];
|
||||
profileToMigrate = await AutoMigrate.pickProfile(fakeMigrator);
|
||||
Assert.equal(profileToMigrate, profA, "Should return the only profile if only one is present.");
|
||||
});
|
||||
|
||||
@ -115,7 +129,7 @@ add_task(async function checkProfilePicking() {
|
||||
*/
|
||||
add_task(async function checkIntegration() {
|
||||
gShimmedMigrator = {
|
||||
get sourceProfiles() {
|
||||
getSourceProfiles() {
|
||||
info("Read sourceProfiles");
|
||||
return null;
|
||||
},
|
||||
@ -130,7 +144,7 @@ add_task(async function checkIntegration() {
|
||||
gShimmedMigratorKeyPicker = function() {
|
||||
return "gobbledygook";
|
||||
};
|
||||
AutoMigrate.migrate("startup");
|
||||
await AutoMigrate.migrate("startup");
|
||||
Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
|
||||
"getMigrateData called with 'null' as a profile");
|
||||
|
||||
@ -146,7 +160,7 @@ add_task(async function checkIntegration() {
|
||||
add_task(async function checkUndoPreconditions() {
|
||||
let shouldAddData = false;
|
||||
gShimmedMigrator = {
|
||||
get sourceProfiles() {
|
||||
getSourceProfiles() {
|
||||
info("Read sourceProfiles");
|
||||
return null;
|
||||
},
|
||||
@ -174,7 +188,7 @@ add_task(async function checkUndoPreconditions() {
|
||||
gShimmedMigratorKeyPicker = function() {
|
||||
return "gobbledygook";
|
||||
};
|
||||
AutoMigrate.migrate("startup");
|
||||
await AutoMigrate.migrate("startup");
|
||||
let migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
|
||||
Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
|
||||
"getMigrateData called with 'null' as a profile");
|
||||
@ -193,7 +207,7 @@ add_task(async function checkUndoPreconditions() {
|
||||
Preferences.reset("browser.migrate.automigrate.browser");
|
||||
shouldAddData = true;
|
||||
|
||||
AutoMigrate.migrate("startup");
|
||||
await AutoMigrate.migrate("startup");
|
||||
migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
|
||||
Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
|
||||
"getMigrateData called with 'null' as a profile");
|
||||
|
@ -8,14 +8,14 @@
|
||||
* Toolkit glue for the remote debugging protocol, loaded into the
|
||||
* debugging global.
|
||||
*/
|
||||
var { Ci, Cc, CC, Cu, Cr } = require("chrome");
|
||||
var { Ci, Cc } = require("chrome");
|
||||
var Services = require("Services");
|
||||
var { ActorPool, OriginalLocation, RegisteredActorFactory,
|
||||
ObservedActorFactory } = require("devtools/server/actors/common");
|
||||
var { LocalDebuggerTransport, ChildDebuggerTransport, WorkerDebuggerTransport } =
|
||||
require("devtools/shared/transport/transport");
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var { dumpn, dumpv } = DevToolsUtils;
|
||||
var { dumpn } = DevToolsUtils;
|
||||
var flags = require("devtools/shared/flags");
|
||||
var OldEventEmitter = require("devtools/shared/old-event-emitter");
|
||||
var SyncPromise = require("devtools/shared/deprecated-sync-thenables");
|
||||
@ -33,20 +33,6 @@ DevToolsUtils.defineLazyGetter(this, "generateUUID", () => {
|
||||
return generateUUID;
|
||||
});
|
||||
|
||||
// On B2G, `this` != Global scope, so `Ci` won't be binded on `this`
|
||||
// (i.e. this.Ci is undefined) Then later, when using loadSubScript,
|
||||
// Ci,... won't be defined for sub scripts.
|
||||
this.Ci = Ci;
|
||||
this.Cc = Cc;
|
||||
this.CC = CC;
|
||||
this.Cu = Cu;
|
||||
this.Cr = Cr;
|
||||
this.Services = Services;
|
||||
this.ActorPool = ActorPool;
|
||||
this.DevToolsUtils = DevToolsUtils;
|
||||
this.dumpn = dumpn;
|
||||
this.dumpv = dumpv;
|
||||
|
||||
// Overload `Components` to prevent SDK loader exception on Components
|
||||
// object usage
|
||||
Object.defineProperty(this, "Components", {
|
||||
|
@ -380,6 +380,7 @@ WebAuthnManager::MakeCredential(const MakePublicKeyCredentialOptions& aOptions,
|
||||
|
||||
const auto& selection = aOptions.mAuthenticatorSelection;
|
||||
const auto& attachment = selection.mAuthenticatorAttachment;
|
||||
const AttestationConveyancePreference& attestation = aOptions.mAttestation;
|
||||
|
||||
// Does the RP require attachment == "platform"?
|
||||
bool requirePlatformAttachment =
|
||||
@ -389,6 +390,15 @@ WebAuthnManager::MakeCredential(const MakePublicKeyCredentialOptions& aOptions,
|
||||
bool requireUserVerification =
|
||||
selection.mUserVerification == UserVerificationRequirement::Required;
|
||||
|
||||
// Does the RP desire direct attestation? Indirect attestation is not
|
||||
// implemented, and thus is equivilent to None.
|
||||
bool requestDirectAttestation =
|
||||
attestation == AttestationConveyancePreference::Direct;
|
||||
|
||||
// In Bug 1430150, if requestDirectAttestation is true, we will need to prompt
|
||||
// the user for permission to proceed. For now, we ignore it.
|
||||
Unused << requestDirectAttestation;
|
||||
|
||||
// Create and forward authenticator selection criteria.
|
||||
WebAuthnAuthenticatorSelection authSelection(selection.mRequireResidentKey,
|
||||
requireUserVerification,
|
||||
|
@ -7,6 +7,7 @@ skip-if = !e10s
|
||||
scheme = https
|
||||
|
||||
[test_webauthn_abort_signal.html]
|
||||
[test_webauthn_attestation_conveyance.html]
|
||||
[test_webauthn_authenticator_selection.html]
|
||||
[test_webauthn_authenticator_transports.html]
|
||||
[test_webauthn_loopback.html]
|
||||
|
113
dom/webauthn/tests/test_webauthn_attestation_conveyance.html
Normal file
113
dom/webauthn/tests/test_webauthn_attestation_conveyance.html
Normal file
@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<head>
|
||||
<title>W3C Web Authentication - Attestation Conveyance</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script type="text/javascript" src="u2futil.js"></script>
|
||||
<script type="text/javascript" src="pkijs/common.js"></script>
|
||||
<script type="text/javascript" src="pkijs/asn1.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
|
||||
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
|
||||
<script type="text/javascript" src="cbor/cbor.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>W3C Web Authentication - Attestation Conveyance</h1>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1428916">Mozilla Bug 1428916</a>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1416056">Mozilla Bug 1416056</a>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
function getAttestationCertFromAttestationBuffer(aAttestationBuffer) {
|
||||
return webAuthnDecodeCBORAttestation(aAttestationBuffer)
|
||||
.then((aAttestationObj) => {
|
||||
let attestationCertDER = aAttestationObj.attStmt.x5c[0];
|
||||
let certDERBuffer = attestationCertDER.slice(0, attestationCertDER.byteLength).buffer;
|
||||
let certAsn1 = org.pkijs.fromBER(certDERBuffer);
|
||||
return new org.pkijs.simpl.CERT({ schema: certAsn1.result });
|
||||
});
|
||||
}
|
||||
|
||||
function verifyAnonymizedCertificate(aResult) {
|
||||
// TODO: Update this logic with Bug 1430150.
|
||||
// Until then, all certificates are direct.
|
||||
return verifyDirectCertificate(aResult);
|
||||
}
|
||||
|
||||
function verifyDirectCertificate(aResult) {
|
||||
return getAttestationCertFromAttestationBuffer(aResult.response.attestationObject)
|
||||
.then((attestationCert) => {
|
||||
let subject = attestationCert.subject.types_and_values[0].value.value_block.value;
|
||||
is(subject, "Firefox U2F Soft Token", "Subject name matches the direct Soft Token")
|
||||
});
|
||||
}
|
||||
|
||||
function arrivingHereIsBad(aResult) {
|
||||
ok(false, "Bad result! Received a: " + aResult);
|
||||
}
|
||||
|
||||
function expectTypeError(aResult) {
|
||||
ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError, got " + aResult);
|
||||
}
|
||||
|
||||
add_task(() => {
|
||||
// Enable the softtoken.
|
||||
return SpecialPowers.pushPrefEnv({"set": [
|
||||
["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", true],
|
||||
["security.webauth.webauthn_enable_usbtoken", false],
|
||||
]});
|
||||
});
|
||||
|
||||
// Start a new MakeCredential() request.
|
||||
function requestMakeCredential(attestation) {
|
||||
let publicKey = {
|
||||
rp: {id: document.domain, name: "none", icon: "none"},
|
||||
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
|
||||
challenge: crypto.getRandomValues(new Uint8Array(16)),
|
||||
timeout: 5000, // the minimum timeout is actually 15 seconds
|
||||
pubKeyCredParams: [{type: "public-key", alg: cose_alg_ECDSA_w_SHA256}],
|
||||
attestation,
|
||||
};
|
||||
|
||||
return navigator.credentials.create({publicKey});
|
||||
}
|
||||
|
||||
// Test success cases for make credential.
|
||||
add_task(async () => {
|
||||
// No selection criteria should be equal to none, which means anonymized
|
||||
await requestMakeCredential()
|
||||
.then(verifyAnonymizedCertificate)
|
||||
.catch(arrivingHereIsBad);
|
||||
|
||||
// Request no attestation.
|
||||
await requestMakeCredential("none")
|
||||
.then(verifyAnonymizedCertificate)
|
||||
.catch(arrivingHereIsBad);
|
||||
|
||||
// Request indirect attestation, which is the same as none.
|
||||
await requestMakeCredential("indirect")
|
||||
.then(verifyAnonymizedCertificate)
|
||||
.catch(arrivingHereIsBad);
|
||||
|
||||
// Request direct attestation, which should prompt for user intervention,
|
||||
// once 1430150 lands.
|
||||
await requestMakeCredential("direct")
|
||||
.then(verifyDirectCertificate)
|
||||
.catch(arrivingHereIsBad);
|
||||
});
|
||||
|
||||
// Test failure cases for make credential.
|
||||
add_task(async () => {
|
||||
// Request a platform authenticator.
|
||||
await requestMakeCredential("unknown")
|
||||
.then(arrivingHereIsBad)
|
||||
.catch(expectTypeError);
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -54,6 +54,7 @@ dictionary MakePublicKeyCredentialOptions {
|
||||
unsigned long timeout;
|
||||
sequence<PublicKeyCredentialDescriptor> excludeCredentials = [];
|
||||
AuthenticatorSelectionCriteria authenticatorSelection;
|
||||
AttestationConveyancePreference attestation = "none";
|
||||
// Extensions are not supported yet.
|
||||
// AuthenticationExtensions extensions; // Add in Bug 1406458
|
||||
};
|
||||
@ -83,6 +84,12 @@ enum AuthenticatorAttachment {
|
||||
"cross-platform" // Cross-platform attachment
|
||||
};
|
||||
|
||||
enum AttestationConveyancePreference {
|
||||
"none",
|
||||
"indirect",
|
||||
"direct"
|
||||
};
|
||||
|
||||
enum UserVerificationRequirement {
|
||||
"required",
|
||||
"preferred",
|
||||
|
@ -4416,6 +4416,20 @@ void AsyncPanZoomController::ShareCompositorFrameMetrics()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AsyncPanZoomController::SetTestAsyncScrollOffset(const CSSPoint& aPoint)
|
||||
{
|
||||
mTestAsyncScrollOffset = aPoint;
|
||||
ScheduleComposite();
|
||||
}
|
||||
|
||||
void
|
||||
AsyncPanZoomController::SetTestAsyncZoom(const LayerToParentLayerScale& aZoom)
|
||||
{
|
||||
mTestAsyncZoom = aZoom;
|
||||
ScheduleComposite();
|
||||
}
|
||||
|
||||
Maybe<CSSPoint> AsyncPanZoomController::FindSnapPointNear(
|
||||
const CSSPoint& aDestination, nsIScrollableFrame::ScrollUnit aUnit) {
|
||||
mRecursiveMutex.AssertCurrentThreadIn();
|
||||
|
@ -1305,17 +1305,11 @@ public:
|
||||
/**
|
||||
* Set an extra offset for testing async scrolling.
|
||||
*/
|
||||
void SetTestAsyncScrollOffset(const CSSPoint& aPoint)
|
||||
{
|
||||
mTestAsyncScrollOffset = aPoint;
|
||||
}
|
||||
void SetTestAsyncScrollOffset(const CSSPoint& aPoint);
|
||||
/**
|
||||
* Set an extra offset for testing async scrolling.
|
||||
*/
|
||||
void SetTestAsyncZoom(const LayerToParentLayerScale& aZoom)
|
||||
{
|
||||
mTestAsyncZoom = aZoom;
|
||||
}
|
||||
void SetTestAsyncZoom(const LayerToParentLayerScale& aZoom);
|
||||
|
||||
void MarkAsyncTransformAppliedToContent()
|
||||
{
|
||||
|
@ -50,23 +50,6 @@ MainThreadRuntime::MainThreadRuntime()
|
||||
, mActCtxRgn(a11y::Compatibility::GetActCtxResourceId())
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
{
|
||||
#if defined(ACCESSIBILITY)
|
||||
GeckoProcessType procType = XRE_GetProcessType();
|
||||
if (procType == GeckoProcessType_Default ||
|
||||
procType == GeckoProcessType_Content) {
|
||||
auto actctx = ActivationContext::GetCurrent();
|
||||
nsAutoCString strActCtx;
|
||||
if (actctx.isOk()) {
|
||||
strActCtx.AppendPrintf("0x%p", actctx.unwrap());
|
||||
} else {
|
||||
strActCtx.AppendPrintf("HRESULT 0x%08X", actctx.unwrapErr());
|
||||
}
|
||||
|
||||
CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AssemblyManifestCtx"),
|
||||
strActCtx);
|
||||
}
|
||||
#endif // defined(ACCESSIBILITY)
|
||||
|
||||
// We must be the outermost COM initialization on this thread. The COM runtime
|
||||
// cannot be configured once we start manipulating objects
|
||||
MOZ_ASSERT(mStaRegion.IsValidOutermost());
|
||||
|
@ -137,21 +137,6 @@ CanCreateUserNamespace()
|
||||
return cached[0] > '0';
|
||||
}
|
||||
|
||||
// Valgrind might allow the clone, but doesn't know what to do with
|
||||
// unshare. Check for that by unsharing nothing. (Valgrind will
|
||||
// probably need sandboxing disabled entirely, but no need to break
|
||||
// things worse than strictly necessary.)
|
||||
if (syscall(__NR_unshare, 0) != 0) {
|
||||
#ifdef MOZ_VALGRIND
|
||||
MOZ_ASSERT(errno == ENOSYS);
|
||||
#else
|
||||
// If something else can cause that call to fail, we's like to know
|
||||
// about it; the right way to handle it might not be the same.
|
||||
MOZ_ASSERT(false);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
pid_t pid = syscall(__NR_clone, SIGCHLD | CLONE_NEWUSER,
|
||||
nullptr, nullptr, nullptr, nullptr);
|
||||
if (pid == 0) {
|
||||
|
1
servo/etc/ci/performance/.gitignore
vendored
1
servo/etc/ci/performance/.gitignore
vendored
@ -4,6 +4,7 @@ output/*
|
||||
.cache/*
|
||||
page_load_test/tp5n/*
|
||||
page_load_test/tp5n.zip
|
||||
servo-warc-tests/*
|
||||
venv/*
|
||||
|
||||
__pycache__/
|
||||
|
@ -26,13 +26,27 @@ else
|
||||
echo "Found existing test cases, skipping download and unzip."
|
||||
fi
|
||||
|
||||
WARC_DIR="./servo-warc-tests"
|
||||
WARC_REPO="git@github.com:servo/servo-warc-tests.git"
|
||||
|
||||
# Clone the warc tests if they don't exist
|
||||
if [[ ! -d ${WARC_DIR} ]]; then
|
||||
git clone ${WARC_REPO}
|
||||
fi
|
||||
|
||||
# Make sure we're running with an up-to-date warc test repo
|
||||
git -C ${WARC_DIR} pull
|
||||
|
||||
virtualenv venv --python="$(which python3)"
|
||||
PS1="" source venv/bin/activate
|
||||
# `PS1` must be defined before activating virtualenv
|
||||
pip install "boto3>=1.4.0"
|
||||
pip install \
|
||||
"boto3>=1.4.0" \
|
||||
git+https://github.com/ikreymer/pywb.git
|
||||
|
||||
mkdir -p servo
|
||||
mkdir -p output # Test result will be saved to output/perf-<timestamp>.json
|
||||
./git_log_to_json.sh > servo/revision.json
|
||||
|
||||
./test_all.sh --servo ${*}
|
||||
SERVO_DIR="../../.." ${WARC_DIR}/run-warc-tests.sh
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
/* import-globals-from ../../devtools/server/main.js */
|
||||
|
||||
const Cu = Components.utils;
|
||||
const { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
const { devtools } = Cu.import("resource://devtools/shared/Loader.jsm", {});
|
||||
|
@ -13,14 +13,33 @@ const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
|
||||
const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
|
||||
.getService(Ci.nsIPaymentRequestService);
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "profileStorage", () => {
|
||||
let profileStorage;
|
||||
try {
|
||||
profileStorage = Cu.import("resource://formautofill/ProfileStorage.jsm", {}).profileStorage;
|
||||
profileStorage.initialize();
|
||||
} catch (ex) {
|
||||
profileStorage = null;
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
|
||||
return profileStorage;
|
||||
});
|
||||
|
||||
var PaymentDialog = {
|
||||
componentsLoaded: new Map(),
|
||||
frame: null,
|
||||
mm: null,
|
||||
request: null,
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
|
||||
init(requestId, frame) {
|
||||
if (!requestId || typeof(requestId) != "string") {
|
||||
throw new Error("Invalid PaymentRequest ID");
|
||||
@ -134,6 +153,58 @@ var PaymentDialog = {
|
||||
return component.createInstance(componentInterface);
|
||||
},
|
||||
|
||||
fetchSavedAddresses() {
|
||||
let savedAddresses = {};
|
||||
for (let address of profileStorage.addresses.getAll()) {
|
||||
savedAddresses[address.guid] = address;
|
||||
}
|
||||
return savedAddresses;
|
||||
},
|
||||
|
||||
fetchSavedPaymentCards() {
|
||||
let savedBasicCards = {};
|
||||
for (let card of profileStorage.creditCards.getAll()) {
|
||||
savedBasicCards[card.guid] = card;
|
||||
// Filter out the encrypted card number since the dialog content is
|
||||
// considered untrusted and runs in a content process.
|
||||
delete card["cc-number-encrypted"];
|
||||
}
|
||||
return savedBasicCards;
|
||||
},
|
||||
|
||||
onAutofillStorageChange() {
|
||||
this.mm.sendAsyncMessage("paymentChromeToContent", {
|
||||
messageType: "updateState",
|
||||
data: {
|
||||
savedAddresses: this.fetchSavedAddresses(),
|
||||
savedBasicCards: this.fetchSavedPaymentCards(),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
initializeFrame() {
|
||||
let requestSerialized = JSON.parse(JSON.stringify(this.request));
|
||||
|
||||
// Manually serialize the nsIPrincipal.
|
||||
let displayHost = this.request.topLevelPrincipal.URI.displayHost;
|
||||
requestSerialized.topLevelPrincipal = {
|
||||
URI: {
|
||||
displayHost,
|
||||
},
|
||||
};
|
||||
|
||||
this.mm.sendAsyncMessage("paymentChromeToContent", {
|
||||
messageType: "showPaymentRequest",
|
||||
data: {
|
||||
request: requestSerialized,
|
||||
savedAddresses: this.fetchSavedAddresses(),
|
||||
savedBasicCards: this.fetchSavedPaymentCards(),
|
||||
},
|
||||
});
|
||||
|
||||
Services.obs.addObserver(this, "formautofill-storage-changed", true);
|
||||
},
|
||||
|
||||
onPaymentCancel() {
|
||||
const showResponse = this.createShowResponse({
|
||||
acceptStatus: Ci.nsIPaymentActionResponse.PAYMENT_REJECTED,
|
||||
@ -161,27 +232,30 @@ var PaymentDialog = {
|
||||
paymentSrv.respondPayment(showResponse);
|
||||
},
|
||||
|
||||
/**
|
||||
* @implements {nsIObserver}
|
||||
* @param {nsISupports} subject
|
||||
* @param {string} topic
|
||||
* @param {string} data
|
||||
*/
|
||||
observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "formautofill-storage-changed": {
|
||||
if (data == "notifyUsed") {
|
||||
break;
|
||||
}
|
||||
this.onAutofillStorageChange();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
receiveMessage({data}) {
|
||||
let {messageType} = data;
|
||||
|
||||
switch (messageType) {
|
||||
case "initializeRequest": {
|
||||
let requestSerialized = JSON.parse(JSON.stringify(this.request));
|
||||
|
||||
// Manually serialize the nsIPrincipal.
|
||||
let displayHost = this.request.topLevelPrincipal.URI.displayHost;
|
||||
requestSerialized.topLevelPrincipal = {
|
||||
URI: {
|
||||
displayHost,
|
||||
},
|
||||
};
|
||||
|
||||
this.mm.sendAsyncMessage("paymentChromeToContent", {
|
||||
messageType: "showPaymentRequest",
|
||||
data: {
|
||||
request: requestSerialized,
|
||||
},
|
||||
});
|
||||
this.initializeFrame();
|
||||
break;
|
||||
}
|
||||
case "paymentCancel": {
|
||||
|
@ -14,7 +14,8 @@
|
||||
id="paymentRequestFrame"
|
||||
mozbrowser="true"
|
||||
remote="true"
|
||||
name="paymentRequestFrame"></iframe>
|
||||
height="400"
|
||||
width="700"></iframe>
|
||||
<script src="chrome://payments/content/paymentDialog.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -7,8 +7,8 @@ address-option {
|
||||
grid-row-gap: 5px;
|
||||
grid-column-gap: 10px;
|
||||
grid-template-areas:
|
||||
"recipient "
|
||||
"addressLine";
|
||||
"name "
|
||||
"street-address";
|
||||
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: #fff;
|
||||
@ -20,31 +20,31 @@ address-option {
|
||||
|
||||
rich-select[open] > .rich-select-popup-box > address-option {
|
||||
grid-template-areas:
|
||||
"recipient recipient"
|
||||
"addressLine addressLine"
|
||||
"email phone ";
|
||||
"name name "
|
||||
"street-address street-address"
|
||||
"email tel ";
|
||||
}
|
||||
|
||||
address-option > .recipient {
|
||||
grid-area: recipient;
|
||||
address-option > .name {
|
||||
grid-area: name;
|
||||
}
|
||||
|
||||
address-option > .addressLine {
|
||||
grid-area: addressLine;
|
||||
address-option > .street-address {
|
||||
grid-area: street-address;
|
||||
}
|
||||
|
||||
address-option > .email {
|
||||
grid-area: email;
|
||||
}
|
||||
|
||||
address-option > .phone {
|
||||
grid-area: phone;
|
||||
address-option > .tel {
|
||||
grid-area: tel;
|
||||
}
|
||||
|
||||
address-option > .recipient,
|
||||
address-option > .addressLine,
|
||||
address-option > .name,
|
||||
address-option > .street-address,
|
||||
address-option > .email,
|
||||
address-option > .phone {
|
||||
address-option > .tel {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@ -53,6 +53,6 @@ address-option > .phone {
|
||||
}
|
||||
|
||||
rich-select > .rich-select-selected-clone > .email,
|
||||
rich-select > .rich-select-selected-clone > .phone {
|
||||
rich-select > .rich-select-selected-clone > .tel {
|
||||
display: none;
|
||||
}
|
||||
|
@ -4,16 +4,18 @@
|
||||
|
||||
/**
|
||||
* <rich-select>
|
||||
* <address-option addressLine="1234 Anywhere St"
|
||||
* city="Some City"
|
||||
* <address-option guid="98hgvnbmytfc"
|
||||
* address-level1="MI"
|
||||
* address-level2="Some City"
|
||||
* email="foo@example.com"
|
||||
* country="USA"
|
||||
* dependentLocality=""
|
||||
* languageCode="en-US"
|
||||
* phone=""
|
||||
* postalCode="90210"
|
||||
* recipient="Jared Wein"
|
||||
* region="MI"></address-option>
|
||||
* name="Jared Wein"
|
||||
* postal-code="90210"
|
||||
* street-address="1234 Anywhere St"
|
||||
* tel="+1 650 555-5555"></address-option>
|
||||
* </rich-select>
|
||||
*
|
||||
* Attribute names follow ProfileStorage.jsm.
|
||||
*/
|
||||
|
||||
/* global ObservedPropertiesMixin, RichOption */
|
||||
@ -21,18 +23,15 @@
|
||||
class AddressOption extends ObservedPropertiesMixin(RichOption) {
|
||||
static get observedAttributes() {
|
||||
return RichOption.observedAttributes.concat([
|
||||
"addressLine",
|
||||
"city",
|
||||
"address-level1",
|
||||
"address-level2",
|
||||
"country",
|
||||
"dependentLocality",
|
||||
"email",
|
||||
"languageCode",
|
||||
"organization",
|
||||
"phone",
|
||||
"postalCode",
|
||||
"recipient",
|
||||
"region",
|
||||
"sortingCode",
|
||||
"guid",
|
||||
"name",
|
||||
"postal-code",
|
||||
"street-address",
|
||||
"tel",
|
||||
]);
|
||||
}
|
||||
|
||||
@ -42,10 +41,10 @@ class AddressOption extends ObservedPropertiesMixin(RichOption) {
|
||||
}
|
||||
|
||||
let fragment = document.createDocumentFragment();
|
||||
RichOption._createElement(fragment, "recipient");
|
||||
RichOption._createElement(fragment, "addressLine");
|
||||
RichOption._createElement(fragment, "name");
|
||||
RichOption._createElement(fragment, "street-address");
|
||||
RichOption._createElement(fragment, "email");
|
||||
RichOption._createElement(fragment, "phone");
|
||||
RichOption._createElement(fragment, "tel");
|
||||
this.appendChild(fragment);
|
||||
|
||||
super.connectedCallback();
|
||||
@ -56,13 +55,12 @@ class AddressOption extends ObservedPropertiesMixin(RichOption) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.querySelector(".recipient").textContent = this.recipient;
|
||||
this.querySelector(".addressLine").textContent =
|
||||
`${this.addressLine} ${this.city} ${this.region} ${this.postalCode} ${this.country}`;
|
||||
this.querySelector(".name").textContent = this.name;
|
||||
this.querySelector(".street-address").textContent = `${this.streetAddress} ` +
|
||||
`${this.addressLevel2} ${this.addressLevel1} ${this.postalCode} ${this.country}`;
|
||||
this.querySelector(".email").textContent = this.email;
|
||||
this.querySelector(".phone").textContent = this.phone;
|
||||
this.querySelector(".tel").textContent = this.tel;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("address-option", AddressOption);
|
||||
|
||||
|
@ -14,6 +14,7 @@ class BasicCardOption extends ObservedPropertiesMixin(RichOption) {
|
||||
static get observedAttributes() {
|
||||
return RichOption.observedAttributes.concat([
|
||||
"expiration",
|
||||
"guid",
|
||||
"number",
|
||||
"owner",
|
||||
"type",
|
||||
|
@ -48,6 +48,10 @@ class RichSelect extends ObservedPropertiesMixin(HTMLElement) {
|
||||
return this.popupBox.querySelector(":scope > [selected]");
|
||||
}
|
||||
|
||||
namedItem(name) {
|
||||
return this.popupBox.querySelector(`:scope > [name="${CSS.escape(name)}"]`);
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case "blur": {
|
||||
|
49
toolkit/components/payments/res/containers/address-picker.js
Normal file
49
toolkit/components/payments/res/containers/address-picker.js
Normal file
@ -0,0 +1,49 @@
|
||||
/* 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/. */
|
||||
|
||||
/* global PaymentStateSubscriberMixin, PaymentRequest */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* <address-picker></address-picker>
|
||||
* Container around <rich-select> (eventually providing add/edit links) with
|
||||
* <address-option> listening to savedAddresses.
|
||||
*/
|
||||
|
||||
class AddressPicker extends PaymentStateSubscriberMixin(HTMLElement) {
|
||||
constructor() {
|
||||
super();
|
||||
this.dropdown = document.createElement("rich-select");
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.appendChild(this.dropdown);
|
||||
super.connectedCallback();
|
||||
}
|
||||
|
||||
render(state) {
|
||||
let {savedAddresses} = state;
|
||||
let desiredOptions = [];
|
||||
for (let [guid, address] of Object.entries(savedAddresses)) {
|
||||
let optionEl = this.dropdown.namedItem(guid);
|
||||
if (!optionEl) {
|
||||
optionEl = document.createElement("address-option");
|
||||
optionEl.name = guid;
|
||||
optionEl.guid = guid;
|
||||
}
|
||||
for (let [key, val] of Object.entries(address)) {
|
||||
optionEl.setAttribute(key, val);
|
||||
}
|
||||
desiredOptions.push(optionEl);
|
||||
}
|
||||
let el = null;
|
||||
while ((el = this.dropdown.popupBox.querySelector(":scope > address-option"))) {
|
||||
el.remove();
|
||||
}
|
||||
this.dropdown.popupBox.append(...desiredOptions);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("address-picker", AddressPicker);
|
@ -54,7 +54,14 @@ class PaymentDialog extends PaymentStateSubscriberMixin(HTMLElement) {
|
||||
});
|
||||
}
|
||||
|
||||
setLoadingState(state) {
|
||||
/**
|
||||
* Set some state from the privileged parent process.
|
||||
* Other elements that need to set state should use their own `this.requestStore.setState`
|
||||
* method provided by the `PaymentStateSubscriberMixin`.
|
||||
*
|
||||
* @param {object} state - See `PaymentsStore.setState`
|
||||
*/
|
||||
setStateFromParent(state) {
|
||||
this.requestStore.setState(state);
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
<button id="logState">Log state</button>
|
||||
<button id="setRequest1">Set Request 1</button>
|
||||
<button id="setRequest2">Set Request 2</button>
|
||||
<button id="setAddresses1">Set Addreses 1</button>
|
||||
<button id="delete1Address">Delete 1 Address</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -109,8 +109,38 @@ let REQUEST_2 = {
|
||||
},
|
||||
};
|
||||
|
||||
let ADDRESSES_1 = {
|
||||
"48bnds6854t": {
|
||||
"address-level1": "MI",
|
||||
"address-level2": "Some City",
|
||||
"country": "US",
|
||||
"guid": "48bnds6854t",
|
||||
"name": "Mr. Foo",
|
||||
"postal-code": "90210",
|
||||
"street-address": "123 Sesame Street,\nApt 40",
|
||||
"tel": "+1 519 555-5555",
|
||||
},
|
||||
"68gjdh354j": {
|
||||
"address-level1": "CA",
|
||||
"address-level2": "Mountain View",
|
||||
"country": "US",
|
||||
"guid": "68gjdh354j",
|
||||
"name": "Mrs. Bar",
|
||||
"postal-code": "94041",
|
||||
"street-address": "P.O. Box 123",
|
||||
"tel": "+1 650 555-5555",
|
||||
},
|
||||
};
|
||||
|
||||
let buttonActions = {
|
||||
delete1Address() {
|
||||
let savedAddresses = Object.assign({}, requestStore.getState().savedAddresses);
|
||||
delete savedAddresses[Object.keys(savedAddresses)[0]];
|
||||
requestStore.setState({
|
||||
savedAddresses,
|
||||
});
|
||||
},
|
||||
|
||||
logState() {
|
||||
let state = requestStore.getState();
|
||||
// eslint-disable-next-line no-console
|
||||
@ -122,6 +152,10 @@ let buttonActions = {
|
||||
window.parent.location.reload(true);
|
||||
},
|
||||
|
||||
setAddresses1() {
|
||||
requestStore.setState({savedAddresses: ADDRESSES_1});
|
||||
},
|
||||
|
||||
setRequest1() {
|
||||
requestStore.setState({request: REQUEST_1});
|
||||
},
|
||||
|
@ -35,8 +35,8 @@ let requestStore = new PaymentsStore({
|
||||
shippingType: "shipping",
|
||||
},
|
||||
},
|
||||
savedAddresses: [],
|
||||
savedBasicCards: [],
|
||||
savedAddresses: {},
|
||||
savedBasicCards: {},
|
||||
});
|
||||
|
||||
|
||||
|
@ -73,6 +73,10 @@ let PaymentRequest = {
|
||||
this.onShowPaymentRequest(detail);
|
||||
break;
|
||||
}
|
||||
case "updateState": {
|
||||
document.querySelector("payment-dialog").setStateFromParent(detail);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -85,7 +89,7 @@ let PaymentRequest = {
|
||||
// Handle getting called before the DOM is ready.
|
||||
await this.domReadyPromise;
|
||||
|
||||
document.querySelector("payment-dialog").setLoadingState({
|
||||
document.querySelector("payment-dialog").setStateFromParent({
|
||||
request: detail.request,
|
||||
savedAddresses: detail.savedAddresses,
|
||||
savedBasicCards: detail.savedBasicCards,
|
||||
|
@ -22,6 +22,7 @@
|
||||
<script src="components/rich-select.js"></script>
|
||||
<script src="components/rich-option.js"></script>
|
||||
<script src="components/address-option.js"></script>
|
||||
<script src="containers/address-picker.js"></script>
|
||||
<script src="containers/payment-dialog.js"></script>
|
||||
|
||||
<script src="paymentRequest.js"></script>
|
||||
@ -33,6 +34,11 @@
|
||||
<h2 class="label"></h2>
|
||||
<currency-amount></currency-amount>
|
||||
</div>
|
||||
|
||||
<div><label>Shipping Address</label></div>
|
||||
<address-picker>
|
||||
</address-picker>
|
||||
|
||||
<div id="controls-container">
|
||||
<button id="cancel">Cancel</button>
|
||||
<button id="pay">Pay</button>
|
||||
@ -41,51 +47,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="debugging-console" hidden="hidden" src="debugging.html"></iframe>
|
||||
|
||||
<rich-select>
|
||||
<address-option email="emzembrano92@example.com"
|
||||
recipient="Emily Zembrano"
|
||||
addressLine="717 Hyde Street #6"
|
||||
city="San Francisco"
|
||||
region="CA"
|
||||
phone="415 203 0845"
|
||||
postalCode="94109"
|
||||
country="USA"></address-option>
|
||||
<address-option email="jenz9382@example.com"
|
||||
recipient="Jennifer Zembrano"
|
||||
addressLine="42 Fairydust Lane"
|
||||
city="Lala Land"
|
||||
region="HI"
|
||||
phone="415 439 2827"
|
||||
postalCode="98765"
|
||||
country="USA"></address-option>
|
||||
<address-option email="johnz9382@example.com"
|
||||
recipient="John Zembrano"
|
||||
addressLine="42 Fairydust Lane"
|
||||
city="Lala Land"
|
||||
missinginformation="true"
|
||||
region="HI"
|
||||
phone="415 439 2827"
|
||||
postalCode="98765"
|
||||
country="USA"></address-option>
|
||||
<address-option email="adbrwodne@example.com"
|
||||
recipient="Andrew Browne"
|
||||
addressLine="42 Fairydust Lane"
|
||||
city="Lala Land"
|
||||
region="HI"
|
||||
phone="517 410 0845"
|
||||
postalCode="98765"
|
||||
country="USA"></address-option>
|
||||
<address-option email="johnz9382@example.com"
|
||||
recipient="Jacob Humphrey"
|
||||
addressLine="1855 Pinecrest Rd"
|
||||
city="East Lansing"
|
||||
region="MI"
|
||||
phone="517 439 2827"
|
||||
postalCode="48823"
|
||||
country="USA"></address-option>
|
||||
</rich-select>
|
||||
|
||||
<payment-dialog></payment-dialog>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -7,6 +7,7 @@ support-files =
|
||||
blank_page.html
|
||||
|
||||
[browser_host_name.js]
|
||||
[browser_profile_storage.js]
|
||||
[browser_request_summary.js]
|
||||
[browser_show_dialog.js]
|
||||
skip-if = os == 'win' && debug # bug 1418385
|
||||
|
@ -0,0 +1,192 @@
|
||||
"use strict";
|
||||
|
||||
// Disable CPOW checks because they have false-positives from use of ContentTask in a helper.
|
||||
/* eslint-disable mozilla/no-cpows-in-tests */
|
||||
|
||||
const methodData = [PTU.MethodData.basicCard];
|
||||
const details = PTU.Details.total60USD;
|
||||
|
||||
add_task(async function test_initial_state() {
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "add");
|
||||
let address1GUID = profileStorage.addresses.add({
|
||||
"given-name": "Timothy",
|
||||
"additional-name": "John",
|
||||
"family-name": "Berners-Lee",
|
||||
organization: "World Wide Web Consortium",
|
||||
"street-address": "32 Vassar Street\nMIT Room 32-G524",
|
||||
"address-level2": "Cambridge",
|
||||
"address-level1": "MA",
|
||||
"postal-code": "02139",
|
||||
country: "US",
|
||||
tel: "+16172535702",
|
||||
email: "timbl@w3.org",
|
||||
});
|
||||
await onChanged;
|
||||
|
||||
onChanged = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "add");
|
||||
let card1GUID = profileStorage.creditCards.add({
|
||||
"cc-name": "John Doe",
|
||||
"cc-number": "1234567812345678",
|
||||
"cc-exp-month": 4,
|
||||
"cc-exp-year": 2028,
|
||||
});
|
||||
await onChanged;
|
||||
|
||||
await BrowserTestUtils.withNewTab({
|
||||
gBrowser,
|
||||
url: BLANK_PAGE_URL,
|
||||
}, async browser => {
|
||||
let dialogReadyPromise = waitForWidgetReady();
|
||||
// start by creating a PaymentRequest, and show it
|
||||
await ContentTask.spawn(browser, {methodData, details}, PTU.ContentTasks.createAndShowRequest);
|
||||
|
||||
// get a reference to the UI dialog and the requestId
|
||||
let win = await getPaymentWidget();
|
||||
let requestId = paymentUISrv.requestIdForWindow(win);
|
||||
ok(requestId, "requestId should be defined");
|
||||
is(win.closed, false, "dialog should not be closed");
|
||||
|
||||
let frame = await getPaymentFrame(win);
|
||||
ok(frame, "Got payment frame");
|
||||
await dialogReadyPromise;
|
||||
info("dialog ready");
|
||||
|
||||
await spawnPaymentDialogTask(frame, async function checkInitialStore({
|
||||
address1GUID,
|
||||
card1GUID,
|
||||
}) {
|
||||
info("checkInitialStore");
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
let {
|
||||
savedAddresses,
|
||||
savedBasicCards,
|
||||
} = contentWin.document.querySelector("payment-dialog").requestStore.getState();
|
||||
|
||||
is(Object.keys(savedAddresses).length, 1, "Initially one savedAddresses");
|
||||
is(savedAddresses[address1GUID].name, "Timothy John Berners-Lee", "Check full name");
|
||||
is(savedAddresses[address1GUID].guid, address1GUID, "Check address guid matches key");
|
||||
|
||||
is(Object.keys(savedBasicCards).length, 1, "Initially one savedBasicCards");
|
||||
is(savedBasicCards[card1GUID]["cc-number"], "************5678", "Check cc-number");
|
||||
is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
|
||||
}, {
|
||||
address1GUID,
|
||||
card1GUID,
|
||||
});
|
||||
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "add");
|
||||
info("adding an address");
|
||||
let address2GUID = profileStorage.addresses.add({
|
||||
"given-name": "John",
|
||||
"additional-name": "",
|
||||
"family-name": "Smith",
|
||||
"street-address": "331 E. Evelyn Ave.",
|
||||
"address-level2": "Mountain View",
|
||||
"address-level1": "CA",
|
||||
"postal-code": "94041",
|
||||
country: "US",
|
||||
});
|
||||
await onChanged;
|
||||
|
||||
await spawnPaymentDialogTask(frame, async function checkAdd({
|
||||
address1GUID,
|
||||
address2GUID,
|
||||
card1GUID,
|
||||
}) {
|
||||
info("checkAdd");
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
let {
|
||||
savedAddresses,
|
||||
savedBasicCards,
|
||||
} = contentWin.document.querySelector("payment-dialog").requestStore.getState();
|
||||
|
||||
let addressGUIDs = Object.keys(savedAddresses);
|
||||
is(addressGUIDs.length, 2, "Now two savedAddresses");
|
||||
is(addressGUIDs[0], address1GUID, "Check first address GUID");
|
||||
is(savedAddresses[address1GUID].guid, address1GUID, "Check address 1 guid matches key");
|
||||
is(addressGUIDs[1], address2GUID, "Check second address GUID");
|
||||
is(savedAddresses[address2GUID].guid, address2GUID, "Check address 2 guid matches key");
|
||||
|
||||
is(Object.keys(savedBasicCards).length, 1, "Still one savedBasicCards");
|
||||
is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
|
||||
}, {
|
||||
address1GUID,
|
||||
address2GUID,
|
||||
card1GUID,
|
||||
});
|
||||
|
||||
onChanged = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "update");
|
||||
info("updating the credit expiration");
|
||||
profileStorage.creditCards.update(card1GUID, {
|
||||
"cc-exp-month": 6,
|
||||
"cc-exp-year": 2029,
|
||||
}, true);
|
||||
await onChanged;
|
||||
|
||||
await spawnPaymentDialogTask(frame, async function checkUpdate({
|
||||
address1GUID,
|
||||
address2GUID,
|
||||
card1GUID,
|
||||
}) {
|
||||
info("checkUpdate");
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
let {
|
||||
savedAddresses,
|
||||
savedBasicCards,
|
||||
} = contentWin.document.querySelector("payment-dialog").requestStore.getState();
|
||||
|
||||
let addressGUIDs = Object.keys(savedAddresses);
|
||||
is(addressGUIDs.length, 2, "Still two savedAddresses");
|
||||
is(addressGUIDs[0], address1GUID, "Check first address GUID");
|
||||
is(savedAddresses[address1GUID].guid, address1GUID, "Check address 1 guid matches key");
|
||||
is(addressGUIDs[1], address2GUID, "Check second address GUID");
|
||||
is(savedAddresses[address2GUID].guid, address2GUID, "Check address 2 guid matches key");
|
||||
|
||||
is(Object.keys(savedBasicCards).length, 1, "Still one savedBasicCards");
|
||||
is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
|
||||
is(savedBasicCards[card1GUID]["cc-exp-month"], 6, "Check expiry month");
|
||||
is(savedBasicCards[card1GUID]["cc-exp-year"], 2029, "Check expiry year");
|
||||
}, {
|
||||
address1GUID,
|
||||
address2GUID,
|
||||
card1GUID,
|
||||
});
|
||||
|
||||
onChanged = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "remove");
|
||||
info("removing the first address");
|
||||
profileStorage.addresses.remove(address1GUID);
|
||||
await onChanged;
|
||||
|
||||
await spawnPaymentDialogTask(frame, async function checkRemove({
|
||||
address2GUID,
|
||||
card1GUID,
|
||||
}) {
|
||||
info("checkRemove");
|
||||
let contentWin = Components.utils.waiveXrays(content);
|
||||
let {
|
||||
savedAddresses,
|
||||
savedBasicCards,
|
||||
} = contentWin.document.querySelector("payment-dialog").requestStore.getState();
|
||||
|
||||
is(Object.keys(savedAddresses).length, 1, "Now one savedAddresses");
|
||||
is(savedAddresses[address2GUID].name, "John Smith", "Check full name");
|
||||
is(savedAddresses[address2GUID].guid, address2GUID, "Check address guid matches key");
|
||||
|
||||
is(Object.keys(savedBasicCards).length, 1, "Still one savedBasicCards");
|
||||
is(savedBasicCards[card1GUID]["cc-number"], "************5678", "Check cc-number");
|
||||
is(savedBasicCards[card1GUID].guid, card1GUID, "Check card guid matches key");
|
||||
}, {
|
||||
address2GUID,
|
||||
card1GUID,
|
||||
});
|
||||
|
||||
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
|
||||
|
||||
await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
|
||||
});
|
||||
});
|
@ -42,8 +42,6 @@ add_task(async function test_show_manualAbort_dialog() {
|
||||
// abort the payment request manually
|
||||
let frame = await getPaymentFrame(win);
|
||||
ok(frame, "Got payment frame");
|
||||
await dialogReadyPromise;
|
||||
info("dialog ready");
|
||||
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.manuallyClickCancel);
|
||||
await BrowserTestUtils.waitForCondition(() => win.closed, "dialog should be closed");
|
||||
});
|
||||
@ -67,8 +65,7 @@ add_task(async function test_show_completePayment() {
|
||||
|
||||
let frame = await getPaymentFrame(win);
|
||||
ok(frame, "Got payment frame");
|
||||
await dialogReadyPromise;
|
||||
info("dialog ready, clicking pay");
|
||||
info("clicking pay");
|
||||
spawnPaymentDialogTask(frame, PTU.DialogContentTasks.completePayment);
|
||||
|
||||
// Add a handler to complete the payment above.
|
||||
|
@ -16,6 +16,7 @@ const paymentSrv = Cc["@mozilla.org/dom/payments/payment-request-service;1"]
|
||||
.getService(Ci.nsIPaymentRequestService);
|
||||
const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
|
||||
.getService().wrappedJSObject;
|
||||
const {profileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
|
||||
const {PaymentTestUtils: PTU} = Cu.import("resource://testing-common/PaymentTestUtils.jsm", {});
|
||||
|
||||
function getPaymentRequests() {
|
||||
@ -160,7 +161,10 @@ async function spawnInDialogForMerchantTask(merchantTaskFn, dialogTaskFn, taskAr
|
||||
}
|
||||
|
||||
add_task(async function setup_head() {
|
||||
SimpleTest.registerCleanupFunction(function cleanup() {
|
||||
await profileStorage.initialize();
|
||||
registerCleanupFunction(function cleanup() {
|
||||
paymentSrv.cleanup();
|
||||
profileStorage.addresses._nukeAllRecords();
|
||||
profileStorage.creditCards._nukeAllRecords();
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,6 @@
|
||||
[DEFAULT]
|
||||
prefs =
|
||||
dom.webcomponents.customelements.enabled=false
|
||||
support-files =
|
||||
../../../../../testing/modules/sinon-2.3.2.js
|
||||
../../res/PaymentsStore.js
|
||||
@ -10,12 +12,14 @@ support-files =
|
||||
../../res/components/rich-option.js
|
||||
../../res/components/rich-select.css
|
||||
../../res/components/rich-select.js
|
||||
../../res/containers/address-picker.js
|
||||
../../res/mixins/ObservedPropertiesMixin.js
|
||||
../../res/mixins/PaymentStateSubscriberMixin.js
|
||||
../../res/vendor/custom-elements.min.js
|
||||
../../res/vendor/custom-elements.min.js.map
|
||||
payments_common.js
|
||||
|
||||
[test_address_picker.html]
|
||||
[test_currency_amount.html]
|
||||
[test_rich_select.html]
|
||||
[test_ObservedPropertiesMixin.html]
|
||||
|
@ -0,0 +1,141 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Test the address-picker component
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test the address-picker component</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
|
||||
<script src="payments_common.js"></script>
|
||||
<script src="custom-elements.min.js"></script>
|
||||
<script src="PaymentsStore.js"></script>
|
||||
<script src="ObservedPropertiesMixin.js"></script>
|
||||
<script src="PaymentStateSubscriberMixin.js"></script>
|
||||
<script src="rich-select.js"></script>
|
||||
<script src="address-picker.js"></script>
|
||||
<script src="rich-option.js"></script>
|
||||
<script src="address-option.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="rich-select.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="address-option.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<p id="display">
|
||||
<address-picker id="picker1"></address-picker>
|
||||
</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
<script type="application/javascript">
|
||||
/** Test the address-picker component **/
|
||||
|
||||
/* import-globals-from payments_common.js */
|
||||
/* import-globals-from ../../res/components/address-option.js */
|
||||
|
||||
let picker1 = document.getElementById("picker1");
|
||||
|
||||
add_task(async function test_empty() {
|
||||
ok(picker1, "Check picker1 exists");
|
||||
let {savedAddresses} = picker1.requestStore.getState();
|
||||
is(Object.keys(savedAddresses).length, 0, "Check empty initial state");
|
||||
is(picker1.dropdown.popupBox.children.length, 0, "Check dropdown is empty");
|
||||
});
|
||||
|
||||
add_task(async function test_initialSet() {
|
||||
picker1.requestStore.setState({
|
||||
savedAddresses: {
|
||||
"48bnds6854t": {
|
||||
"address-level1": "MI",
|
||||
"address-level2": "Some City",
|
||||
"country": "US",
|
||||
"guid": "48bnds6854t",
|
||||
"name": "Mr. Foo",
|
||||
"postal-code": "90210",
|
||||
"street-address": "123 Sesame Street,\nApt 40",
|
||||
"tel": "+1 519 555-5555",
|
||||
},
|
||||
"68gjdh354j": {
|
||||
"address-level1": "CA",
|
||||
"address-level2": "Mountain View",
|
||||
"country": "US",
|
||||
"guid": "68gjdh354j",
|
||||
"name": "Mrs. Bar",
|
||||
"postal-code": "94041",
|
||||
"street-address": "P.O. Box 123",
|
||||
"tel": "+1 650 555-5555",
|
||||
},
|
||||
},
|
||||
});
|
||||
await asyncElementRendered();
|
||||
let options = picker1.dropdown.popupBox.children;
|
||||
is(options.length, 2, "Check dropdown has both addresses");
|
||||
ok(options[0].textContent.includes("123 Sesame Street"), "Check first address");
|
||||
ok(options[1].textContent.includes("P.O. Box 123"), "Check second address");
|
||||
});
|
||||
|
||||
add_task(async function test_update() {
|
||||
picker1.requestStore.setState({
|
||||
savedAddresses: {
|
||||
"48bnds6854t": {
|
||||
// Same GUID, different values to trigger an update
|
||||
"address-level1": "MI-edit",
|
||||
"address-level2": "Some City-edit",
|
||||
"country": "CA",
|
||||
"guid": "48bnds6854t",
|
||||
"name": "Mr. Foo-edit",
|
||||
"postal-code": "90210-1234",
|
||||
"street-address": "new-edit",
|
||||
"tel": "+1 650 555-5555",
|
||||
},
|
||||
"68gjdh354j": {
|
||||
"address-level1": "CA",
|
||||
"address-level2": "Mountain View",
|
||||
"country": "US",
|
||||
"guid": "68gjdh354j",
|
||||
"name": "Mrs. Bar",
|
||||
"postal-code": "94041",
|
||||
"street-address": "P.O. Box 123",
|
||||
"tel": "+1 650 555-5555",
|
||||
},
|
||||
},
|
||||
});
|
||||
await asyncElementRendered();
|
||||
let options = picker1.dropdown.popupBox.children;
|
||||
is(options.length, 2, "Check dropdown still has both addresses");
|
||||
ok(options[0].textContent.includes("MI-edit"), "Check updated first address-level1");
|
||||
ok(options[0].textContent.includes("Some City-edit"), "Check updated first address-level2");
|
||||
ok(options[0].textContent.includes("new-edit"), "Check updated first address");
|
||||
|
||||
ok(options[1].textContent.includes("P.O. Box 123"), "Check second address is the same");
|
||||
});
|
||||
|
||||
add_task(async function test_delete() {
|
||||
picker1.requestStore.setState({
|
||||
savedAddresses: {
|
||||
// 48bnds6854t was deleted
|
||||
"68gjdh354j": {
|
||||
"address-level1": "CA",
|
||||
"address-level2": "Mountain View",
|
||||
"country": "US",
|
||||
"guid": "68gjdh354j",
|
||||
"name": "Mrs. Bar",
|
||||
"postal-code": "94041",
|
||||
"street-address": "P.O. Box 123",
|
||||
"tel": "+1 650 555-5555",
|
||||
},
|
||||
},
|
||||
});
|
||||
await asyncElementRendered();
|
||||
let options = picker1.dropdown.popupBox.children;
|
||||
is(options.length, 1, "Check dropdown has one remaining address");
|
||||
ok(options[0].textContent.includes("P.O. Box 123"), "Check remaining address");
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -26,31 +26,31 @@ Test the rich-select component
|
||||
<rich-select id="select1">
|
||||
<address-option id="option1"
|
||||
email="emzembrano92@email.com"
|
||||
recipient="Emily Zembrano"
|
||||
addressLine="717 Hyde Street #6"
|
||||
city="San Francisco"
|
||||
region="CA"
|
||||
phone="415 203 0845"
|
||||
postalCode="94109"
|
||||
name="Emily Zembrano"
|
||||
street-address="717 Hyde Street #6"
|
||||
address-level2="San Francisco"
|
||||
address-level1="CA"
|
||||
tel="415 203 0845"
|
||||
postal-code="94109"
|
||||
country="USA"></address-option>
|
||||
<address-option id="option2"
|
||||
email="jenz9382@email.com"
|
||||
recipient="Jennifer Zembrano"
|
||||
addressLine="42 Fairydust Lane"
|
||||
city="Lala Land"
|
||||
region="HI"
|
||||
phone="415 439 2827"
|
||||
postalCode="98765"
|
||||
name="Jennifer Zembrano"
|
||||
street-address="42 Fairydust Lane"
|
||||
address-level2="Lala Land"
|
||||
address-level1="HI"
|
||||
tel="415 439 2827"
|
||||
postal-code="98765"
|
||||
country="USA"></address-option>
|
||||
<address-option id="option3"
|
||||
email="johnz9382@email.com"
|
||||
recipient="John Zembrano"
|
||||
addressLine="42 Fairydust Lane"
|
||||
city="Lala Land"
|
||||
name="John Zembrano"
|
||||
street-address="42 Fairydust Lane"
|
||||
address-level2="Lala Land"
|
||||
missinginformation="true"
|
||||
region="HI"
|
||||
phone="415 439 2827"
|
||||
postalCode="98765"
|
||||
address-level1="HI"
|
||||
tel="415 439 2827"
|
||||
postal-code="98765"
|
||||
country="USA"></address-option>
|
||||
</rich-select>
|
||||
|
||||
@ -102,12 +102,12 @@ function dispatchKeyDown(key, keyCode) {
|
||||
select1.dispatchEvent(new KeyboardEvent("keydown", {key, keyCode}));
|
||||
}
|
||||
|
||||
add_task(async function test_addressLine_combines_address_city_region_postalCode_country() {
|
||||
add_task(async function test_streetAddress_combines_street_level2_level1_postalCode_country() {
|
||||
ok(option1, "option1 exists");
|
||||
let addressLine = option1.querySelector(".addressLine");
|
||||
let streetAddress = option1.querySelector(".street-address");
|
||||
/* eslint-disable max-len */
|
||||
is(addressLine.textContent,
|
||||
`${option1.addressLine} ${option1.city} ${option1.region} ${option1.postalCode} ${option1.country}`);
|
||||
is(streetAddress.textContent,
|
||||
`${option1.streetAddress} ${option1.addressLevel2} ${option1.addressLevel1} ${option1.postalCode} ${option1.country}`);
|
||||
/* eslint-enable max-len */
|
||||
});
|
||||
|
||||
@ -125,8 +125,8 @@ add_task(async function test_no_option_selected_first_displayed() {
|
||||
is_visible(selectedClone, "The selected clone should be visible at all times");
|
||||
is(selectedClone.getAttribute("email"), option1.getAttribute("email"),
|
||||
"The selected clone email should be equivalent to the selected option 1");
|
||||
is(selectedClone.getAttribute("recipient"), option1.getAttribute("recipient"),
|
||||
"The selected clone recipient should be equivalent to the selected option 1");
|
||||
is(selectedClone.getAttribute("name"), option1.getAttribute("name"),
|
||||
"The selected clone name should be equivalent to the selected option 1");
|
||||
});
|
||||
|
||||
add_task(async function test_clicking_on_select_shows_all_options() {
|
||||
@ -157,8 +157,8 @@ add_task(async function test_clicking_on_select_shows_all_options() {
|
||||
is_visible(selectedClone, "The selected clone should be visible at all times");
|
||||
is(selectedClone.getAttribute("email"), option2.getAttribute("email"),
|
||||
"The selected clone email should be equivalent to the selected option 2");
|
||||
is(selectedClone.getAttribute("recipient"), option2.getAttribute("recipient"),
|
||||
"The selected clone recipient should be equivalent to the selected option 2");
|
||||
is(selectedClone.getAttribute("name"), option2.getAttribute("name"),
|
||||
"The selected clone name should be equivalent to the selected option 2");
|
||||
});
|
||||
|
||||
add_task(async function test_changing_option_selected_affects_other_options() {
|
||||
|
Loading…
Reference in New Issue
Block a user