Repository navigation
Add CEF (Chromium Embedded Framework) as alternative browser engine #3989
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ericwang520
wants to merge
11
commits into
manaflow-ai:main
Choose a base branch
from
ericwang520:cef-webview
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
275fe34
Vendor CEF Chrome runtime Swift package
ericwang520 a051244
Phase 2: wire CEF as alternative browser engine behind Debug menu flag
ericwang520 d33a908
fix: download CEF runtime on first use
ericwang520 0dcf92b
Merge upstream/main into cef-webview
ericwang520 87339b1
docs: explain tagged reload command
ericwang520 5eb79dd
fix: gate CEF selection on supported macOS
ericwang520 7aa9174
fix: address CEF review blockers
ericwang520 117406f
fix: clean up CEF panel and demo review issues
ericwang520 25900f2
fix: show resolved browser engine selection
ericwang520 27e31b3
fix: support Xcode 16.4 CEF package builds
ericwang520 88f888d
fix: avoid CEF message pump re-entry
ericwang520 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Frameworks |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| # Wiring CMUXCEF into the cmux Xcode project | ||
|
|
||
| This is the manual checklist a human (or Xcode-aware agent) needs to run | ||
| once to make cmux build with the bundled CEF browser engine. After this | ||
| is done, `import CMUXCEF` works from any Swift file under `Sources/`, | ||
| and the CEF helper apps + framework get embedded into the cmux.app | ||
| bundle at build time. | ||
|
|
||
| The CMUXCEF Swift package itself (this directory) is already self- | ||
| contained. `swift build` succeeds from `CEF/` without touching the cmux | ||
| Xcode project. | ||
|
|
||
| ## 1. Provision CEF binaries (one-off after a fresh checkout) | ||
|
|
||
| ```bash | ||
| cd CEF | ||
| vendor/fetch_cef.sh | ||
| ``` | ||
|
|
||
| This downloads `cef_binary_146.0.10+g8219561+chromium-146.0.7680.179_macosarm64.tar.bz2` | ||
| (~270 MiB), verifies its SHA1 against `cef.lock.json`, extracts under | ||
| `CEF/CEF/`, builds the C++ wrapper, and lays out | ||
| `CEF/Frameworks/Chromium Embedded Framework.framework` + helpers. | ||
|
|
||
| The downloads are cached at `~/Library/Caches/cmux-cef-vendor/` so | ||
| subsequent runs are fast / offline-safe. | ||
|
|
||
| ## 2. Add CMUXCEF as a local Swift Package dependency | ||
|
|
||
| In `GhosttyTabs.xcodeproj` → Project → Package Dependencies: | ||
|
|
||
| - Click **+** → **Add Local…** | ||
| - Navigate to `cmux/cef` and add the package. | ||
| - Add the **CMUXCEF** library to the `cmux` target's | ||
| "Frameworks, Libraries, and Embedded Content". | ||
|
|
||
| `CMUXCEFHelper` and `CMUXCEFHelperRenderer` are exposed as executable | ||
| products of the same package — they will be needed by Step 4. | ||
|
|
||
| ## 3. "Fetch CEF" build phase | ||
|
|
||
| On the `cmux` target → Build Phases, add a new **Run Script Phase** | ||
| above the "Copy Bundle Resources" phase: | ||
|
|
||
| ```bash | ||
| "${SRCROOT}/CEF/vendor/fetch_cef.sh" | ||
| ``` | ||
|
|
||
| Input files: `$(SRCROOT)/CEF/vendor/cef.lock.json` | ||
| Output files: `$(SRCROOT)/CEF/Frameworks/Chromium Embedded Framework.framework/Versions/A/Chromium Embedded Framework` | ||
|
|
||
| This makes Xcode rerun the fetch only when the lockfile changes. | ||
|
|
||
| ## 4. Embed the CEF framework + helper apps | ||
|
|
||
| Add another Run Script Phase **after** "Compile Sources" and **before** | ||
| "Embed Frameworks": | ||
|
|
||
| ```bash | ||
| set -euo pipefail | ||
| FRAMEWORKS_DIR="${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/../Frameworks" | ||
| mkdir -p "${FRAMEWORKS_DIR}" | ||
|
|
||
| # 1. Framework | ||
| rsync -a --delete \ | ||
| "${SRCROOT}/CEF/Frameworks/Chromium Embedded Framework.framework" \ | ||
| "${FRAMEWORKS_DIR}/" | ||
|
|
||
| # 2. Build SPM helper executables for the cmux configuration. | ||
| SWIFT_CFG=$(echo "${CONFIGURATION}" | tr '[:upper:]' '[:lower:]') | ||
| case "${SWIFT_CFG}" in | ||
| debug|release) ;; | ||
| *) SWIFT_CFG="debug" ;; | ||
| esac | ||
|
|
||
| (cd "${SRCROOT}/CEF" && xcrun swift build -c "${SWIFT_CFG}" \ | ||
| --product CMUXCEFHelper --product CMUXCEFHelperRenderer) | ||
|
|
||
| ARCH="$(uname -m)" | ||
| BUILD_BIN="${SRCROOT}/CEF/.build/${ARCH}-apple-macosx/${SWIFT_CFG}" | ||
|
|
||
| # 3. Wrap helpers into the .app bundles that CEF expects. | ||
| "${SRCROOT}/CEF/Scripts/embed_helpers_into_bundle.sh" \ | ||
| "${BUILD_BIN}/CMUXCEFHelper" \ | ||
| "${BUILD_BIN}/CMUXCEFHelperRenderer" \ | ||
| "${FRAMEWORKS_DIR}" | ||
| ``` | ||
|
|
||
| `Scripts/embed_helpers_into_bundle.sh` is a thin wrapper around the | ||
| prototype's `Sources/WebView/Scripts/embed_cef_helpers.sh` adapted to | ||
| this layout — I will land that script once the build phase is in place. | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| ## 5. Entitlements | ||
|
|
||
| The browser-process target (cmux) and the helper apps all need V8 JIT | ||
| and unsigned executable memory. Add to **cmux.entitlements**: | ||
|
|
||
| ```xml | ||
| <key>com.apple.security.cs.allow-jit</key> | ||
| <true/> | ||
| <key>com.apple.security.cs.allow-unsigned-executable-memory</key> | ||
| <true/> | ||
| ``` | ||
|
|
||
| For **debug builds only** (cmux.entitlements is shared) the CEF | ||
| framework is ad-hoc signed by `vendor/fetch_cef.sh`, so library | ||
| validation has to be relaxed: | ||
|
|
||
| ```xml | ||
| <key>com.apple.security.cs.disable-library-validation</key> | ||
| <true/> | ||
| ``` | ||
|
|
||
| Production builds re-sign CEF with the cmux Developer ID; the | ||
| `disable-library-validation` entitlement is removed from the release | ||
| entitlements file. | ||
|
|
||
| Helper apps get their own entitlements plist with the same three | ||
| entries plus `get-task-allow` for dev builds. The embed script in | ||
| Step 4 writes that plist. | ||
|
|
||
| ## 6. Verify | ||
|
|
||
| ```bash | ||
| ./scripts/reload.sh --tag cef-feature | ||
| ``` | ||
|
|
||
| Should build cleanly. Launch the resulting `cmux DEV cef-feature.app` | ||
| — it boots normally because the CEF code path is gated behind a | ||
| feature flag and the flag is off by default. The build success is the | ||
| acceptance criterion for this step. | ||
|
|
||
| ## 7. Flip the flag and write `CEFBrowserPanel` | ||
|
|
||
| That's Step 3 of `Prototypes/cef-webview/notes/cmux-integration-plan.md` | ||
| and is done in a follow-up PR. | ||
|
|
||
| --- | ||
|
|
||
| ## Why this is split out | ||
|
|
||
| `project.pbxproj` is brittle; editing it via text manipulation breaks | ||
| the project file in subtle ways that only surface when Xcode tries to | ||
| parse it. The five edits above are simpler to do in Xcode's UI than to | ||
| script, so the integration is documented as a checklist rather than | ||
| applied automatically. | ||
|
|
||
| The CMUXCEF package itself is fully self-sufficient (it builds with | ||
| `swift build` from this directory). Everything else is "Xcode plumbing | ||
| for embedding it inside cmux." | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| // swift-tools-version: 6.2 | ||
| // | ||
| // CMUXCEF — cmux-owned Swift facade for embedding CEF Chrome runtime | ||
| // browsers inside cmux. See DESIGN.md alongside this package for the | ||
| // architectural decisions. | ||
| // | ||
| // This package is intentionally **isolated**: | ||
| // * It depends only on the system AppKit / Foundation modules and on | ||
| // the CEF framework + libcef_dll_wrapper.a placed by | ||
| // vendor/fetch_cef.sh. | ||
| // * It does **not** import anything from cmux.app. | ||
| // * It produces a public Swift library (CMUXCEF) plus two helper | ||
| // executables (CMUXCEFHelper, CMUXCEFHelperRenderer) that ship as | ||
| // embedded helper .app bundles inside cmux.app/Contents/Frameworks/. | ||
| // | ||
| // The Frameworks/ directory layout consumed here is the one prepared by | ||
| // `vendor/fetch_cef.sh`. Run that script before building this package. | ||
| // | ||
| // Path used here is relative to the package root and points at the | ||
| // existing prototype Frameworks/ directory. When this package is moved | ||
| // into cmux proper, the path moves with it (e.g. `../../../Frameworks`) | ||
| // or — preferably — the cmux Xcode project sets `FRAMEWORK_SEARCH_PATHS` | ||
| // / `LIBRARY_SEARCH_PATHS` and we drop the `.unsafeFlags` here. | ||
|
|
||
| import Foundation | ||
| import PackageDescription | ||
|
|
||
| // `vendor/fetch_cef.sh` produces: | ||
| // <package>/Frameworks/Chromium Embedded Framework.framework | ||
| // <package>/Frameworks/libcef_dll_wrapper.a | ||
| // <package>/Frameworks/include/ | ||
| // We point cxxSettings + linkerSettings at that Frameworks/ directory. | ||
| // (Inside the prototype we used a `CEFArtifacts` symlink; in the cmux | ||
| // vendored copy we drop straight into `Frameworks/`.) | ||
| private let cefFrameworksDir: String = { | ||
| let pkgDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent() | ||
| return pkgDir.appendingPathComponent("Frameworks").path | ||
| }() | ||
|
|
||
| let package = Package( | ||
| name: "CMUXCEF", | ||
| platforms: [ | ||
| // CEF 146 requires macOS 14+ at runtime. We declare 14 to match cmux's | ||
| // deployment target so linking succeeds; CEFEngine.start() runtime- | ||
| // checks for macOS 15 features (menubar / window-controls) and | ||
| // throws CEFEngineError.unsupportedOperatingSystem on older hosts. | ||
| .macOS(.v14), | ||
| ], | ||
| products: [ | ||
| .library(name: "CMUXCEF", targets: ["CMUXCEF"]), | ||
| .executable(name: "CMUXCEFHelper", targets: ["CMUXCEFHelper"]), | ||
| .executable(name: "CMUXCEFHelperRenderer", targets: ["CMUXCEFHelperRenderer"]), | ||
| .executable(name: "CMUXCEFDemoApp", targets: ["CMUXCEFDemoApp"]), | ||
| ], | ||
| targets: [ | ||
| // MARK: - Bridge — ObjC++ bridge that owns all CEF C++ interop. | ||
| .target( | ||
| name: "CMUXCEFBridge", | ||
| publicHeadersPath: "include", | ||
| cxxSettings: [ | ||
| .headerSearchPath("../../Frameworks/include"), | ||
| .headerSearchPath("../../Frameworks"), | ||
| .unsafeFlags(["-std=c++20"]), | ||
| ], | ||
| linkerSettings: [ | ||
| // Everything in one unsafeFlags block so the linker sees | ||
| // -L/-F BEFORE -l/-framework. `.linkedLibrary` / | ||
| // `.linkedFramework` are not used because SwiftPM/Xcode | ||
| // do not preserve their position relative to unsafeFlags | ||
| // on the resulting link command, which made the linker | ||
| // skip libcef_dll_wrapper.a and import nothing from the | ||
| // Chromium framework. | ||
| .unsafeFlags([ | ||
| "-L", cefFrameworksDir, | ||
| "-F", cefFrameworksDir, | ||
| "-Xlinker", "-rpath", "-Xlinker", cefFrameworksDir, | ||
| "-lcef_dll_wrapper", | ||
| "-framework", "Chromium Embedded Framework", | ||
| ]), | ||
| ] | ||
| ), | ||
|
|
||
| // MARK: - CMUXCEF — Swift facade. The only API cmux app code | ||
| // imports. | ||
| .target( | ||
| name: "CMUXCEF", | ||
| dependencies: ["CMUXCEFBridge"], | ||
| swiftSettings: [ | ||
| .defaultIsolation(MainActor.self), | ||
| ] | ||
| ), | ||
|
|
||
| // MARK: - Helper executables. Both are tiny — they exist solely | ||
| // so CEF's multi-process launcher has a binary to spawn for each | ||
| // helper role. Real per-process logic lives in CEF itself. | ||
| .executableTarget( | ||
| name: "CMUXCEFHelper", | ||
| dependencies: ["CMUXCEFBridge"], | ||
| sources: ["main.mm"], | ||
| cxxSettings: [ | ||
| .headerSearchPath("../../Frameworks/include"), | ||
| .headerSearchPath("../../Frameworks"), | ||
| .unsafeFlags(["-std=c++20"]), | ||
| ], | ||
| linkerSettings: [ | ||
| .linkedFramework("Foundation"), | ||
| .linkedFramework("AppKit"), | ||
| .unsafeFlags([ | ||
| "-L", cefFrameworksDir, | ||
| "-F", cefFrameworksDir, | ||
| "-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../..", | ||
| "-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../../Frameworks", | ||
| ]), | ||
| ] | ||
| ), | ||
| .executableTarget( | ||
| name: "CMUXCEFHelperRenderer", | ||
| dependencies: ["CMUXCEFBridge"], | ||
| sources: ["main.mm"], | ||
| cxxSettings: [ | ||
| .headerSearchPath("../../Frameworks/include"), | ||
| .headerSearchPath("../../Frameworks"), | ||
| .unsafeFlags(["-std=c++20"]), | ||
| ], | ||
| linkerSettings: [ | ||
| .linkedFramework("Foundation"), | ||
| .linkedFramework("AppKit"), | ||
| .unsafeFlags([ | ||
| "-L", cefFrameworksDir, | ||
| "-F", cefFrameworksDir, | ||
| "-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../..", | ||
| "-Xlinker", "-rpath", "-Xlinker", "@loader_path/../../../Frameworks", | ||
| ]), | ||
| ] | ||
| ), | ||
|
|
||
| // MARK: - Demo / smoke executable. Runs an end-to-end check that | ||
| // CEFEngine.start + makeBrowser produce a working Chrome runtime | ||
| // window. Not shipped; for local development only. | ||
| .executableTarget( | ||
| name: "CMUXCEFDemoApp", | ||
| dependencies: ["CMUXCEF"], | ||
| linkerSettings: [ | ||
| .unsafeFlags([ | ||
| "-Xlinker", "-rpath", "-Xlinker", cefFrameworksDir, | ||
| ]), | ||
| ] | ||
| ), | ||
|
|
||
| // MARK: - Tests. Behavioral; no source-grep tests. | ||
| .testTarget( | ||
| name: "CMUXCEFTests", | ||
| dependencies: ["CMUXCEF"] | ||
| ), | ||
| ], | ||
| swiftLanguageModes: [.v6] | ||
| ) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.