Errors: a page the browser will not script and a non-JSON answer show raw JavaScript errors, not translated sentences #19

Closed
opened 2026-10-01 10:47:16 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo-chromium at 7998de372, by reading how the popup, the outbox and Test turn failures into sentences, and by running readResponse() under Node against a 200 HTML response.

Two failures reach the person as the JavaScript engine's own English text instead of one of the extension's translated sentences. When the browser refuses to inject the reading script, the popup prints the browser's developer message. When the configured address answers a request with 2xx but not JSON, readResponse() returns null, its caller reads a property of null, and the resulting TypeError becomes what Test says and what a waiting capture stores as its problem. The person is told nothing they can act on, in no language but English.

  • Where:
    • src/lib/browser.js:41-48, readCurrentTab(): await ext.scripting.executeScript(...) has no catch; only an empty result becomes Refusal("unreadable").
    • src/lib/api.js:71-82, readResponse(): a body that fails to parse becomes null, and if (response.ok) { return body; } hands that null back as success.
    • src/lib/outbox.js:62-66, deliver(): reviewUrl: result.review_url with result === null.
    • src/options.js:106-108, the Test handler: (me.scopes || []) with me === null.
    • src/lib/i18n.js:55-63, explain(): anything without a key is shown as its message.
  • What happens:
    1. On an https: page the browser will not let extensions script (the browser's own store, a host blocked by policy, a tab showing an error page), executeScript rejects. src/popup.js:112-115 catches it and explain() prints the browser's message, such as "Cannot access contents of url …", although unreadable ("The page could not be read. Some pages block extensions.") exists in all 40 catalogues for exactly this case.
    2. When the instance address answers /api/v1/me or /api/v1/captures with 200 and an HTML page (a sign-in page served on the same origin by a proxy in front of Postulo, a captive portal, or another site at a mistyped address), readResponse() returns null. Test then says "Cannot read properties of null (reading 'scopes')". Delivery throws "Cannot read properties of null (reading 'review_url')"; problemOf() stores it as { message }, kindOfFailure() counts it as later (no status), and every waiting capture keeps that sentence as its reason, shown again on the captures page (src/captures.js:89) after each retry. Running readResponse() under Node on new Response("<html>…", { status: 200 }) returns null, and the property read gives exactly that message and kind.
  • Why it matters: the person whose capture will not go cannot tell that the address or a proxy is at fault, and the message ignores the browser's language, which every other failure respects. The Firefox extension is built from this source (postulo-firefox/source.json), so it inherits both.
  • Fix:
    • In readCurrentTab(), wrap executeScript in try/catch and throw Refusal("unreadable"); log the original message with the error so it is not lost.
    • In readResponse(), treat a 2xx whose body did not parse or is not a JSON object as a refusal with a new key (for example notPostulo: "That address answered, but not as Postulo. Check the address, and any sign-in page in front of it."), thrown without a status so kindOfFailure() keeps the capture waiting. The key goes into all 40 _locales at once, as test/i18n.test.mjs requires.

Done when

  • test/api.test.mjs asserts that readResponse() on a 200 response with an HTML body rejects with the new refusal and no status, and that kindOfFailure() calls it later.
  • test/outbox.test.mjs asserts that a delivery answered 200 with HTML leaves the record waiting with { key: "notPostulo" } as its problem, not a message.
  • A test with a stubbed ext.scripting.executeScript that rejects asserts that readCurrentTab() throws Refusal("unreadable").
Found on 2026-09-29 in a read-only review of postulo-chromium at 7998de372, by reading how the popup, the outbox and *Test* turn failures into sentences, and by running `readResponse()` under Node against a `200` HTML response. Two failures reach the person as the JavaScript engine's own English text instead of one of the extension's translated sentences. When the browser refuses to inject the reading script, the popup prints the browser's developer message. When the configured address answers a request with `2xx` but not JSON, `readResponse()` returns `null`, its caller reads a property of `null`, and the resulting `TypeError` becomes what *Test* says and what a waiting capture stores as its problem. The person is told nothing they can act on, in no language but English. - **Where:** - `src/lib/browser.js:41-48`, `readCurrentTab()`: `await ext.scripting.executeScript(...)` has no `catch`; only an empty result becomes `Refusal("unreadable")`. - `src/lib/api.js:71-82`, `readResponse()`: a body that fails to parse becomes `null`, and `if (response.ok) { return body; }` hands that `null` back as success. - `src/lib/outbox.js:62-66`, `deliver()`: `reviewUrl: result.review_url` with `result === null`. - `src/options.js:106-108`, the *Test* handler: `(me.scopes || [])` with `me === null`. - `src/lib/i18n.js:55-63`, `explain()`: anything without a `key` is shown as its `message`. - **What happens:** 1. On an `https:` page the browser will not let extensions script (the browser's own store, a host blocked by policy, a tab showing an error page), `executeScript` rejects. `src/popup.js:112-115` catches it and `explain()` prints the browser's message, such as "Cannot access contents of url …", although `unreadable` ("The page could not be read. Some pages block extensions.") exists in all 40 catalogues for exactly this case. 2. When the instance address answers `/api/v1/me` or `/api/v1/captures` with `200` and an HTML page (a sign-in page served on the same origin by a proxy in front of Postulo, a captive portal, or another site at a mistyped address), `readResponse()` returns `null`. *Test* then says "Cannot read properties of null (reading 'scopes')". Delivery throws "Cannot read properties of null (reading 'review_url')"; `problemOf()` stores it as `{ message }`, `kindOfFailure()` counts it as `later` (no status), and every waiting capture keeps that sentence as its reason, shown again on the captures page (`src/captures.js:89`) after each retry. Running `readResponse()` under Node on `new Response("<html>…", { status: 200 })` returns `null`, and the property read gives exactly that message and kind. - **Why it matters:** the person whose capture will not go cannot tell that the address or a proxy is at fault, and the message ignores the browser's language, which every other failure respects. The Firefox extension is built from this source (`postulo-firefox/source.json`), so it inherits both. - **Fix:** - In `readCurrentTab()`, wrap `executeScript` in `try`/`catch` and throw `Refusal("unreadable")`; log the original message with the error so it is not lost. - In `readResponse()`, treat a `2xx` whose body did not parse or is not a JSON object as a refusal with a new key (for example `notPostulo`: "That address answered, but not as Postulo. Check the address, and any sign-in page in front of it."), thrown without a `status` so `kindOfFailure()` keeps the capture waiting. The key goes into all 40 `_locales` at once, as `test/i18n.test.mjs` requires. ## Done when - `test/api.test.mjs` asserts that `readResponse()` on a `200` response with an HTML body rejects with the new refusal and no `status`, and that `kindOfFailure()` calls it `later`. - `test/outbox.test.mjs` asserts that a delivery answered `200` with HTML leaves the record waiting with `{ key: "notPostulo" }` as its problem, not a `message`. - A test with a stubbed `ext.scripting.executeScript` that rejects asserts that `readCurrentTab()` throws `Refusal("unreadable")`.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo-chromium#19
No description provided.