Backed out changeset 10c9e57fb4f8 (bug 1641777) for perma failures on browser_openImportCSV.js.

This commit is contained in:
Razvan Maries 2020-07-04 12:54:15 +03:00
Родитель 8ab981b6ee
Коммит 1fa2c4a121
29 изменённых файлов: 75 добавлений и 1034 удалений

Просмотреть файл

@ -178,6 +178,8 @@ var whitelist = [
},
// Bug 1356031 (only used by devtools)
{ file: "chrome://global/skin/icons/error-16.png" },
// Bug 1641777 (only used by devtools)
{ file: "chrome://global/content/third_party/d3/d3.js" },
// Bug 1344267
{ file: "chrome://marionette/content/test.xhtml" },
{ file: "chrome://marionette/content/test_dialog.properties" },

Просмотреть файл

@ -96,8 +96,7 @@ let JSWINDOWACTORS = {
AboutLoginsDeleteLogin: { wantUntrusted: true },
AboutLoginsDismissBreachAlert: { wantUntrusted: true },
AboutLoginsHideFooter: { wantUntrusted: true },
AboutLoginsImportFromBrowser: { wantUntrusted: true },
AboutLoginsImportFromFile: { wantUntrusted: true },
AboutLoginsImport: { wantUntrusted: true },
AboutLoginsInit: { wantUntrusted: true },
AboutLoginsGetHelp: { wantUntrusted: true },
AboutLoginsOpenMobileAndroid: { wantUntrusted: true },

Просмотреть файл

@ -125,10 +125,6 @@ class AboutLoginsChild extends JSWindowActorChild {
});
break;
}
case "AboutLoginsExportPasswords": {
this.sendAsyncMessage("AboutLogins:ExportPasswords");
break;
}
case "AboutLoginsGetHelp": {
this.sendAsyncMessage("AboutLogins:GetHelp");
break;
@ -137,7 +133,7 @@ class AboutLoginsChild extends JSWindowActorChild {
this.sendAsyncMessage("AboutLogins:HideFooter");
break;
}
case "AboutLoginsImportFromBrowser": {
case "AboutLoginsImport": {
this.sendAsyncMessage("AboutLogins:Import");
recordTelemetryEvent({
object: "import_from_browser",
@ -145,14 +141,6 @@ class AboutLoginsChild extends JSWindowActorChild {
});
break;
}
case "AboutLoginsImportFromFile": {
this.sendAsyncMessage("AboutLogins:ImportPasswords");
recordTelemetryEvent({
object: "import_from_csv",
method: "mgmt_menu_item_used",
});
break;
}
case "AboutLoginsOpenMobileAndroid": {
this.sendAsyncMessage("AboutLogins:OpenMobileAndroid", {
source: event.detail,
@ -215,6 +203,10 @@ class AboutLoginsChild extends JSWindowActorChild {
});
break;
}
case "AboutLoginsExportPasswords": {
this.sendAsyncMessage("AboutLogins:ExportPasswords");
break;
}
}
}

Просмотреть файл

@ -17,7 +17,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
LoginBreaches: "resource:///modules/LoginBreaches.jsm",
LoginHelper: "resource://gre/modules/LoginHelper.jsm",
LoginExport: "resource://gre/modules/LoginExport.jsm",
LoginCSVImport: "resource://gre/modules/LoginCSVImport.jsm",
MigrationUtils: "resource:///modules/MigrationUtils.jsm",
OSKeyStore: "resource://gre/modules/OSKeyStore.jsm",
Services: "resource://gre/modules/Services.jsm",
@ -517,7 +516,7 @@ class AboutLoginsParent extends JSWindowActorParent {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(
Ci.nsIFilePicker
);
function fpCallback(aResult) {
let fpCallback = function fpCallback_done(aResult) {
if (aResult != Ci.nsIFilePicker.returnCancel) {
LoginExport.exportAsCSV(fp.file.path);
Services.telemetry.recordEvent(
@ -526,7 +525,7 @@ class AboutLoginsParent extends JSWindowActorParent {
"export_complete"
);
}
}
};
let [
title,
defaultFilename,
@ -556,43 +555,6 @@ class AboutLoginsParent extends JSWindowActorParent {
fp.open(fpCallback);
break;
}
case "AboutLogins:ImportPasswords": {
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(
Ci.nsIFilePicker
);
async function fpCallback(aResult) {
if (aResult != Ci.nsIFilePicker.returnCancel) {
await LoginCSVImport.importFromCSV(fp.file.path);
Services.telemetry.recordEvent(
"pwmgr",
"mgmt_menu_item_used",
"import_csv_complete"
);
}
}
let [
title,
okButtonLabel,
csvFilterTitle,
] = await AboutLoginsL10n.formatValues([
{
id: "about-logins-import-file-picker-title",
},
{
id: "about-logins-import-file-picker-import-button",
},
{
id: "about-logins-import-file-picker-csv-filter-title",
},
]);
fp.init(ownerGlobal, title, Ci.nsIFilePicker.modeOpen);
fp.appendFilter(csvFilterTitle, "*.csv");
fp.appendFilters(Ci.nsIFilePicker.filterAll);
fp.okButtonLabel = okButtonLabel;
fp.open(fpCallback);
break;
}
}
}

Просмотреть файл

@ -131,7 +131,7 @@
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-intro.css">
<img class="illustration" src="chrome://browser/content/aboutlogins/icons/intro-illustration.svg"/>
<h1 class="heading" data-l10n-id="about-logins-login-intro-heading-logged-out"></h1>
<h1 class="heading" data-l10n-id="login-intro-heading"></h1>
<section>
<p class="description" data-l10n-id="login-intro-description"></p>
<ul>
@ -141,9 +141,8 @@
<a data-l10n-name="help-link" class="intro-help-link" target="_blank" rel="noreferrer"></a>
</li>
</ul>
<p class="description intro-import-text" hidden data-l10n-id="about-logins-intro-import2">
<a data-l10n-name="import-browser-link" href="#"></a>
<a data-l10n-name="import-file-link" href="#"></a>
<p class="description intro-import-text" hidden data-l10n-id="about-logins-intro-import">
<a data-l10n-name="import-link" href="#"></a>
</p>
</section>
</template>
@ -271,8 +270,7 @@
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/menu-button.css">
<button class="menu-button ghost-button" data-l10n-id="menu"></button>
<ul class="menu" role="menu" hidden>
<button role="menuitem" class="menuitem-button menuitem-import-browser ghost-button" hidden data-supported-platforms="Win32,MacIntel" data-event-name="AboutLoginsImportFromBrowser" data-l10n-id="about-logins-menu-menuitem-import-from-another-browser"></button>
<button role="menuitem" class="menuitem-button menuitem-import-file ghost-button" data-event-name="AboutLoginsImportFromFile" data-l10n-id="about-logins-menu-menuitem-import-from-a-file"></button>
<button role="menuitem" class="menuitem-button menuitem-import ghost-button" hidden data-supported-platforms="Win32,MacIntel" data-event-name="AboutLoginsImport" data-l10n-id="about-logins-menu-menuitem-import-from-another-browser"></button>
<button role="menuitem" class="menuitem-button menuitem-export ghost-button" data-event-name="AboutLoginsExportPasswordsDialog" data-l10n-id="about-logins-menu-menuitem-export-logins"></button>
<hr role="separator" class="menuitem-separator"></hr>
<button role="menuitem" class="menuitem-button menuitem-preferences ghost-button" data-event-name="AboutLoginsOpenPreferences" data-l10n-id="menu-menuitem-preferences"></button>

Просмотреть файл

@ -35,12 +35,8 @@ export default class LoginIntro extends HTMLElement {
event.currentTarget.classList.contains("intro-import-text") &&
event.target.localName == "a"
) {
let eventName =
event.target.dataset.l10nName == "import-file-link"
? "AboutLoginsImportFromFile"
: "AboutLoginsImportFromBrowser";
document.dispatchEvent(
new CustomEvent(eventName, {
new CustomEvent("AboutLoginsImport", {
bubbles: true,
})
);
@ -51,7 +47,7 @@ export default class LoginIntro extends HTMLElement {
updateState(syncState) {
let l10nId = syncState.loggedIn
? "about-logins-login-intro-heading-logged-in"
: "about-logins-login-intro-heading-logged-out";
: "login-intro-heading";
document.l10n.setAttributes(
this.shadowRoot.querySelector(".heading"),
l10nId

Просмотреть файл

@ -66,14 +66,10 @@
background-image: url("chrome://global/skin/icons/help.svg");
}
.menuitem-import-browser {
.menuitem-import {
background-image: url("chrome://browser/skin/import.svg");
}
.menuitem-import-file {
background-image: url("chrome://browser/skin/open.svg");
}
.menuitem-export {
background-image: url("chrome://browser/skin/save.svg");
}

Просмотреть файл

@ -32,7 +32,6 @@ skip-if = (os == 'linux') # bug 1569789
[browser_openFiltered.js]
[browser_openImport.js]
skip-if = (os != "win" && os != "mac") # import is only available on Windows and macOS
[browser_openImportCSV.js]
[browser_openPreferences.js]
[browser_openPreferencesExternal.js]
[browser_openSite.js]

Просмотреть файл

@ -56,7 +56,7 @@ add_task(async function test_no_logins_class() {
content.document.l10n.getAttributes(
loginIntro.shadowRoot.querySelector(".heading")
).id,
"about-logins-login-intro-heading-logged-out",
"login-intro-heading",
"The default message should be the non-logged-in message"
);

Просмотреть файл

@ -40,7 +40,7 @@ add_task(async function test_open_import() {
function getImportItem() {
let menuButton = window.document.querySelector("menu-button");
return menuButton.shadowRoot.querySelector(".menuitem-import-browser");
return menuButton.shadowRoot.querySelector(".menuitem-import");
}
await BrowserTestUtils.synthesizeMouseAtCenter(getImportItem, {}, browser);

Просмотреть файл

@ -1,100 +0,0 @@
/* 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/. */
let { TelemetryTestUtils } = ChromeUtils.import(
"resource://testing-common/TelemetryTestUtils.jsm"
);
const { FileTestUtils } = ChromeUtils.import(
"resource://testing-common/FileTestUtils.jsm"
);
let { MockFilePicker } = SpecialPowers;
/**
* Waits until the mock file picker is opened and sets the destFilePath as it's selected file.
*
* @param {nsIFile} destFile
* The file being passed to the picker.
* @returns {string} A promise that is resolved when the picker selects the file.
*/
function waitForOpenFilePicker(destFile) {
return new Promise(resolve => {
MockFilePicker.showCallback = function(fp) {
info("showCallback");
info("fileName: " + destFile.path);
MockFilePicker.setFiles([destFile]);
MockFilePicker.filterIndex = 1;
info("done showCallback");
resolve();
};
});
}
add_task(async function test_open_import_from_csv() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:logins" },
async function(browser) {
MockFilePicker.init(window);
MockFilePicker.returnValue = MockFilePicker.returnOK;
let csvFile = await LoginTestUtils.file.setupCsvFileWithLines([
"url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
"https://example.com,joe@example.com,qwerty,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814635,1589710449871,1589617846802",
]);
await BrowserTestUtils.synthesizeMouseAtCenter(
"menu-button",
{},
browser
);
await SpecialPowers.spawn(browser, [], async () => {
let menuButton = content.document.querySelector("menu-button");
return ContentTaskUtils.waitForCondition(function waitForMenu() {
return !menuButton.shadowRoot.querySelector(".menu").hidden;
}, "waiting for menu to open");
});
function getImportMenuItem() {
let menuButton = window.document.querySelector("menu-button");
let importButton = menuButton.shadowRoot.querySelector(
".menuitem-import-file"
);
// Force the menu item to be visible for the test.
importButton.hidden = false;
return importButton;
}
EXPECTED_ERROR_MESSAGE = "Couldn't parse origin for";
let filePicker = waitForOpenFilePicker(csvFile);
await BrowserTestUtils.synthesizeMouseAtCenter(
getImportMenuItem,
{},
browser
);
// First event is for opening about:logins
await LoginTestUtils.telemetry.waitForEventCount(2);
TelemetryTestUtils.assertEvents(
[["pwmgr", "mgmt_menu_item_used", "import_from_csv"]],
{ category: "pwmgr", method: "mgmt_menu_item_used" },
{ process: "content", clear: false }
);
info("waiting for Import file picker to get opened");
await filePicker;
ok(true, "Import file picker opened");
info("Waiting for the import to complete");
await LoginTestUtils.telemetry.waitForEventCount(1, "parent");
TelemetryTestUtils.assertEvents(
[["pwmgr", "mgmt_menu_item_used", "import_csv_complete"]],
{ category: "pwmgr", method: "mgmt_menu_item_used" },
{ process: "parent" }
);
EXPECTED_ERROR_MESSAGE = null;
}
);
});

Просмотреть файл

@ -73,7 +73,6 @@ add_task(async function test_menu_open_close() {
await SimpleTest.promiseWaitForCondition(() => firstVisibleItem.matches(":focus"),
"waiting for firstVisibleItem to get focus again");
ok(firstVisibleItem.matches(":focus"), "firstVisibleItem should be focused after tabbing to it again");
sendKey("TAB"); // Import from file
sendKey("TAB"); // Export
if (navigator.platform == "Win32" || navigator.platform == "MacIntel") {
@ -124,7 +123,6 @@ add_task(async function test_menu_keyboard_cycling() {
}
let allItems = [
"menuitem-import-file",
"menuitem-export",
"menuitem-preferences",
"menuitem-help",
@ -132,7 +130,7 @@ add_task(async function test_menu_keyboard_cycling() {
"menuitem-mobile-ios",
];
if (navigator.platform == "Win32" || navigator.platform == "MacIntel") {
allItems = ["menuitem-import-browser", ...allItems];
allItems = ["menuitem-import", ...allItems];
}
let menu = gMenuButton.shadowRoot.querySelector(".menu");

Просмотреть файл

@ -30,7 +30,6 @@ menu =
.title = Open menu
# This menuitem is only visible on Windows and macOS
about-logins-menu-menuitem-import-from-another-browser = Import from Another Browser…
about-logins-menu-menuitem-import-from-a-file = Import from a File…
about-logins-menu-menuitem-export-logins = Export Logins…
menu-menuitem-preferences =
{ PLATFORM() ->
@ -70,13 +69,13 @@ about-logins-list-item-vulnerable-password-icon =
## Introduction screen
about-logins-login-intro-heading-logged-out = Looking for your saved logins? Set up { -sync-brand-short-name } or Import Them.
login-intro-heading = Looking for your saved logins? Set up { -sync-brand-short-name }.
about-logins-login-intro-heading-logged-in = No synced logins found.
login-intro-description = If you saved your logins to { -brand-product-name } on a different device, heres how to get them here:
login-intro-instruction-fxa = Create or sign in to your { -fxaccount-brand-name } on the device where your logins are saved
login-intro-instruction-fxa-settings = Make sure youve selected the Logins checkbox in { -sync-brand-short-name } Settings
about-logins-intro-instruction-help = Visit <a data-l10n-name="help-link">{ -lockwise-brand-short-name } Support</a> for more help
about-logins-intro-import2 = If your logins are saved outside of { -brand-product-name }, you can <a data-l10n-name="import-browser-link">import them from another browser</a> or <a data-l10n-name="import-file-link">from a file</a>
about-logins-intro-import = If your logins are saved in another browser, you can <a data-l10n-name="import-link">import them into { -lockwise-brand-short-name }</a>
## Login
@ -224,16 +223,3 @@ about-logins-export-file-picker-csv-filter-title =
[macos] CSV Document
*[other] CSV File
}
## Login Import Dialog
# Title of the file picker dialog
about-logins-import-file-picker-title = Import Logins File
about-logins-import-file-picker-import-button = Import
# A description for the .csv file format that may be shown as the file type
# filter by the operating system.
about-logins-import-file-picker-csv-filter-title =
{ PLATFORM() ->
[macos] CSV Document
*[other] CSV File
}

3
third_party/moz.build поставляемый
Просмотреть файл

@ -10,9 +10,6 @@ with Files('aom/**'):
with Files('dav1d/**'):
BUG_COMPONENT = ('Core', 'Audio/Video: Playback')
with Files('js/d3/**'):
BUG_COMPONENT = ('Toolkit', 'General')
with Files('rust/**'):
BUG_COMPONENT = ('Firefox Build System', 'General')

Просмотреть файл

@ -1,169 +0,0 @@
/* 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/. */
/**
* Provides a class to import login-related data CSV files.
*/
"use strict";
const EXPORTED_SYMBOLS = ["LoginCSVImport"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
LoginHelper: "resource://gre/modules/LoginHelper.jsm",
OS: "resource://gre/modules/osfile.jsm",
ResponsivenessMonitor: "resource://gre/modules/ResponsivenessMonitor.jsm",
Services: "resource://gre/modules/Services.jsm",
});
XPCOMUtils.defineLazyGetter(this, "d3", () => {
let d3Scope = Cu.Sandbox(null);
Services.scriptloader.loadSubScript(
"chrome://global/content/third_party/d3/d3.js",
d3Scope
);
return Cu.waiveXrays(d3Scope.d3);
});
const FIELD_TO_CSV_COLUMNS = {
origin: ["url", "login_uri"],
username: ["username", "login_username"],
password: ["password", "login_password"],
httpRealm: ["httpRealm"],
formActionOrigin: ["formActionOrigin"],
guid: ["guid"],
timeCreated: ["timeCreated"],
timeLastUsed: ["timeLastUsed"],
timePasswordChanged: ["timePasswordChanged"],
};
/**
* Provides an object that has a method to import login-related data CSV files
*/
class LoginCSVImport {
static get MIGRATION_HISTOGRAM_KEY() {
return "login_csv";
}
/**
* Returns a map that has the csv column name as key and the value the field name.
*
* @returns {Map} A map that has the csv column name as key and the value the field name.
*/
static _getCSVColumnToFieldMap() {
let csvColumnToField = new Map();
for (let [field, columns] of Object.entries(FIELD_TO_CSV_COLUMNS)) {
for (let column of columns) {
csvColumnToField.set(column, field);
}
}
return csvColumnToField;
}
/**
* Builds a vanilla JS object containing all the login fields from a row of CSV cells.
*
* @param {object} csvObject
* An object created from a csv row. The keys are the csv column names, the values are the cells.
* @param {Map} csvColumnToFieldMap
* A map where the keys are the csv properties and the values are the object keys.
* @returns {object} Representing login object with only properties, not functions.
*/
static _getVanillaLoginFromCSVObject(csvObject, csvColumnToFieldMap) {
let vanillaLogin = Object.create(null);
for (let columnName of Object.keys(csvObject)) {
let fieldName = csvColumnToFieldMap.get(columnName);
if (!fieldName) {
continue;
}
if (
typeof vanillaLogin[fieldName] != "undefined" &&
vanillaLogin[fieldName] !== csvObject[columnName]
) {
// Differing column values map to one property.
// e.g. if two headings map to `origin` we won't know which to use.
return {};
}
vanillaLogin[fieldName] = csvObject[columnName];
}
// Since `null` can't be represented in a CSV file and the httpRealm header
// cannot be an empty string, assume that an empty httpRealm means this is
// a form login and therefore null-out httpRealm.
if (vanillaLogin.httpRealm === "") {
vanillaLogin.httpRealm = null;
}
return vanillaLogin;
}
/**
* Imports logins from a CSV file (comma-separated values file).
* Existing logins may be updated in the process.
*
* @param {string} filePath
*/
static async importFromCSV(filePath) {
TelemetryStopwatch.startKeyed(
"FX_MIGRATION_LOGINS_IMPORT_MS",
LoginCSVImport.MIGRATION_HISTOGRAM_KEY
);
let responsivenessMonitor = new ResponsivenessMonitor();
let csvColumnToFieldMap = LoginCSVImport._getCSVColumnToFieldMap();
let csvString = await OS.File.read(filePath, { encoding: "utf-8" });
let parsedLines = d3.csv.parse(csvString);
let fieldsInFile = new Set(
Object.keys(parsedLines[0] || {}).map(col => csvColumnToFieldMap.get(col))
);
if (
parsedLines[0] &&
(!fieldsInFile.has("origin") ||
!fieldsInFile.has("username") ||
!fieldsInFile.has("password"))
) {
// The username *value* can be empty but we require a username column to
// ensure that we don't import logins without their usernames due to the
// username column not being recognized.
TelemetryStopwatch.cancelKeyed(
"FX_MIGRATION_LOGINS_IMPORT_MS",
LoginCSVImport.MIGRATION_HISTOGRAM_KEY
);
throw new Error(
"CSV file must contain origin, username, and password columns"
);
}
let loginsToImport = parsedLines.map(csvObject => {
return LoginCSVImport._getVanillaLoginFromCSVObject(
csvObject,
csvColumnToFieldMap
);
});
await LoginHelper.maybeImportLogins(loginsToImport);
// Record quantity, jank, and duration telemetry.
try {
Services.telemetry
.getKeyedHistogramById("FX_MIGRATION_LOGINS_QUANTITY")
.add(LoginCSVImport.MIGRATION_HISTOGRAM_KEY, parsedLines.length);
let accumulatedDelay = responsivenessMonitor.finish();
Services.telemetry
.getKeyedHistogramById("FX_MIGRATION_LOGINS_JANK_MS")
.add(LoginCSVImport.MIGRATION_HISTOGRAM_KEY, accumulatedDelay);
TelemetryStopwatch.finishKeyed(
"FX_MIGRATION_LOGINS_IMPORT_MS",
LoginCSVImport.MIGRATION_HISTOGRAM_KEY
);
} catch (ex) {
Cu.reportError(ex);
}
}
}

Просмотреть файл

@ -50,13 +50,8 @@ class LoginExport {
*
* @param {string} path
* The file path to save the login to.
* @param {nsILoginInfo[]} [logins = null]
* An optional list of logins.
*/
static async exportAsCSV(path, logins = null) {
if (!logins) {
logins = await Services.logins.getAllLoginsAsync();
}
static async exportAsCSV(path) {
let columns = [
"origin",
"username",
@ -77,6 +72,7 @@ class LoginExport {
let rows = [];
rows.push(csvHeader);
let logins = await Services.logins.getAllLoginsAsync();
for (let login of logins) {
rows.push(LoginExport._buildCSVRow(login, columns));
}

Просмотреть файл

@ -14,6 +14,8 @@
const EXPORTED_SYMBOLS = ["LoginHelper"];
// Globals
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
@ -175,10 +177,9 @@ this.LoginHelper = {
},
/**
* Due to the way the signons2.txt file was formatted, we needed to make
* Due to the way the signons2.txt file is formatted, we need to make
* sure certain field values or characters do not cause the file to
* be parsed incorrectly. These characters can cause problems in other
* formats/languages too so reject logins that may not be stored correctly.
* be parsed incorrectly. Reject logins that we can't store correctly.
*
* @throws String with English message in case validation failed.
*/
@ -199,10 +200,6 @@ this.LoginHelper = {
throw new Error("login values can't contain nulls");
}
if (!aLogin.password || typeof aLogin.password != "string") {
throw new Error("passwords must be non-empty strings");
}
// In theory these nulls should just be rolled up into the encrypted
// values, but nsISecretDecoderRing doesn't use nsStrings, so the
// nulls cause truncation. Check for them here just to avoid
@ -288,7 +285,7 @@ this.LoginHelper = {
* Get the parts of the URL we want for identification.
* Strip out things like the userPass portion and handle javascript:.
*/
getLoginOrigin(uriString, allowJS = false) {
getLoginOrigin(uriString, allowJS) {
let realm = "";
try {
let uri = Services.io.newURI(uriString);
@ -450,14 +447,14 @@ this.LoginHelper = {
* Creates a new login object that results by modifying the given object with
* the provided data.
*
* @param {nsILoginInfo} aOldStoredLogin
* Existing login object to modify.
* @param {nsILoginInfo|nsIProperyBag} aNewLoginData
* The new login values, either as an nsILoginInfo or nsIProperyBag.
* @param aOldStoredLogin
* Existing nsILoginInfo object to modify.
* @param aNewLoginData
* The new login values, either as nsILoginInfo or nsIProperyBag.
*
* @return {nsILoginInfo} The newly created nsILoginInfo object.
* @return The newly created nsILoginInfo object.
*
* @throws {Error} With English message in case validation failed.
* @throws String with English message in case validation failed.
*/
buildModifiedLogin(aOldStoredLogin, aNewLoginData) {
function bagHasProperty(aPropName) {
@ -966,55 +963,16 @@ this.LoginHelper = {
async maybeImportLogins(loginDatas) {
let loginsToAdd = [];
let loginMap = new Map();
for (let rawLoginData of loginDatas) {
// Do some sanitization on a clone of the loginData.
let loginData = ChromeUtils.shallowClone(rawLoginData);
loginData.origin = this.getLoginOrigin(loginData.origin);
if (!loginData.origin) {
continue;
}
loginData.formActionOrigin =
this.getLoginOrigin(loginData.formActionOrigin, true) ||
(typeof loginData.httpRealm == "string" ? null : "");
loginData.httpRealm =
typeof loginData.httpRealm == "string" ? loginData.httpRealm : null;
if (loginData.guid) {
// First check for `guid` matches if it's set.
// `guid` matches will allow every kind of update, including reverting
// to older passwords which can be useful if the user wants to recover
// an old password.
let existingLogins = await Services.logins.searchLoginsAsync({
guid: loginData.guid,
origin: loginData.origin, // Ignored outside of GV.
});
if (existingLogins.length) {
log.debug("maybeImportLogins: Found existing login with GUID");
// There should only be one `guid` match.
let existingLogin = existingLogins[0].QueryInterface(
Ci.nsILoginMetaInfo
);
// Use a property bag rather than an nsILoginInfo so we don't clobber
// properties that the import source doesn't provide.
let propBag = this.newPropertyBag(loginData);
Services.logins.modifyLogin(existingLogin, propBag);
// Updated a login so we're done.
continue;
}
}
for (let loginData of loginDatas) {
// create a new login
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
Ci.nsILoginInfo
);
login.init(
loginData.origin,
loginData.formActionOrigin,
loginData.httpRealm,
loginData.formActionOrigin ||
(typeof loginData.httpRealm == "string" ? null : ""),
typeof loginData.httpRealm == "string" ? loginData.httpRealm : null,
loginData.username,
loginData.password,
loginData.usernameElement || "",
@ -1027,7 +985,6 @@ this.LoginHelper = {
login.timePasswordChanged =
loginData.timePasswordChanged || loginData.timeCreated;
login.timesUsed = loginData.timesUsed || 1;
login.guid = loginData.guid || null;
try {
// Ensure we only send checked logins through, since the validation is optimized
@ -1125,9 +1082,9 @@ this.LoginHelper = {
/**
* Convert an array of nsILoginInfo to vanilla JS objects suitable for
* sending over IPC. Avoid using this in other cases.
* sending over IPC.
*
* NB: All members of nsILoginInfo (not nsILoginMetaInfo) are strings.
* NB: All members of nsILoginInfo and nsILoginMetaInfo are strings.
*/
loginsToVanillaObjects(logins) {
return logins.map(this.loginToVanillaObject);

Просмотреть файл

@ -724,7 +724,7 @@ class LoginManagerParent extends JSWindowActorParent {
if (autoFilledLoginGuid) {
let loginsForGuid = await Services.logins.searchLoginsAsync({
guid: autoFilledLoginGuid,
origin: formOrigin, // Ignored outside of GV.
origin: formOrigin,
});
if (
loginsForGuid.length == 1 &&
@ -971,7 +971,7 @@ class LoginManagerParent extends JSWindowActorParent {
if (autoFilledLoginGuid) {
let [matchedLogin] = await Services.logins.searchLoginsAsync({
guid: autoFilledLoginGuid,
origin: formOrigin, // Ignored outside of GV.
origin: formOrigin,
});
if (
matchedLogin &&
@ -1045,7 +1045,7 @@ class LoginManagerParent extends JSWindowActorParent {
if (generatedPW.storageGUID) {
[autoSavedLogin] = await Services.logins.searchLoginsAsync({
guid: generatedPW.storageGUID,
origin: formOrigin, // Ignored outside of GV.
origin: formOrigin,
});
if (autoSavedLogin) {

Просмотреть файл

@ -53,7 +53,6 @@ if CONFIG['OS_TARGET'] == 'Android':
]
else:
EXTRA_JS_MODULES += [
'LoginCSVImport.jsm',
'LoginExport.jsm',
'LoginStore.jsm',
]

Просмотреть файл

@ -22,8 +22,7 @@ interface nsILoginInfo : nsISupports {
* The origin the login applies to.
*
* For example,
* "https://site.com", "http://site.com:1234", "ftp://ftp.site.com",
* "moz-proxy://127.0.0.1:8888, "chrome://FirefoxAccounts", "file://".
* "https://site.com", "http://site.com:1234", "ftp://ftp.site.com".
*/
attribute AString origin;

Просмотреть файл

@ -113,8 +113,6 @@ interface nsILoginManager : nsISupports {
* Fetch all logins in the login manager. An array is always returned;
* if there are no logins the array is empty.
*
* @deprecated Use `getAllLoginsAsync` instead.
*
* @return An array of nsILoginInfo objects.
*/
Array<nsILoginInfo> getAllLogins();
@ -159,8 +157,6 @@ interface nsILoginManager : nsISupports {
* Search for logins matching the specified criteria. Called when looking
* for logins that might be applicable to a form or authentication request.
*
* @deprecated Use `searchLoginsAsync` instead.
*
* @param aOrigin
* The origin to restrict searches to. For example: "http://www.site.com".
* To find logins for a given nsIURI, you would typically pass in
@ -211,8 +207,7 @@ interface nsILoginManager : nsISupports {
* @param {object} matchData
* The data used to search as a JS object. This does not follow the same
* requirements as findLogins for those fields. Wildcard matches are
* simply not specified. If a `guid` is specified then no other properties
* are used (outside of GeckoView).
* simply not specified.
* @return A promise resolving to an array of nsILoginInfo objects.
*/
Promise searchLoginsAsync(in jsval matchData);
@ -226,8 +221,7 @@ interface nsILoginManager : nsISupports {
* @param matchData
* The data used to search. This does not follow the same
* requirements as findLogins for those fields. Wildcard matches are
* simply not specified. If a `guid` is specified then no other properties
* are used (outside of GeckoView).
* simply not specified.
* @return An array of nsILoginInfo objects.
*/
Array<nsILoginInfo> searchLogins(in nsIPropertyBag matchData);

Просмотреть файл

@ -30,6 +30,7 @@ interface nsILoginManagerStorage : nsISupports {
*/
Promise initialize();
/**
* Ensures that all data has been written to disk and all files are closed.
*
@ -42,6 +43,7 @@ interface nsILoginManagerStorage : nsISupports {
*/
Promise terminate();
/**
* Store a new login in the storage module.
*
@ -61,6 +63,7 @@ interface nsILoginManagerStorage : nsISupports {
*/
nsILoginInfo addLogin(in nsILoginInfo aLogin, [optional] in boolean aPreEncrypted, [optional] in jsval aPlaintextUsername, [optional] in jsval aPlaintextPassword);
/**
* Remove a login from the storage module.
*
@ -72,6 +75,7 @@ interface nsILoginManagerStorage : nsISupports {
*/
void removeLogin(in nsILoginInfo aLogin);
/**
* Modify an existing login in the storage module.
*
@ -93,6 +97,7 @@ interface nsILoginManagerStorage : nsISupports {
*/
void modifyLogin(in nsILoginInfo oldLogin, in nsISupports newLoginData);
/**
* Record that the password of a saved login was used (e.g. submitted or copied).
*
@ -105,6 +110,7 @@ interface nsILoginManagerStorage : nsISupports {
*/
void recordPasswordUse(in nsILoginInfo aLogin);
/**
* Remove all stored logins.
*
@ -114,12 +120,11 @@ interface nsILoginManagerStorage : nsISupports {
*/
void removeAllLogins();
/**
* Fetch all logins in the login manager. An array is always returned;
* if there are no logins the array is empty.
*
* @deprecated Use `getAllLoginsAsync` instead.
*
* @return An array of nsILoginInfo objects.
*/
Array<nsILoginInfo> getAllLogins();
@ -147,7 +152,6 @@ interface nsILoginManagerStorage : nsISupports {
/**
* Search for logins in the login manager. An array is always returned;
* if there are no logins the array is empty.
*
* @deprecated New code should use `searchLoginsAsync`.
* Only autocomplete, prompt, and test code still use this.
*
@ -163,8 +167,6 @@ interface nsILoginManagerStorage : nsISupports {
* Search for logins matching the specified criteria. Called when looking
* for logins that might be applicable to a form or authentication request.
*
* @deprecated Use `searchLoginsAsync` instead.
*
* @param aOrigin
* The origin to restrict searches to. For example: "http://www.site.com".
* @param aActionURL
@ -180,6 +182,7 @@ interface nsILoginManagerStorage : nsISupports {
Array<nsILoginInfo> findLogins(in AString aOrigin, in AString aActionOrigin,
in AString aHttpRealm);
/**
* Search for logins matching the specified criteria, as with
* findLogins(). This interface only returns the number of matching

Просмотреть файл

@ -20,12 +20,6 @@ const { TestUtils } = ChromeUtils.import(
"resource://testing-common/TestUtils.jsm"
);
const { FileTestUtils } = ChromeUtils.import(
"resource://testing-common/FileTestUtils.jsm"
);
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const LoginInfo = Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
"nsILoginInfo",
@ -89,39 +83,22 @@ this.LoginTestUtils = {
/**
* Checks that the currently stored list of nsILoginInfo matches the provided
* array. If no `checkFn` is provided, the comparison uses the "equals"
* method of nsILoginInfo, that does not include nsILoginMetaInfo properties in the test.
* array. The comparison uses the "equals" method of nsILoginInfo, that does
* not include nsILoginMetaInfo properties in the test.
*/
checkLogins(expectedLogins, msg = "checkLogins", checkFn = undefined) {
this.assertLoginListsEqual(
Services.logins.getAllLogins(),
expectedLogins,
msg,
checkFn
);
checkLogins(expectedLogins) {
this.assertLoginListsEqual(Services.logins.getAllLogins(), expectedLogins);
},
/**
* Checks that the two provided arrays of nsILoginInfo have the same length,
* and every login in "expected" is also found in "actual". If no `checkFn`
* is provided, the comparison uses the "equals" method of nsILoginInfo, that
* does not include nsILoginMetaInfo properties in the test.
* and every login in "expected" is also found in "actual". The comparison
* uses the "equals" method of nsILoginInfo, that does not include
* nsILoginMetaInfo properties in the test.
*/
assertLoginListsEqual(
actual,
expected,
msg = "assertLoginListsEqual",
checkFn = undefined
) {
Assert.equal(expected.length, actual.length, msg);
Assert.ok(
expected.every(e =>
actual.some(a => {
return checkFn ? checkFn(a, e) : a.equals(e);
})
),
msg
);
assertLoginListsEqual(actual, expected) {
Assert.equal(expected.length, actual.length);
Assert.ok(expected.every(e => actual.some(a => a.equals(e))));
},
/**
@ -455,11 +432,11 @@ LoginTestUtils.testData = {
"the password two"
),
// -- file:// URIs throw accessing nsIURI.host
// -- file:/// URIs throw accessing nsIURI.host
new LoginInfo(
"file://",
"file://",
"file:///",
"file:///",
null,
"file: username",
"file: password"
@ -582,21 +559,3 @@ LoginTestUtils.telemetry = {
return events;
},
};
LoginTestUtils.file = {
/**
* Given an array of strings it creates a temporary CSV file that has them as content.
*
* @param {string[]} csvLines
* The lines that make up the CSV file.
* @returns {window.File} The File to the CSV file that was created.
*/
async setupCsvFileWithLines(csvLines) {
let tmpFile = FileTestUtils.getTempFile("firefox_logins.csv");
await OS.File.writeAtomic(
tmpFile.path,
new TextEncoder().encode(csvLines.join("\r\n"))
);
return tmpFile;
},
};

Просмотреть файл

@ -17,7 +17,7 @@ add_task(function test_displayOrigin() {
Assert.greater(displayOrigin.length, 0, "Check length");
if (
loginInfo.origin.startsWith("chrome://") ||
loginInfo.origin.startsWith("file://")
loginInfo.origin.startsWith("file:///")
) {
Assert.ok(displayOrigin.startsWith(loginInfo.origin), "Contains origin");
} else {

Просмотреть файл

@ -1,7 +1,7 @@
"use strict";
const HOST1 = "https://www.example.com";
const HOST2 = "https://www.mozilla.org";
const HOST1 = "https://www.example.com/";
const HOST2 = "https://www.mozilla.org/";
const USER1 = "myuser";
const USER2 = "anotheruser";
@ -10,52 +10,13 @@ const PASS1 = "mypass";
const PASS2 = "anotherpass";
const PASS3 = "yetanotherpass";
add_task(async function test_invalid_logins() {
let importedLogins = await LoginHelper.maybeImportLogins([
{
username: USER1,
password: PASS1,
origin: "example.com", // Not an origin
formActionOrigin: HOST1,
},
{
username: USER1,
// no password
origin: HOST1,
formActionOrigin: HOST1,
},
{
username: USER2,
password: "", // Empty password
origin: HOST1,
formActionOrigin: HOST1,
},
]);
Assert.equal(
importedLogins.length,
0,
`Return value should indicate no imported login: ${JSON.stringify(
importedLogins,
null,
2
)}`
);
let savedLogins = Services.logins.getAllLogins();
Assert.equal(
savedLogins.length,
0,
`Should have no logins in storage: ${JSON.stringify(savedLogins, null, 2)}`
);
Services.logins.removeAllLogins();
});
add_task(async function test_new_logins() {
let [importedLogin] = await LoginHelper.maybeImportLogins([
{
username: USER1,
password: PASS1,
origin: HOST1 + "/",
formActionOrigin: HOST1 + "/",
origin: HOST1,
formActionOrigin: HOST1,
},
]);
Assert.ok(importedLogin, "Return value should indicate imported login.");
@ -211,7 +172,7 @@ add_task(async function test_different_passwords() {
Services.logins.removeAllLogins();
});
add_task(async function test_different_usernames_without_guid() {
add_task(async function test_different_usernames() {
let [importedLogin] = await LoginHelper.maybeImportLogins([
{
username: USER1,
@ -250,48 +211,6 @@ add_task(async function test_different_usernames_without_guid() {
Services.logins.removeAllLogins();
});
add_task(async function test_different_usernames_with_guid() {
let [importedLogin] = await LoginHelper.maybeImportLogins([
{
username: USER1,
password: PASS1,
origin: HOST1,
formActionOrigin: HOST1,
},
]);
Assert.ok(importedLogin, "Return value should indicate imported login.");
let matchingLogins = LoginHelper.searchLoginsWithObject({ origin: HOST1 });
Assert.equal(
matchingLogins.length,
1,
`There should be 1 login for ${HOST1}`
);
info("Changing both the origin and username using the GUID");
let importedLogins = await LoginHelper.maybeImportLogins([
{
username: USER2,
password: PASS1,
origin: HOST2,
formActionOrigin: HOST1,
guid: importedLogin.guid,
},
]);
Assert.ok(!importedLogins.length, "Return value should indicate an update");
matchingLogins = LoginHelper.searchLoginsWithObject({ origin: HOST2 });
Assert.equal(
matchingLogins.length,
1,
`The 1 login for ${HOST1} should have been updated`
);
let storageLogin = matchingLogins[0];
Assert.equal(storageLogin.guid, importedLogin.guid, "Check same guid");
Assert.equal(storageLogin.username, USER2, "Check username updated");
Assert.equal(storageLogin.origin, HOST2, "Check origin updated");
Services.logins.removeAllLogins();
});
add_task(async function test_different_targets() {
let [importedLogin] = await LoginHelper.maybeImportLogins([
{

Просмотреть файл

@ -1,439 +0,0 @@
/* 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/. */
/**
* Tests the LoginCSVImport module.
*/
"use strict";
const { LoginCSVImport } = ChromeUtils.import(
"resource://gre/modules/LoginCSVImport.jsm"
);
const { LoginExport } = ChromeUtils.import(
"resource://gre/modules/LoginExport.jsm"
);
const { TelemetryTestUtils: TTU } = ChromeUtils.import(
"resource://testing-common/TelemetryTestUtils.jsm"
);
// Enable the collection (during test) for all products so even products
// that don't collect the data will be able to run the test without failure.
Services.prefs.setBoolPref(
"toolkit.telemetry.testing.overrideProductsCheck",
true
);
/**
* Given an array of strings it creates a temporary CSV file that has them as content.
*
* @param {string[]} csvLines
* The lines that make up the CSV file.
* @returns {string} The path to the CSV file that was created.
*/
async function setupCsv(csvLines) {
// Cleanup state.
TTU.getAndClearKeyedHistogram("FX_MIGRATION_LOGINS_QUANTITY");
TTU.getAndClearKeyedHistogram("FX_MIGRATION_LOGINS_IMPORT_MS");
TTU.getAndClearKeyedHistogram("FX_MIGRATION_LOGINS_JANK_MS");
Services.logins.removeAllLogins();
let tmpFile = await LoginTestUtils.file.setupCsvFileWithLines(csvLines);
return tmpFile.path;
}
function checkMetaInfo(
actual,
expected,
props = ["timesUsed", "timeCreated", "timePasswordChanged", "timeLastUsed"]
) {
for (let prop of props) {
// This will throw if not equal.
equal(actual[prop], expected[prop], `Check ${prop}`);
}
return true;
}
function checkLoginNewlyCreated(login) {
// These will throw if not equal.
LoginTestUtils.assertTimeIsAboutNow(login.timeCreated);
LoginTestUtils.assertTimeIsAboutNow(login.timePasswordChanged);
LoginTestUtils.assertTimeIsAboutNow(login.timeLastUsed);
return true;
}
/**
* Ensure that an import fails if there is no username column. We don't want
*/
add_task(async function test_import_tsv() {
let csvFilePath = await setupCsv([
"url\tusernameTypo\tpassword\thttpRealm\tformActionOrigin\tguid\ttimeCreated\ttimeLastUsed\ttimePasswordChanged",
"https://example.com\tjoe@example.com\tqwerty\tMy realm\t\t{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}\t1589617814635\t1589710449871\t1589617846802",
]);
await Assert.rejects(
LoginCSVImport.importFromCSV(csvFilePath),
/must contain origin, username, and password columns/,
"Ensure non-CSV throws"
);
LoginTestUtils.checkLogins(
[],
"Check that no login was added without finding columns"
);
});
/**
* Ensure that an import fails if there is no username column. We don't want
* to accidentally import duplicates due to a name mismatch for the username column.
*/
add_task(async function test_import_lacking_username_column() {
let csvFilePath = await setupCsv([
"url,usernameTypo,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
`https://example.com,joe@example.com,qwerty,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814635,1589710449871,1589617846802`,
]);
await Assert.rejects(
LoginCSVImport.importFromCSV(csvFilePath),
/must contain origin, username, and password columns/,
"Ensure missing username throws"
);
LoginTestUtils.checkLogins(
[],
"Check that no login was added without finding a username column"
);
});
/**
* Ensure that an import fails if there are two headings that map to one login field.
*/
add_task(async function test_import_with_duplicate_columns() {
// Two origin columns (url & login_uri).
// One row has different values and the other has the same.
let csvFilePath = await setupCsv([
"url,login_uri,username,login_password",
"https://example.com/path,https://example.org,john@example.com,azerty",
"https://mozilla.org,https://mozilla.org,jdoe@example.com,qwerty",
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.formLogin({
formActionOrigin: "",
httpRealm: null,
origin: "https://mozilla.org",
password: "qwerty",
passwordField: "",
timesUsed: 1,
username: "jdoe@example.com",
usernameField: "",
}),
],
"Check that no login was added with duplicate columns of differing values"
);
});
/**
* Ensure that an import doesn't throw with only a header row.
*/
add_task(async function test_import_only_header_row() {
let csvFilePath = await setupCsv([
"url,usernameTypo,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
]);
// Shouldn't throw
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[],
"Check that no login was added without non-header rows."
);
});
/**
* Ensure that import is allowed with only origin, username, password and that
* one can mix and match column naming between conventions from different
* password managers (so that we better support new/unknown password managers).
*/
add_task(async function test_import_minimal_with_mixed_naming() {
let csvFilePath = await setupCsv([
"url,username,login_password",
"ftp://example.com,john@example.com,azerty",
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.formLogin({
formActionOrigin: "",
httpRealm: null,
origin: "ftp://example.com",
password: "azerty",
passwordField: "",
timesUsed: 1,
username: "john@example.com",
usernameField: "",
}),
],
"Check that a new login was added with the correct fields",
(a, e) =>
a.equals(e) &&
checkMetaInfo(a, e, ["timesUsed"]) &&
checkLoginNewlyCreated(a)
);
});
/**
* Imports login data from the latest Firefox CSV file for various logins from
* LoginTestUtils.testData.loginList().
*/
add_task(async function test_import_from_firefox_various_latest() {
await setupCsv([]);
info("Populate the login list for export");
let logins = LoginTestUtils.testData.loginList();
for (let loginInfo of logins) {
Services.logins.addLogin(loginInfo);
}
let tmpFilePath = FileTestUtils.getTempFile("logins.csv").path;
await LoginExport.exportAsCSV(tmpFilePath);
await LoginCSVImport.importFromCSV(tmpFilePath);
LoginTestUtils.checkLogins(
logins,
"Check that all of LoginTestUtils.testData.loginList can be re-imported"
);
});
/**
* Imports login data from a Firefox CSV file without quotes.
*/
add_task(async function test_import_from_firefox_auth() {
let csvFilePath = await setupCsv([
"url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
`https://example.com:8080,joe@example.com,qwerty,My realm,"",{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814635,1589710449871,1589617846802`,
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.authLogin({
formActionOrigin: null,
guid: "{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}",
httpRealm: "My realm",
origin: "https://example.com:8080",
password: "qwerty",
passwordField: "",
timeCreated: 1589617814635,
timeLastUsed: 1589710449871,
timePasswordChanged: 1589617846802,
timesUsed: 1,
username: "joe@example.com",
usernameField: "",
}),
],
"Check that a new login was added with the correct fields",
(a, e) => a.equals(e) && checkMetaInfo(a, e)
);
});
/**
* Imports login data from a Firefox CSV file with quotes.
*/
add_task(async function test_import_from_firefox_auth_with_quotes() {
let csvFilePath = await setupCsv([
'"url","username","password","httpRealm","formActionOrigin","guid","timeCreated","timeLastUsed","timePasswordChanged"',
'"https://example.com","joe@example.com","qwerty2","My realm",,"{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}","1589617814635","1589710449871","1589617846802"',
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.authLogin({
formActionOrigin: null,
httpRealm: "My realm",
origin: "https://example.com",
password: "qwerty2",
passwordField: "",
timeCreated: 1589617814635,
timeLastUsed: 1589710449871,
timePasswordChanged: 1589617846802,
timesUsed: 1,
username: "joe@example.com",
usernameField: "",
}),
],
"Check that a new login was added with the correct fields",
(a, e) => a.equals(e) && checkMetaInfo(a, e)
);
});
/**
* Imports login data from a Firefox CSV file where only cells containing a comma are quoted.
*/
add_task(async function test_import_from_firefox_auth_some_quoted_fields() {
let csvFilePath = await setupCsv([
"url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
'https://example.com,joe@example.com,"one,two,tree","My realm",,{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814635,1589710449871,1589617846802',
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.authLogin({
formActionOrigin: null,
httpRealm: "My realm",
origin: "https://example.com",
password: "one,two,tree",
passwordField: "",
timeCreated: 1589617814635,
timePasswordChanged: 1589617846802,
timeLastUsed: 1589710449871,
timesUsed: 1,
username: "joe@example.com",
usernameField: "",
}),
],
"Check that a new login was added with the correct fields",
(a, e) => a.equals(e) && checkMetaInfo(a, e)
);
});
/**
* Imports login data from a Firefox CSV file with an empty formActionOrigin and null httpRealm
*/
add_task(async function test_import_from_firefox_form_empty_formActionOrigin() {
let csvFilePath = await setupCsv([
"url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
"https://example.com,joe@example.com,s3cret1,,,{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814636,1589710449872,1589617846803",
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.formLogin({
formActionOrigin: "",
httpRealm: null,
origin: "https://example.com",
password: "s3cret1",
passwordField: "",
timeCreated: 1589617814636,
timePasswordChanged: 1589617846803,
timeLastUsed: 1589710449872,
timesUsed: 1,
username: "joe@example.com",
usernameField: "",
}),
],
"Check that a new login was added with the correct fields",
(a, e) => a.equals(e) && checkMetaInfo(a, e)
);
});
/**
* Imports login data from a Firefox CSV file with a non-empty formActionOrigin and null httpRealm.
*/
add_task(async function test_import_from_firefox_form_with_formActionOrigin() {
let csvFilePath = await setupCsv([
"url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
"http://example.com,joe@example.com,s3cret1,,https://other.example.org,{5ec0d12f-e194-4279-ae1b-d7d281bb46f1},1589617814635,1589710449871,1589617846802",
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.formLogin({
formActionOrigin: "https://other.example.org",
httpRealm: null,
origin: "http://example.com",
password: "s3cret1",
passwordField: "",
timeCreated: 1589617814635,
timePasswordChanged: 1589617846802,
timeLastUsed: 1589710449871,
timesUsed: 1,
username: "joe@example.com",
usernameField: "",
}),
],
"Check that a new login was added with the correct fields",
(a, e) => a.equals(e) && checkMetaInfo(a, e)
);
});
/**
* Imports login data from a Bitwarden CSV file.
* `name` is ignored until bug 1433770.
*/
add_task(async function test_import_from_bitwarden_csv() {
let csvFilePath = await setupCsv([
"folder,favorite,type,name,notes,fields,login_uri,login_username,login_password,login_totp",
`,,note,jane's note,"secret note, ignore me!",,,,,`,
",,login,example.com,,,https://example.com/login,jane@example.com,secret_password",
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.formLogin({
formActionOrigin: "",
httpRealm: null,
origin: "https://example.com",
password: "secret_password",
passwordField: "",
timesUsed: 1,
username: "jane@example.com",
usernameField: "",
}),
],
"Check that a new Bitwarden login was added with the correct fields",
(a, e) =>
a.equals(e) &&
checkMetaInfo(a, e, ["timesUsed"]) &&
checkLoginNewlyCreated(a)
);
});
/**
* Imports login data from a Chrome CSV file.
* `name` is ignored until bug 1433770.
*/
add_task(async function test_import_from_chrome_csv() {
let csvFilePath = await setupCsv([
"name,url,username,password",
"example.com,https://example.com/login,jane@example.com,secret_chrome_password",
]);
await LoginCSVImport.importFromCSV(csvFilePath);
LoginTestUtils.checkLogins(
[
TestData.formLogin({
formActionOrigin: "",
httpRealm: null,
origin: "https://example.com",
password: "secret_chrome_password",
passwordField: "",
timesUsed: 1,
username: "jane@example.com",
usernameField: "",
}),
],
"Check that a new Chrome login was added with the correct fields",
(a, e) =>
a.equals(e) &&
checkMetaInfo(a, e, ["timesUsed"]) &&
checkLoginNewlyCreated(a)
);
});

Просмотреть файл

@ -197,7 +197,7 @@ add_task(async function test_export_multiple_rows() {
'"ftp://example.net","the username","the password","ftp://example.net",,,,,',
'"chrome://example_extension","the username","the password one","Example Login One",,,,,',
'"chrome://example_extension","the username","the password two","Example Login Two",,,,,',
'"file://","file: username","file: password",,"file://",,,,',
'"file:///","file: username","file: password",,"file:///",,,,',
'"https://js.example.com","javascript: username","javascript: password",,"javascript:",,,,',
];

Просмотреть файл

@ -44,7 +44,6 @@ skip-if = os == "android" # Bug 1171687: Needs fixing on Android
[test_logins_search.js]
[test_maybeImportLogin.js]
skip-if = os == "android" # Only used by migrator, which isn't on Android
[test_module_LoginCSVImport.js]
[test_module_LoginExport.js]
skip-if = os == "android" # there is no export for android
[test_notifications.js]

Просмотреть файл

@ -768,7 +768,6 @@ pwmgr:
objects: [
"import_from_browser",
"import_from_csv",
"import_csv_complete",
"export",
"export_complete",
"preferences",