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 except a page in a Firefox private window) 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 } };
|