fix: AutistMask does not run in Firefox private windows (closes #646)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

Firefox gives an extension page in a private window locks of its own. A
popup there could save between the background's read and its write of the
stored record, and undo a network switch the background had saved.

manifest/firefox.json now sets "incognito": "not_allowed", so Firefox never
runs AutistMask in a private window. tests/manifest.test.js pins the key.
The README and five code comments no longer describe a private-window
exception to the lock.

Model: opus-5-5
This commit is contained in:
2026-10-26 21:38:59 +00:00
parent 387f40689e
commit cb44e9903d
8 changed files with 30 additions and 27 deletions
+5 -7
View File
@@ -18,11 +18,10 @@
// updateState(fn) read fresh, apply fn to that fresh record, write it
// back — all holding the lock every write of the stored
// record takes, so no other write lands in between, by
// the background or by any page except a page in a
// Firefox private window (README, "Stored state and its
// version"). The read is one storage round trip ahead of
// the write rather than a page lifetime ahead of it
// (which is what made the popup's saveState() need a
// the background or by any page (README, "Stored state
// and its version"). The read is one storage round trip
// ahead of the write rather than a page lifetime ahead of
// it (which is what made the popup's saveState() need a
// per-field merge against a baseline at all).
//
// A handler that must both read and write therefore does its network work
@@ -73,8 +72,7 @@ async function updateStateOnce(mutate) {
// src/shared/browserApi.js) is held from the read to the write: two
// background turns, or a turn and a page's save, never interleave
// (https://git.eeqj.de/sneak/AutistMask/issues/641). Every page's save waits
// while it is held, except a page in a Firefox private window, which Firefox
// gives locks of its own (README, "Stored state and its version").
// while it is held (README, "Stored state and its version").
//
// `mutate` must also not call updateState() itself, directly or through
// anything it awaits: the inner turn waits for the lock the outer one holds,