Repository navigation
Synchronous cross-world function calls: can a contextBridge-exposed function return a value synchronously on WKWebView/WebKitGTK worlds? #399
Description
Activity
- addedwayfinder:prototypeWayfinder prototype decisionWayfinder prototype decisionelectron-compatElectron compatibility program areaElectron compatibility program area
on Oct 6, 2026 Generated by an AI agent (Claude Code, Fable 5.1) on behalf of @0monish, 2026-10-06, under the map's execution doctrine. Statements are labelled FACT / INFERENCE / UNKNOWN by their author; nothing here authorizes implementation.
Decision packet
Decision: Whether a contextBridge-exposed function can return its value synchronously across WKWebView content worlds (and later WebKitGTK), or whether exposed functions are Promise-returning with a labelled divergence.
Classification:
needs-experiment· Milestone (YAGNI test against the first proof):next· Reversible: yes - parking changes nothing in the tree; the relay is an internal detail behindcontextBridge.exposeInMainWorld, so moving between A and B later changes only compat cells. · Owner: KEL-142 seam (Done; Linear, fetched 2026-10-06) under its parent bridge owner KEL-80 (Backlog; Linear, fetched 2026-10-06); crate keld-wv.Facts
- Every function draw.io exposes (
request,registerMsgListener,sendMessage,listenOnce) returns undefined; the second exposed key is a plain value object{type, versions}; page functions are passed in as arguments, including one nested inside an object (msg.listener) (corpus drawio-desktop@2edf9fb src/main/electron-preload.js). - draw.io has 0
sendSync(sites (grep of corpus drawio-desktop@2edf9fb src, 2026-10-06). - Zettlr's preload exposes functions whose return value is consumed synchronously:
ipc.on()returns an unsubscribe function,config.get()returns a sendSync result,getCitationCallback()returns a function that itself returns a sendSync result (corpus zettlr@e6c7fd8 source/common/modules/preload/index.ts lines 28-80). - The live macOS bridge relays between the page world and an isolated content world; the published plan lists F04-A3 (Synchronous cross-world function calls: can a contextBridge-exposed function return a value synchronously on WKWebView/WebKitGTK worlds? #399) as a blocker of F04-T4 (feat(ipc): contextBridge.exposeInMainWorld value table + ipcRenderer send/on facade + IpcRendererEvent in the app content world (draw.io first-proof slice) #467) (publish_plan.json F04-T4 (feat(ipc): contextBridge.exposeInMainWorld value table + ipcRenderer send/on facade + IpcRendererEvent in the app content world (draw.io first-proof slice) #467) blocked_by).
- KEL-142 (owner of the bridge seam) is Done as of 2026-10-01 (Linear, fetched 2026-10-06).
Inferences
- The first proof does not need a synchronous cross-world return: draw.io's exposed functions are void, so an ordered asynchronous relay with function proxies in both directions satisfies it. F04-T4 (feat(ipc): contextBridge.exposeInMainWorld value table + ipcRenderer send/on facade + IpcRendererEvent in the app content world (draw.io first-proof slice) #467)'s draw.io slice should not be blocked by this ticket; only the Zettlr sync-return cells are.
- A shared-DOM relay is plausible because DOM event dispatch is synchronous and both worlds see the same DOM; this is unverified on WKWebView.
- With an asynchronous relay, successive calls keep their order but a call followed immediately by unload could be lost where Electron would have sent it; no draw.io workflow step is known to depend on that.
Unknowns
- Whether an isolated-world listener runs synchronously inside a page-world
dispatchEventon WKWebView. - What
CustomEvent.detailcarries across worlds (null, a clone, or a shared object) and whether any JS object identity leaks. - Per-call cost of a DOM-string relay versus the asynchronous relay.
- WebKitGTK behaviour (needs a Linux machine).
Alternatives
Option Cost New invariant created Existing invariant at risk A. Shared-DOM synchronous relay (page dispatches a DOM event; the preload world's listener writes the serialized result to shared DOM state the page reads back). A small WKWebView experiment, then serialization of every call through DOM strings; a second call path beside the asynchronous relay. Cross-world values travel only as serialized strings; no JS object or function identity crosses worlds. KEL-142 isolation rule that the page must not reach the native handler or document nonce (the relay must carry neither). B. Asynchronous-only exposure: every exposed function returns a Promise; labelled divergence with a compat diagnostic. Zettlr's sync-return functions break without source edits; draw.io is unaffected. Exposed functions are never synchronous on isolated-world engines. KEL-127 direction 'never a silent downgrade' if the label or diagnostic is missing. C. Run the app preload in the page world to get synchronous calls for free. Gives up engine isolation that exists today. None. KEL-142/KEL-139 isolation acceptance. Rejected. Recommendation
Park for the first proof and remove the F04-A3 (#399) edge from F04-T4 (#467)'s draw.io slice (asynchronous relay with void-return functions). Before any Zettlr work, run the smallest experiment on a macOS WKWebView harness with two content worlds. Arms: (1) page calls
dispatchEventand reads the result in the same task with no await - sync return observed or not; (2) negative control - the same assertion over apostMessagerelay must fail; (3) leak check - an object and a function placed indetailmust not be reachable or callable from the other world; (4) re-entrancy - a preload listener calling back into a page listener during the call preserves order; (5) cost - 10,000 calls at 64 B and 4 KiB, p50/p99 recorded as a diagnostic. Exit: arms 1, 3 and 4 pass and arm 2 fails as designed gives option A; arm 1 failing gives option B as a labelled divergence. WebKitGTK is a separate Linux observation.Falsifier: The populated draw.io webapp reads a return value from a
window.electron.*call or depends on a send completing before a synchronous unload; or arm 3 shows a shared object identity across worlds, which would make option A an isolation regression.Missing evidence: The experiment transcript on real WKWebView (arms 1-5); a scan of the populated draw.io webapp for uses of return values from
window.electron.*.Next action: Orchestrator edits the map: drop F04-A3 (#399) from F04-T4 (#467)'s first-proof blockers and mark this ticket as Zettlr-gated. First observable completion check: F04-T4 (#467)'s blocked-by list for the draw.io slice no longer names F04-A3 (#399).
- Every function draw.io exposes (
- addedmilestone:nextNeeded after the first proof (Zettlr or strict profile)Needed after the first proof (Zettlr or strict profile)
on Oct 6, 2026
Parent map: #391 · Unit: F04 (ipc-bridge) · Wayfinder type:
prototype· Status: open (not resolvable from current evidence)Tracker of record: this GitHub issue (decision ticket; claim by self-assignment). Linear reference: KEL-80 (Backlog) carries a link here.
Question
Zettlr exposes functions whose page-visible return is produced synchronously (config.get via sendSync wrapper). Both worlds share the DOM, so a shared-DOM relay is a lead; unverified. Measure a cross-world relay (per-call cost, sync return feasibility) in the KEL-142 fixture; fallback is async-only exposure (▲) which keeps draw.io config-only.
What is known / why it is still open
No evidence-backed resolution yet; see the unit research note for the proposed answer and refuter verdicts.
Context
Epic #463 · research note
wayfinder/electron-compat/notes/F04.mdon the research branch.