Repository navigation
Commit 665059b
fix(service-print): a server print works under the console's default CSP (the ready wait passes a function, not a string) (#26783)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X5qLZ9SK4dTsHt1W8ph2VBe)_
Fixes #26664
Clause-②: no
Metadata-protocol: none
## 维护者速读
- **改了什么**:服务端打印的 Chromium 驱动等待打印页就绪时,改为传一个函数谓词,不再传字符串。
- **为什么改**:默认部署下控制台 CSP 不含 `'unsafe-eval'`,字符串谓词每次轮询都要在页面里 eval,第二次轮询即抛
EvalError,所有服务端打印都失败。
- **风险与回滚**:只动 `chromium-driver.ts` 一处等待;就绪含义、超时、轮询间隔不变,控制台 CSP
不变。回滚即还原该文件。
-
**先看**:`packages/services/service-print/src/chromium-driver.ts`(`printReady`
与 `waitForFunction` 那一行);`chromium-driver.unit.test.ts` 末尾新增的两条。
- **两分钟验证**:`OS_TEST_PRINT_CHROMIUM_PATH=/opt/pw-browsers/chromium pnpm
--filter @objectstack/service-print exec vitest run
src/chromium-driver.test.ts`,应见 CSP 那条在两种放置下都通过(main 上是 EvalError)。
- **需要你做的**:无,席位落地。
## What changes
`packages/services/service-print/src/chromium-driver.ts` waited for the
print page with `page.waitForFunction(PRINT_READY_PREDICATE, …)`, where
the predicate was a **string**. It is now a function,
`printReady(stablePolls)`, passed as `page.waitForFunction(printReady,
STABLE_POLLS, { timeout: remaining(), polling: POLL_INTERVAL_MS })`.
**Which evaluation path, and why `script-src` does not block it** (read
in playwright-core 1.63.0, `lib/coreBundle.js`
`waitForFunctionExpression`, and measured below). The wait installs a
poller through the injected script's `evaluateHandle`, a DevTools
`Runtime.callFunctionOn`. The poller's `predicate()` runs
`globalThis.eval(expression)`:
- with a **string** (`isFunction: false`), on **every** poll. The first
poll runs synchronously inside the DevTools call, where the inspector
permits code generation; every later poll runs from a page `setTimeout`,
where the page's CSP governs, and without `'unsafe-eval'` it throws
EvalError. The predicate needs three stable polls, so the second poll
always fails.
- with a **function** (`isFunction: true`), **once**: the first,
synchronous poll caches the evaluated function (`evaledExpression`), and
every later poll calls that cached function. No string is evaluated from
a page timer, so the CSP has nothing to refuse.
The readiness meaning is unchanged: the same three conditions (a
`[data-print-page]` root, `window.__objectui` present, `idle === true`),
the same in-page run counter (`__objectstackPrintStable`), the same
`STABLE_POLLS` (3), the same 100 ms polling and the same share of the
one render budget. The page receives the function's text only, so the
poll count it used to read from a template literal now arrives as its
argument. The built output was read: tsup emits `printReady` as plain
ES2020 in both `dist/index.js` and `dist/index.cjs`, with no injected
helper names.
The console CSP (`packages/cli/src/utils/console.ts`) is not touched.
`page.evaluate(FONTS_READY)` stays a string: it is evaluated
synchronously inside its DevTools call, and the CSP leg below shows it
passing.
A `patch` changeset on `@objectstack/service-print`.
## Tests
- `chromium-driver.unit.test.ts` (runs in CI, no browser): the staged
page's `waitForFunction` must receive a **function**, `STABLE_POLLS` as
its argument, `polling: 100` and a timeout inside the budget. A second
case runs the received function against a staged `document` /
`__objectui` and pins the meaning: false with no console, false when
idle with no root, false with the root while busy, then exactly
`STABLE_POLLS` idle polls to true, and a busy poll resets the run.
- `chromium-driver.test.ts` (real Chromium, both placements): the
fixture now also serves the print page under the console's default
policy (`script-src 'self' 'nonce-…' 'wasm-unsafe-eval'`, the script
carrying the nonce), and a new case per placement renders it.
- The render dogfood
(`packages/qa/dogfood/test/print-service-render.dogfood.test.ts`, which
carries the named-page leg from #26662) is unchanged; it is the
end-to-end reading below.
## Acceptance notes
- CI skips both real-Chromium files (`describe.skipIf(!CHROMIUM)` /
`!CHROMIUM || !CONSOLE_BUILT`): Test Core has no Chromium and no console
bundle. The unit test is the guard CI runs. Turning the render tests on
in CI is a CI-provisioning question and is not done here.
- `page.evaluate(FONTS_READY)` and the predicate's first poll rely on
DevTools-initiated evaluation being exempt from the page's
`'unsafe-eval'` check, which is Chromium's behaviour today and is what
playwright-core's own function-predicate path relies on.
## Verification
All readings at head `dfd3fc087`, with `OS_PAGE_REACT_ENABLED` (and
legacy `OS_PAGE_REACT`) unset, Chromium `/opt/pw-browsers/chromium`,
console bundle from `pnpm console:build`.
<details><summary>Red on main's driver (base 1685a06):
EvalError</summary>
`chromium-driver.ts` byte-equal to `origin/main`, the new tests
committed:
- `vitest run src/chromium-driver.unit.test.ts
src/chromium-driver.test.ts`: Tests 4 failed, 16 passed (20). Both unit
cases fail; the CSP case fails on both placements with `The print engine
failed at the local executable /opt/pw-browsers/chromium:
page.waitForFunction: EvalError: Evaluating a string as JavaScript
violates the following Content Security Policy directive because
'unsafe-eval' is not an allowed source of script: script-src 'self'
'nonce-print-fixture-nonce' 'wasm-unsafe-eval'` (and the same at the
remote endpoint).
- Dogfood `print-service-render.dogfood.test.ts` +
`print-service-named-page.dogfood.test.ts`: Tests 6 failed, 6 passed
(12). All 6 render legs (invoice, named print page,
revoke-after-failure; local and remote) fail with the EvalError under
the console's real nonce policy. The named-page file uses a stand-in
driver and passes either way.
</details>
<details><summary>Green with the fix</summary>
- Same two driver files: Test Files 2 passed, Tests 20 passed (20).
- Same two dogfood files: Test Files 2 passed, Tests 12 passed (12).
- `pnpm --filter @objectstack/service-print build`, `typecheck`: exit 0.
`test` (with Chromium): Test Files 8 passed, Tests 118 passed (118).
- `pnpm exec eslint --no-inline-config packages/services/service-print`:
exit 0.
</details>
<details><summary>Gates</summary>
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `dfd3fc087` (4 paths, +129 / -13): 59
commands, plus the 4 roster gates it marks as keeping a roster under one
of these paths (`check:authz-resolver`, `check:changeset-fixed`,
`check:error-code-casing`, `check:filter-alias-parity`). Each run alone,
exit code captured before any pipe:
- 61 of 63 exit 0 on the first run.
- `node scripts/check-plugin-teardown-shape.mjs --self-test`: exit 3 on
the shallow clone (its pinned fixture commit `621a4876` was not in the
object store). After `git fetch origin
621a487`: exit 0, 48 cases pass.
- `pnpm check:dual-build-cjs-loads`: exit 3, PREREQUISITE NOT MET (7
untouched packages had no `dist/`). After building those 7: exit 0, 112
require entry points across 69 packages load.
`dispatch-gates --ran` with every exit code recorded: 59 derived, 59
run, 0 NOT-MEASURED, 0 UNRUN (exit 0).
`origin/main` moved 11 commits since the base, none touching
`packages/services/service-print`, the print dogfood or
`packages/cli/src/utils/console.ts`; `git merge-tree` of head with it is
clean. Every family CI owns (Lint & Repo Gates, Test Core, the
type-check lanes) is read from this PR's checks.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [ ] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5kpFFUuQY5FA7FCdwY)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 607d3bb commit 665059b
4 files changed
Lines changed: 129 additions & 13 deletions
File tree
- .changeset
- packages/services/service-print/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
42 | 51 | | |
43 | 52 | | |
44 | 53 | | |
| |||
82 | 91 | | |
83 | 92 | | |
84 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
85 | 99 | | |
86 | | - | |
| 100 | + | |
87 | 101 | | |
88 | 102 | | |
89 | 103 | | |
90 | | - | |
| 104 | + | |
91 | 105 | | |
92 | 106 | | |
93 | 107 | | |
| |||
113 | 127 | | |
114 | 128 | | |
115 | 129 | | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
116 | 135 | | |
117 | 136 | | |
118 | 137 | | |
| |||
293 | 312 | | |
294 | 313 | | |
295 | 314 | | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
296 | 326 | | |
297 | 327 | | |
298 | 328 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | | - | |
145 | | - | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
146 | 155 | | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
153 | 167 | | |
154 | 168 | | |
155 | 169 | | |
| |||
300 | 314 | | |
301 | 315 | | |
302 | 316 | | |
303 | | - | |
| 317 | + | |
304 | 318 | | |
305 | 319 | | |
306 | 320 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
202 | 202 | | |
203 | 203 | | |
204 | 204 | | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
0 commit comments