docs: code comments name the headings README has now (closes #724)
The README rewrite renamed or merged several headings that comments still cited. Each comment now names the current heading. Line number citations of README become headings too, and one test name drops its line numbers. The e2e workflow's citations of "In CI" are dropped, because no section has what they pointed at. Its comment also corrects what it says about required jobs and open reports. The comment on withLock() no longer states a Firefox private window exception, because the manifest keeps the extension out of those windows. Two comments also take corrections from reviews: the cases with no total in DeleteAddress's warning, and the screens that say a swap's amount could not be decoded. Model: opus-5-5
This commit is contained in:
@@ -19,13 +19,10 @@ on: [push]
|
||||
# by the host daemon and mounts an empty directory, and the runner image's
|
||||
# node is too old to install this repo's dependencies.
|
||||
#
|
||||
# These jobs REPORT, they do not gate. Whether a check blocks a merge is
|
||||
# Gitea branch protection, which this repo does not configure, so a failure
|
||||
# here is a red mark a reviewer has to account for rather than a hard
|
||||
# block. Making e2e-chrome a required check is blocked while reports of the
|
||||
# Chrome suite failing under load are still open; the "In CI" section of
|
||||
# README.md names them. A gate that fails at random teaches people to merge
|
||||
# past red.
|
||||
# These jobs REPORT, they do not gate. No branch requires these two jobs, so
|
||||
# a failure is a red mark a reviewer has to account for. The Chrome suite has
|
||||
# failed under load before, and no such report is open. A gate that fails at
|
||||
# random teaches people to merge past red.
|
||||
#
|
||||
# Nothing here may pass vacuously. There is no continue-on-error and no
|
||||
# `|| true`. Both scripts exit non-zero when docker is missing, when the
|
||||
@@ -36,8 +33,7 @@ jobs:
|
||||
e2e-chrome:
|
||||
runs-on: ubuntu-latest
|
||||
# Bounds the image build, a cold cache included, and the three Chrome
|
||||
# programs, so a hung browser frees the shared runner. README.md
|
||||
# "In CI" has the measured times.
|
||||
# programs, so a hung browser frees the shared runner.
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
# actions/checkout v4.2.2, 2026-02-22
|
||||
@@ -47,8 +43,7 @@ jobs:
|
||||
e2e-firefox:
|
||||
runs-on: ubuntu-latest
|
||||
# Bounds the image build, a cold cache included, and both Firefox
|
||||
# programs, so a hung browser frees the shared runner. README.md
|
||||
# "In CI" has the measured times.
|
||||
# programs, so a hung browser frees the shared runner.
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
# actions/checkout v4.2.2, 2026-02-22
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Turn a verified dist/ into the two distributable archives.
|
||||
//
|
||||
// Invoked by script/package, which runs `make build` first so that dist/ has
|
||||
// already been checked against the build's own receipt (see the Build Receipts
|
||||
// already been checked against the build's own receipt (see the Builds
|
||||
// section of README.md). This program does not build anything and does not
|
||||
// write into dist/: it reads the emitted tree and writes release/.
|
||||
//
|
||||
|
||||
@@ -60,12 +60,12 @@ main() {
|
||||
|
||||
echo "Running the Firefox e2e suite..."
|
||||
# --shm-size=1g: Firefox needs more than the default 64MB /dev/shm.
|
||||
# --network none: the suite stubs nothing, so this is what keeps the
|
||||
# run offline and deterministic. The extension swallows its own
|
||||
# fetch failures, so the popup flows work unchanged; see the
|
||||
# network note in README.md. Weaker than the Chrome suite's
|
||||
# fixture interception, and honestly so — it proves no request
|
||||
# escaped, but it cannot report which ones were attempted.
|
||||
# --network none: the container has no network, and the suite reaches
|
||||
# only the test page and stub node on loopback (README.md, Browser
|
||||
# tests). The extension swallows its own fetch failures, so the popup
|
||||
# flows work unchanged. Weaker than the Chrome suite's fixture
|
||||
# interception, and honestly so — it proves no request escaped, but it
|
||||
# cannot report which ones were attempted.
|
||||
# HOME=/tmp: the image's root home is not a reliable place for the
|
||||
# browser profile.
|
||||
#
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
// 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 (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).
|
||||
// the background or by any page (README, "Stored data").
|
||||
// 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
|
||||
// against a snapshot it owns, and applies the RESULT inside updateState().
|
||||
@@ -72,7 +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 (README, "Stored state and its version").
|
||||
// while it is held (README, "Stored data").
|
||||
//
|
||||
// `mutate` must also not call updateState() itself, directly or through
|
||||
// anything it awaits: the inner turn waits for the lock the outer one holds,
|
||||
|
||||
@@ -319,12 +319,12 @@ function showTxApproval(details) {
|
||||
);
|
||||
}
|
||||
// A swap the decoder read no amount for, such as one whose V4 swap
|
||||
// action could not be read or a V4 exact-out swap. The wait and error
|
||||
// screens say the amount could not be decoded, with no currency after
|
||||
// it, rather than show the transaction's ETH value as the amount. A
|
||||
// swap paid in native ETH keeps that value: it is the ETH the swap
|
||||
// pays (for an exact-out swap, the most it pays), not a figure that
|
||||
// had to be read from the calldata.
|
||||
// action could not be read or a V4 exact-out swap. The wait, success
|
||||
// and error screens say the amount could not be decoded, with no
|
||||
// currency after it, rather than show the transaction's ETH value as
|
||||
// the amount. A swap paid in native ETH keeps that value: it is the
|
||||
// ETH the swap pays (for an exact-out swap, the most it pays), not a
|
||||
// figure that had to be read from the calldata.
|
||||
const tokenIn = decoded.details.find((d) => d.label === "Token In");
|
||||
if (
|
||||
tokenIn &&
|
||||
|
||||
@@ -83,10 +83,11 @@ function recoveryPathText(wallet) {
|
||||
// rounded number would report "0.0000 ETH" for an address holding real money.
|
||||
// The lines below it carry the amounts, in the same format as Home and
|
||||
// AddressDetail, followed by the USD total when there is one to give — no
|
||||
// total line at all on testnet or before the first price fetch, and no figure
|
||||
// when every holding here is one with no price, since "$0.00" directly under
|
||||
// "This address holds a balance." is a contradiction. A token holding below
|
||||
// 0.000001 does not count, as the lines below leave it out.
|
||||
// total line at all on testnet, before the first price fetch or while no
|
||||
// refresh has read the ETH balance, and no figure when every holding here is
|
||||
// one with no price, since "$0.00" directly under "This address holds a
|
||||
// balance." is a contradiction. A token holding below 0.000001 does not count,
|
||||
// as the lines below leave it out.
|
||||
//
|
||||
// While no refresh has read the ETH balance the address may be empty, so the
|
||||
// sentence says "may hold".
|
||||
|
||||
@@ -272,7 +272,7 @@ function clearFlash() {
|
||||
}
|
||||
|
||||
// The flash line reserves exactly one line, and a message that wrapped would
|
||||
// push the screen below it down (README, No Layout Shift). So #flash-msg never
|
||||
// push the screen below it down (README, Appearance). So #flash-msg never
|
||||
// wraps: text too long for the line is cut with an ellipsis, and the whole
|
||||
// message is also put in the line's title. Write messages to fit, at most 50
|
||||
// characters, so none is cut.
|
||||
@@ -528,9 +528,9 @@ const ADDRESS_MIN_DISPLAY_LEN = 32;
|
||||
|
||||
// Truncate the middle of a string, replacing removed characters with "…".
|
||||
// Safety: refuses to truncate more than 10 characters, which is the maximum
|
||||
// that still prevents address spoofing attacks (see Display Consistency in
|
||||
// README). Callers that need to display less should use a different UI
|
||||
// approach rather than silently making addresses insecure.
|
||||
// that still prevents address spoofing attacks (see Addresses in README).
|
||||
// Callers that need to display less should use a different UI approach rather
|
||||
// than silently making addresses insecure.
|
||||
function truncateMiddle(str, maxLen) {
|
||||
if (str.length <= maxLen) return str;
|
||||
const removed = str.length - maxLen + 1; // +1 for the ellipsis char
|
||||
@@ -633,9 +633,9 @@ function formatAddressHtml(address, ensName, maxLen, title) {
|
||||
return renderAddressHtml(address, { title, ensName, maxLen });
|
||||
}
|
||||
|
||||
// A transaction's time as every screen shows it (README, Display
|
||||
// Consistency): the ISO datetime, in UTC when the UTC Timestamps setting is
|
||||
// on, and the relative age. Views import these two; they keep no copies.
|
||||
// A transaction's time as every screen shows it (README, Amounts): the ISO
|
||||
// datetime, in UTC when the UTC Timestamps setting is on, and the relative age.
|
||||
// Views import these two; they keep no copies.
|
||||
function isoDate(timestamp) {
|
||||
const d = new Date(timestamp * 1000);
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
|
||||
@@ -265,7 +265,7 @@ async function renderSiteLists() {
|
||||
// A network switch, made by the user here or saved by the background once the
|
||||
// user approved a site's request (src/popup/index.js), leaves every screen on
|
||||
// the page showing the previous network. Two functions leave the page as
|
||||
// README's Screen Map describes:
|
||||
// README's Navigation section describes:
|
||||
//
|
||||
// - cutViewStack(), before the switch is saved, so that save keeps the cut for
|
||||
// a popup closed straight after
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The 4-decimal amount rule from README.md's Display Consistency section, and
|
||||
// The 4-decimal amount rule from README.md's Amounts section, and
|
||||
// the one exception to it, in one place. Three call sites had grown their own
|
||||
// copy of the truncation — the history and balance lists
|
||||
// (`src/shared/transactions.js`), the approval screen's ERC-20 amount line
|
||||
|
||||
@@ -240,10 +240,9 @@ function onStorageChanged(listener) {
|
||||
/**
|
||||
* Run `fn` while holding the browser's lock called `name`
|
||||
* (`navigator.locks`), and settle as `fn` settles. Every page of the extension
|
||||
* and its background share these locks, except a page in a Firefox private
|
||||
* window (README, "Stored state and its version"), so while one of them holds
|
||||
* `name` another asking for it waits, in the order they asked. The lock is let
|
||||
* go when `fn` settles, whether it resolved or rejected.
|
||||
* and its background share these locks, so while one of them holds `name`
|
||||
* another asking for it waits, in the order they asked. The lock is let go when
|
||||
* `fn` settles, whether it resolved or rejected.
|
||||
*
|
||||
* Every write of the stored record holds the lock named after its key,
|
||||
* "autistmask", from its read to its write: saveState() in
|
||||
@@ -266,8 +265,8 @@ function withLock(name, fn) {
|
||||
|
||||
// `storage.session`, the browser's session storage: what is kept there lasts
|
||||
// until the browser closes. It survives the background worker being stopped
|
||||
// and started again, which module-level variables do not (README, Background
|
||||
// scheduling). The background keeps the connections allowed without
|
||||
// and started again, which module-level variables do not (README, The
|
||||
// background worker). The background keeps the connections allowed without
|
||||
// "Remember" there; see src/background/index.js. Firefox has it from version
|
||||
// 115, the minimum manifest/firefox.json declares.
|
||||
//
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// The floor of the approval screen's amount line.
|
||||
//
|
||||
// Amounts are truncated to four decimal places for scannability (README.md,
|
||||
// Display Consistency). With the token's true scale resolved, that truncation
|
||||
// can still take a real amount below the floor and print it as `0.0000`: one
|
||||
// base unit of an 18-decimal token, or a few hundred of an 8-decimal one. On
|
||||
// the one screen whose job is to state what is being authorized, a nonzero
|
||||
// transfer or allowance then reads as nothing.
|
||||
// Amounts). With the token's true scale resolved, that truncation can still
|
||||
// take a real amount below the floor and print it as `0.0000`: one base unit of
|
||||
// an 18-decimal token, or a few hundred of an 8-decimal one. On the one screen
|
||||
// whose job is to state what is being authorized, a nonzero transfer or
|
||||
// allowance then reads as nothing.
|
||||
//
|
||||
// The invariant asserted here is narrow: a nonzero amount never renders as
|
||||
// zero. The four-decimal rule itself is unchanged, and the string the
|
||||
|
||||
@@ -74,7 +74,7 @@ describe("parsing the dust threshold", () => {
|
||||
});
|
||||
|
||||
describe("the rejection message", () => {
|
||||
// README, Language & Labeling: error messages are full sentences.
|
||||
// README, Wording: error messages are full sentences.
|
||||
test("is a full sentence naming the constraint", () => {
|
||||
expect(DUST_THRESHOLD_MESSAGE).toMatch(/^[A-Z].*\.$/);
|
||||
expect(DUST_THRESHOLD_MESSAGE).toContain("whole number of gwei");
|
||||
@@ -95,9 +95,9 @@ describe("the flash line the message is shown in", () => {
|
||||
// NOT and CANNOT assert that the message fits inside it: jest runs on
|
||||
// the node environment here, with no layout engine, so every rendered
|
||||
// height is zero. An earlier version of this block claimed to pin the
|
||||
// No Layout Shift policy with this regex, and it passed at any message
|
||||
// length, including one that wrapped to two lines and pushed the
|
||||
// settings view down 12px.
|
||||
// reserved space rule in README, Appearance, with this regex, and it
|
||||
// passed at any message length, including one that wrapped to two lines
|
||||
// and pushed the settings view down 12px.
|
||||
//
|
||||
// The line cuts a message too long for it with an ellipsis (see
|
||||
// showFlash() in src/popup/views/helpers.js). The assertions that
|
||||
|
||||
+7
-7
@@ -1280,7 +1280,7 @@ test("the correct password reveals the full phrase, and nothing logs it (#161)",
|
||||
);
|
||||
assert(!promptShown, "the password prompt is still shown after unlock");
|
||||
|
||||
// Full Identifiers Policy: shown whole, and copyable.
|
||||
// README, ShowRecoveryPhrase: shown whole, and copyable.
|
||||
const title = await env.page.getAttribute(
|
||||
"#show-phrase-value",
|
||||
"title",
|
||||
@@ -2255,7 +2255,7 @@ async function waitForFilledFlashLine(page) {
|
||||
}
|
||||
}
|
||||
|
||||
// README, No Layout Shift: the rejection message goes into #flash-msg,
|
||||
// README, Appearance: the rejection message goes into #flash-msg,
|
||||
// whose min-h-[1.25rem] reserves exactly ONE line at text-xs, and which
|
||||
// cuts a message too long for that line with an ellipsis rather than wrap
|
||||
// it. This shows the real message and measures that nothing moves; the
|
||||
@@ -2603,7 +2603,7 @@ function toHexWei(wei) {
|
||||
}
|
||||
|
||||
// A fee in wei as the confirmation screen writes it: truncated to four decimal
|
||||
// places (README.md, Display Consistency). Deliberately a second
|
||||
// places (README.md, Amounts). Deliberately a second
|
||||
// implementation rather than an import of src/shared/amountDisplay.js:
|
||||
// asserting against an independent rendering is stronger than asserting a
|
||||
// function equals itself. The fixture's fees are above 0.0001 ETH, so the
|
||||
@@ -3298,7 +3298,7 @@ async function goToTokenConfirm(env) {
|
||||
amount: TOKEN_AMOUNT,
|
||||
});
|
||||
await waitForEstimate(env.page);
|
||||
// TOKEN_AMOUNT to four decimal places (README.md, Display Consistency).
|
||||
// TOKEN_AMOUNT to four decimal places (README.md, Amounts).
|
||||
const shown = await shownAmount(env.page);
|
||||
assert(
|
||||
shown === "0.2500 " + STUB_TOKEN.symbol,
|
||||
@@ -3453,7 +3453,7 @@ test("a token that lies about decimals() at signing time broadcasts nothing (#30
|
||||
|
||||
// ------------------------- a send Sign & Send refuses moves nothing (#583)
|
||||
//
|
||||
// README, No Layout Shift. Sign & Send refuses a send on the confirmation
|
||||
// README, Appearance. Sign & Send refuses a send on the confirmation
|
||||
// screen when the active network changed since review and when its gas limit
|
||||
// or fee is over its bound, and each refusal's sentence sits in a box that held
|
||||
// its space from the first paint. This drives the total fee refusal, the longer
|
||||
@@ -5770,8 +5770,8 @@ async function main() {
|
||||
// There is no fourth: once the context is closed nothing can record,
|
||||
// because the route handler and the console listeners died with it.
|
||||
// Traffic that a test defers past the trailing drain is therefore
|
||||
// never observed at all — a real limit of this design, stated in the
|
||||
// README, and not one any post-teardown hook could close.
|
||||
// never observed at all — a real limit of this design, and not one
|
||||
// any post-teardown hook could close.
|
||||
//
|
||||
// Two green-but-vacuous runs on this harness were the same shape: a
|
||||
// record falling outside somebody's window and being dropped. First
|
||||
|
||||
@@ -165,9 +165,9 @@ describe("declared icons", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// README, Installing on Firefox: the extension does not run in private windows.
|
||||
// Firefox gives a page in a private window locks of its own, so that page's
|
||||
// save could land between the background's read and its write (README,
|
||||
// "Stored state and its version").
|
||||
// save could land between the background's read and its write.
|
||||
describe("private windows", () => {
|
||||
test("a page in a firefox private window cannot read or write extension storage", () => {
|
||||
expect(readManifest("firefox").incognito).toBe("not_allowed");
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// 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 the extension's background and its pages share the browser's
|
||||
// (README, "Stored state and its version").
|
||||
// (README, "Stored data").
|
||||
//
|
||||
// Run before each test file by `setupFiles` in package.json.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// A transaction's time is written by isoDate() and timeAgo() in
|
||||
// src/popup/views/helpers.js on every screen that shows one (README, Display
|
||||
// Consistency; https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail
|
||||
// and AddressToken used to define their own copies, so a fix to the shared pair
|
||||
// src/popup/views/helpers.js on every screen that shows one (README, Amounts;
|
||||
// https://git.eeqj.de/sneak/AutistMask/issues/168). AddressDetail and
|
||||
// AddressToken used to define their own copies, so a fix to the shared pair
|
||||
// would not have reached them.
|
||||
//
|
||||
// The pair is replaced before the views are loaded, because a view takes it
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Tests for the address-poisoning defense in src/shared/transactions.js.
|
||||
//
|
||||
// README.md:730-814 describes four filters as a core security property of
|
||||
// the wallet: known token symbol verification, the low-holder threshold, the
|
||||
// README, Scam protection, describes four filters as a core security property
|
||||
// of the wallet: known token symbol verification, the low-holder threshold, the
|
||||
// fraud contract blocklist, and the dust threshold. A regression in any of
|
||||
// them does not crash — it silently stops filtering, and poisoned look-alike
|
||||
// addresses reappear in the user's transaction history. These tests pin the
|
||||
@@ -46,11 +46,12 @@ const { debugFetch } = require("../src/shared/log");
|
||||
const { state } = require("../src/shared/state");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Addresses and hashes from the two attacks documented in README.md:730-814.
|
||||
// Addresses and hashes from two real attacks. README, Scam protection, has the
|
||||
// filters that stop them.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// The fake "Ethereum" token with symbol "ETH" and zero holders
|
||||
// (README.md:735-750).
|
||||
// The fake "Ethereum" token with symbol "ETH" and zero holders. See "Known
|
||||
// symbol spoofing" and "Low-holder tokens" in README, Scam protection.
|
||||
const FAKE_ETH_CONTRACT = "0xd05339f9ea5ab9d9f03b9d57f671d2abd1f55c82";
|
||||
// The fraudulent Transfer event it emitted.
|
||||
const FAKE_ETH_TRANSFER_HASH =
|
||||
@@ -62,7 +63,8 @@ const LEGIT_RECIPIENT = "0xc3c693ae04bad5f13c45885c1e85a9557798f37e";
|
||||
// The scam address the fake token transfer pointed at ("0xC3C0" vs "0xC3c6").
|
||||
const TOKEN_SCAM_LOOKALIKE = "0xc3c0aea127c575b9ffd03bf11c6a878e8979c37f";
|
||||
|
||||
// The second wave: a real 1 gwei native transfer (README.md:800-808).
|
||||
// The second wave: a real 1 gwei native transfer. See "Dust transactions" in
|
||||
// README, Scam protection.
|
||||
const DUST_TX_HASH =
|
||||
"0x2708ebddfb9b5fa3f7a89d3ea398ef9fd8771b83ed861ecb7c21cd55d18edc74";
|
||||
const DUST_SENDER_LOOKALIKE = "0xc3c6b3b4402bd78a9582ab6b00e747769344f37e";
|
||||
@@ -79,7 +81,7 @@ const NOVEL_SPAM_SYMBOL = "SPAMTKN";
|
||||
// An ordinary counterparty for legitimate-transaction fixtures.
|
||||
const ORDINARY_PEER = "0x5aa0f9f1e0a1d0e0e5c1e7ce3b7dbbe9c19f0a11";
|
||||
|
||||
// The documented default settings (README.md:810-814, state.js:24-27).
|
||||
// The documented default settings (README, Settings; state.js:24-27).
|
||||
const DEFAULT_FILTERS = {
|
||||
hideSpoofedSymbols: true,
|
||||
hideLowHolderTokens: true,
|
||||
@@ -220,7 +222,7 @@ describe("token list assumptions the fixtures rely on", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("the real attacks documented in README.md:730-814", () => {
|
||||
describe("the real attacks", () => {
|
||||
test('the fake "Ethereum"/"ETH" token transfer is filtered by default', () => {
|
||||
const attack = fakeEthTokenTransfer();
|
||||
const result = filterTransactions([attack], filters());
|
||||
|
||||
Reference in New Issue
Block a user