From cc0fc557cff8a53cf3936eaf0c899d6fe10378ba Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 20 Mar 2026 03:57:36 -0700 Subject: [PATCH] Share browser context with OAuth popups (#1600) * Add popup browser context regression tests * Share browser context with OAuth popups --------- Co-authored-by: Lawrence Chen --- Sources/Panels/BrowserPanel.swift | 65 +++++++++++-------- .../Panels/BrowserPopupWindowController.swift | 15 ++++- cmuxTests/GhosttyConfigTests.swift | 42 ++++++++++++ 3 files changed, 93 insertions(+), 29 deletions(-) diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 1fa0570a..da61b37e 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -2447,33 +2447,9 @@ final class BrowserPanel: Panel, ObservableObject { websiteDataStore: WKWebsiteDataStore? = nil ) -> CmuxWebView { let config = WKWebViewConfiguration() - config.processPool = BrowserPanel.sharedProcessPool - config.mediaTypesRequiringUserActionForPlayback = [] - // Ensure browser cookies/storage persist across navigations and launches. - // This reduces repeated consent/bot-challenge flows on sites like Google. - config.websiteDataStore = websiteDataStore ?? BrowserProfileStore.shared.websiteDataStore(for: profileID) - - // Enable developer extras (DevTools) - config.preferences.setValue(true, forKey: "developerExtrasEnabled") - - // Enable JavaScript - config.defaultWebpagePreferences.allowsContentJavaScript = true - // Keep browser console/error/dialog telemetry active from document start on every navigation. - config.userContentController.addUserScript( - WKUserScript( - source: Self.telemetryHookBootstrapScriptSource, - injectionTime: .atDocumentStart, - forMainFrameOnly: false - ) - ) - // Track the last editable focused element continuously so omnibar exit can - // restore page input focus even if capture runs after first-responder handoff. - config.userContentController.addUserScript( - WKUserScript( - source: Self.addressBarFocusTrackingBootstrapScript, - injectionTime: .atDocumentStart, - forMainFrameOnly: false - ) + configureWebViewConfiguration( + config, + websiteDataStore: websiteDataStore ?? BrowserProfileStore.shared.websiteDataStore(for: profileID) ) let webView = CmuxWebView(frame: .zero, configuration: config) @@ -2489,6 +2465,41 @@ final class BrowserPanel: Panel, ObservableObject { return webView } + static func configureWebViewConfiguration( + _ configuration: WKWebViewConfiguration, + websiteDataStore: WKWebsiteDataStore, + processPool: WKProcessPool = BrowserPanel.sharedProcessPool + ) { + configuration.processPool = processPool + configuration.mediaTypesRequiringUserActionForPlayback = [] + // Ensure browser cookies/storage persist across navigations and launches. + // This reduces repeated consent/bot-challenge flows on sites like Google. + configuration.websiteDataStore = websiteDataStore + + // Enable developer extras (DevTools) + configuration.preferences.setValue(true, forKey: "developerExtrasEnabled") + + // Enable JavaScript + configuration.defaultWebpagePreferences.allowsContentJavaScript = true + // Keep browser console/error/dialog telemetry active from document start on every navigation. + configuration.userContentController.addUserScript( + WKUserScript( + source: Self.telemetryHookBootstrapScriptSource, + injectionTime: .atDocumentStart, + forMainFrameOnly: false + ) + ) + // Track the last editable focused element continuously so omnibar exit can + // restore page input focus even if capture runs after first-responder handoff. + configuration.userContentController.addUserScript( + WKUserScript( + source: Self.addressBarFocusTrackingBootstrapScript, + injectionTime: .atDocumentStart, + forMainFrameOnly: false + ) + ) + } + private func bindWebView(_ webView: CmuxWebView) { webView.onContextMenuDownloadStateChanged = { [weak self] downloading in if downloading { diff --git a/Sources/Panels/BrowserPopupWindowController.swift b/Sources/Panels/BrowserPopupWindowController.swift index 692e6376..0c97d5a8 100644 --- a/Sources/Panels/BrowserPopupWindowController.swift +++ b/Sources/Panels/BrowserPopupWindowController.swift @@ -92,13 +92,24 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate { self.parentPopupController = parentPopupController self.nestingDepth = nestingDepth - // Create popup web view with WebKit's supplied configuration (preserves - // internal browsing-context state for opener linkage / postMessage). + let browserContextSource = parentPopupController?.webView.configuration ?? openerPanel?.webView.configuration + if let browserContextSource { + BrowserPanel.configureWebViewConfiguration( + configuration, + websiteDataStore: browserContextSource.websiteDataStore, + processPool: browserContextSource.processPool + ) + } + + // Create popup web view with WebKit's supplied configuration after + // overlaying the opener's browser context so OAuth popups keep cmux's + // shared cookie/storage scope and opener linkage. let webView = CmuxWebView(frame: .zero, configuration: configuration) webView.allowsBackForwardNavigationGestures = true if #available(macOS 13.3, *) { webView.isInspectable = true } + webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor() webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent self.webView = webView diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index 57895a69..a2c8ccf6 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -1003,6 +1003,48 @@ final class GhosttyTerminalStartupEnvironmentTests: XCTestCase { } } +@MainActor +final class BrowserPanelPopupContextTests: XCTestCase { + func testFloatingPopupInheritsOpenerBrowserContext() throws { + let panel = BrowserPanel(workspaceId: UUID(), isRemoteWorkspace: false) + let popupWebView = try XCTUnwrap( + panel.createFloatingPopup( + configuration: WKWebViewConfiguration(), + windowFeatures: WKWindowFeatures() + ) + ) + defer { popupWebView.window?.close() } + + XCTAssertTrue( + popupWebView.configuration.processPool === panel.webView.configuration.processPool + ) + XCTAssertTrue( + popupWebView.configuration.websiteDataStore === panel.webView.configuration.websiteDataStore + ) + } + + func testFloatingPopupInheritsRemoteWorkspaceWebsiteDataStore() throws { + let remoteWorkspaceId = UUID() + let panel = BrowserPanel( + workspaceId: remoteWorkspaceId, + isRemoteWorkspace: true, + remoteWebsiteDataStoreIdentifier: remoteWorkspaceId + ) + let popupWebView = try XCTUnwrap( + panel.createFloatingPopup( + configuration: WKWebViewConfiguration(), + windowFeatures: WKWindowFeatures() + ) + ) + defer { popupWebView.window?.close() } + + XCTAssertTrue( + popupWebView.configuration.websiteDataStore === panel.webView.configuration.websiteDataStore + ) + XCTAssertFalse(popupWebView.configuration.websiteDataStore === WKWebsiteDataStore.default()) + } +} + @MainActor final class BrowserPanelRemoteStoreTests: XCTestCase { func testRemoteWorkspacePanelsShareWorkspaceScopedWebsiteDataStore() {