diff --git a/.gitignore b/.gitignore index 0a67299..8f66413 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,10 @@ migrate_working_dir/ /build/ /coverage/ -# Native CEF host: build output + fetched CEF SDK (see native/build_cef_host.sh). -native/cef_host/build/ -native/**/cef_binary_*/ +# Native CEF host: build output + fetched CEF SDK. Path-independent so it keeps +# matching regardless of where the macOS package lives (see build_cef_host.sh). +**/cef_host/build/ +**/cef_binary_*/ + +# Federated sub-packages are libraries too — no pubspec.lock. +/packages/*/pubspec.lock diff --git a/CHANGELOG.md b/CHANGELOG.md index 27c322d..08f27a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 0.1.3 + +* **Federated package structure** (no API change): `flutter_cef` is now a + federated plugin — the app-facing package plus + `flutter_cef_platform_interface` (the shared Dart types + method-channel + contract) and the endorsed `flutter_cef_macos` (Swift plugin + `cef_host`). + Consumers keep depending on `flutter_cef` and importing + `package:flutter_cef/flutter_cef.dart` exactly as before. A Windows or Linux + implementation can now be added as a sibling `flutter_cef_` package — see + [`PORTING.md`](PORTING.md) for the contract and the platform-seam map. + ## 0.1.2 * **Navigation scheme allowlist**: `CefWebView(allowedSchemes: {...})` restricts diff --git a/PORTING.md b/PORTING.md new file mode 100644 index 0000000..638c621 --- /dev/null +++ b/PORTING.md @@ -0,0 +1,122 @@ +# Porting flutter_cef to a new platform + +`flutter_cef` is a **federated** Flutter plugin. macOS ships today +(`flutter_cef_macos`); Windows and Linux can be added as sibling +implementation packages without touching the app-facing API or the macOS +implementation. CEF itself is cross-platform — the work is re-implementing the +thin platform glue around it. + +``` +flutter_cef/ # app-facing package (re-exports the API) + packages/ + flutter_cef_platform_interface/ # the shared Dart contract (no platform code) + flutter_cef_macos/ # the macOS implementation (reference) + flutter_cef_windows/ _linux/ # <- you add these +``` + +## 1. The Dart / package side (small) + +The Dart `lib/` is already platform-neutral and the cross-platform contract is +the **method-channel protocol** (channel name + method names + event names), +so a new platform needs almost no Dart. + +1. Create `packages/flutter_cef_/` with a `pubspec.yaml`: + ```yaml + name: flutter_cef_ + dependencies: + flutter_cef_platform_interface: { path: ../flutter_cef_platform_interface } + flutter: + plugin: + implements: flutter_cef + platforms: + : + pluginClass: FlutterCefPlugin # your native host plugin + dartPluginClass: FlutterCef # endorses the channel instance + ``` +2. `lib/flutter_cef_.dart` — endorse the default method-channel instance + (mirror `flutter_cef_macos`'s `FlutterCefMacos.registerWith`): + ```dart + class FlutterCef { + static void registerWith() => + FlutterCefPlatform.instance = MethodChannelFlutterCef(); + } + ``` +3. Endorse it from the app-facing package: in `flutter_cef/pubspec.yaml` add + `platforms: : default_package: flutter_cef_` and a path dependency on + it (mirror the `macos:` entry). + +That's the entire Dart side. Everything else is native. + +## 2. The host plugin (your `FlutterCefPlugin` equivalent) + +Reference: `packages/flutter_cef_macos/macos/Classes/` (Swift). The host plugin +lives in the Flutter app's process and: + +- Registers a **platform texture** (a `FlutterTexture` on macOS) backed by a + shared GPU surface, and hands its id to the page. +- Spawns **one `cef_host` subprocess per view**, passing + `--url --width --height --dpr --iosurface-id --ipc --allowed-schemes` as argv. +- Relays method-channel calls → IPC opcodes to `cef_host`, and IPC events from + `cef_host` → `invokeMethod` back to Dart. + +The method/event protocol it must implement is the same on every platform — see +the `case` labels in `FlutterCefPlugin.handle` (host→native verbs: `create`, +`navigate`, `loadTrusted`, `resize`, `dispose`, `pointer`, `key`, `reload`, +`executeJavaScript`, cookies, IME, …) and the `emit(...)` calls (native→Dart +events: `cursor`, `loadingState`, `title`, `url`, `consoleMessage`, `jsDialog`, +`cookies`, `imeCompositionBounds`, …). + +## 3. The `cef_host` subprocess — the platform seams + +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): + +| 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 / DXGI keyed-mutex handle. **Linux**: shared memory or a DMA-buf, presented via the platform texture. Look the surface up by the `--iosurface-id`-equivalent handle the host passes. | +| **IPC transport** — a framed bidirectional byte stream to the host. Wire format: 4-byte big-endian length prefix, then `[opcode][payload]`. | `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. | +| **App / run loop** — give CEF a host application + message loop, and a per-process cache dir. | `CefHostApplication : NSApplication` (304–330); `@autoreleasepool` + `sharedApplication` (1420+); `NSTemporaryDirectory()` cache; `_NSGetExecutablePath` (1335). | **Windows/Linux**: the platform's CEF message-loop integration (`CefRunMessageLoop` or OS loop) and a per-user, per-process cache path. | +| **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**: link the `cef_sandbox` static lib + `CefScopedSandboxContext`. **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. | +| **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 +config (`cef_binary_*_{macosarm64,windows64,linux64}`), so most of +`build_cef_host.sh` and `CMakeLists.txt` is reusable — adjust the +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: + +``` +native/cef_host/ + core/ # protocol (opcodes + framing), CEF client/app, browser control, + # navigation allowlist, dispatch loop — depends only on platform.h + platform/ + mac/ # IOSurface/Metal, Cocoa app, Unix socket, sandbox, paths + / # your implementations of the same platform.h interface +``` + +This split is deliberately **not** done up front: a platform abstraction +designed without a second consumer tends to guess the seam wrong. Do it as the +first step of your port, using the table above as the contract — extract the +portable pieces into `core/` behind a small `platform.h`, implement `platform.h` +for macOS (moving the existing code) and for your OS, and you have a shared core +validated by two real platforms. + +## Checklist + +- [ ] `flutter_cef_` package: pubspec (`implements`, `pluginClass`, + `dartPluginClass`) + `registerWith`. +- [ ] Endorse via `default_package` in the app-facing `flutter_cef/pubspec.yaml`. +- [ ] Host plugin: texture registration, subprocess spawn, channel ↔ IPC relay. +- [ ] `cef_host`: implement the five seams (surface, transport, app loop, + sandbox, paths); reuse the CEF logic + opcode protocol. +- [ ] Build + bundle + (release) signing for the platform. +- [ ] `example/` runs on the platform; pointer / keyboard / IME / navigation work. diff --git a/README.md b/README.md index ee582ec..d8c9d6f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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 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+ 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. ```dart import 'package:flutter_cef/flutter_cef.dart'; @@ -76,9 +76,11 @@ Same pattern JCEF (JetBrains) and CefSharp use to render Chromium into a non-nat CEF (~200 MB) is **fetched**, not vendored. Build the renderer once: ```sh +# The macOS implementation lives in packages/flutter_cef_macos. +cd packages/flutter_cef_macos native/build_cef_host.sh # fetches CEF + builds cef_host.app export FLUTTER_CEF_HOST="$PWD/native/cef_host/build/cef_host.app/Contents/MacOS/cef_host" -cd example && flutter run -d macos +cd ../../example && flutter run -d macos ``` ### Bundling into a distributable app @@ -89,11 +91,12 @@ automatically (`$FLUTTER_CEF_HOST` → pod resources → `Contents/Frameworks` `Contents/Helpers`). After `flutter build macos`, run: ```sh -path/to/flutter_cef/tool/bundle_cef_host.sh "build/macos/.../YourApp.app" "" "" +packages/flutter_cef_macos/tool/bundle_cef_host.sh "build/macos/.../YourApp.app" "" "" ``` or wire it as a Run Script build phase on your Runner target (snippet in -`tool/bundle_cef_host.sh`) so it runs before Xcode's code-sign phase. Your host +`packages/flutter_cef_macos/tool/bundle_cef_host.sh`) so it runs before Xcode's +code-sign phase. Your host app **must not be App-Sandboxed** (CEF spawns the helper, shares a global IOSurface, writes a cache); entitlements need `com.apple.security.cs.disable-library-validation` + JIT — see @@ -117,7 +120,8 @@ build flag, `CEF_HOST_ADHOC` (default `ON`): The `OFF` posture only *validates* under correct **inside-out Developer-ID signing** of the `cef_host` tree (deepest helper → `libcef_sandbox.dylib` + CEF framework → host, depth-first, Hardened Runtime + trusted timestamp). Build it -with `CEF_HOST_ADHOC=OFF CODESIGN_ID="" native/build_cef_host.sh`, +with `CEF_HOST_ADHOC=OFF CODESIGN_ID="" +packages/flutter_cef_macos/native/build_cef_host.sh`, or — when bundled into a host app — let the app's own signing re-sign the tree with those entitlements. Ad-hoc/dev builds run unsandboxed by necessity (the sandbox can't validate without proper signing), which is why `ON` is the default. @@ -178,8 +182,11 @@ 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 federated structure is ready; each needs its own host - + shared-texture path. +- **Windows / Linux** — the package is **federated** (`flutter_cef` + + `flutter_cef_platform_interface` + `flutter_cef_macos`); a new platform is a + sibling `flutter_cef_` 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. ## Credits diff --git a/analysis_options.yaml b/analysis_options.yaml index a5744c1..93371c0 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,13 @@ include: package:flutter_lints/flutter.yaml +analyzer: + errors: + # Federation members are intentionally path deps: this repo is consumed from + # source (path / git), where path deps resolve the siblings automatically and + # hosted constraints would force every consumer to add dependency_overrides. + # `pub publish` still enforces the swap to hosted constraints — see the + # publish note in pubspec.yaml — so this only silences `flutter analyze`. + invalid_dependency: ignore + # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options diff --git a/example/README.md b/example/README.md index 3cd8c23..12bcccb 100644 --- a/example/README.md +++ b/example/README.md @@ -11,10 +11,10 @@ CEF (~200 MB) is fetched, not vendored — build the renderer once, point the plugin at it, then run: ```sh -cd .. # package root -native/build_cef_host.sh # fetches CEF + builds cef_host.app (one-time) +cd ../packages/flutter_cef_macos # the macOS implementation package +native/build_cef_host.sh # fetches CEF + builds cef_host.app (one-time) export FLUTTER_CEF_HOST="$PWD/native/cef_host/build/cef_host.app/Contents/MacOS/cef_host" -cd example && flutter run -d macos +cd ../../example && flutter run -d macos ``` Without `FLUTTER_CEF_HOST` (or a bundled `cef_host.app` — see the root diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 93593d7..9b44c46 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,7 +5,7 @@ import FlutterMacOS import Foundation -import flutter_cef +import flutter_cef_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterCefPlugin.register(with: registry.registrar(forPlugin: "FlutterCefPlugin")) diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index c77b628..c43960d 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,20 +1,20 @@ PODS: - - flutter_cef (0.1.2): + - flutter_cef_macos (0.1.2): - FlutterMacOS - FlutterMacOS (1.0.0) DEPENDENCIES: - - flutter_cef (from `Flutter/ephemeral/.symlinks/plugins/flutter_cef/macos`) + - flutter_cef_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_cef_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) EXTERNAL SOURCES: - flutter_cef: - :path: Flutter/ephemeral/.symlinks/plugins/flutter_cef/macos + flutter_cef_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_cef_macos/macos FlutterMacOS: :path: Flutter/ephemeral SPEC CHECKSUMS: - flutter_cef: 923b658c344928eb53d81cf23367973f5737457f + flutter_cef_macos: 29ec435deecded6902b4ea56dcf9e134d165a25d FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 diff --git a/example/pubspec.lock b/example/pubspec.lock index 508c415..65d6942 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -76,7 +76,21 @@ packages: path: ".." relative: true source: path - version: "0.1.2" + version: "0.1.3" + flutter_cef_macos: + dependency: transitive + description: + path: "../packages/flutter_cef_macos" + relative: true + source: path + version: "0.1.3" + flutter_cef_platform_interface: + dependency: transitive + description: + path: "../packages/flutter_cef_platform_interface" + relative: true + source: path + version: "0.1.3" flutter_driver: dependency: transitive description: flutter @@ -177,6 +191,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" process: dependency: transitive description: diff --git a/lib/flutter_cef.dart b/lib/flutter_cef.dart index 603c075..d6f63e4 100644 --- a/lib/flutter_cef.dart +++ b/lib/flutter_cef.dart @@ -3,10 +3,11 @@ /// The page renders off-screen in a `cef_host` subprocess into a shared /// IOSurface and is shown as a [Texture] (so it composites/transforms/clips /// like any widget). Pointer + keyboard input is forwarded by coordinate, and -/// the page cursor drives a [MouseRegion]. macOS only, for now. +/// the page cursor drives a [MouseRegion]. macOS only, for now (the package is +/// federated — see `flutter_cef_macos` and `PORTING.md`). library; -export 'src/cef_events.dart' +export 'package:flutter_cef_platform_interface/flutter_cef_platform_interface.dart' show CefCookie, CefConsoleMessage, diff --git a/lib/src/cef_web_controller.dart b/lib/src/cef_web_controller.dart index 7803440..df104b9 100644 --- a/lib/src/cef_web_controller.dart +++ b/lib/src/cef_web_controller.dart @@ -4,8 +4,7 @@ import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'cef_events.dart'; -import 'cef_input.dart'; +import 'package:flutter_cef_platform_interface/flutter_cef_platform_interface.dart'; /// Controls one CEF browser session: navigate, drive history, run JavaScript, /// forward input, and observe page state (loading, title, url, cursor). Backed @@ -23,7 +22,10 @@ class CefWebController { _installHandler(); } - static const MethodChannel _channel = MethodChannel('flutter_cef'); + // The method channel of the endorsed platform implementation (the default + // MethodChannelFlutterCef on macOS). A platform plugin may swap the instance + // in its registerWith; this reflects whatever is current. + static MethodChannel get _channel => FlutterCefPlatform.instance.channel; static int _counter = 0; bool _disposed = false; @@ -114,6 +116,10 @@ class CefWebController { {}; static void _installHandler() { + // Process-global, installed once on the first controller and never torn + // down; it fans events out to live controllers by sessionId and is a no-op + // for disposed ones. It binds the channel of whatever FlutterCefPlatform + // instance is current at first install (one channel in practice). if (_handlerInstalled) return; _handlerInstalled = true; _channel.setMethodCallHandler((call) async { diff --git a/lib/src/cef_web_view.dart b/lib/src/cef_web_view.dart index ccbd5b8..3ae5d9b 100644 --- a/lib/src/cef_web_view.dart +++ b/lib/src/cef_web_view.dart @@ -2,7 +2,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'cef_input.dart'; +import 'package:flutter_cef_platform_interface/flutter_cef_platform_interface.dart'; + import 'cef_web_controller.dart'; /// Per-event gain applied to trackpad two-finger pan when forwarding it to the @@ -141,6 +142,11 @@ class _CefWebViewState extends State } Future _ensureSession(Size size) async { + // Scheduled via a post-frame callback, which can fire after this State was + // disposed (same-frame removal). Bail before touching context / the + // controller so we don't read a deactivated MediaQuery or resize a + // torn-down session. + if (!mounted) return; final dpr = MediaQuery.maybeOf(context)?.devicePixelRatio ?? 1.0; final w = size.width.round(); final h = size.height.round(); @@ -316,6 +322,8 @@ class _CefWebViewState extends State // to the page as a scroll. (pan delta ≈ −scroll delta for the same intent, so // we forward it un-negated to match the wheel path above.) The OS doesn't add // momentum to OSR, so a per-event gain brings the distance closer to Chrome's. + // Required to make the Listener route pan-zoom *update* events; nothing to do + // on the start of a trackpad gesture. void _onPointerPanZoomStart(PointerPanZoomStartEvent e) {} void _onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) { @@ -628,6 +636,10 @@ class _CefWebViewState extends State _textInput?.setEditingState(_editingState); } + // Deliberately returns the empty scratch state, never a real buffer: the page + // (not Flutter) owns the text, so we keep `_editingState` as a fixed insertion + // point for composition and never mirror the page's content back to the + // framework. See `_editingState` / `_pushEditableGeometry`. @override TextEditingValue? get currentTextEditingValue => _editingState; diff --git a/packages/flutter_cef_macos/CHANGELOG.md b/packages/flutter_cef_macos/CHANGELOG.md new file mode 100644 index 0000000..3d81ed8 --- /dev/null +++ b/packages/flutter_cef_macos/CHANGELOG.md @@ -0,0 +1,7 @@ +## 0.1.3 + +* Initial release of the federated macOS implementation of `flutter_cef` (the + Swift host plugin + the `cef_host` subprocess). Split out of the `flutter_cef` + package; no API change for app-facing consumers. Carries the navigation scheme + allowlist, the host-trusted-load exemption, and the `CEF_HOST_ADHOC` build + flag (signed-release sandbox + real Keychain + release entitlements). diff --git a/packages/flutter_cef_macos/LICENSE b/packages/flutter_cef_macos/LICENSE new file mode 100644 index 0000000..3f83b7d --- /dev/null +++ b/packages/flutter_cef_macos/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 the flutter_cef authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/flutter_cef_macos/README.md b/packages/flutter_cef_macos/README.md new file mode 100644 index 0000000..9f072d9 --- /dev/null +++ b/packages/flutter_cef_macos/README.md @@ -0,0 +1,16 @@ +# flutter_cef_macos + +The macOS implementation of the +[`flutter_cef`](https://github.com/FlutterFlow/flutter_cef) plugin. + +It renders a live Chromium (CEF) browser off-screen in a `cef_host` subprocess +and presents it as a Flutter `Texture`. This is the **endorsed** macOS +implementation, pulled in automatically when you depend on `flutter_cef` — you +should depend on `flutter_cef`, not this package directly. + +Native build + bundling lives here (`native/build_cef_host.sh`, +`tool/bundle_cef_host.sh`); see the root +[README](https://github.com/FlutterFlow/flutter_cef#building) for build, +bundling, and signing, and +[`PORTING.md`](https://github.com/FlutterFlow/flutter_cef/blob/main/PORTING.md) +for the platform-seam map. diff --git a/packages/flutter_cef_macos/analysis_options.yaml b/packages/flutter_cef_macos/analysis_options.yaml new file mode 100644 index 0000000..397776d --- /dev/null +++ b/packages/flutter_cef_macos/analysis_options.yaml @@ -0,0 +1,12 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + # Depends on flutter_cef_platform_interface via a path dep on purpose: this + # repo is consumed from source (path / git). `pub publish` still enforces the + # swap to a hosted constraint — see the publish note in the root pubspec — so + # this only silences `flutter analyze`. + invalid_dependency: ignore + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_cef_macos/lib/flutter_cef_macos.dart b/packages/flutter_cef_macos/lib/flutter_cef_macos.dart new file mode 100644 index 0000000..d58e84c --- /dev/null +++ b/packages/flutter_cef_macos/lib/flutter_cef_macos.dart @@ -0,0 +1,19 @@ +import 'package:flutter_cef_platform_interface/flutter_cef_platform_interface.dart'; + +/// The macOS implementation of `flutter_cef`. +/// +/// The macOS plugin is native-only: the Swift `FlutterCefPlugin` (see +/// `macos/Classes/`) spawns and talks to a per-view `cef_host` subprocess over +/// the `flutter_cef` method channel. This Dart class exists only to **endorse** +/// the default method-channel platform instance at registration time; there is +/// no macOS-specific Dart behavior. +/// +/// Registered via `dartPluginClass: FlutterCefMacos` in this package's pubspec — +/// the Flutter tool calls [registerWith] during plugin registration. +class FlutterCefMacos { + /// Sets the [FlutterCefPlatform] instance to the method-channel + /// implementation (the contract the native macOS plugin speaks). + static void registerWith() { + FlutterCefPlatform.instance = MethodChannelFlutterCef(); + } +} diff --git a/macos/Classes/CefWebSession.swift b/packages/flutter_cef_macos/macos/Classes/CefWebSession.swift similarity index 100% rename from macos/Classes/CefWebSession.swift rename to packages/flutter_cef_macos/macos/Classes/CefWebSession.swift diff --git a/macos/Classes/FlutterCefPlugin.swift b/packages/flutter_cef_macos/macos/Classes/FlutterCefPlugin.swift similarity index 95% rename from macos/Classes/FlutterCefPlugin.swift rename to packages/flutter_cef_macos/macos/Classes/FlutterCefPlugin.swift index cc5b9d2..2f216f8 100644 --- a/macos/Classes/FlutterCefPlugin.swift +++ b/packages/flutter_cef_macos/macos/Classes/FlutterCefPlugin.swift @@ -1,14 +1,13 @@ import Cocoa import FlutterMacOS -/// macOS plugin entry point. Channel `flutter_cef`. Verbs: -/// create {sessionId, url, width, height, dpr} -> {textureId, width, height} -/// navigate{sessionId, url} -/// resize {sessionId, width, height, dpr} -/// dispose {sessionId} -/// pointer {sessionId, type, button, clickCount, modifiers, x, y, dx, dy} -/// key {sessionId, type, modifiers, windowsKeyCode, nativeKeyCode, character} -/// Host -> Dart: invokeMethod("cursor", {sessionId, cursor}). +/// macOS plugin entry point. Channel `flutter_cef`. The host->native verbs (the +/// `case` labels in `handle(_:result:)`) and the native->Dart events (the +/// `emit(...)` calls in `create`) together form the cross-platform method-channel +/// protocol — see PORTING.md for the authoritative list. Each verb carries a +/// `sessionId`; `create` returns `{textureId, width, height}`. The Swift side +/// only relays: it spawns and talks to a per-view `cef_host` subprocess (see +/// `CefWebSession`) over the IPC opcode protocol. public class FlutterCefPlugin: NSObject, FlutterPlugin { private weak var textureRegistry: FlutterTextureRegistry? private var channel: FlutterMethodChannel? diff --git a/macos/Resources/PrivacyInfo.xcprivacy b/packages/flutter_cef_macos/macos/Resources/PrivacyInfo.xcprivacy similarity index 100% rename from macos/Resources/PrivacyInfo.xcprivacy rename to packages/flutter_cef_macos/macos/Resources/PrivacyInfo.xcprivacy diff --git a/macos/flutter_cef.podspec b/packages/flutter_cef_macos/macos/flutter_cef_macos.podspec similarity index 95% rename from macos/flutter_cef.podspec rename to packages/flutter_cef_macos/macos/flutter_cef_macos.podspec index acbb104..738df28 100644 --- a/macos/flutter_cef.podspec +++ b/packages/flutter_cef_macos/macos/flutter_cef_macos.podspec @@ -5,8 +5,8 @@ # that with native/build_cef_host.sh; see the README for bundling/signing. # Pod::Spec.new do |s| - s.name = 'flutter_cef' - s.version = '0.1.2' + s.name = 'flutter_cef_macos' + s.version = '0.1.3' s.summary = 'Live Chromium (CEF) browser as a Flutter Texture (macOS).' s.description = <<-DESC Embed a live Chromium browser via CEF off-screen rendering, shown as a Flutter diff --git a/native/build_cef_host.sh b/packages/flutter_cef_macos/native/build_cef_host.sh similarity index 100% rename from native/build_cef_host.sh rename to packages/flutter_cef_macos/native/build_cef_host.sh diff --git a/native/cef_host/CMakeLists.txt b/packages/flutter_cef_macos/native/cef_host/CMakeLists.txt similarity index 100% rename from native/cef_host/CMakeLists.txt rename to packages/flutter_cef_macos/native/cef_host/CMakeLists.txt diff --git a/native/cef_host/Info.plist b/packages/flutter_cef_macos/native/cef_host/Info.plist similarity index 100% rename from native/cef_host/Info.plist rename to packages/flutter_cef_macos/native/cef_host/Info.plist diff --git a/native/cef_host/entitlements.plist b/packages/flutter_cef_macos/native/cef_host/entitlements.plist similarity index 100% rename from native/cef_host/entitlements.plist rename to packages/flutter_cef_macos/native/cef_host/entitlements.plist diff --git a/native/cef_host/entitlements.release.plist b/packages/flutter_cef_macos/native/cef_host/entitlements.release.plist similarity index 100% rename from native/cef_host/entitlements.release.plist rename to packages/flutter_cef_macos/native/cef_host/entitlements.release.plist diff --git a/native/cef_host/helper-Info.plist.in b/packages/flutter_cef_macos/native/cef_host/helper-Info.plist.in similarity index 100% rename from native/cef_host/helper-Info.plist.in rename to packages/flutter_cef_macos/native/cef_host/helper-Info.plist.in diff --git a/native/cef_host/main.mm b/packages/flutter_cef_macos/native/cef_host/main.mm similarity index 98% rename from native/cef_host/main.mm rename to packages/flutter_cef_macos/native/cef_host/main.mm index 5f41bbb..c7211aa 100644 --- a/native/cef_host/main.mm +++ b/packages/flutter_cef_macos/native/cef_host/main.mm @@ -27,11 +27,11 @@ // Keychain (OSCrypt) — and so requires correct inside-out Developer-ID signing. // // Args: --url= --width= --height= --dpr= --iosurface-id= -// --ipc= +// --ipc= --allowed-schemes= // // IPC wire format: 4-byte big-endian length prefix, then [opcode][payload]. // The full opcode table lives in the `kOp*` constants below — each carries its -// payload layout; cef_host -> host are 0x01-0x1a, host -> cef_host 0x10-0x33. +// payload layout; cef_host -> host are 0x01-0x1a, host -> cef_host 0x10-0x34. #import #import @@ -154,6 +154,9 @@ // the exact URL (and main frame) in OnBeforeBrowse means a page nav to a // different URL can never steal another load's exemption. A multiset tolerates // identical concurrent trusted loads. UI-thread only, so no lock. +// (A page racing the host to the EXACT same data:/file: URL could consume one +// armed entry, but that is benign — it loads the same content the host chose — +// so it is not defended beyond exact-URL matching.) std::multiset g_trusted_pending; // Pending JS dialog callbacks, keyed by id. UI-thread-only (OnJSDialog and the @@ -818,7 +821,8 @@ void OnBeforeCommandLineProcessing( } void OnContextInitialized() override { CEF_REQUIRE_UI_THREAD(); - fprintf(stderr, "[cef_host] OnContextInitialized\n"); + if (std::getenv("FLUTTER_CEF_DEBUG")) + fprintf(stderr, "[cef_host] OnContextInitialized\n"); CefWindowInfo window_info; window_info.SetAsWindowless(0); #ifdef CEF_HOST_MULTIPROCESS @@ -837,7 +841,8 @@ void OnContextInitialized() override { g_browser = CefBrowserHost::CreateBrowserSync( window_info, new HostClient(), g_initial_url, settings, nullptr, nullptr); - fprintf(stderr, "[cef_host] browser=%p\n", (void*)g_browser.get()); + if (std::getenv("FLUTTER_CEF_DEBUG")) + fprintf(stderr, "[cef_host] browser=%p\n", (void*)g_browser.get()); SendFrame(kOpReady, nullptr, 0); } IMPLEMENT_REFCOUNTING(HostApp); @@ -1470,16 +1475,26 @@ int main(int argc, char* argv[]) { fprintf(stderr, "[cef_host] CefInitialize failed\n"); return 1; } - fprintf(stderr, "[cef_host] CefInitialize OK (fd=%d surface=%p)\n", g_ipc_fd, - (void*)g_surface); + if (std::getenv("FLUTTER_CEF_DEBUG")) + fprintf(stderr, "[cef_host] CefInitialize OK (fd=%d surface=%p)\n", + g_ipc_fd, (void*)g_surface); std::thread reader; if (g_ipc_fd >= 0) reader = std::thread(&IpcReadLoop); std::thread(&WatchParentDeath, getppid()).detach(); CefRunMessageLoop(); if (reader.joinable()) { - shutdown(g_ipc_fd, SHUT_RDWR); + shutdown(g_ipc_fd, SHUT_RDWR); // unblock the reader's blocking read reader.join(); } + // Reader is joined (no more reads); close the socket under the write mutex + // (no concurrent SendFrame) and clear the fd so any late write is a no-op. + { + std::lock_guard lock(g_ipc_write_mutex); + if (g_ipc_fd >= 0) { + close(g_ipc_fd); + g_ipc_fd = -1; + } + } CefShutdown(); } return 0; diff --git a/native/cef_host/process_helper.mm b/packages/flutter_cef_macos/native/cef_host/process_helper.mm similarity index 100% rename from native/cef_host/process_helper.mm rename to packages/flutter_cef_macos/native/cef_host/process_helper.mm diff --git a/packages/flutter_cef_macos/pubspec.yaml b/packages/flutter_cef_macos/pubspec.yaml new file mode 100644 index 0000000..5e29bdb --- /dev/null +++ b/packages/flutter_cef_macos/pubspec.yaml @@ -0,0 +1,37 @@ +name: flutter_cef_macos +description: "macOS implementation of the flutter_cef plugin: a live Chromium (CEF) browser rendered off-screen in a cef_host subprocess and shown as a Flutter Texture." +version: 0.1.3 +homepage: https://github.com/FlutterFlow/flutter_cef +repository: https://github.com/FlutterFlow/flutter_cef +issue_tracker: https://github.com/FlutterFlow/flutter_cef/issues +topics: + - webview + - chromium + - cef + - browser + +environment: + sdk: ^3.4.0 + flutter: '>=3.22.0' + +dependencies: + flutter: + sdk: flutter + flutter_cef_platform_interface: + path: ../flutter_cef_platform_interface + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + plugin: + # Endorsed implementation of the app-facing `flutter_cef` plugin. + implements: flutter_cef + platforms: + macos: + # Swift host plugin (spawns + talks to the cef_host subprocess). + pluginClass: FlutterCefPlugin + # Dart entrypoint: endorses the default method-channel platform instance. + dartPluginClass: FlutterCefMacos diff --git a/tool/bundle_cef_host.sh b/packages/flutter_cef_macos/tool/bundle_cef_host.sh similarity index 74% rename from tool/bundle_cef_host.sh rename to packages/flutter_cef_macos/tool/bundle_cef_host.sh index b370ee6..b280110 100755 --- a/tool/bundle_cef_host.sh +++ b/packages/flutter_cef_macos/tool/bundle_cef_host.sh @@ -14,6 +14,13 @@ # native/cef_host/build/cef_host.app # [signing-identity] default: "-" (ad-hoc). Pass your Developer ID / Apple # Development identity for a distributable build. +# +# Entitlements are chosen by signing posture: an ad-hoc ("-") signature is a dev +# bundle and keeps entitlements.plist (with get-task-allow, for local debugging); +# a real identity is a distributable build and uses entitlements.release.plist +# (NO get-task-allow — it hard-fails notarization and is a task-port +# privilege-escalation vector on a process running untrusted JIT'd web content). +# Override the choice explicitly with CEF_HOST_ENTITLEMENTS=. set -euo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" @@ -21,7 +28,14 @@ APP="${1:?usage: bundle_cef_host.sh [cef_host.app] [identity]}" HOST_APP="${2:-${FLUTTER_CEF_HOST_APP:-$HERE/../native/cef_host/build/cef_host.app}}" IDENTITY="${3:-${EXPANDED_CODE_SIGN_IDENTITY:-}}" [ -z "$IDENTITY" ] && IDENTITY="-" -ENT="$HERE/../native/cef_host/entitlements.plist" +if [ -n "${CEF_HOST_ENTITLEMENTS:-}" ]; then + ENT="$CEF_HOST_ENTITLEMENTS" +elif [ "$IDENTITY" = "-" ]; then + ENT="$HERE/../native/cef_host/entitlements.plist" # dev / ad-hoc +else + ENT="$HERE/../native/cef_host/entitlements.release.plist" # distributable +fi +echo "[flutter_cef] signing identity: $IDENTITY ; entitlements: $ENT" [ -d "$APP" ] || { echo "no such app bundle: $APP" >&2; exit 1; } [ -d "$HOST_APP" ] || { diff --git a/packages/flutter_cef_platform_interface/CHANGELOG.md b/packages/flutter_cef_platform_interface/CHANGELOG.md new file mode 100644 index 0000000..30b8f07 --- /dev/null +++ b/packages/flutter_cef_platform_interface/CHANGELOG.md @@ -0,0 +1,6 @@ +## 0.1.3 + +* Initial release of the federated common platform interface for `flutter_cef`: + the shared Dart types (`cef_events`, `cef_input`) and the `FlutterCefPlatform` + contract with its default `MethodChannelFlutterCef`. Split out of the + `flutter_cef` package; no API change for app-facing consumers. diff --git a/packages/flutter_cef_platform_interface/LICENSE b/packages/flutter_cef_platform_interface/LICENSE new file mode 100644 index 0000000..3f83b7d --- /dev/null +++ b/packages/flutter_cef_platform_interface/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 the flutter_cef authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/flutter_cef_platform_interface/README.md b/packages/flutter_cef_platform_interface/README.md new file mode 100644 index 0000000..316d2fb --- /dev/null +++ b/packages/flutter_cef_platform_interface/README.md @@ -0,0 +1,14 @@ +# flutter_cef_platform_interface + +The common platform interface for the +[`flutter_cef`](https://github.com/FlutterFlow/flutter_cef) plugin. + +It holds the shared Dart types (`CefCookie`, `CefLoadError`, the input +mappings, …) and the `FlutterCefPlatform` contract that each platform +implementation (macOS today, Windows / Linux later) speaks. The cross-platform +contract is the method-channel protocol — see +[`PORTING.md`](https://github.com/FlutterFlow/flutter_cef/blob/main/PORTING.md). + +App developers should depend on `flutter_cef`, not this package directly. A new +platform implementation depends on this package and endorses the default +method-channel instance from its `registerWith`. diff --git a/packages/flutter_cef_platform_interface/analysis_options.yaml b/packages/flutter_cef_platform_interface/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/packages/flutter_cef_platform_interface/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/flutter_cef_platform_interface/lib/flutter_cef_platform_interface.dart b/packages/flutter_cef_platform_interface/lib/flutter_cef_platform_interface.dart new file mode 100644 index 0000000..7aa4410 --- /dev/null +++ b/packages/flutter_cef_platform_interface/lib/flutter_cef_platform_interface.dart @@ -0,0 +1,12 @@ +/// The common platform interface for the `flutter_cef` plugin. +/// +/// Holds the shared Dart types ([CefCookie], [CefLoadError], input mappings, +/// …) and the [FlutterCefPlatform] contract that each platform implementation +/// (macOS, and future Windows / Linux) speaks. App-facing code depends on +/// `package:flutter_cef/flutter_cef.dart`, which re-exports the types here. +library; + +export 'src/cef_events.dart'; +export 'src/cef_input.dart'; +export 'src/flutter_cef_platform.dart'; +export 'src/method_channel_flutter_cef.dart'; diff --git a/lib/src/cef_events.dart b/packages/flutter_cef_platform_interface/lib/src/cef_events.dart similarity index 100% rename from lib/src/cef_events.dart rename to packages/flutter_cef_platform_interface/lib/src/cef_events.dart diff --git a/lib/src/cef_input.dart b/packages/flutter_cef_platform_interface/lib/src/cef_input.dart similarity index 100% rename from lib/src/cef_input.dart rename to packages/flutter_cef_platform_interface/lib/src/cef_input.dart diff --git a/packages/flutter_cef_platform_interface/lib/src/flutter_cef_platform.dart b/packages/flutter_cef_platform_interface/lib/src/flutter_cef_platform.dart new file mode 100644 index 0000000..cceb114 --- /dev/null +++ b/packages/flutter_cef_platform_interface/lib/src/flutter_cef_platform.dart @@ -0,0 +1,49 @@ +import 'package:flutter/services.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'method_channel_flutter_cef.dart'; + +/// The interface that platform-specific implementations of `flutter_cef` must +/// implement to be endorsed (macOS today; Windows / Linux down the line). +/// +/// The cross-platform contract is the **method-channel protocol**: every +/// platform exposes a [MethodChannel] named [channelName] over which the +/// app-facing `CefWebController` drives a per-view `cef_host` subprocess +/// (create / navigate / input / dispose) and receives page events. The native +/// side of each platform plugin implements that protocol; the Dart side is +/// usually just this endorsement (see the default [MethodChannelFlutterCef]). +/// +/// A platform plugin registers by setting [instance] from its `registerWith` +/// (wired via `dartPluginClass` in the implementation package's pubspec). +abstract class FlutterCefPlatform extends PlatformInterface { + /// Constructs a [FlutterCefPlatform]. + FlutterCefPlatform() : super(token: _token); + + static final Object _token = Object(); + + static FlutterCefPlatform _instance = MethodChannelFlutterCef(); + + /// The default instance to use — the method-channel implementation, which + /// works for any platform whose native plugin speaks the [channelName] + /// protocol (all of them today). + static FlutterCefPlatform get instance => _instance; + + /// Platform implementations set this in their `registerWith`. The setter + /// verifies the [PlatformInterface] token to discourage implementations that + /// `implements` rather than `extends` this class. + static set instance(FlutterCefPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// The name of the method channel the app-facing code and the native plugin + /// communicate over. This is the heart of the cross-platform contract — see + /// `PORTING.md` for the full method + event + IPC-opcode protocol. + static const String channelName = 'flutter_cef'; + + /// The channel the app-facing `CefWebController` talks over. The default + /// returns `MethodChannel(channelName)`; a platform only overrides this if it + /// needs a non-standard transport (none do today). + MethodChannel get channel => + throw UnimplementedError('channel has not been implemented.'); +} diff --git a/packages/flutter_cef_platform_interface/lib/src/method_channel_flutter_cef.dart b/packages/flutter_cef_platform_interface/lib/src/method_channel_flutter_cef.dart new file mode 100644 index 0000000..e00939c --- /dev/null +++ b/packages/flutter_cef_platform_interface/lib/src/method_channel_flutter_cef.dart @@ -0,0 +1,18 @@ +import 'package:flutter/services.dart'; + +import 'flutter_cef_platform.dart'; + +/// The default [FlutterCefPlatform] implementation: a plain [MethodChannel] +/// named [FlutterCefPlatform.channelName]. +/// +/// This works for any platform whose native plugin speaks the channel protocol +/// (macOS today, Windows / Linux later), so most platform implementations need +/// no Dart-side override — they just provide the native plugin and endorse this +/// default instance from their `registerWith`. +class MethodChannelFlutterCef extends FlutterCefPlatform { + final MethodChannel _channel = + const MethodChannel(FlutterCefPlatform.channelName); + + @override + MethodChannel get channel => _channel; +} diff --git a/packages/flutter_cef_platform_interface/pubspec.yaml b/packages/flutter_cef_platform_interface/pubspec.yaml new file mode 100644 index 0000000..3569721 --- /dev/null +++ b/packages/flutter_cef_platform_interface/pubspec.yaml @@ -0,0 +1,29 @@ +name: flutter_cef_platform_interface +description: "A common platform interface for the flutter_cef plugin: the shared Dart types and the method-channel contract that each platform implementation (macOS, and future Windows/Linux) speaks." +version: 0.1.3 +homepage: https://github.com/FlutterFlow/flutter_cef +repository: https://github.com/FlutterFlow/flutter_cef +issue_tracker: https://github.com/FlutterFlow/flutter_cef/issues +topics: + - webview + - chromium + - cef + - browser +# NOTE: We strongly prefer a constraint that allows the current version and +# future patch/minor releases of the interface so federated implementations +# stay compatible. Breaking changes to this package require a major bump and a +# coordinated update of all implementations. + +environment: + sdk: ^3.4.0 + flutter: '>=3.22.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index b564a45..db7cac3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_cef description: "Embed a live Chromium (CEF) browser as a Flutter Texture on macOS: composites and clips like any widget, with input forwarding, a JS bridge, navigation and page events." -version: 0.1.2 +version: 0.1.3 homepage: https://github.com/FlutterFlow/flutter_cef repository: https://github.com/FlutterFlow/flutter_cef issue_tracker: https://github.com/FlutterFlow/flutter_cef/issues @@ -17,6 +17,19 @@ environment: dependencies: flutter: sdk: flutter + # Federation members are wired as path deps: the repo is consumed from source + # (path / git), not pub.dev. PUBLISHING (when it happens): `pub publish` + # rejects path deps, so publish bottom-up — flutter_cef_platform_interface, + # then flutter_cef_macos, then this package — and at each step swap the sibling + # `path:` deps for hosted caret constraints (`^0.1.x`, the + # minor-compatible policy noted in the platform_interface pubspec), keeping the + # `path:` entries under `dependency_overrides` for local dev. + flutter_cef_platform_interface: + path: packages/flutter_cef_platform_interface + # Endorsed default macOS implementation (see `default_package` below). Depended + # on directly so a plain dependency on `flutter_cef` provides macOS support. + flutter_cef_macos: + path: packages/flutter_cef_macos dev_dependencies: flutter_test: @@ -41,7 +54,9 @@ flutter: plugin: platforms: macos: - pluginClass: FlutterCefPlugin + # Federated: the macOS implementation lives in the endorsed + # flutter_cef_macos package (see packages/flutter_cef_macos). + default_package: flutter_cef_macos # To add assets to your plugin package, add an assets section, like this: # assets: diff --git a/specs/cross-platform/PLAN.md b/specs/cross-platform/PLAN.md new file mode 100644 index 0000000..a6445c6 --- /dev/null +++ b/specs/cross-platform/PLAN.md @@ -0,0 +1,88 @@ +# flutter_cef — federated cross-platform restructure + +Goal: restructure the package so a Windows/Linux implementation can be added +later as an independent, endorsed federated package, without touching the +app-facing API or the macOS implementation. The Dart `lib/` is already +platform-neutral; the work is (1) splitting into federated packages and (2) +giving the native `cef_host` an internal portable-core / platform-glue seam so +the C++ is shareable across OS implementations down the line. + +## Target layout + +``` +flutter_cef/ # repo root = app-facing package (unchanged name/version) + lib/ + flutter_cef.dart # re-exports public API + shared types + src/cef_web_view.dart # widget (app-facing) + src/cef_web_controller.dart # controller (app-facing) — talks the shared channel + example/ # unchanged location; deps resolve to the federated packages + packages/ + flutter_cef_platform_interface/ # the cross-platform contract + lib/flutter_cef_platform_interface.dart + lib/src/flutter_cef_platform.dart # FlutterCefPlatform (PlatformInterface) + channel name + lib/src/method_channel_flutter_cef.dart + lib/src/cef_events.dart # shared DTOs (moved from root lib/src) + lib/src/cef_input.dart # shared input enums/helpers (moved) + flutter_cef_macos/ # endorsed macOS implementation (self-contained) + lib/flutter_cef_macos.dart # registerWith → sets the default platform instance + macos/ # Swift plugin + podspec (moved from root macos/) + native/cef_host/ # moved from root native/; restructured: + core/ # PORTABLE C++: protocol + CEF client/app + browser control + platform/mac/ # macOS glue: IOSurface/Metal/Cocoa/Unix-socket/sandbox + tool/bundle_cef_host.sh # moved from root tool/ + PORTING.md # what a flutter_cef_windows / _linux must implement +``` + +`flutter_cef` depends on `flutter_cef_platform_interface` and endorses +`flutter_cef_macos` via `plugin: platforms: macos: default_package`. + +## The cross-platform contract (what a new platform implements) + +1. **Dart**: a `flutter_cef_` package with `dartPluginClass` that sets + `FlutterCefPlatform.instance`. In practice the contract is the method-channel + protocol (channel name + method names + event names + the IPC opcode table), + so a platform plugin mostly implements the native side. +2. **Native `cef_host`** — implement `core/platform.h` for the OS: + - **Surface**: allocate/lookup a shared GPU surface by id + present painted + frames. macOS = IOSurface-backed CVPixelBuffer + Metal; Windows = shared + D3D11 texture / DXGI handle; Linux = shared memory / DMA-buf. + - **IPC transport**: framed read/write. macOS/Linux = Unix domain socket; + Windows = named pipe. + - **App bootstrap / run loop**: macOS = NSApplication; Windows/Linux = their + message loops. + - **Sandbox**: macOS = `CefScopedSandboxContext`; Windows = `cef_sandbox` + static lib + `CefScopedSandboxContext`; Linux = SUID/userns sandbox. + - **Framework/resource path resolution** for the CEF binary distribution. +3. **Host plugin** (`macos/` equivalent): spawn the `cef_host` subprocess, own + the texture registration, relay channel calls to the IPC opcodes. + +## Phases (each ends at a green gate: analyze + test + macOS example build) + +- **P1 — platform_interface package.** Create + `packages/flutter_cef_platform_interface`; move `cef_events.dart` + + `cef_input.dart`; add `FlutterCefPlatform` (PlatformInterface) holding the + channel name + default `MethodChannelFlutterCef`. Root `flutter_cef` depends on + it; controller uses the shared channel name. Gate: `flutter analyze` + `flutter test`. +- **P2 — flutter_cef_macos package.** Move `macos/`, `native/`, `tool/` into + `packages/flutter_cef_macos`; rename podspec → `flutter_cef_macos.podspec`; fix + every relative path (`build_cef_host.sh`, `bundle_cef_host.sh`, podspec, + CMake, FLUTTER_CEF_HOST resolution in the Swift plugin); add + `lib/flutter_cef_macos.dart` + `dartPluginClass`. Root pubspec endorses it via + `default_package`. Gate: example macOS build + signed-host smoke. +- **P3 — native core/platform split. DEFERRED BY DESIGN.** A platform + abstraction designed without a second consumer tends to guess the seam wrong, + and a blind 1486-line teardown risks regressing the just-proven macOS build for + no validated benefit. Instead the seam is **mapped precisely in `PORTING.md`** + (surface / IPC transport / app loop / sandbox / framework-path, with + `main.mm` file:line references), and the `core/` + `platform/` code-split is + the documented first step of the first real non-macOS port — where two + consumers validate the abstraction. +- **P4 — docs.** `PORTING.md`, README structure, CHANGELOG. Gate: `git diff --check`. + +## Invariants + +- macOS must build + render at every phase gate (verify, don't assume). +- App-facing API (`CefWebView`, `CefWebController`, exported types) unchanged — + consumers' imports of `package:flutter_cef/flutter_cef.dart` keep working. +- No behavior change; this is structure only. +- Dev path-deps during the restructure; switch to version deps only at publish. diff --git a/test/cef_input_test.dart b/test/cef_input_test.dart index 7c7f320..6bb1250 100644 --- a/test/cef_input_test.dart +++ b/test/cef_input_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_cef/src/cef_input.dart'; +import 'package:flutter_cef_platform_interface/flutter_cef_platform_interface.dart'; void main() { group('cefWindowsKeyCode — editing/navigation keys resolve to Windows VK',