Fix browser pane dark-mode leak on light pages (#2346)

* Fix browser pane dark-mode leak

* Restore browser theme mode without CSS injection
This commit is contained in:
Austin Wang 2026-03-30 00:57:25 -07:00 committed by GitHub
parent 35cb42fbc8
commit 79bcf1d370
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 60 deletions

View file

@ -210,6 +210,17 @@ enum BrowserThemeSettings {
return defaultMode
}
static func apply(_ mode: BrowserThemeMode, to webView: WKWebView) {
switch mode {
case .system:
webView.appearance = nil
case .light:
webView.appearance = NSAppearance(named: .aqua)
case .dark:
webView.appearance = NSAppearance(named: .darkAqua)
}
}
}
enum BrowserImportHintVariant: String, CaseIterable, Identifiable {
@ -2295,6 +2306,10 @@ final class BrowserPanel: Panel, ObservableObject {
profileID == BrowserProfileStore.shared.builtInDefaultProfileID
}
var currentBrowserThemeMode: BrowserThemeMode {
browserThemeMode
}
private static let portalHostAreaThreshold: CGFloat = 4
private static let portalHostReplacementAreaGainRatio: CGFloat = 1.2
@ -2468,8 +2483,9 @@ final class BrowserPanel: Panel, ObservableObject {
if #available(macOS 13.3, *) {
webView.isInspectable = true
}
// Match the empty-page background to the terminal theme so newly-created browsers
// don't flash white before content loads.
// Match only the unpainted/loading background so newly-created browsers don't flash
// white before content loads. Do not force page appearance or inject color-scheme CSS;
// websites must keep control of their own theme.
webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor()
// Always present as Safari.
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
@ -2544,7 +2560,6 @@ final class BrowserPanel: Panel, ObservableObject {
self.realignRestoredSessionHistoryToLiveCurrentIfPossible()
boundHistoryStore.recordVisit(url: webView.url, title: webView.title)
self.refreshFavicon(from: webView)
self.applyBrowserThemeModeIfNeeded()
// Keep find-in-page open through load completion and refresh matches for the new DOM.
self.restoreFindStateAfterNavigation(replaySearch: true)
}
@ -4929,6 +4944,9 @@ extension BrowserPanel {
func setBrowserThemeMode(_ mode: BrowserThemeMode) {
browserThemeMode = mode
applyBrowserThemeModeIfNeeded()
for controller in popupControllers {
controller.setBrowserThemeMode(mode)
}
}
func refreshAppearanceDrivenColors() {
@ -5431,63 +5449,7 @@ extension BrowserPanel {
private extension BrowserPanel {
func applyBrowserThemeModeIfNeeded() {
switch browserThemeMode {
case .system:
webView.appearance = nil
case .light:
webView.appearance = NSAppearance(named: .aqua)
case .dark:
webView.appearance = NSAppearance(named: .darkAqua)
}
let script = makeBrowserThemeModeScript(mode: browserThemeMode)
webView.evaluateJavaScript(script) { _, error in
#if DEBUG
if let error {
dlog("browser.themeMode error=\(error.localizedDescription)")
}
#endif
}
}
func makeBrowserThemeModeScript(mode: BrowserThemeMode) -> String {
let colorSchemeLiteral: String
switch mode {
case .system:
colorSchemeLiteral = "null"
case .light:
colorSchemeLiteral = "'light'"
case .dark:
colorSchemeLiteral = "'dark'"
}
return """
(() => {
const metaId = 'cmux-browser-theme-mode-meta';
const colorScheme = \(colorSchemeLiteral);
const root = document.documentElement || document.body;
if (!root) return;
let meta = document.getElementById(metaId);
if (colorScheme) {
root.style.setProperty('color-scheme', colorScheme, 'important');
root.setAttribute('data-cmux-browser-theme', colorScheme);
if (!meta) {
meta = document.createElement('meta');
meta.id = metaId;
meta.name = 'color-scheme';
(document.head || root).appendChild(meta);
}
meta.setAttribute('content', colorScheme);
} else {
root.style.removeProperty('color-scheme');
root.removeAttribute('data-cmux-browser-theme');
if (meta) {
meta.remove();
}
}
})();
"""
BrowserThemeSettings.apply(browserThemeMode, to: webView)
}
func scheduleDeveloperToolsRestoreRetry() {

View file

@ -111,6 +111,7 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
}
webView.underPageBackgroundColor = GhosttyBackgroundTheme.currentColor()
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
BrowserThemeSettings.apply(openerPanel?.currentBrowserThemeMode ?? BrowserThemeSettings.mode(), to: webView)
self.webView = webView
// --- Window sizing from WKWindowFeatures ---
@ -251,6 +252,13 @@ final class BrowserPopupWindowController: NSObject, NSWindowDelegate {
childPopups.removeAll { $0 === child }
}
func setBrowserThemeMode(_ mode: BrowserThemeMode) {
BrowserThemeSettings.apply(mode, to: webView)
for child in childPopups {
child.setBrowserThemeMode(mode)
}
}
// MARK: - Popup lifecycle
func closePopup() {