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
17 changes: 8 additions & 9 deletions PORTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,16 @@ Reference: `packages/flutter_cef_macos/native/cef_host/` (`main.mm` is the
browser process, `process_helper.mm` the CEF child processes). The CEF client /
app / handler logic, the browser-control functions, the navigation scheme
allowlist, and the **IPC opcode protocol** are platform-agnostic C++ and can be
reused verbatim. Only these seams are macOS-specific (file:line are into
`main.mm` at the time of writing):
reused verbatim. Only these seams are macOS-specific:

| Seam | macOS reference | What your platform provides |
| --- | --- | --- |
| **Shared surface** — receive painted frames and present them to the host texture. CEF delivers either software `OnPaint` (CPU buffer) or `OnAcceleratedPaint` (a shared GPU texture handle). | `OnPaint` / `OnAcceleratedPaint` + the `IOSurface*` ops (~lines 346–450); `g_surface` (~133). macOS uses an IOSurface-backed `CVPixelBuffer`. | **Windows**: a shared D3D11 texture. Note (verified against CEF 144 `cef_types_win.h` + Flutter engine `external_texture_d3d.cc`): CEF's `OnAcceleratedPaint` delivers an **NT** handle (no keyed mutex, non-owning, pool-released when the callback returns — open it synchronously via `ID3D11Device1::OpenSharedResource1`), while Flutter's ANGLE compositor requires a **legacy** `D3D11_RESOURCE_MISC_SHARED` handle (`EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE`); the two are mutually exclusive on one texture, so exactly one `CopyResource` NT→legacy is structurally mandatory (macOS also blits — `CompositeMetalLocked`). **Linux**: shared memory or a DMA-buf, presented via the platform texture. Either way the model is **producer-allocates**: `cef_host` mints the shared surface and announces it via `opPresent` — the host does NOT pass a surface id in the create (the macOS field name differs too: `shared_texture_io_surface` vs Windows `shared_texture_handle`, so the accelerated-paint glue takes a per-OS `#if`, not a typedef). |
| **IPC transport** — a framed bidirectional byte stream to the host. Wire format: 4-byte big-endian length prefix, then `[u32 browserId][opcode][payload]` (`browserId 0` = process-level: `opReady`, process logs, `opShutdown`). | `WriteAll`/`ReadAll` (207–235), `SendFrame` (~237–249), `ConnectUnixSocket` (1341+), the read loop (~1140). Unix domain socket. | **Windows**: a named pipe. **Linux**: a Unix domain socket (reuse as-is). Keep the same length-prefixed framing and the `browserId` dimension. |
| **App / run loop** — give CEF a host application + message loop, and a per-profile cache dir. | `CefHostApplication : NSApplication<CefAppProtocol>` (304–330); `@autoreleasepool` + `sharedApplication` (1420+); `--profile-dir` → `settings.root_cache_path` cache; `_NSGetExecutablePath` (1335). | **Windows/Linux**: the platform's CEF message-loop integration (`CefRunMessageLoop` or OS loop) and the caller-supplied `--profile-dir` (per-profile, persistent) as the cache path. |
| **Shared surface** — receive painted frames and present them to the host texture. CEF delivers either software `OnPaint` (CPU buffer) or `OnAcceleratedPaint` (a shared GPU texture handle). | `OnPaint` / `OnAcceleratedPaint` + the `IOSurface*` ops on each slot's `surface` (`render_handler.mm`). macOS uses an IOSurface-backed `CVPixelBuffer`. | **Windows**: a shared D3D11 texture. Note (verified against CEF 144 `cef_types_win.h` + Flutter engine `external_texture_d3d.cc`): CEF's `OnAcceleratedPaint` delivers an **NT** handle (no keyed mutex, non-owning, pool-released when the callback returns — open it synchronously via `ID3D11Device1::OpenSharedResource1`), while Flutter's ANGLE compositor requires a **legacy** `D3D11_RESOURCE_MISC_SHARED` handle (`EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE`); the two are mutually exclusive on one texture, so exactly one `CopyResource` NT→legacy is structurally mandatory (macOS also blits — `CompositeMetalLocked`). **Linux**: shared memory or a DMA-buf, presented via the platform texture. Either way the model is **producer-allocates**: `cef_host` mints the shared surface and announces it via `opPresent` — the host does NOT pass a surface id in the create (the macOS field name differs too: `shared_texture_io_surface` vs Windows `shared_texture_handle`, so the accelerated-paint glue takes a per-OS `#if`, not a typedef). |
| **IPC transport** — a framed bidirectional byte stream to the host. Wire format: 4-byte big-endian length prefix, then `[u32 browserId][opcode][payload]` (`browserId 0` = process-level: `opReady`, process logs, `opShutdown`). | `WriteAll`/`ReadAll` and `SendFrame` (`ipc.mm`), `ConnectUnixSocket` (`main.mm`), the read loop (`IpcReadLoop`, `ipc_reader.mm`). Unix domain socket. | **Windows**: a named pipe. **Linux**: a Unix domain socket (reuse as-is). Keep the same length-prefixed framing and the `browserId` dimension. |
| **App / run loop** — give CEF a host application + message loop, and a per-profile cache dir. | `CefHostApplication : NSApplication<CefAppProtocol>`; `@autoreleasepool` + `sharedApplication`; `--profile-dir` → `settings.root_cache_path` cache; `_NSGetExecutablePath` (all in `main.mm`). | **Windows/Linux**: the platform's CEF message-loop integration (`CefRunMessageLoop` or OS loop) and the caller-supplied `--profile-dir` (per-profile, persistent) as the cache path. |
| **Agent-control CDP pipe (optional)** — for `agentControl`, the host launches `cef_host` so Chromium's `--remote-debugging-pipe` (NUL-framed CDP JSON) rides **inherited file descriptors 3=read / 4=write** instead of a TCP port. | `CefProfileHost.launchViaPosixSpawn` (Swift): two `pipe()` pairs `dup2`'d onto fds 3/4 via `posix_spawn_file_actions`, parent ends marked `FD_CLOEXEC`; `cef_host` appends `remote-debugging-pipe` in `OnBeforeCommandLineProcessing`. The relay (`CdpRelay.swift`) + per-tile Target filter are platform-agnostic; only the fd placement is OS-specific. | **Linux**: reuse the `posix_spawn_file_actions` fd-3/4 recipe verbatim. **Windows**: a different model — `CreatePipe` + `SetHandleInformation(HANDLE_FLAG_INHERIT)` + `STARTUPINFOEX` `PROC_THREAD_ATTRIBUTE_HANDLE_LIST`, and Chromium takes the two inherited pipe **HANDLE values** via the separate switch `--remote-debugging-io-pipes=<read>,<write>` (uint32-serialized HANDLEs, passed **in addition to** the bare `--remote-debugging-pipe` — verified against `content_switches.cc` at chromium 144.0.7559.254; `--remote-debugging-pipe` itself takes no arguments on any platform). The default (non-agent) launch also needs a native `exec`/`CreateProcess` (Foundation.Process is macOS-only). |
| **Sandbox** — bring the child processes into the Chromium sandbox in a signed/release build. | `process_helper.mm`: `CefScopedSandboxContext` (release only); `settings.no_sandbox` toggled by `CEF_HOST_ADHOC` (1426/1433). | **Windows**: CEF 144 ships **no `cef_sandbox.lib`** — the current model (`cef_sandbox_win.h`, CEF issue #3824) is: build your app as a **DLL** exporting `RunWinMain`/`RunConsoleMain` and launch it through the prebuilt `bootstrap.exe`/`bootstrapc.exe` from the CEF distribution, which establishes the sandbox before entering your code. **Linux**: the SUID / user-namespace sandbox helper. |
| **Framework / resource path** — point CEF at the CEF binary distribution. | `CefScopedLibraryLoader::LoadInMain`/`LoadInHelper`; `framework_dir_path` / `main_bundle_path` (1453/1466). | The equivalent paths for your bundle layout. |
| **Sandbox** — bring the child processes into the Chromium sandbox in a signed/release build. | `process_helper.mm`: `CefScopedSandboxContext` (release only); `settings.no_sandbox` toggled by `CEF_HOST_ADHOC` (`main.mm`). | **Windows**: CEF 144 ships **no `cef_sandbox.lib`** — the current model (`cef_sandbox_win.h`, CEF issue #3824) is: build your app as a **DLL** exporting `RunWinMain`/`RunConsoleMain` and launch it through the prebuilt `bootstrap.exe`/`bootstrapc.exe` from the CEF distribution, which establishes the sandbox before entering your code. **Linux**: the SUID / user-namespace sandbox helper. |
| **Framework / resource path** — point CEF at the CEF binary distribution. | `CefScopedLibraryLoader::LoadInMain`/`LoadInHelper`; `framework_dir_path` / `main_bundle_path` (`main.mm`). | The equivalent paths for your bundle layout. |
| **Build + bundle + sign** | `native/build_cef_host.sh` (CMake, `CEF_MULTI_PROCESS` / `CEF_HOST_ADHOC` flags), `tool/bundle_cef_host.sh`. | A platform build that produces `cef_host` + the CEF runtime, and a bundling step into the host app. |

The fetched CEF distribution already ships per-platform binaries and a CMake
Expand All @@ -127,8 +126,8 @@ platform-specific link libs and the surface/transport sources.

## 4. Recommended: extract a `core/` + `platform/` split *with* your port

The macOS `main.mm` currently keeps the portable CEF logic and the macOS glue in
one translation unit. The seams above are the natural cut line:
The macOS host currently keeps the portable CEF logic and the macOS glue in
the same files. The seams above are the natural cut line:

```
native/cef_host/
Expand Down
2 changes: 1 addition & 1 deletion example/lib/agentcontrol_probe.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Agent-control (P9) end-to-end probe — drives the Windows token-gated loopback
// Agent-control end-to-end probe — drives the Windows token-gated loopback
// CDP relay through a real CDP WebSocket client, entirely in-process (no
// Playwright / node / python needed: dart:io's WebSocket does the RFC-6455
// handshake and forwards a custom Authorization header).
Expand Down
6 changes: 3 additions & 3 deletions example/lib/cull_wedge_probe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// transitions that used to wedge it permanently blank (only relaunch recovered):
// * setVisible(false) → resize while hidden → setVisible(true)
// * setVisible(false) → setVisible(true) after the off-screen frame is evicted
// The native fix (F-1): DoSetVisible(true) forces a full repaint on the hidden→visible
// edge; (F-2) DoResize defers its paint while hidden; (F-4) the resize watchdog never
// The native fix: DoSetVisible(true) forces a full repaint on the hidden→visible
// edge; DoResize defers its paint while hidden; the resize watchdog never
// force-promotes a hidden (never-painted) surface. Without these, the page below stays
// BLANK after "Wedge cycle"; with them it reappears (gradient + the ticking clock proves
// the frame is FRESH, not a stale cached one).
Expand Down Expand Up @@ -55,7 +55,7 @@ class _WedgeAppState extends State<WedgeApp> {
_controller.onPageStarted = (_) => _controller.loadHtmlString(_html);
// Self-driving evidence run: a few seconds after first paint, run several wedge
// cycles back-to-back then settle SHOWN, so a screenshot of the final state proves the
// page repainted (F-1) rather than wedged blank — no clicking needed.
// page repainted on show rather than wedged blank — no clicking needed.
Future<void>.delayed(const Duration(seconds: 4), _runAutoCycles);
}

Expand Down
2 changes: 1 addition & 1 deletion example/lib/interaction_soak_probe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class _AppState extends State<App> {
t.controller.setVisible(false);
case 2: // logical resize WHILE the tile may be hidden
t.big = !t.big;
case 3: // un-cull (show) — F-1 must repaint, size-gate must promote
case 3: // un-cull (show) — show must repaint, size-gate must promote
t.visible = true;
t.controller.setVisible(true);
}
Expand Down
2 changes: 1 addition & 1 deletion example/lib/jsbridge_smoke.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// P7 JS-bridge smoke harness (Windows integration proof) — reload-tolerant.
// JS-bridge smoke harness (Windows integration proof) — reload-tolerant.
//
// loadHtmlString settles with an extra page reload, so linear orchestration on
// one page session is fragile. Instead: confirm() + the download click run in
Expand Down
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ and committed text — including emoji — should appear intact.</p>
onPressed: _controller.openDevTools,
),
// macOS-only: showEmojiPicker drives the AppKit Character
// Palette; there is no supported Win32 equivalent (PLAN §6),
// Palette; there is no supported Win32 equivalent,
// so the button is hidden per-platform.
if (Platform.isMacOS)
IconButton(
Expand Down
12 changes: 6 additions & 6 deletions example/lib/multiview_probe.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// P2-step2 LIVE probe (cef-multiview PLAN Tests A + D + E) — flutter_cef side.
// Multi-view agent-control LIVE probe (checks A, D, E, F below) — flutter_cef side.
//
// Auto-running, headless-friendly self-test: mounts TWO CefWebViews on ONE shared
// named profile (an isolated 'p2probe' — deliberately NOT Campus's real 'campus-web'
Expand All @@ -18,11 +18,11 @@
// E. each relay's Target.getTargets returns ONLY its own target (A can't see B),
// and presenting tile A's token to tile B's port is rejected.
// F. concurrency + lifecycle: enabling both CONCURRENTLY brings up two isolated
// relays (the per-browserId dict, not the P1 scalar); disabling A kills only
// relays (one per browserId, not a single per-host slot); disabling A kills only
// A's grant (its endpoint goes dead) while B keeps driving; and A can be
// RE-ENABLED after disable — a fresh port+token, the torn-down grant stays dead.
//
// Not covered here — reader-stall isolation (PLAN Test G, the SO_SNDTIMEO reaping of
// Not covered here — reader-stall isolation (the SO_SNDTIMEO reaping of
// a wedged client + no sibling starvation): a faithful repro needs a real CDP driver
// that completes the flatten auto-attach handshake and drives pipe-routed commands
// (this probe's Target.getTargets is synthesized client-side and bypasses the shared
Expand Down Expand Up @@ -125,8 +125,8 @@ class _ProbeAppState extends State<ProbeApp> {
final out = <String, dynamic>{};
try {
setState(() => _status = 'enabling agent-control on both views CONCURRENTLY…');
// F — concurrent enable: fire BOTH at once (not sequentially). The P1 scalar
// relay/relayBrowserId would have lost this race; the per-browserId dict +
// F — concurrent enable: fire BOTH at once (not sequentially). A single
// per-host relay/relayBrowserId would have lost this race; the per-browserId dict +
// cdpHandlerLock must bring up two isolated relays under simultaneous enable.
final grants = await Future.wait([_enable(_a), _enable(_b)]);
final gA = grants[0], gB = grants[1];
Expand Down Expand Up @@ -242,7 +242,7 @@ class _ProbeAppState extends State<ProbeApp> {
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Text('P2-step2 probe — $_status',
child: Text('multiview probe — $_status',
style: const TextStyle(fontWeight: FontWeight.w600)),
),
Expanded(
Expand Down
4 changes: 2 additions & 2 deletions example/lib/profile_probe.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Windows profile + cookie END-TO-END probe (P6 foundation + P11 profile slice).
// Windows profile + cookie END-TO-END probe (shared hosts, named profiles, cookies).
//
// Auto-running, no-interaction self-test that drives the REAL integrated stack —
// the public Dart API (CefWebController) -> the method channel -> the Windows
Expand Down Expand Up @@ -34,7 +34,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_cef/flutter_cef.dart';

// A dev probe artifact — write evidence under the OS temp dir, not a
// maintainer-specific absolute path (PLAN P1: probe outputs -> systemTemp).
// maintainer-specific absolute path.
final _evidenceDir =
'${Directory.systemTemp.path}${Platform.pathSeparator}flutter_cef_profile_evidence';
const _sharedProfile = 'evi_shared';
Expand Down
2 changes: 1 addition & 1 deletion example/lib/recreate_soak_probe.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Recreate SOAK probe — mimics Campus's CefSessionController.recover() (the one pattern
// the other probes never exercised): on a paint-stall / F-6 stall Campus DISPOSES the
// the other probes never exercised): on a paint stall or liveness stall Campus DISPOSES the
// controller, builds a FRESH one, and REMOUNTS the CefWebView against it (a generation
// ValueKey bump). This probe drives that recreate cycle interleaved with zoom (renderScale)
// and cull (setVisible) — the suspected source of "looks fine, then after interaction
Expand Down
2 changes: 1 addition & 1 deletion example/run_conformance_oracle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# • NO-RENDER : the harness never established / never adopted a surface
#
# This turns the manual "read the logs" check into a repeatable gate. Run after any change to
# CefWebSession.swift / CefProfileHost.swift / cef_host/main.mm.
# CefWebSession.swift / CefProfileHost*.swift / cef_host/*.mm.
#
# FLUTTER_CEF_HOST=/path/to/cef_host.app/Contents/MacOS/cef_host ./run_conformance_oracle.sh
#
Expand Down
4 changes: 2 additions & 2 deletions example/run_leak_soak.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash
# LEAK-SOAK GATE — guards the producer-allocates IOSurface LIFETIME invariant (the property the
# three sev-8 audit findings probed and which the render oracle does NOT cover): cef_host mints a
# LEAK-SOAK GATE — guards the producer-allocates IOSurface LIFETIME invariant (which the render
# oracle does NOT cover): cef_host mints a
# surface per paint/recreate and CFReleases the old; the consumer's CVPixelBuffer holds the only
# remaining ref until it adopts the next id. If that ledger ever regresses (producer forgets to
# release, or the consumer never drops the old ref), surfaces accumulate — invisible to the render
Expand Down
12 changes: 6 additions & 6 deletions lib/src/cef_web_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ class CefWebController {
onProcessGone?.call(reason);
break;
case 'paintStalled':
// C1: the browser came up but never delivered its first frame even after a
// The browser came up but never delivered its first frame even after a
// re-kick — the texture is (still) blank with no other signal. Surface it so
// the consumer can recover (e.g. recreate the view) instead of a silent blank.
onPaintStalled?.call();
Expand Down Expand Up @@ -829,9 +829,9 @@ class CefWebController {
? allowedSchemes.map((s) => s.toLowerCase()).join(',')
: null,
enableCdp: enableCdp,
// Agent-control / pipe mode (CEF-1): the native side launches cef_host
// via posix_spawn with CDP over inherited fds 3/4 (--cdp-pipe) instead
// of a TCP --cdp-port.
// Agent-control / pipe mode: the native side launches cef_host with
// CDP over inherited pipes (fds 3/4 on macOS, two anonymous pipes on
// Windows) instead of a TCP --cdp-port.
agentControl: agentControl,
profile: profile != null && profile!.isNotEmpty ? profile : null,
hostGroup:
Expand Down Expand Up @@ -922,7 +922,7 @@ class CefWebController {
return _platform.openAuthWindow(sessionId, url);
}

/// CEF-2a — enable agent control for this tile and return a brokered, token-gated
/// Enable agent control for this tile and return a brokered, token-gated
/// CDP endpoint a standard CDP client (e.g. `agent-browser`) can connect to.
///
/// Requires the controller to have been created with `agentControl: true` (the
Expand Down Expand Up @@ -959,7 +959,7 @@ class CefWebController {
return (wsUrl: wsUrl, token: token, port: port);
}

/// CEF-2a — tear down the agent-control relay (closes the listener and any client,
/// Tear down the agent-control relay (closes the listener and any client,
/// invalidates the token). The tile itself keeps running. Idempotent.
Future<void> disableAgentControl() =>
_platform.disableAgentControl(sessionId);
Expand Down
Loading
Loading