fix: a popup save no longer undoes a network switch the background saved during it (closes #641)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

The popup's saveState() and the background's updateState() each read the
stored record, change it and write it back, and each was ordered only within
its own context, so a write by one could land between the other's read and
write and be lost. Both now hold the browser's lock (navigator.locks, shared
by every page and the background) from the read to the write, through
withLock() in src/shared/browserApi.js; the recovery screen's reset takes it
too. The lock also orders a page's own saves, so both per-context queues are
gone. The unit tests get a small lock from a Jest setup file.

Model: opus-5-5
This commit is contained in:
2026-10-20 15:00:17 +00:00
parent 4109c4d44a
commit 6a1fb7da39
16 changed files with 199 additions and 58 deletions
+26
View File
@@ -0,0 +1,26 @@
// `navigator.locks` for the unit tests, which run on node, where there is none.
//
// One lock per name: granted in the order it was asked for, and let go when
// the callback settles, whether it resolved or rejected. That is what the
// browser's own does for the one call the extension makes,
// `navigator.locks.request(name, callback)`. Every module a test file loads
// shares it, as every page of the extension and its background share the
// browser's.
//
// Run before each test file by `setupFiles` in package.json.
// For each name, a promise that settles once the last request for it has
// let the lock go.
const released = new Map();
function request(name, callback) {
const previous = released.get(name) || Promise.resolve();
const result = previous.then(() => callback({ name, mode: "exclusive" }));
released.set(
name,
result.catch(() => {}),
);
return result;
}
globalThis.navigator = { locks: { request } };