Skip to content

Commit 1ae17a9

Browse files
committed
Wire Chromium browser into UI with command palette entry
- BrowserPanelView: branches on panel.engineType to show either the existing WebViewRepresentable (WebKit) or the new CEFBrowserViewRepresentable (Chromium). Falls back to CEFDownloadView if CEF isn't initialized. - CEFBrowserViewRepresentable: NSViewRepresentable that hosts a CEFBrowserView in SwiftUI. - AppDelegate: added openChromiumBrowserAndFocusAddressBar() that finds or creates a Chromium profile and opens a browser tab. - ContentView: added "New Tab (Chromium Browser)" command palette entry (palette.newChromiumBrowserTab). Users can now open a Chromium-engine browser tab via the command palette. The tab shows the CEF download UI if the framework isn't available, or renders pages via Chromium if it is.
1 parent 59b55b5 commit 1ae17a9

5 files changed

Lines changed: 89 additions & 1 deletion

File tree

GhosttyTabs.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
CEF00003 /* CEFRuntime.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF00013 /* CEFRuntime.swift */; };
1313
CEF00004 /* CEFDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF00014 /* CEFDownloadView.swift */; };
1414
CEF00005 /* CEFBrowserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF00015 /* CEFBrowserView.swift */; };
15+
CEF00006 /* CEFBrowserViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF00016 /* CEFBrowserViewRepresentable.swift */; };
1516
A5001001 /* cmuxApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001011 /* cmuxApp.swift */; };
1617
A5001002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5001012 /* ContentView.swift */; };
1718
E62155868BB29FEB5DAAAF25 /* SidebarSelectionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD52285508B1D6A9875E7B3 /* SidebarSelectionState.swift */; };
@@ -186,6 +187,7 @@
186187
CEF00013 /* CEFRuntime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserEngine/CEFRuntime.swift; sourceTree = "<group>"; };
187188
CEF00014 /* CEFDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserEngine/CEFDownloadView.swift; sourceTree = "<group>"; };
188189
CEF00015 /* CEFBrowserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserEngine/CEFBrowserView.swift; sourceTree = "<group>"; };
190+
CEF00016 /* CEFBrowserViewRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserEngine/CEFBrowserViewRepresentable.swift; sourceTree = "<group>"; };
189191
A5001000 /* cmux.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cmux.app; sourceTree = BUILT_PRODUCTS_DIR; };
190192
F1000002A1B2C3D4E5F60718 /* cmuxTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = cmuxTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
191193
7E7E6EF344A568AC7FEE3715 /* cmuxUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = cmuxUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -473,6 +475,7 @@
473475
CEF00013 /* CEFRuntime.swift */,
474476
CEF00014 /* CEFDownloadView.swift */,
475477
CEF00015 /* CEFBrowserView.swift */,
478+
CEF00016 /* CEFBrowserViewRepresentable.swift */,
476479
);
477480
path = Sources;
478481
sourceTree = "<group>";
@@ -773,6 +776,7 @@
773776
CEF00003 /* CEFRuntime.swift in Sources */,
774777
CEF00004 /* CEFDownloadView.swift in Sources */,
775778
CEF00005 /* CEFBrowserView.swift in Sources */,
779+
CEF00006 /* CEFBrowserViewRepresentable.swift in Sources */,
776780
);
777781
runOnlyForDeploymentPostprocessing = 0;
778782
};

Sources/AppDelegate.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9904,6 +9904,32 @@ final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCent
99049904
return panelId
99059905
}
99069906

9907+
/// Open a browser tab using the Chromium engine. Creates a Chromium
9908+
/// profile if none exists, then opens a browser tab with that profile.
9909+
@discardableResult
9910+
func openChromiumBrowserAndFocusAddressBar(url: URL? = nil) -> UUID? {
9911+
let store = BrowserProfileStore.shared
9912+
// Find or create a Chromium profile
9913+
let chromiumProfile: BrowserProfileDefinition
9914+
if let existing = store.profiles.first(where: { $0.engineType == .chromium }) {
9915+
chromiumProfile = existing
9916+
} else {
9917+
guard let created = store.createProfile(named: "Chromium", engineType: .chromium) else {
9918+
return nil
9919+
}
9920+
chromiumProfile = created
9921+
}
9922+
9923+
guard let panelId = tabManager?.openBrowser(
9924+
url: url,
9925+
preferredProfileID: chromiumProfile.id,
9926+
insertAtEnd: true
9927+
) else { return nil }
9928+
9929+
_ = focusBrowserAddressBar(panelId: panelId)
9930+
return panelId
9931+
}
9932+
99079933
private func focusBrowserAddressBar(in panel: BrowserPanel) {
99089934
#if DEBUG
99099935
let requestId = panel.requestAddressBarFocus()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import SwiftUI
2+
import AppKit
3+
4+
/// NSViewRepresentable that hosts a CEFBrowserView in SwiftUI.
5+
struct CEFBrowserViewRepresentable: NSViewRepresentable {
6+
let cefBrowserView: CEFBrowserView
7+
8+
func makeNSView(context: Context) -> CEFBrowserView {
9+
cefBrowserView
10+
}
11+
12+
func updateNSView(_ nsView: CEFBrowserView, context: Context) {
13+
// Layout is handled by autoresizing masks and CEFBrowserView.layout()
14+
}
15+
}

Sources/ContentView.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5342,6 +5342,14 @@ struct ContentView: View {
53425342
keywords: ["new", "browser", "tab", "web"]
53435343
)
53445344
)
5345+
contributions.append(
5346+
CommandPaletteCommandContribution(
5347+
commandId: "palette.newChromiumBrowserTab",
5348+
title: constant(String(localized: "command.newChromiumBrowserTab.title", defaultValue: "New Tab (Chromium Browser)")),
5349+
subtitle: constant(String(localized: "command.newChromiumBrowserTab.subtitle", defaultValue: "Tab")),
5350+
keywords: ["new", "chromium", "browser", "tab", "web", "chrome", "extension"]
5351+
)
5352+
)
53455353
contributions.append(
53465354
CommandPaletteCommandContribution(
53475355
commandId: "palette.closeTab",
@@ -6000,6 +6008,11 @@ struct ContentView: View {
60006008
_ = AppDelegate.shared?.openBrowserAndFocusAddressBar()
60016009
}
60026010
}
6011+
registry.register(commandId: "palette.newChromiumBrowserTab") {
6012+
DispatchQueue.main.async {
6013+
_ = AppDelegate.shared?.openChromiumBrowserAndFocusAddressBar()
6014+
}
6015+
}
60036016
registry.register(commandId: "palette.closeTab") {
60046017
tabManager.closeCurrentPanelWithConfirmation()
60056018
}

Sources/Panels/BrowserPanelView.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,9 @@ struct BrowserPanelView: View {
11171117
isCurrentPaneOwner
11181118

11191119
return Group {
1120-
if panel.shouldRenderWebView {
1120+
if panel.engineType == .chromium {
1121+
chromiumWebView
1122+
} else if panel.shouldRenderWebView {
11211123
WebViewRepresentable(
11221124
panel: panel,
11231125
paneId: paneId,
@@ -1189,6 +1191,34 @@ struct BrowserPanelView: View {
11891191
.zIndex(0)
11901192
}
11911193

1194+
/// View for Chromium-engine browser panels. Shows the CEFBrowserView
1195+
/// if CEF is initialized, or the download/error UI otherwise.
1196+
@ViewBuilder
1197+
private var chromiumWebView: some View {
1198+
if let cefView = panel.cefBrowserView, CEFRuntime.shared.isInitialized {
1199+
CEFBrowserViewRepresentable(cefBrowserView: cefView)
1200+
.accessibilityIdentifier("ChromiumBrowserView")
1201+
} else if !CEFRuntime.shared.isInitialized {
1202+
CEFDownloadView {
1203+
// User cancelled, nothing to do
1204+
}
1205+
} else {
1206+
VStack {
1207+
Text(String(
1208+
localized: "cef.error.title",
1209+
defaultValue: "Chromium engine unavailable"
1210+
))
1211+
.font(.headline)
1212+
if let error = CEFRuntime.shared.initError {
1213+
Text(error)
1214+
.font(.caption)
1215+
.foregroundStyle(.secondary)
1216+
}
1217+
}
1218+
.frame(maxWidth: .infinity, maxHeight: .infinity)
1219+
}
1220+
}
1221+
11921222
private func triggerFocusFlashAnimation() {
11931223
focusFlashAnimationGeneration &+= 1
11941224
let generation = focusFlashAnimationGeneration

0 commit comments

Comments
 (0)