Use sheet modal for insecure HTTP warning prompt (#511)

Fixes CMUXTERM-MACOS-DD
This commit is contained in:
Lawrence Chen 2026-02-25 18:00:45 -08:00 committed by GitHub
parent 501bb43d04
commit 930a6b5bc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 122 additions and 3 deletions

View file

@ -1233,6 +1233,8 @@ final class BrowserPanel: Panel, ObservableObject {
private let maxPageZoom: CGFloat = 5.0
private let pageZoomStep: CGFloat = 0.1
private var insecureHTTPBypassHostOnce: String?
private var insecureHTTPAlertFactory: () -> NSAlert
private var insecureHTTPAlertWindowProvider: () -> NSWindow?
// Persist user intent across WebKit detach/reattach churn (split/layout updates).
private var preferredDeveloperToolsVisible: Bool = false
private var forceDeveloperToolsRefreshOnNextAttach: Bool = false
@ -1296,6 +1298,10 @@ final class BrowserPanel: Panel, ObservableObject {
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
self.webView = webView
self.insecureHTTPAlertFactory = { NSAlert() }
self.insecureHTTPAlertWindowProvider = { [weak webView] in
webView?.window ?? NSApp.keyWindow ?? NSApp.mainWindow
}
// Set up navigation delegate
let navDelegate = BrowserNavigationDelegate()
@ -1835,7 +1841,7 @@ final class BrowserPanel: Panel, ObservableObject {
guard let url = request.url else { return }
guard let host = BrowserInsecureHTTPSettings.normalizeHost(url.host ?? "") else { return }
let alert = NSAlert()
let alert = insecureHTTPAlertFactory()
alert.alertStyle = .warning
alert.messageText = "Connection isn't secure"
alert.informativeText = """
@ -1849,10 +1855,38 @@ final class BrowserPanel: Panel, ObservableObject {
alert.showsSuppressionButton = true
alert.suppressionButton?.title = "Always allow this host in cmux"
let response = alert.runModal()
let handleResponse: (NSApplication.ModalResponse) -> Void = { [weak self, weak alert] response in
self?.handleInsecureHTTPAlertResponse(
response,
alert: alert,
host: host,
request: request,
url: url,
intent: intent,
recordTypedNavigation: recordTypedNavigation
)
}
if let alertWindow = insecureHTTPAlertWindowProvider() {
alert.beginSheetModal(for: alertWindow, completionHandler: handleResponse)
return
}
handleResponse(alert.runModal())
}
private func handleInsecureHTTPAlertResponse(
_ response: NSApplication.ModalResponse,
alert: NSAlert?,
host: String,
request: URLRequest,
url: URL,
intent: BrowserInsecureHTTPNavigationIntent,
recordTypedNavigation: Bool
) {
if browserShouldPersistInsecureHTTPAllowlistSelection(
response: response,
suppressionEnabled: alert.suppressionButton?.state == .on
suppressionEnabled: alert?.suppressionButton?.state == .on
) {
BrowserInsecureHTTPSettings.addAllowedHost(host)
}
@ -2475,6 +2509,32 @@ private extension BrowserPanel {
#if DEBUG
extension BrowserPanel {
func configureInsecureHTTPAlertHooksForTesting(
alertFactory: @escaping () -> NSAlert,
windowProvider: @escaping () -> NSWindow?
) {
insecureHTTPAlertFactory = alertFactory
insecureHTTPAlertWindowProvider = windowProvider
}
func resetInsecureHTTPAlertHooksForTesting() {
insecureHTTPAlertFactory = { NSAlert() }
insecureHTTPAlertWindowProvider = { [weak weakWebView = self.webView] in
weakWebView?.window ?? NSApp.keyWindow ?? NSApp.mainWindow
}
}
func presentInsecureHTTPAlertForTesting(
url: URL,
recordTypedNavigation: Bool = false
) {
presentInsecureHTTPAlert(
for: URLRequest(url: url),
intent: .currentTab,
recordTypedNavigation: recordTypedNavigation
)
}
private static func debugRectDescription(_ rect: NSRect) -> String {
String(
format: "%.1f,%.1f %.1fx%.1f",

View file

@ -1274,6 +1274,65 @@ final class BrowserDeveloperToolsConfigurationTests: XCTestCase {
}
}
@MainActor
final class BrowserInsecureHTTPAlertPresentationTests: XCTestCase {
private final class BrowserInsecureHTTPAlertSpy: NSAlert {
private(set) var beginSheetModalCallCount = 0
private(set) var runModalCallCount = 0
var nextResponse: NSApplication.ModalResponse = .alertThirdButtonReturn
override func beginSheetModal(
for sheetWindow: NSWindow,
completionHandler handler: ((NSApplication.ModalResponse) -> Void)?
) {
beginSheetModalCallCount += 1
handler?(nextResponse)
}
override func runModal() -> NSApplication.ModalResponse {
runModalCallCount += 1
return nextResponse
}
}
func testInsecureHTTPPromptUsesSheetWhenWindowIsAvailable() {
let panel = BrowserPanel(workspaceId: UUID())
defer { panel.resetInsecureHTTPAlertHooksForTesting() }
let alertSpy = BrowserInsecureHTTPAlertSpy()
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 320),
styleMask: [.titled],
backing: .buffered,
defer: false
)
panel.configureInsecureHTTPAlertHooksForTesting(
alertFactory: { alertSpy },
windowProvider: { window }
)
panel.presentInsecureHTTPAlertForTesting(url: URL(string: "http://example.com")!)
XCTAssertEqual(alertSpy.beginSheetModalCallCount, 1)
XCTAssertEqual(alertSpy.runModalCallCount, 0)
}
func testInsecureHTTPPromptFallsBackToRunModalWithoutWindow() {
let panel = BrowserPanel(workspaceId: UUID())
defer { panel.resetInsecureHTTPAlertHooksForTesting() }
let alertSpy = BrowserInsecureHTTPAlertSpy()
panel.configureInsecureHTTPAlertHooksForTesting(
alertFactory: { alertSpy },
windowProvider: { nil }
)
panel.presentInsecureHTTPAlertForTesting(url: URL(string: "http://example.com")!)
XCTAssertEqual(alertSpy.beginSheetModalCallCount, 0)
XCTAssertEqual(alertSpy.runModalCallCount, 1)
}
}
final class BrowserNavigationNewTabDecisionTests: XCTestCase {
func testLinkActivatedCmdClickOpensInNewTab() {
XCTAssertTrue(