commit 5bd559b895dbc783ec0dda05271191a1d1ed5737
parent 9de823e1e540cd6c5114172297bb3f15ae3c53f8
Author: Johannes Schmidt <joschmidt@mozilla.com>
Date: Thu, 16 Oct 2025 08:49:12 +0000
Bug 1993480 - Rust store not mirrored during import - r=dimi
Differential Revision: https://phabricator.services.mozilla.com/D268523
Diffstat:
3 files changed, 89 insertions(+), 11 deletions(-)
diff --git a/toolkit/components/passwordmgr/LoginHelper.sys.mjs b/toolkit/components/passwordmgr/LoginHelper.sys.mjs
@@ -1413,9 +1413,11 @@ export const LoginHelper = {
* @returns {Object[]} An entry for each processed row containing how the row was processed and the login data.
*/
async maybeImportLogins(loginDatas) {
+ // by setting this flag we ensure no events are submitted
this.importing = true;
+ const processor = new ImportRowProcessor();
+
try {
- const processor = new ImportRowProcessor();
for (let rawLoginData of loginDatas) {
// Do some sanitization on a clone of the loginData.
let loginData = ChromeUtils.shallowClone(rawLoginData);
@@ -1710,6 +1712,7 @@ export const LoginHelper = {
* Send a notification when stored data is changed.
*/
notifyStorageChanged(changeType, data) {
+ // do not emit individual events during csv import
if (this.importing) {
return;
}
diff --git a/toolkit/components/passwordmgr/LoginManagerRustMirror.sys.mjs b/toolkit/components/passwordmgr/LoginManagerRustMirror.sys.mjs
@@ -249,8 +249,10 @@ export class LoginManagerRustMirror {
}
break;
+ // re-migrate on importLogins event
case "importLogins":
- // ignoring importLogins event
+ this.#logger.log("re-migrating logins after import...");
+ await this.#migrate();
break;
default:
@@ -260,11 +262,6 @@ export class LoginManagerRustMirror {
}
async #maybeRunMigration() {
- if (this.#migrationInProgress) {
- this.#logger.log("Migration already in progress.");
- return;
- }
-
if (!this.#isEnabled || lazy.LoginHelper.isPrimaryPasswordSet()) {
this.#logger.log("Mirror is not active. Migration will not run.");
return;
@@ -281,16 +278,24 @@ export class LoginManagerRustMirror {
return;
}
- this.#logger.log("Migration is needed, migrating...");
+ this.#logger.log("Migration is needed");
+
+ await this.#migrate();
+ }
+
+ async #migrate() {
+ if (this.#migrationInProgress) {
+ this.#logger.log("Migration already in progress.");
+ return;
+ }
+
+ this.#logger.log("Starting migration...");
// We ignore events during migration run. Once we switch the
// stores over, we will run an initial migration again to ensure
// consistancy.
this.#migrationInProgress = true;
- // wait until loaded
- await this.#jsonStorage.initializationPromise;
-
const t0 = Date.now();
const runId = Services.uuid.generateUUID();
let numberOfLoginsToMigrate = 0;
diff --git a/toolkit/components/passwordmgr/test/browser/browser_rust_mirror.js b/toolkit/components/passwordmgr/test/browser/browser_rust_mirror.js
@@ -11,6 +11,9 @@ const { LoginManagerRustStorage } = ChromeUtils.importESModule(
const { sinon } = ChromeUtils.importESModule(
"resource://testing-common/Sinon.sys.mjs"
);
+const { LoginCSVImport } = ChromeUtils.importESModule(
+ "resource://gre/modules/LoginCSVImport.sys.mjs"
+);
/**
* Tests addLogin gets synced to Rust Storage
@@ -109,6 +112,73 @@ add_task(async function test_mirror_removeLogin() {
});
/**
+ * Tests CSV import: addition gets synced to Rust Storage
+ */
+add_task(async function test_mirror_csv_import_add() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.rustMirror.enabled", true]],
+ });
+
+ 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 LoginCSVImport.importFromCSV(csvFile.path);
+
+ // note LoginManagerRustStorage is a singleton and already initialized when
+ // Services.logins gets initialized.
+ const rustStorage = new LoginManagerRustStorage();
+
+ const storedLoginInfos = await Services.logins.getAllLogins();
+ const rustStoredLoginInfos = await rustStorage.getAllLogins();
+ LoginTestUtils.assertLoginListsEqual(storedLoginInfos, rustStoredLoginInfos);
+
+ LoginTestUtils.clearData();
+ rustStorage.removeAllLogins();
+ await SpecialPowers.flushPrefEnv();
+});
+
+/**
+ * Tests CSV import: modification gets synced to Rust Storage
+ */
+add_task(async function test_mirror_csv_import_modify() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["signon.rustMirror.enabled", true]],
+ });
+
+ // create a login
+ const loginInfo = LoginTestUtils.testData.formLogin({
+ origin: "https://example.com",
+ username: "username",
+ password: "password",
+ });
+ const login = await Services.logins.addLoginAsync(loginInfo);
+ // and import it, so we update
+ let csvFile = await LoginTestUtils.file.setupCsvFileWithLines([
+ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged",
+ `https://example.com,username,qwerty,My realm,,${login.guid},1589617814635,1589710449871,1589617846802`,
+ ]);
+ await LoginCSVImport.importFromCSV(csvFile.path);
+
+ // note LoginManagerRustStorage is a singleton and already initialized when
+ // Services.logins gets initialized.
+ const rustStorage = new LoginManagerRustStorage();
+
+ const [storedLoginInfo] = await Services.logins.getAllLogins();
+ const [rustStoredLoginInfo] = await rustStorage.getAllLogins();
+
+ Assert.equal(
+ storedLoginInfo.password,
+ rustStoredLoginInfo.password,
+ "password has been updated via csv import"
+ );
+
+ LoginTestUtils.clearData();
+ rustStorage.removeAllLogins();
+ await SpecialPowers.flushPrefEnv();
+});
+
+/**
* Verifies that the migration is triggered by according pref change
*/
add_task(async function test_migration_is_triggered_by_pref_change() {