fix(tauri): correct malformed fixture CSP (root cause) + harden macOS DirectEval against WebKit code-4 (#540) - #552
Conversation
|
Standing release PR: #456 · 15 packages queued · open 34d 12h 8m · ✅ ready to merge Release Preview — 17 packages
These changes will be added to the release PR (#456) when merged: Changelog@wdio/dioxus-bridge 1.0.0-next.3 → 1.0.0-next.4
@wdio/dioxus-service 1.0.0-next.3 → 1.0.0-next.4
wdio-dioxus-driver 1.0.0-next.3 → 1.0.0-next.4
wdio-dioxus-embedded-driver 1.0.0-next.3 → 1.0.0-next.4
@wdio/tauri-plugin 1.2.0 → 1.2.1
@wdio/tauri-service 1.2.0 → 1.2.1
tauri-plugin-wdio-webdriver 1.2.0 → 1.2.1
@wdio/flutter-service 1.0.0-next.1 → 1.0.0-next.2
wdio_flutter N/A → 0.1.1
@wdio/electrobun-service 0.1.0 → 0.1.1
@wdio/electron-service 10.1.0 → 10.1.1
@wdio/native-cdp-bridge 1.0.0 → 1.0.1
@wdio/native-core 1.0.0 → 1.0.1
@wdio/native-mobile-core 1.0.0 → 1.0.1
@wdio/native-spy 1.1.0 → 1.1.1
@wdio/native-types 2.4.0 → 2.4.1
@wdio/native-utils 2.5.0 → 2.5.1
@wdio/react-native-service 1.0.0-next.0 → 1.0.0-next.1
After merge — predicted release
Updated automatically by ReleaseKit |
|
| Filename | Overview |
|---|---|
| packages/tauri-plugin-webdriver/src/platform/macos.rs | Adds the macOS WebKit message handler and changes DirectEval to await out-of-band results. |
| packages/tauri-plugin-webdriver/src/eval_channel.rs | Introduces the synchronized, keyed oneshot registry used to correlate DirectEval responses. |
| packages/tauri-plugin-webdriver/src/lib.rs | Registers the shared macOS evaluation-result registry during plugin setup. |
| packages/tauri-plugin-webdriver/Cargo.toml | Enables the objc2 WebKit features required for configuration and script-message handling. |
| fixtures/e2e-apps/tauri/src-tauri/tauri.conf.json | Corrects custom-protocol CSP scheme sources and permits the IPC localhost endpoint. |
| fixtures/package-tests/tauri-app/src-tauri/tauri.conf.json | Applies the same corrected IPC CSP configuration to the package-test fixture. |
| .github/workflows/_ci-e2e-tauri-all-providers.reusable.yml | Replaces process sampling with WebKit log streaming and broader crash-report collection. |
Sequence Diagram
sequenceDiagram
participant Client as DirectEval client
participant Executor as macOS executor
participant WebKit as WKWebView
participant Handler as wdioEvalResult handler
participant Registry as EvalResultRegistry
Client->>Executor: POST /wdio/eval
Executor->>Registry: register(id)
Executor->>WebKit: callAsyncJavaScript(wrapper)
WebKit->>Handler: "postMessage({id, result})"
Handler->>Registry: complete(id, result)
Registry-->>Executor: resolve oneshot
Executor-->>Client: evaluation response
Reviews (11): Last reviewed commit: "fix(tauri-fixture): correct malformed CS..." | Re-trigger Greptile
…4 hang #540 root cause (confirmed via #550's sampler): on macOS 26.4 the occluded/ background CI app's WebContent process is throttled/suspended by WebKit — the app sends RunJavaScriptInFrameInScriptWorld and never gets a reply, so in-webview evaluateJavaScript wedges and the :4445 WebDriver server stops responding. The main thread just idles in the Cocoa run loop; it is neither deadlocked nor crashed. The earlier App-Nap approach was the wrong lever (host App Nap != WebKit's per-page WebContent throttling). Instead, disable window-occlusion detection on the WebDriver webview so its page is always treated as visible and its WebContent process is never throttled. Private WKWebView SPI, guarded via respondsToSelector; logs whether it applied so CI confirms it. Refs #540. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
1710f7c to
b7a2803
Compare
…olds The occlusion-detection fix greened all macOS embedded legs on the 26.4 runner with no captured hang (the sampler showed a healthy app, zero stalled-eval IPC), so the background process sampler, DiagnosticReports collection, and the standalone backend/frontend log capture added in #550 are no longer needed. Revert them. Refs #540. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…on mitigation (#540) The occlusion-disable fix alone did not hold — run 2 hung identically (Script execution timed out). The earlier diagnostics had a blind spot: they sampled/collected only the app process, never the WebContent/GPU helpers, so 'occluded-page suspension' was inferred, not proven — the hang is equally consistent with a WebContent-process CRASH on the headless 26.4 runner. Discriminate + mitigate in one run: - macos.rs: also activate the app (NSApplication::activate) alongside the occlusion-detection disable, in case the page is treated as background rather than occluded. Both guarded/benign with a real display. - workflow: passively stream the WebKit unified log during the embedded step (no that could keep the process warm) and collect crash reports for ALL WebKit helpers (WebContent/GPU/Networking), not just the app. The log says outright whether a web process crashed or was suspended. - api.spec.ts: restore backend/frontend log capture so the plugin's occlusion/activation tracing lands in the artifacts. Refs #540. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…root crash reports The prior discriminator run captured 1.4MB of WebKit unified log but the upload glob is e2e/logs/**/*.log and the file was webkit-log.txt, so it was dropped. Write it as .log. Also scan /Library/Logs/DiagnosticReports (where WebContent/GPU helper crashes land) in addition to $HOME, via sudo -n. Both mitigations (occlusion-disable + activation) are now in the app and it still hung, which argues against page suspension and toward a WebContent crash or a genuine 26.4 WebKit IPC bug — this run retrieves the log that says which. Refs #540. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…hang #540 root cause (from the WebKit unified log on a hung run): NOT WebContent suspension or a crash — the log shows WebContent healthy, foregrounded, and running runJavaScript ~60x/sec for the full 30s (1594 evals) = this poll loop spinning. #549's fire-and-forget + 15ms poll hammers the webview, which starves Tauri's own core.invoke response eval (delivered by eval-ing into the SAME webview) on a slow/headless runner, so the fired script's awaited invoke never resolves and the loop times out. Explains why only the DirectEval legs (standalone/deeplink/package) fail while the session-path legs (standard/window/multiremote) pass, why it's intermittent (a race), and why it doesn't repro on a fast local machine. Poll gently: wait 150ms before the first read and between reads, leaving the webview free for Tauri's IPC-response eval. Reverts the disproven occlusion-detection / app-activation mitigations. A durable follow-up (deliver the result via a dedicated Rust command + oneshot channel, eliminating result-polling entirely) is tracked separately. Refs #540. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
… session path)
Definitive root cause (WebKit unified log on a hung run + the passing legs):
core.invoke('get_platform_info') works on macos-26 (26.4) via the session
path's callAsyncJavaScript, but hangs via #549's DirectEval fire-and-forget.
WebContent was healthy/foregrounded the whole time — not suspended, not
crashed — running the 15ms poll ~60x/sec for the full 30s. callAsyncJavaScript
keeps a WebKit run-loop activity alive that pumps Tauri's core.invoke response
IPC; fire-and-forget left nothing driving it, so the invoke never resolved
(independent of poll rate: 1594 @15ms and 268 @150MS both hung).
Rework execute_direct_eval to use callAsyncJavaScript — the same mechanism the
session execute_async_script path uses and that works on 26.4 — with a single
minimal wrap bridging the pre-wrapped callback-style script to the promise
contract. #549 abandoned callAsyncJavaScript only because it had routed this
script through execute_async_script's extra (double) wrap, which tripped the
'completion handler no longer reachable' reclaim; a single clean wrap, like the
session path, does not. Deletes the poll loop; leaves execute_async_script
untouched.
Refs #540.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…oid WKError 4 The callAsyncJavaScript bridge still hit 'Completion handler no longer reachable' (code 4) because it used 'return await new Promise(...)'. The working session execute_async_script path uses 'return new Promise(...)' — handing the promise object straight to callAsyncJavaScript to await natively. The extra await hop makes WebKit 26.4 lose the completion handler's reachability. Match the session form exactly. Refs #540. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…e channel
Root cause (WebKit unified log on hung 26.4 runs): the DirectEval result
was taken from callAsyncJavaScript's completion handler, which macOS 26.4's
WebKit reclaims intermittently ('Completion handler for function call is no
longer reachable'). callAsyncJavaScript is still needed to keep the run loop
pumping so the app's own core.invoke IPC resolves on the headless runner —
but the result must not depend on that handler.
Deliver the result out-of-band, mirroring the Windows AsyncScriptState
native-handler path: the eval wrapper posts { id, result } to a new
'wdioEvalResult' WKScriptMessageHandler, which completes a oneshot the
executor awaits (keyed by id via EvalResultRegistry). A completion-handler
reclaim is now harmless — the result already arrived over the channel. The
poll loop is gone.
macOS-only: other platforms use the trait-default execute_direct_eval
(execute_async_script) unchanged. Diagnostics retained for this validation
run; to be removed once green.
Refs #540
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
…540) The fixture's connect-src used 'ipc:*' / 'plugin:*' / 'tauri:*' — the stray '*' makes them malformed CSP scheme-sources that don't match the custom protocol URLs (on macOS Tauri fetches IPC at ipc://localhost/<cmd>). WebKit blocked that fetch, so Tauri sticky-fell-back to the postMessage IPC interface — whose response is delivered by eval-ing into the webview and only completes while the run loop is pumping. On the idle headless macos-26.4 CI runner it isn't, so core.invoke stalled and DirectEval timed out (the #540 flake). Locally a real display keeps the loop pumping, which is why it never reproduced. Use proper scheme-sources (ipc:, plugin:, tauri:) + http://ipc.localhost for the Windows/Android form. Verified locally: IPC-custom-protocol failures drop 66 -> 0, so core.invoke uses the fast custom protocol and needs no pumping. Refs #540 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
Same ipc:*/plugin:*/tauri:* -> ipc:/plugin:/tauri: (+ http://ipc.localhost) fix as the e2e fixture. CI proved the point as a clean A/B: with the e2e fixture's CSP fixed, every DirectEval macOS leg went green and IPC custom-protocol failures dropped to 0 on the runner, while the package-test leg — whose CSP was still malformed — failed with the same 'Script execution timed out' pump stall. Fixing its CSP removes the postMessage fallback here too. Refs #540 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CKc4JHZKXfyqCiiU4C2b7V
Addresses the macOS-26.4 embedded-DirectEval flake (#540) —
browser.tauri.executein standalone/deeplink/package modes. Root-caused from the WebKit unified log + local reproduction.1. Root cause — malformed fixture CSP (the real fix)
Both Tauri fixtures (
fixtures/e2e-apps/tauriandfixtures/package-tests/tauri-app) usedconnect-src: ["ipc:*", "plugin:*", "tauri:*"]. The stray*makes them malformed CSP scheme-sources that don't match the IPC custom-protocol URL (macOS/Linux fetchipc://localhost/<cmd>; Windows/Androidhttp://ipc.localhost). WebKit blocked thatfetch, so Tauri sticky-fell-back to the postMessage IPC interface — whose response is delivered by eval-ing into the webview and only completes while the run loop is pumping. On the idle headless macos-26.4 runner it isn't, socore.invokestalled and DirectEval timed out. Locally a real display keeps the loop pumping, which is why it never reproduced — the custom-protocol failure was present locally too (66×/run).Fix:
ipc:/plugin:/tauri:(+http://ipc.localhost). Verified as a controlled A/B on CI: with the e2e fixture's CSP fixed, every DirectEval macOS leg went green and IPC-custom-protocol failures dropped to 0 on the runner, while the package-test leg — CSP still malformed — failed with the sameScript execution timed outstall; fixing its CSP greened it too.2. Harden the DirectEval result path against WebKit code-4 (defense-in-depth)
Independently, macOS 26.4's WebKit intermittently reclaims
callAsyncJavaScript's completion handler (Completion handler for function call is no longer reachable, WKError code 4). This delivers the DirectEval result out-of-band instead, mirroring the WindowsAsyncScriptStatenative-handler path:EvalResultRegistry(eval_channel.rs, keyedoneshot) + awdioEvalResultWKScriptMessageHandler(macos.rs);execute_direct_evalruns the script viacallAsyncJavaScript(to pump) but takes the result from the message channel, so a reclaim is harmless. macOS-only.Residual — tracked separately
A residual intermittent stall remains on the off-screen headless 26.4 runner: with nothing driving the run loop, WebKit doesn't promptly schedule work (page-load / eval), so a DirectEval leg can still occasionally time out. This is environmental (no display → idle run loop), re-runnable, and unrelated to the two fixes here. A follow-up PR attempts a run-loop-pump mitigation; the WebKit-log diagnostics are retained on this branch for that work.
Refs #540
🤖 Generated with Claude Code