Work / dogear
Browser automation written one step at a time, in a side panel, running on pages whose Content-Security-Policy blocks eval
At a glance
Why this stamp Public MIT repo with a downloadable v0.1.0 release · The CSP claim is re-runnable — 8 policies × 2 execution worlds × 4 probes
- Role
- Author
- Period
- Since July 2026
- Stack
- Preact · htm · CodeMirror 6 · WXT · TypeScript · Vitest · Bun
- Limits
- 7 stated below
- Proof
- Repositorygithub.com
- Release v0.1.0github.com
Problem
Writing automation for a real website is mostly debugging someone else’s page. The usual loop is edit a script, reload, watch it fail at step four, and start again from step one.
Four things in a real browser work against a step-at-a-time tool. A strict Content-Security-Policy kills eval, new Function and blob: scripts, which is how most “run my script on this page” tools execute. Navigation throws away whatever a running script held in memory. A native-messaging port dies outright when one message crosses Chromium’s size limit. And a background tab is throttled, so a script that waits by sleeping behaves differently from the one you tested in the foreground.
dogear continues the work published earlier as nb-steprunner — same author, same engine, rewritten as an extension. That earlier repository is private; this one is public, so the claims below can be checked.
Decisions
Execute through chrome.userScripts, not eval or a blob
Chosen: steps run through the chrome.userScripts API, which is not subject to the page’s own CSP. Not chosen: eval, new Function, or injecting a blob: script — the approach nb-steprunner used, and the one that dies on sites with a strict policy.
This is the claim most worth distrusting, so it is measured rather than asserted: a local fixture is served under eight different Content-Security-Policy headers, four probes are injected into each of the two execution worlds, and the full 64-cell matrix is written to a findings file. Anyone can re-run it with one command.
Steps are real ES modules, not pasted text
Chosen: each step is an ordinary .js file that can import shared helpers from its own notebook, linked before execution. Not chosen: concatenating source into a generated bundle. The consequence is practical — a stack trace points at your file and line, such as steps/03-report.js:5:12, instead of at a bundle nobody can read.
Split native messages on the encoded envelope, at a measured boundary
Chromium kills a native messaging port when a single message exceeds 1,048,576 bytes. The boundary was measured, not guessed: 1,048,576 passes, 1,048,640 kills the port. Outgoing messages are therefore split, and the size is measured on the encoded envelope rather than the raw fragment, so escaping cannot push a frame over the line. A quote-dense payload splits into 7 frames, the largest 999,989 bytes, reassembled and verified byte-for-byte by SHA-256 through a real host process.
Results
- Published as a public MIT repository with a downloadable v0.1.0 release.
- Steps run on pages that block
eval, verified across a 64-cell matrix (8 CSP policies × 2 execution worlds × 4 probes) that ships with the repo and can be re-run. - A step can commit a checkpoint, let the page navigate, and resume with its data intact. Cross-document reach, including cross-domain iframes, was measured rather than hoped for.
- Native messaging survives the 1 MiB wall: 7 frames for a quote-dense payload, largest 999,989 bytes, reassembled and SHA-256 verified end to end.
- Background-tab throttling was measured over 15 runs per cell: a step built from
sleep()runs about 14.7× slower in a background tab, while a step that waits for the DOM runs about 1.35× slower. That single number is why the helper API pushes you toward waiting for elements instead of sleeping. - Seventeen helpers are available inside a step.
Evidence
The code is public under the MIT license in the dogear repository, and the packaged extension is on the releases page.
The CSP measurement is reproducible from the repo: one command serves the fixture under all eight policies, injects the probes, and writes the 64-cell matrix to a findings file. The helper reference is in the repository’s SINTAKS.md, written in Indonesian.



Limits
- Chrome and Chromium only. A Firefox build compiles, but Firefox does not expose
chrome.userScripts, which is the entire execution path, so nothing runs there yet. - The “Allow user scripts” toggle is off by default since Chrome 138 and there is no API to turn it on. Every install needs that one human click.
- Keyboard events are synthetic: they reach the page’s own listeners, but carry
isTrusted: false, soCtrl+Cdoes not touch the clipboard andTabdoes not move focus. - A synchronous
while (true) {}cannot be stopped; cancellation happens atawaitpoints. - Notebooks live in the extension’s private OPFS storage. They are not files you can open in your editor, and they do not sync between machines.
- No scheduler, no queue, no dashboard.
- No claims about evading bot detection. It automates a browser you are already logged into, and it is not designed to look like anything else.
