Repository navigation
Add configurable browser engine selection #8163
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
Closed
Closed
Changes from all commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
fc602cd
Add configurable browser engine selection
austinywang 29423c6
Merge remote-tracking branch 'origin/main' into feat-browser-engine-c…
austinywang 799dcfc
Bound Chromium engine resource lifecycles
austinywang 2c2700d
Test saturated Chromium input ordering
austinywang a3efcdb
Complete Chromium engine automation parity
austinywang 8d0d2a0
Test browser engine preference on new tabs
austinywang 1857d3c
Respect browser engine preference for new tabs
austinywang 608a6c0
Test Chromium navigation request rejection
austinywang c482722
Reject unsupported Chromium navigation requests
austinywang 5a1e9f0
Test Chromium composition and paste input
austinywang c82056e
Complete Chromium viewport input harness
austinywang b9b654a
Support Chromium composition and paste input
austinywang 4ae8917
Test Chromium screencast and cleanup safeguards
austinywang 2ebefdb
Bound Chromium viewport resource usage
austinywang 470db8f
Test Chromium topology and drag input
austinywang 5ddc905
Report Chromium topology and drag input correctly
austinywang e2bd4cb
Test Chromium input backpressure isolation
austinywang 7624c3f
Keep Chromium alive under input backpressure
austinywang 36dabc2
Test Chromium zoom and live title updates
austinywang 2511ed6
Test Chromium URL restoration on engine downgrade
austinywang 016aa3f
Fix Chromium live title and zoom state
austinywang 416e8c2
Merge remote-tracking branch 'origin/main' into feat-browser-engine-c…
austinywang 986bd39
Test WebKit async JavaScript evaluation
austinywang a673dcd
Await WebKit browser evaluation promises
austinywang 4b92120
Test Chromium engine policy invariants
austinywang 478bc00
Fix Chromium engine policy invariants
austinywang 322a2d9
Test Chromium pre-commit navigation policy
austinywang 67f6e33
Enforce Chromium navigation policy before commit
austinywang 73a1deb
Test Chromium JavaScript exception reporting
austinywang e4ad3a9
Preserve Chromium JavaScript exception text
austinywang e87c303
Test Chromium history completion tracking
austinywang 49d45af
Record Chromium history on navigation completion
austinywang 817a820
Test bounded Chromium process termination
austinywang 8311654
Bound Chromium process termination
austinywang c68f744
Align browser engine code with architecture policy
austinywang 20359a1
Test browser engine event and script regressions
austinywang b0d6af7
Address browser engine review feedback
austinywang 130f393
Merge origin/main into feat-browser-engine-choice
austinywang 8ec926e
Test bounded Chromium control event pressure
austinywang d12a9e9
Bound Chromium control event buffering
austinywang 4b934cb
Test Chromium main-frame loading state
austinywang 5bd81fd
Track Chromium main-frame loading state
austinywang 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
25 changes: 25 additions & 0 deletions
25
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserApplication.swift
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,25 @@ | ||
| public import Foundation | ||
|
|
||
| /// An installed browser application that can host a Chromium engine process. | ||
| public struct BrowserApplication: Equatable, Sendable { | ||
| /// The application's bundle identifier. | ||
| public let bundleIdentifier: String | ||
|
|
||
| /// The URL of the application bundle. | ||
| public let bundleURL: URL | ||
|
|
||
| /// The executable launched for Chrome DevTools Protocol control. | ||
| public let executableURL: URL | ||
|
|
||
| /// Creates an installed-browser description. | ||
| /// | ||
| /// - Parameters: | ||
| /// - bundleIdentifier: The application's bundle identifier. | ||
| /// - bundleURL: The application bundle URL. | ||
| /// - executableURL: The executable inside the application bundle. | ||
| public init(bundleIdentifier: String, bundleURL: URL, executableURL: URL) { | ||
| self.bundleIdentifier = bundleIdentifier | ||
| self.bundleURL = bundleURL | ||
| self.executableURL = executableURL | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserApplicationProviding.swift
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,9 @@ | ||
| /// Supplies LaunchServices browser-handler and installed-application information. | ||
| @MainActor | ||
| public protocol BrowserApplicationProviding: AnyObject { | ||
| /// Returns the applications LaunchServices selects for representative HTTPS and HTTP URLs. | ||
| func defaultBrowserApplications() -> [BrowserApplication] | ||
|
|
||
| /// Returns installed Chromium-family applications that cmux knows how to launch. | ||
| func installedChromiumApplications() -> [BrowserApplication] | ||
| } | ||
59 changes: 59 additions & 0 deletions
59
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineCookie.swift
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,59 @@ | ||
| public import Foundation | ||
|
|
||
| /// A browser cookie copied across the engine-neutral automation boundary. | ||
| /// | ||
| /// Use this value when reading or mutating cookies without depending on WebKit | ||
| /// or Chrome DevTools Protocol types. | ||
| public struct BrowserEngineCookie: Hashable, Sendable { | ||
| /// The cookie name. | ||
| public let name: String | ||
|
|
||
| /// The cookie value. | ||
| public let value: String | ||
|
|
||
| /// The host or domain scope, including a leading dot when supplied by the engine. | ||
| public let domain: String | ||
|
|
||
| /// The URL path scope. | ||
| public let path: String | ||
|
|
||
| /// Whether the cookie is restricted to secure transports. | ||
| public let isSecure: Bool | ||
|
|
||
| /// Whether page JavaScript is prevented from reading the cookie. | ||
| public let isHTTPOnly: Bool | ||
|
|
||
| /// The expiration time, or `nil` for a session cookie. | ||
| public let expiresDate: Date? | ||
|
|
||
| /// Whether the cookie expires with its browser-engine session. | ||
| public var isSessionOnly: Bool { expiresDate == nil } | ||
|
|
||
| /// Creates an engine-neutral cookie. | ||
| /// | ||
| /// - Parameters: | ||
| /// - name: The cookie name. | ||
| /// - value: The cookie value. | ||
| /// - domain: The host or domain scope. | ||
| /// - path: The URL path scope. Defaults to `/`. | ||
| /// - isSecure: Whether the cookie requires a secure transport. | ||
| /// - isHTTPOnly: Whether page JavaScript is prevented from reading the cookie. | ||
| /// - expiresDate: The expiration time, or `nil` for a session cookie. | ||
| public init( | ||
| name: String, | ||
| value: String, | ||
| domain: String, | ||
| path: String = "/", | ||
| isSecure: Bool = false, | ||
| isHTTPOnly: Bool = false, | ||
| expiresDate: Date? = nil | ||
| ) { | ||
| self.name = name | ||
| self.value = value | ||
| self.domain = domain | ||
| self.path = path | ||
| self.isSecure = isSecure | ||
| self.isHTTPOnly = isHTTPOnly | ||
| self.expiresDate = expiresDate | ||
| } | ||
| } |
8 changes: 8 additions & 0 deletions
8
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineNavigationDecision.swift
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,8 @@ | ||
| /// A pre-commit decision for an engine-originated browser navigation. | ||
| public enum BrowserEngineNavigationDecision: Sendable { | ||
| /// Allow the browser engine to continue the original navigation. | ||
| case allow | ||
|
|
||
| /// Cancel the browser engine's original navigation. | ||
| case cancel | ||
| } |
8 changes: 8 additions & 0 deletions
8
...ges/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineNavigationDisposition.swift
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,8 @@ | ||
| /// The cmux destination requested by an engine-originated browser navigation. | ||
| public enum BrowserEngineNavigationDisposition: Equatable, Sendable { | ||
| /// Continue navigation in the current browser pane. | ||
| case currentTab | ||
|
|
||
| /// Route a new-window request into a cmux browser tab. | ||
| case newTab | ||
| } |
4 changes: 4 additions & 0 deletions
4
...s/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineNavigationPolicyHandler.swift
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,4 @@ | ||
| /// Evaluates an engine-originated navigation before the browser commits it. | ||
| public typealias BrowserEngineNavigationPolicyHandler = @MainActor @Sendable ( | ||
| BrowserEngineNavigationRequest | ||
| ) -> BrowserEngineNavigationDecision |
23 changes: 23 additions & 0 deletions
23
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineNavigationRequest.swift
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,23 @@ | ||
| public import Foundation | ||
|
|
||
| /// An engine-neutral navigation request awaiting cmux policy evaluation. | ||
| public struct BrowserEngineNavigationRequest: Sendable { | ||
| /// The original browser request represented by the engine event. | ||
| public let request: URLRequest | ||
|
|
||
| /// The cmux destination requested by the page. | ||
| public let disposition: BrowserEngineNavigationDisposition | ||
|
|
||
| /// Creates a navigation request for policy evaluation. | ||
| /// | ||
| /// - Parameters: | ||
| /// - request: The original request represented by the browser engine. | ||
| /// - disposition: The current-pane or new-tab destination requested by the page. | ||
| public init( | ||
| request: URLRequest, | ||
| disposition: BrowserEngineNavigationDisposition | ||
| ) { | ||
| self.request = request | ||
| self.disposition = disposition | ||
| } | ||
| } |
72 changes: 72 additions & 0 deletions
72
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineResolver.swift
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,72 @@ | ||
| public import CmuxCore | ||
| import Foundation | ||
|
|
||
| /// Resolves a browser-engine preference against LaunchServices handler identities. | ||
| public struct BrowserEngineResolver: Sendable { | ||
| /// Creates an engine resolver. | ||
| public init() {} | ||
|
|
||
| /// Resolves the effective engine. | ||
| /// | ||
| /// Handler identifiers should be ordered by preference, normally HTTPS then | ||
| /// HTTP. Explicit preferences do not inspect the handlers. Automatic mode | ||
| /// selects Chromium only for a known Chromium-family bundle identifier; | ||
| /// Safari, Firefox-family handlers, and unknown applications use WebKit. | ||
| /// | ||
| /// - Parameters: | ||
| /// - preference: The stored user preference. | ||
| /// - defaultHandlerBundleIdentifiers: Bundle identifiers returned by | ||
| /// LaunchServices for representative HTTPS and HTTP URLs. | ||
| /// - Returns: The engine to use for a new browser surface. | ||
| public func resolve( | ||
| preference: BrowserEnginePreference, | ||
| defaultHandlerBundleIdentifiers: [String] | ||
| ) -> BrowserEngineKind { | ||
| switch preference { | ||
| case .webKit: | ||
| return .webKit | ||
| case .chromium: | ||
| return .chromium | ||
| case .automatic: | ||
| guard let preferredHandler = defaultHandlerBundleIdentifiers.first(where: { | ||
| !$0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty | ||
| }) else { | ||
| return .webKit | ||
| } | ||
| return isChromiumFamilyBundleIdentifier(preferredHandler) | ||
| ? .chromium | ||
| : .webKit | ||
| } | ||
| } | ||
|
|
||
| /// Returns whether a LaunchServices handler identity belongs to a supported | ||
| /// Chromium-family application. | ||
| /// | ||
| /// - Parameter bundleIdentifier: An application bundle identifier. | ||
| /// - Returns: `true` for Chromium-family applications cmux can drive. | ||
| public func isChromiumFamilyBundleIdentifier(_ bundleIdentifier: String) -> Bool { | ||
| let normalized = bundleIdentifier.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() | ||
| guard !normalized.isEmpty else { return false } | ||
|
|
||
| if BrowserImportBrowserDescriptor.allBrowserDescriptors.contains(where: { descriptor in | ||
| descriptor.family == .chromium && descriptor.bundleIdentifiers.contains { | ||
| $0.caseInsensitiveCompare(normalized) == .orderedSame | ||
| } | ||
| }) { | ||
| return true | ||
| } | ||
|
|
||
| return Self.chromiumBundleIdentifierPrefixes.contains { normalized.hasPrefix($0) } | ||
| } | ||
|
|
||
| private static let chromiumBundleIdentifierPrefixes = [ | ||
| "com.google.chrome", | ||
| "com.brave.browser", | ||
| "com.microsoft.edge", | ||
| "com.microsoft.edgemac", | ||
| "com.operasoftware.", | ||
| "com.vivaldi.vivaldi", | ||
| "company.thebrowser.", | ||
| "org.chromium.", | ||
| ] | ||
| } |
23 changes: 23 additions & 0 deletions
23
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineSelection.swift
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,23 @@ | ||
| public import CmuxCore | ||
|
|
||
| /// The resolved engine and, when needed, the Chromium application that implements it. | ||
| public struct BrowserEngineSelection: Equatable, Sendable { | ||
| /// The effective engine for the browser surface. | ||
| public let kind: BrowserEngineKind | ||
|
|
||
| /// The application cmux launches for a Chromium surface. | ||
| public let chromiumApplication: BrowserApplication? | ||
|
|
||
| /// Creates a resolved browser-engine selection. | ||
| /// | ||
| /// - Parameters: | ||
| /// - kind: The effective engine. | ||
| /// - chromiumApplication: The Chromium application, required for a usable Chromium surface. | ||
| public init(kind: BrowserEngineKind, chromiumApplication: BrowserApplication? = nil) { | ||
| self.kind = kind | ||
| self.chromiumApplication = chromiumApplication | ||
| } | ||
|
|
||
| /// A deterministic WebKit selection for direct construction and tests. | ||
| public static let webKit = BrowserEngineSelection(kind: .webKit) | ||
| } |
51 changes: 51 additions & 0 deletions
51
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Engine/BrowserEngineSelectionService.swift
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,51 @@ | ||
| public import CmuxCore | ||
|
|
||
| /// Resolves stored policy and restored-session intent into a launchable browser engine. | ||
| @MainActor | ||
| public struct BrowserEngineSelectionService { | ||
| private let applicationProvider: any BrowserApplicationProviding | ||
| private let resolver: BrowserEngineResolver | ||
|
|
||
| /// Creates a browser-engine selection service with injected system boundaries. | ||
| /// | ||
| /// - Parameters: | ||
| /// - applicationProvider: Supplies LaunchServices handlers and installed browsers. | ||
| /// - resolver: Maps handler identities to an engine family. | ||
| public init( | ||
| applicationProvider: any BrowserApplicationProviding, | ||
| resolver: BrowserEngineResolver = BrowserEngineResolver() | ||
| ) { | ||
| self.applicationProvider = applicationProvider | ||
| self.resolver = resolver | ||
| } | ||
|
|
||
| /// Selects the effective engine for a newly-created or restored surface. | ||
| /// | ||
| /// A restored engine is authoritative. Otherwise the stored preference is | ||
| /// resolved against the actual LaunchServices HTTP/HTTPS handlers. Explicit | ||
| /// Chromium selection remains Chromium even when no compatible application | ||
| /// is installed, allowing the pane to present an actionable error rather | ||
| /// than silently changing engines. | ||
| /// | ||
| /// - Parameters: | ||
| /// - preference: The user's stored engine preference. | ||
| /// - restoredKind: The engine persisted for a restored surface, if any. | ||
| /// - Returns: The effective selection and optional Chromium application. | ||
| public func select( | ||
| preference: BrowserEnginePreference, | ||
| restoredKind: BrowserEngineKind? = nil | ||
| ) -> BrowserEngineSelection { | ||
| let defaultApplications = applicationProvider.defaultBrowserApplications() | ||
| let kind = restoredKind ?? resolver.resolve( | ||
| preference: preference, | ||
| defaultHandlerBundleIdentifiers: defaultApplications.map(\.bundleIdentifier) | ||
| ) | ||
| guard kind == .chromium else { return .webKit } | ||
|
|
||
| let defaultChromium = defaultApplications.first { | ||
| resolver.isChromiumFamilyBundleIdentifier($0.bundleIdentifier) | ||
| } | ||
| let application = defaultChromium ?? applicationProvider.installedChromiumApplications().first | ||
| return BrowserEngineSelection(kind: .chromium, chromiumApplication: application) | ||
| } | ||
| } |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift
Make LaunchServices queries asynchronous to avoid blocking the main thread.
LaunchServices queries (such as resolving default browsers or installed applications) involve IPC and can occasionally hang or block. As per coding guidelines, do not add expensive synchronous syscalls to the main actor or latency-sensitive interactive paths.
Consider making these requirements
async(and the protocolSendable/nonisolated) so that implementations can perform the queries on a background actor, preventing potential UI freezes when creating a new browser pane.♻️ Proposed refactor
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines