Skip to content

Production native filter fast path

Implemented in 1720a274cc94e31fb7777bde184ca0b9621b4601. Measurements below were recorded on 16 September 2026. The native backend is now used by the production renderer, not just the benchmark. The public API and surface eligibility rules are unchanged.

Over-budget follow-up: the controlled attribution report now separates the earlier release/draft gap from memory-budget overhead. The corrected filter descriptor makes legacy blur actually execute; descriptor-only and direct-legacy controls reproduce the same pixels. The historical numbers below are retained, not reclassified as an isolated cost of budget rejection.

Rendering policy

Eligible complete layers use Canvas filters only after a cached, per-Document probe observes actual blur and a correctly colored semitransparent shadow. The probe checks for the API before assigning to it, preventing a JavaScript expando from masquerading as support. Probe canvases are released, and only a boolean is retained.

Filtering happens at alpha 1, with all lengths multiplied by capture scale. Layer opacity is applied in a separate canvas pass after filtering and shadow merge. Opacity-only layers skip the probe. A recoverable native allocation, assignment or draw failure retries the existing SVG path. Failed temporary canvases are released; caller-owned source canvases remain intact. Cancellation is propagated, not converted to fallback.

The native path does not serialize the source or create a data image. It therefore avoids the PNG/SVG round trip and can work under a policy that blocks SVG data images. Canvas-origin taint is not cleared. The existing raster reservation and unsupported-subtree legacy fallback remain unchanged. SVG decoding, timeout, cancellation and blocked-data-image fallback are still tested explicitly.

Verification at 1720a27

Core CI run 35088872841 passed build, bundle-size checks, lint, 1,234 unit tests in 93 files, Chrome/Firefox/Safari browser tests, Chromium/WebKit pixel and allocation regressions, and the native macOS WKWebView pixel probe. CodeQL also passed.

Native/benchmark run 35088872866 completed the independent native/SVG matrix and production benchmarks in Chromium, Firefox and Playwright WebKit. Each engine checks 48 combinations: six filters, four opacity values and scales 1/2. It also checks unavailable/no-op filter implementations, native failure recovery, cancellation, resource cleanup and real CSP behavior. WebKit fallback-vs-SVG comparisons do not prove a native WebKit implementation.

EngineVersionNative selectedCasesMax RGB MAE / 255Max alpha MAE / 255
chromium148.0.7778.96yes480.0000000.000000
firefox150.0.2yes480.0000000.000000
webkit26.4no, SVG fallback480.0000000.000000

The matrix uses a fixed 2/255 mean-error bound, not a blanket claim of pixel equivalence. The existing independent DOM-reference regressions retain their original thresholds and explicit native-WebKit nested-shadow exception.

Same-run performance measurements

CI checkout: b5e68ee379839531c3cd2503ada5ebf7be992af2 (test merge of PR head 1720a27). Environment: linux 6.17.0-1022-azure, x64, AMD EPYC 7763 64-Core Processor, 4 exposed logical CPUs, Node v24.20.0.

Each row has nine measured samples after two excluded warmups and a separately recorded first observation. Backend order rotates. Total includes full output readback; source generation is excluded. These measurements compare the production native dispatcher with the explicit production SVG helper on the same input in the same run. They do not compare the current renderer with an incorrectly rendered release capture.

2000 × 2000 surface (milliseconds)

EngineSource / effectSVG median / p95Native median / p95SVG/native median ratio
chromiumflat / blur91.2 / 113.445.2 / 45.62.02×
chromiumflat / combined136.2 / 146.7101.2 / 105.11.35×
chromiumtextured / blur890.2 / 947.444.6 / 47.819.96×
chromiumtextured / combined924.1 / 1003.2100.6 / 108.89.19×
firefoxflat / blur123.0 / 124.070.0 / 73.01.76×
firefoxflat / combined157.0 / 189.0141.0 / 176.01.11×
firefoxtextured / blur3677.0 / 3686.069.0 / 74.053.29×
firefoxtextured / combined3618.0 / 3671.0138.0 / 146.026.22×
webkitflat / blur409.0 / 414.0unsupported
webkitflat / combined459.0 / 465.0unsupported
webkittextured / blur2011.0 / 2157.0unsupported
webkittextured / combined2108.0 / 2144.0unsupported

For noisy combined effects, production native filtering was about 9.2× faster than explicit SVG in Chromium and 26.2× in Firefox. This is the cost of filtering a prepared surface, not a whole-page speedup. With nine samples, nearest-rank p95 is the maximum, not a stable estimate of tail latency. Flat and high-entropy noise are controlled fixtures; noise is not a claim that every photograph behaves this way.

Full DOM capture (milliseconds)

The published release and current draft capture identical DOM but do not always render equivalent pixels. In particular, the combined-effect overlap is checked against alpha 128 in the draft. Do not treat the following ratios as isolated backend overhead or compare runs on different hosts as a controlled before/after experiment.

EngineScenarioRelease medianDraft medianDraft backend
chromiumcontrol-51256.156.5legacy/no-filter
chromiumcombined-51256.562.6native-surface
chromiumcontrol-200066.166.0legacy/no-filter
chromiumcombined-200068.8157.4native-surface
chromiumsparse-5072.2149.6native-surface
chromiumnested-1257.574.2native-surface
chromiumover-budget79.0508.9legacy-fallback
firefoxcontrol-51264.064.0legacy/no-filter
firefoxcombined-51266.072.0native-surface
firefoxcontrol-200077.078.0legacy/no-filter
firefoxcombined-2000165.0214.0native-surface
firefoxsparse-5096.0199.0native-surface
firefoxnested-1265.086.0native-surface
firefoxover-budget279.0521.0legacy-fallback
webkitcontrol-51260.060.0legacy/no-filter
webkitcombined-51285.070.0svg-surface
webkitcontrol-200076.073.0legacy/no-filter
webkitcombined-2000869.0272.0svg-surface
webkitsparse-50145.0254.0svg-surface
webkitnested-1259.0137.0svg-surface
webkitover-budget1735.01735.0legacy-fallback

Remaining limits

The SVG fallback still pays its encoding/decoding cost on runtimes without working native filters. This change does not fix that cost, nor does it change over-budget legacy rendering. In the historical over-budget Chromium case the draft took 508.9 ms versus 79.0 ms for the release, despite zero SVG encodes. The subsequent five-variant attribution isolates the descriptor fixes: the release was not applying the same blur, and normal rejection matches a direct-legacy control. This explains the reproduction; it does not make legacy filtering fast or correct its group-opacity limitation. CPU scheduling, browser implementation and image entropy affect results. No peak browser-process-memory measurement or universal latency guarantee is claimed.

Performance measurements here are headless Linux Chromium/Firefox/Playwright WebKit. Passing macOS Safari and WKWebView correctness checks is not a macOS performance measurement. Representative real devices and embedded webview hosts still need timing measurements.

Reproduce and inspect

sh
pnpm install --frozen-lockfile
pnpm build
pnpm exec playwright install --with-deps chromium firefox webkit
node scripts/filter-native-regressions.mjs
BENCH_ENGINES=chromium,firefox,webkit node scripts/filter-performance.mjs

The manual page is /tests/manual/filter-performance.html; it uses the same harness, supports cancellation and JSON import/export, and labels historical reports separately. The older measurements remain in filter-performance.md.

Raw results.json, summary.md and native-regressions.json are in the filter-performance artifact of run 35088872866 (artifact 10444095148, ZIP SHA-256 2082921c3064415bc616833541df30dd5eeebd9fc4fbc47af48dfcef57345ad9). The production report includes implementation: production-native-with-svg-fallback and the source/bundle hashes below.

json
{
  "src/render/canvas/filter-surface.ts": "dfc272bff180780b6399aa4bac9b6ab164a3f4b5986a9dd2f59af4f373169f4c",
  "tests/manual/filter-performance.js": "1fb06e3f0a7fd612c313a7d624b08404241cbcea3a4adcbd3e4a0ab4fec2d08d",
  "dist/html2canvas-pro.esm.js": "ffb6a2027baba83c7cb3d3f4796315a334848b88e855fee5d832b46a3a981d3a",
  "build/html2canvas-pro-baseline.esm.js": "d335004b5269e8ae10c139a105df141a66646960c7b1b5f5def18a8eec80c1d8"
}

Updating repository demo sources does not redeploy the existing hosted chatgpt.site URL. Upstream PR-description updates remain unavailable through this integration; this report is committed to the head branch.