Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions docs/cef/OWNERSHIP.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,35 +97,41 @@ documents:
tier: A
owner_role: cef-runtime
backup_role: rust-core
last_verified: null
last_verified:
worldscript: "v1.27.1"
cef: "151.3.18+gbeff58d+chromium-151.0.7922.138"
review_days: 90
related_ci:
- cef-learning-harness
driftCheckTool: "planned — not implemented, see Wave 1"
note: "Skeleton only — Status: Not started"
note: "Real evidence from PR #388 for process model, message loop, subprocess packaging. Sandbox config and a directly-observed process-tree snapshot remain open."

- path: docs/cef/knowledge/cef-rust-binding-cookbook.md
tier: A
owner_role: rust-core
backup_role: cef-runtime
last_verified: null
last_verified:
worldscript: "v1.27.1"
cef: "151.3.18+gbeff58d+chromium-151.0.7922.138"
review_days: 90
related_ci:
- cef-learning-harness
driftCheckTool: "planned — not implemented, see Wave 1"
note: "Skeleton only — Status: Not started"
note: "Real evidence from apps/desktop-cef/rust-core/ (PR #388) — but the FFI surface is one trivial function, not representative of real API coverage."

- path: docs/cef/knowledge/threading-and-lifetimes.md
tier: A
owner_role: cef-runtime
backup_role: rust-core
last_verified: null
last_verified:
worldscript: "v1.27.1"
cef: "151.3.18+gbeff58d+chromium-151.0.7922.138"
review_days: 90
related_ci:
- cef-learning-harness
# cef-competency-gate: no such CI workflow/job exists yet — planned, not implemented (CodeRabbit review finding on PR #389). Re-add once it's a real job.
driftCheckTool: "planned — not implemented, see Wave 1"
note: "Skeleton only — Status: Not started"
note: "Real evidence from PR #388 for UI-thread callback discipline and CEF ref-counting/callback-lifetime patterns. IO-thread and render-process-side rules untouched; full dual-review (§4.11.4) not yet done."

- path: docs/cef/knowledge/subprocess-and-shutdown.md
tier: A
Expand Down
50 changes: 39 additions & 11 deletions docs/cef/knowledge/cef-architecture-primer.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
# CEF Architecture Primer

**Status:** Not started — no CEF integration exists yet in this repository.
**Scope:** How CEF's multi-process architecture (browser process, renderer process, GPU/utility processes; browser/frame/client ownership; message-loop integration; subprocess launch and packaging; sandbox model) maps onto WorldScript Studio's specific host and build, written from our actual integration once it exists — not a generic CEF tutorial.
**Status:** Real evidence from `apps/desktop-cef/` (PR #388) for process model, message loop, and subprocess packaging. Sandbox configuration and a directly-observed full process-tree snapshot remain open.
**Scope:** How CEF's multi-process architecture (browser process, renderer process, GPU/utility processes; browser/frame/client ownership; message-loop integration; subprocess launch and packaging; sandbox model) maps onto WorldScript Studio's specific host and build, written from our actual integration — not a generic CEF tutorial.
**Tier:** A (release/security-critical) — see [`../OWNERSHIP.yaml`](../OWNERSHIP.yaml).
**Roadmap context:** [`../ROADMAP-CEF-DESKTOP-MIGRATION.md`](../ROADMAP-CEF-DESKTOP-MIGRATION.md) §4.11.1 ("CEF architecture" domain), §4.11.2, Wave 2.

## Outline (to be filled in during Wave 2)
## Process model, as implemented (`apps/desktop-cef/src/main.cpp`)

- Process model as implemented in WorldScript's host (which processes exist, what each owns)
- Browser/frame/client object ownership in our integration
- Message-loop choice and why
- Subprocess launch and packaging specifics for our build
- Sandbox configuration as shipped
- Diagram: our actual process tree (not the generic CEF one)
`worldscript_host` is a single executable re-executed by CEF itself for every process role — there is no separate subprocess binary. `main()` calls `CefExecuteProcess(main_args, app.get(), nullptr)` *before* anything else; a non-negative return means *this invocation* is a subprocess (renderer/GPU/utility) that has already run to completion, and `main()` returns immediately. Only when that call returns `-1` (this is the actual browser process) does the code proceed to install the shutdown-signal handler, call `CefInitialize`, and enter the message loop.

## Do not fill this in speculatively
This single-binary-multi-role design is directly why `scripts/cef/run-launch-cycle-proof.mjs`'s orphan check anchors its `pgrep` pattern to the *start* of the binary's own path (`^${binaryPath}`) — every subprocess CEF spawns re-execs that exact same path with different flags (e.g. `--type=renderer`), so they're all catchable by one pattern, and nothing else on the system should share that literal path prefix.

This document should describe **our real, built integration** once Wave 2 exists. Until then, leave sections empty rather than guessing — a wrong architecture primer is worse than a missing one (see risk register R-10, documentation drift).
**Directly observed evidence a renderer process exists and runs the real page**: the CI log for a real production-bundle load shows a `[INFO:CONSOLE:95]` line — a JavaScript console message relayed from the renderer process back to the browser process via CEF's own IPC, not something the browser process could produce itself. **GPU process**: not directly observed by name (no `ps`/`--type=gpu-process` capture was taken), but the build output includes `libvk_swiftshader.so`/`libvulkan.so.1` (Vulkan software rendering) and the ADR-0020 spike separately observed real GPU-fallback warnings (`Bay Trail Vulkan support is incomplete`) — consistent with a GPU process existing and falling back to software rendering, not confirmed as a distinct observed process in this specific proof.

## Browser/frame/client ownership, as implemented

- `WorldScriptApp::OnContextInitialized` creates exactly **one** `CefBrowserView` (`CefBrowserView::CreateBrowserView`) wrapped in exactly one top-level `CefWindow` (`CefWindow::CreateTopLevelWindow`) — single-window, single-browser, by design; nothing in this host creates additional windows or popups.
- `WorldScriptHandler` is the `CefClient` implementation and the sole owner of browser-lifecycle bookkeeping: a `std::list<CefRefPtr<CefBrowser>> browser_list_`, appended to in `OnAfterCreated` and pruned in `OnBeforeClose`. The list shape supports more than one browser in principle, but only one is ever created today.
- Frame-level ownership (multiple frames per browser, cross-frame navigation) has not been touched at all — the production bundle loads as a single top-level document.

## Message-loop choice and why

`CefRunMessageLoop()` (the blocking, OS-native-integrated variant) — not `CefDoMessageLoopWork()` in a manual polling loop — matching the standard `cefsimple` convention and avoiding a busy-poll CPU cost. `WorldScriptHandler::OnBeforeClose` calls `CefQuitMessageLoop()` only once `browser_list_` becomes empty, which is the actual mechanism that makes `CefRunMessageLoop()` in `main()` return — the real, CI-proven signal that it's safe to call `CefShutdown()`. See `docs/cef/knowledge/subprocess-and-shutdown.md` for the full shutdown sequence.

## Subprocess launch and packaging, as implemented

`apps/desktop-cef/CMakeLists.txt` runs two `COPY_FILES` calls (`CEF_BINARY_FILES`, `CEF_RESOURCE_FILES`) that land everything the runtime needs next to the executable — confirmed via a real `ls -la` in CI (PR #388), not just assumed from the macro's documented behavior: `libcef.so`, `icudtl.dat`, `resources.pak`, `chrome_100_percent.pak`, `chrome_200_percent.pak`, `v8_context_snapshot.bin`, `locales/`, `libEGL.so`, `libGLESv2.so`, `libvk_swiftshader.so`, `libvulkan.so.1`, and `chrome-sandbox`. **This is CEF's own unpackaged build-output layout** (`cmake --build` output, run in place) — not a real installer's layout, which is separate, later, unproven scope.

A real launch-path bug was found and fixed here too: Chromium resolves several of these resource paths relative to the process's *working directory*, not the executable's own location — launching the binary from a different cwd produced an ICU-init crash despite every file being correctly present. See `docs/cef/knowledge/linux-runtime-notes.md` for the full finding.

## Sandbox configuration, as shipped

`chrome-sandbox` is present in the output directory (copied automatically as part of `CEF_BINARY_FILES`) but is **not used** — `main.cpp` sets `CefSettings.no_sandbox = true` unconditionally. Zero evidence exists on real sandbox posture; this is explicitly tracked as "Not yet attempted" in `docs/architecture/native-readiness.md` and `false` in the competency manifest.

## Process tree — what we can honestly claim

```text
worldscript_host (browser process, no_sandbox=true)
└── worldscript_host --type=renderer ... (confirmed indirectly: console-log IPC observed;
not directly captured by ps/process-name in this proof)
└── (likely) worldscript_host --type=gpu-process ... (consistent with SwiftShader/Vulkan
files present and GPU-fallback warnings from the ADR-0020 spike; not directly observed
by process name in PR #388's own CI run)
```

Not a diagram of the generic CEF process model — this is what PR #388's evidence actually supports, with each claim's confidence level stated rather than assumed. A real `ps`/process-tree capture during a live run would upgrade the two `(likely)`/"not directly observed" lines to confirmed evidence; that capture has not been taken yet.
40 changes: 30 additions & 10 deletions docs/cef/knowledge/cef-rust-binding-cookbook.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
# CEF Rust Binding Cookbook

**Status:** Not started — no CEF integration exists yet in this repository. No binding crate has been selected.
**Scope:** Practical recipes for the exact Rust CEF-binding crate/version WorldScript adopts (Wave 2 decision, roadmap §10/Appendix H) — unsafe/FFI boundary patterns, wrapper ownership idioms, API coverage gaps we've hit, and workarounds, written against our real usage.
**Status:** Real evidence from `apps/desktop-cef/rust-core/` (PR #388) — but the FFI surface it documents is currently one trivial function, not representative of real API coverage. Treat this as "the pattern is proven," not "the binding is complete."
**Scope:** Practical recipes for the exact RustCEF integration WorldScript adopted (Wave 2 decision, ADR-0020, Appendix H) — unsafe/FFI boundary patterns, wrapper ownership idioms, API coverage gaps we've hit, and workarounds, written against our real usage.
**Tier:** A (release/security-critical) — see [`../OWNERSHIP.yaml`](../OWNERSHIP.yaml).
**Roadmap context:** [`../ROADMAP-CEF-DESKTOP-MIGRATION.md`](../ROADMAP-CEF-DESKTOP-MIGRATION.md) §4.11.1 ("Rust binding layer" domain), §10 (integration choice), Appendix H (decision scorecard).

## Outline (to be filled in during Wave 2)
## There is no "CEF binding crate" — that's the point of Option B

- Selected binding crate, pinned version, and why (link to the Appendix H scorecard decision)
- Unsafe/FFI boundary map — where WorldScript code crosses into unsafe Rust or C/C++, and why each crossing is necessary
- Wrapper ownership model (who owns what CEF object, when it's valid, how it's dropped)
- Known API coverage gaps in the chosen binding and how WorldScript works around them
- Binding upgrade procedure (cross-reference [`binding-upgrade-playbook.md`](binding-upgrade-playbook.md))
ADR-0020 chose Option B (thin C++ host + Rust core) specifically *because* it means Rust never touches CEF's C++ API at all — no `cef`-style Rust crate wrapping `CefRefPtr<T>`/CEF interfaces exists or is needed. The "binding" here is a plain C FFI boundary between the C++ host and a Rust `staticlib`, nothing CEF-specific on the Rust side. If a future wave changes this decision (Option A), this doc's premise would need a full rewrite, not an update.

## Do not fill this in speculatively
## What's actually built (PR #388)

No binding has been chosen as of Wave 0. This file is a placeholder for real recipes derived from actual integration work, not a summary of upstream binding documentation.
- **Crate**: `apps/desktop-cef/rust-core/` (`worldscript_rust_core`), `crate-type = ["staticlib"]`, `panic = "abort"` in the release profile (matches the C++ host's own lack of a Rust-panic-to-C++-exception bridge — a Rust panic across this boundary today is unrecoverable-by-design, not caught and translated).
- **The entire FFI surface right now**: one function.
```rust
#[no_mangle]
pub extern "C" fn worldscript_rust_ping() -> i32 { 424242 }
```
Declared on the C++ side as a bare forward declaration (`extern "C" int worldscript_rust_ping();` in `worldscript_handler.cpp`) — **no shared/generated header exists between the two languages yet**. This is a real, acknowledged gap: today's single trivial function is hand-matched by inspection: a real API surface (multiple functions, structs crossing the boundary) would need a proper shared header or a tool like `cbindgen` to avoid silent ABI drift between the Rust and C++ sides.
- **Build integration**: [Corrosion](https://github.com/corrosion-rs/corrosion) (`corrosion_import_crate(MANIFEST_PATH ...)` in `apps/desktop-cef/CMakeLists.txt`), fetched via CMake `FetchContent` (a small MIT-licensed CMake module, not a vendored binary). Corrosion creates a CMake target named after the crate (`worldscript_rust_core`), linked via a plain `target_link_libraries(worldscript_host ... worldscript_rust_core ...)` — no manual `.a` path (the ADR-0020 spike's own documented workaround, now replaced by real integration).
- **Proven inside the real host, not just isolated**: `worldscript_rust_ping()` is called from `WorldScriptHandler::OnAfterCreated` on every launch cycle, and its exact sentinel return value is observed in CI output (`scripts/cef/run-launch-cycle-proof.mjs`) — stronger evidence than the ADR-0020 spike's decoupled standalone-binary test.

## Unsafe/FFI boundary map

The *entire* unsafe surface today is the `extern "C"` function signature itself — no raw pointers, no shared mutable state, no lifetime crossing the boundary (the function takes no arguments and returns a plain `i32`). This is deliberately the simplest possible boundary, proving the *mechanism* works before any real data crosses it. **Do not extrapolate this simplicity to "the FFI boundary is safe/solved"** — passing strings, structs, or any pointer-based data across this boundary is materially riskier and entirely unproven so far.

## Wrapper ownership model

Not applicable yet — no CEF object (or any pointer-owning type) crosses into Rust. The moment a real API needs to pass e.g. a string or buffer across this boundary, this section needs real content: who allocates, who frees, and on which side.

## Known API coverage gaps

None cataloged — there is no real API surface yet to have gaps in. The competency matrix (`docs/cef/CEF-RUST-COMPETENCY-MATRIX.md`) already flags this explicitly: `binding_model_documented: true` refers to the *integration model* (ADR-0020's Option B choice), not a claim that the FFI surface itself is complete or battle-tested.

## Binding upgrade procedure

Not written yet — see [`binding-upgrade-playbook.md`](binding-upgrade-playbook.md), still a skeleton. With only one trivial function crossing the boundary, there is nothing yet to have an upgrade procedure *for* beyond bumping the Corrosion `GIT_TAG` pin in `CMakeLists.txt`, which has not been exercised even once.
Loading
Loading