Merge autoland to mozilla-central. a=merge

This commit is contained in:
Margareta Eliza Balazs 2018-01-24 12:07:37 +02:00
commit 4611b95418
53 changed files with 1178 additions and 543 deletions

View File

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

View File

@ -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.");
}

View File

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

View File

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

View File

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

View File

@ -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();

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
/**

View File

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

View File

@ -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");

View File

@ -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.");
});

View File

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

View File

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

View File

@ -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({});

View File

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

View File

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

View File

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

View File

@ -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");

View File

@ -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", {

View File

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

View File

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

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

View File

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

View File

@ -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();

View File

@ -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()
{

View File

@ -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());

View File

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

View File

@ -4,6 +4,7 @@ output/*
.cache/*
page_load_test/tp5n/*
page_load_test/tp5n.zip
servo-warc-tests/*
venv/*
__pycache__/

View File

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

View File

@ -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", {});

View File

@ -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": {

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@ class BasicCardOption extends ObservedPropertiesMixin(RichOption) {
static get observedAttributes() {
return RichOption.observedAttributes.concat([
"expiration",
"guid",
"number",
"owner",
"type",

View File

@ -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": {

View 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);

View File

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

View File

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

View File

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

View File

@ -35,8 +35,8 @@ let requestStore = new PaymentsStore({
shippingType: "shipping",
},
},
savedAddresses: [],
savedBasicCards: [],
savedAddresses: {},
savedBasicCards: {},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {