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
27 lines
966 B
JavaScript
27 lines
966 B
JavaScript
// `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 } };
|