Skip to content
Open
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
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
## Unreleased

* **Windows support** (`flutter_cef_windows`, experimental) — the port reaches
feature parity with macOS for browsing, pointer/keyboard/IME input, persistent
+ shared profiles, cookies, the JS bridge (`addJavaScriptChannel` /
`runJavaScriptReturningResult`, per-session routed), JS dialogs, find-in-page,
content zoom, downloads, and single-tile agent-control (CDP over an inherited
pipe, token-gated loopback relay). The app-facing Dart API is unchanged and one
IPC opcode protocol drives both OSes. Windows specifics:
* **Rendering**: CEF `OnAcceleratedPaint`'s shared D3D11 texture is copied into
a DXGI-shared "bridge" texture that Flutter's ANGLE compositor samples (a
`GpuSurfaceTexture`); software `OnPaint` is the fallback.
* **Profiles at rest**: encrypted with **DPAPI** (OSCrypt) — always available
and signing-independent, so a named `profile:` persists in every build with
**no** macOS-style ad-hoc→ephemeral downgrade. DPAPI is same-user-readable
(weaker than the macOS Keychain); the profile dir also gets a current-user
SID DACL.
* **Transport/host**: one `cef_host.exe` per profile over a named pipe
(overlapped I/O); the CEF runtime is fetched + SHA-verified on first build
and `cef_host` is compiled from source (`build_cef_host.bat`, VS2022 via
`vswhere`).
* **Not yet on Windows** (hardening): per-tile CDP isolation (agent-control is
fail-closed single-tile), the Chromium sandbox (`no_sandbox` today), and a
signed prebuilt `cef_host` on GCS (tooling scaffolded, awaits an Authenticode
cert). Requires Windows 10+ with Developer Mode.
* macOS behaviour is **byte-for-byte unchanged**.

## 0.2.0

* **Persistent, shared profiles**: `CefWebView(profile: 'name')` /
Expand Down
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ publish-cef-host:
@test -n "$(CODESIGN_ID)" || { echo "error: no 'Developer ID Application' identity in the keychain"; exit 1; }
@command -v gsutil >/dev/null 2>&1 || { echo "error: gsutil not found (install the Google Cloud SDK)"; exit 1; }
CODESIGN_ID="$(CODESIGN_ID)" bash packages/flutter_cef_macos/tool/publish-cef-host.sh

# publish-cef-host-windows: the Windows analogue (PowerShell). Builds cef_host via
# build_cef_host.bat, (STUBBED) signs it if FLUTTER_CEF_SIGN_THUMBPRINT is set,
# and publishes cef_host-windows-x64.zip to GCS keyed by the input hash. Guards
# (gsutil present, signtool when signing) live inside the .ps1; run from a shell
# where `powershell` resolves. Idempotent.
#
# make publish-cef-host-windows
# GCS_PREFIX=campus_prebuilt_cef_host-staging make publish-cef-host-windows # dry-run to staging
.PHONY: publish-cef-host-windows
publish-cef-host-windows:
powershell -NoProfile -ExecutionPolicy Bypass -File packages/flutter_cef_windows/tool/publish-cef-host.ps1
97 changes: 74 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@

Embed a **live Chromium browser** (via the [Chromium Embedded Framework](https://bitbucket.org/chromiumembedded/cef/)) as a Flutter widget — rendered into a `Texture`, so it composites, transforms, clips, and zooms like any other widget, and **keeps rendering even when off-screen / not focused**. Pointer, scroll, and trackpad two-finger pans are forwarded by coordinate (pans are caught even when an ancestor opts into Flutter's trackpad gesture API, as canvas hosts do), and keyboard input reaches the page as real `keydown → keypress → keyup` events (Enter activates a focused button / submits a form, Space toggles a checkbox) — including platform IME composition for CJK / emoji and the ⌃⌘Space emoji picker. Text input is bound to the hosting `FlutterView` (as `EditableText` does), so it **works in multi-view / multi-window apps**; the page cursor drives a `MouseRegion`.

> Status: **experimental, macOS 12+ only** (CEF 144 runtime floor). Real Chromium (any site — JS/CSS/WebGL/video). **Multi-process by default** (GPU-accelerated OSR — `OnAcceleratedPaint` GPU compositing into a shared IOSurface, Retina-crisp; renderer/utility crashes isolated, so heavy SPAs like Google sign-in render and survive); `CEF_MULTI_PROCESS=OFF packages/flutter_cef_macos/native/build_cef_host.sh` for the simpler single-process build. No mobile (iOS bans third-party engines); desktop by nature.
> Status: **experimental — macOS 12+ and Windows 10+** (CEF 144 runtime floor).
> Real Chromium (any site — JS/CSS/WebGL/video). **Multi-process by default**
> (GPU-accelerated OSR — `OnAcceleratedPaint` GPU compositing into a shared
> surface, HiDPI-crisp; renderer/utility crashes isolated, so heavy SPAs like
> Google sign-in render and survive). macOS composites into an `IOSurface`;
> Windows copies the shared D3D11 texture into a DXGI-shared bridge that Flutter's
> ANGLE compositor samples. **macOS is the reference platform**; the Windows port
> reaches feature parity for browsing, input/IME, persistent+shared profiles,
> cookies, the JS bridge, dialogs/find/zoom/downloads, and single-tile
> agent-control — with per-tile CDP isolation, the Chromium sandbox, and
> code-signing still pending (see [Roadmap](#roadmap)). No mobile (iOS bans
> third-party engines); desktop by nature.

```dart
import 'package:flutter_cef/flutter_cef.dart';
Expand Down Expand Up @@ -79,22 +90,34 @@ bar, live title).

```
Dart CefWebView + CefWebController (MethodChannel "flutter_cef")
→ macOS plugin (FlutterCefPlugin / CefWebSession):
allocates a global IOSurface + CVPixelBuffer, registers a FlutterTexture,
spawns one cef_host.app per view, relays input + cursor over a Unix socket
→ cef_host.app: CEF windowless (OSR), multi-process — the GPU/Viz process
composites the page and hands OnAcceleratedPaint a shared-texture
IOSurface, which cef_host copies into the host-shared IOSurface →
"present" → the texture re-samples. (OnPaint software blit is the
single-process fallback.)
→ host plugin (per OS):
macOS (FlutterCefPlugin / CefWebSession): allocates a global IOSurface +
CVPixelBuffer, registers a FlutterTexture, spawns one cef_host.app
per profile, relays input + cursor over a Unix socket
Windows (FlutterCefPlugin, C++): registers a GpuSurfaceTexture (DXGI shared
handle), spawns one cef_host.exe per profile, relays over a named
pipe (overlapped I/O)
→ cef_host: CEF windowless (OSR), multi-process — the GPU/Viz process
composites the page and hands OnAcceleratedPaint a shared-texture handle:
macOS copies it into the host-shared IOSurface;
Windows opens the shared D3D11 texture and CopyResources it into a
DXGI-shared "bridge" texture (NT→legacy handle) that ANGLE samples.
→ "present" → the texture re-samples. (OnPaint software blit is the fallback.)
```

Same pattern JCEF (JetBrains) and CefSharp use to render Chromium into a non-native toolkit — adapted to Flutter's `Texture` + `IOSurface`.
Same pattern JCEF (JetBrains) and CefSharp use to render Chromium into a
non-native toolkit — adapted to Flutter's `Texture` (IOSurface on macOS, a
DXGI-shared D3D11 texture on Windows). One IPC opcode protocol
([`PROTOCOL.md`](packages/flutter_cef_windows/native/cef_host/PROTOCOL.md))
drives both; only the surface, transport, app-loop, sandbox, and framework-path
seams differ per OS (see [`PORTING.md`](PORTING.md)).

## Building

CEF (~200 MB) is **fetched**, not vendored. Build the renderer once (needs
`cmake` + `ninja` — `brew install cmake ninja`):
CEF (~200 MB) is **fetched**, not vendored, on both platforms.

**macOS** — build the renderer once (needs `cmake` + `ninja` —
`brew install cmake ninja`):

```sh
# The macOS implementation lives in packages/flutter_cef_macos.
Expand All @@ -104,6 +127,23 @@ export FLUTTER_CEF_HOST="$PWD/native/cef_host/build/cef_host.app/Contents/MacOS/
cd ../../example && flutter run -d macos
```

**Windows** — nothing to run by hand: `flutter build windows` (or
`flutter run -d windows`) drives everything from the plugin's CMake. On the
first build it fetches the pinned CEF distribution
(`native/cef_host/fetch_cef.ps1`, SHA-verified, cached under `%LOCALAPPDATA%`)
and compiles `cef_host.exe`/`.dll` (`build_cef_host.bat` → the VS2022 toolchain,
located via `vswhere`; `cmake` + `ninja` ship with VS). Needs Windows 10+ with
Developer Mode on (Flutter uses symlinks for plugins).

```sh
cd example && flutter run -d windows
```

> A prebuilt, content-hash-keyed `cef_host` on GCS (so consumers fetch it
> instead of compiling, as macOS does) is scaffolded in
> `packages/flutter_cef_windows/tool/` but not yet live — it awaits Authenticode
> signing (see [Roadmap](#roadmap)); until then Windows builds from source.

### Bundling into a distributable app

For a shipped `.app` (no dev env var), `cef_host.app` must live in your bundle's
Expand Down Expand Up @@ -452,17 +492,28 @@ Next:
scheme handlers, a typed DevTools/CDP client (the inspector window already
ships via `openDevTools`; this is the programmatic CDP surface), and `CefPermissionHandler`
(WebRTC camera/mic prompts).
- **Windows / Linux** — the package is **federated** (`flutter_cef` +
`flutter_cef_platform_interface` + `flutter_cef_macos`); a new platform is a
sibling `flutter_cef_<os>` package. The CEF logic + IPC protocol are portable;
each OS supplies its own host plugin + shared-texture / transport / sandbox
glue. See [`PORTING.md`](PORTING.md) for the full contract and seam map. A
Windows port (`flutter_cef_windows`) is in progress: the Dart controller /
widget surface is already cross-platform (the JS bridge, JS dialogs,
find-in-page, content zoom, and downloads all speak the same
method-channel/wire protocol on both OSes — see
`packages/flutter_cef_windows/native/cef_host/PROTOCOL.md`), with the Windows
host's sandbox + code-signing story still pending.
- **Windows** (`flutter_cef_windows`) — **landed** and at feature parity for
browsing, pointer/keyboard/IME input, persistent + shared profiles, cookies,
the JS bridge, JS dialogs, find-in-page, content zoom, downloads, and
single-tile agent-control. The Dart controller / widget surface is shared and
one IPC opcode protocol drives both OSes
([`PROTOCOL.md`](packages/flutter_cef_windows/native/cef_host/PROTOCOL.md)).
Still pending on Windows (hardening, not features):
- **Per-tile CDP isolation** — agent-control is enforced single-tile
(fail-closed: no grant on a multi-tile host); the deny-by-default per-tile
Target-domain filter that macOS ships (so N tiles on one profile can each be
agent-driven in isolation) is the next step.
- **Chromium sandbox** — the host currently runs `no_sandbox`; the
`bootstrapc.exe` + LPAC sandbox is designed (spike-proven) but not yet on.
- **Code-signing + prebuilt distribution** — an Authenticode-signed,
content-hash-keyed `cef_host` on GCS (fetched, not compiled, like macOS);
tooling is scaffolded in `packages/flutter_cef_windows/tool/`, awaiting a
signing certificate.
- Double-buffering the shared texture (the ~1.8% tearing measured on
fast-updating pages), and GPU device-lost / leak-soak hardening.
- **Linux** — not started; the same federated seam applies (a sibling
`flutter_cef_linux` supplies shared-memory/DMA-buf surface + Unix-socket
transport). See [`PORTING.md`](PORTING.md) for the contract and seam map.

## Credits

Expand Down
4 changes: 4 additions & 0 deletions packages/flutter_cef_windows/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Standalone cef_host build output (build_cef_host.bat's default BUILD_DIR;
# the flutter-driven build uses the example's binary dir instead).
native/cef_host/build/

# Fetched prebuilt cef_host (tool/fetch_cef_host.ps1 downloads cef_host.exe +
# cef_host.dll + provenance stamps here; never checked in).
native/cef_host/prebuilt/
95 changes: 95 additions & 0 deletions packages/flutter_cef_windows/tool/cef_host_hash.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# cef_host_hash.ps1 - deterministic content hash of the Windows cef_host build
# inputs. Prints a 64-hex digest to STDOUT (Write-Output).
#
# Dot-sourced by BOTH fetch_cef_host.ps1 (consumer, at CMake configure) and
# publish-cef-host.ps1 (CI) so they ALWAYS compute the same digest from the same
# source tree -- that digest IS the GCS object key, so any drift here is a silent
# cache miss. Mirrors packages/flutter_cef_macos/tool/cef_host_hash.sh.
#
# Inputs (the ONLY files that determine the shipped cef_host.dll/.exe bytes):
# build_cef_host.bat, CMakeLists.txt, cef_host_protocol.h, cef_host_win.cc,
# fetch_cef.ps1. fetch_cef.ps1 carries the CEF version pin ($CefVersion) -- the
# Windows analogue of the macOS build_cef_host.sh carrying CEF_VERSION -- so a
# CEF bump moves the digest without hashing the multi-hundred-MB CEF dist.
# Docs (PROTOCOL.md) and the standalone IPC test (test/) are NOT build inputs and
# are excluded; the build/ and prebuilt/ OUTPUT dirs are never inputs.
#
# Determinism across machines: the input list is byte-stable (ordinal) sorted,
# each file's CR (0x0D) is stripped before hashing so a git core.autocrlf checkout
# does NOT perturb the digest, and we emit "<relpath>\n<filesha256>\n" per file
# (LF, lowercase hex) then SHA-256 the whole UTF-8 stream. Same tree -> same
# digest on every machine.
#
# ASCII-ONLY (no em-dashes / smart quotes): PowerShell 5.1 reads a UTF-8-no-BOM
# file as ANSI and mis-parses multibyte characters. Keep this file 7-bit.
#
# Usage:
# Dot-source: . .\cef_host_hash.ps1 ; Get-CefHostInputHash -CefHostDir <dir>
# Standalone: powershell -File .\cef_host_hash.ps1 (prints this pkg's hash)

Set-StrictMode -Version 2.0
$ErrorActionPreference = 'Stop'

# SHA-256 of a byte array, returned as lowercase 64-hex.
function ConvertTo-Sha256Hex {
param([Parameter(Mandatory = $true)][byte[]]$Bytes)
$sha = [System.Security.Cryptography.SHA256]::Create()
try { $digest = $sha.ComputeHash($Bytes) } finally { $sha.Dispose() }
return ([System.BitConverter]::ToString($digest) -replace '-', '').ToLowerInvariant()
}

# SHA-256 of a file with CR bytes stripped (CRLF and LF checkouts hash equal).
# All inputs are text, so a 0x0D only ever appears as part of a CRLF here.
function Get-NormalizedFileSha256 {
param([Parameter(Mandatory = $true)][string]$Path)
$bytes = [System.IO.File]::ReadAllBytes($Path)
$out = New-Object System.Collections.Generic.List[byte]
foreach ($b in $bytes) { if ($b -ne 13) { [void]$out.Add($b) } }
return (ConvertTo-Sha256Hex -Bytes $out.ToArray())
}

function Get-CefHostInputHash {
[CmdletBinding()]
param(
# .../packages/flutter_cef_windows/native/cef_host . Defaults to the copy
# beside this script so dot-source and standalone use both work arg-free.
[string]$CefHostDir = (Join-Path $PSScriptRoot '..\native\cef_host')
)

$root = (Resolve-Path -LiteralPath $CefHostDir).Path

# Explicit, ordered build-input list (relative to $CefHostDir). Extend this if
# a new file starts to affect the shipped host bytes.
$inputs = [string[]]@(
'build_cef_host.bat',
'CMakeLists.txt',
'cef_host_protocol.h',
'cef_host_win.cc',
'fetch_cef.ps1'
)
# Byte-stable (ordinal) sort: identical order on every machine and culture.
[Array]::Sort($inputs, [System.StringComparer]::Ordinal)

$sb = New-Object System.Text.StringBuilder
foreach ($rel in $inputs) {
$path = Join-Path $root $rel
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
throw "cef_host_hash: missing build input '$rel' under '$root'"
}
$fileSha = Get-NormalizedFileSha256 -Path $path
# relpath (forward slashes; inputs are flat, this only future-proofs nested
# entries) then its sha, each LF-terminated.
$relKey = $rel -replace '\\', '/'
[void]$sb.Append($relKey); [void]$sb.Append("`n")
[void]$sb.Append($fileSha); [void]$sb.Append("`n")
}

$streamBytes = [System.Text.Encoding]::UTF8.GetBytes($sb.ToString())
return (ConvertTo-Sha256Hex -Bytes $streamBytes)
}

# Standalone: print the digest for this package's native/cef_host. Dot-sourcing
# ('. .\cef_host_hash.ps1') sets InvocationName to '.', which skips this block.
if ($MyInvocation.InvocationName -ne '.') {
Write-Output (Get-CefHostInputHash)
}
Loading
Loading