Follow up PR 242: refresh browser under-page background on theme updates (#272)
* Address PR 242 follow-ups for titlebar and browser background * Restore titlebar border per follow-up scope * Refresh browser under-page color with Ghostty opacity * Browser: theme blank page fallback for about:blank * Browser: keep new tabs webview-less until first nav
This commit is contained in:
parent
356a20e97a
commit
8ac554fb06
3 changed files with 105 additions and 21 deletions
|
|
@ -1005,6 +1005,27 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
/// Shared process pool for cookie sharing across all browser panels
|
/// Shared process pool for cookie sharing across all browser panels
|
||||||
private static let sharedProcessPool = WKProcessPool()
|
private static let sharedProcessPool = WKProcessPool()
|
||||||
|
|
||||||
|
private static func clampedGhosttyBackgroundOpacity(_ opacity: Double) -> CGFloat {
|
||||||
|
CGFloat(max(0.0, min(1.0, opacity)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func resolvedGhosttyBackgroundColor(from notification: Notification? = nil) -> NSColor {
|
||||||
|
let userInfo = notification?.userInfo
|
||||||
|
let baseColor = (userInfo?[GhosttyNotificationKey.backgroundColor] as? NSColor)
|
||||||
|
?? GhosttyApp.shared.defaultBackgroundColor
|
||||||
|
|
||||||
|
let opacity: Double
|
||||||
|
if let value = userInfo?[GhosttyNotificationKey.backgroundOpacity] as? Double {
|
||||||
|
opacity = value
|
||||||
|
} else if let value = userInfo?[GhosttyNotificationKey.backgroundOpacity] as? NSNumber {
|
||||||
|
opacity = value.doubleValue
|
||||||
|
} else {
|
||||||
|
opacity = GhosttyApp.shared.defaultBackgroundOpacity
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseColor.withAlphaComponent(clampedGhosttyBackgroundOpacity(opacity))
|
||||||
|
}
|
||||||
|
|
||||||
let id: UUID
|
let id: UUID
|
||||||
let panelType: PanelType = .browser
|
let panelType: PanelType = .browser
|
||||||
|
|
||||||
|
|
@ -1027,6 +1048,10 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
/// Published URL being displayed
|
/// Published URL being displayed
|
||||||
@Published private(set) var currentURL: URL?
|
@Published private(set) var currentURL: URL?
|
||||||
|
|
||||||
|
/// Whether the browser panel should render its WKWebView in the content area.
|
||||||
|
/// New browser tabs stay in an empty "new tab" state until first navigation.
|
||||||
|
@Published private(set) var shouldRenderWebView: Bool = false
|
||||||
|
|
||||||
/// Published page title
|
/// Published page title
|
||||||
@Published private(set) var pageTitle: String = ""
|
@Published private(set) var pageTitle: String = ""
|
||||||
|
|
||||||
|
|
@ -1090,7 +1115,7 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
if let url = currentURL {
|
if let url = currentURL {
|
||||||
return url.host ?? url.absoluteString
|
return url.host ?? url.absoluteString
|
||||||
}
|
}
|
||||||
return "Browser"
|
return "New tab"
|
||||||
}
|
}
|
||||||
|
|
||||||
var displayIcon: String? {
|
var displayIcon: String? {
|
||||||
|
|
@ -1130,7 +1155,7 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
|
|
||||||
// Match the empty-page background to the terminal theme so newly-created browsers
|
// Match the empty-page background to the terminal theme so newly-created browsers
|
||||||
// don't flash white before content loads.
|
// don't flash white before content loads.
|
||||||
webView.underPageBackgroundColor = GhosttyApp.shared.defaultBackgroundColor
|
webView.underPageBackgroundColor = Self.resolvedGhosttyBackgroundColor()
|
||||||
|
|
||||||
// Always present as Safari.
|
// Always present as Safari.
|
||||||
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
|
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
|
||||||
|
|
@ -1206,6 +1231,7 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
|
|
||||||
// Navigate to initial URL if provided
|
// Navigate to initial URL if provided
|
||||||
if let url = initialURL {
|
if let url = initialURL {
|
||||||
|
shouldRenderWebView = true
|
||||||
navigate(to: url)
|
navigate(to: url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1295,6 +1321,13 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
webViewObservers.append(progressObserver)
|
webViewObservers.append(progressObserver)
|
||||||
|
|
||||||
|
NotificationCenter.default.publisher(for: .ghosttyDefaultBackgroundDidChange)
|
||||||
|
.sink { [weak self] notification in
|
||||||
|
guard let self else { return }
|
||||||
|
self.webView.underPageBackgroundColor = Self.resolvedGhosttyBackgroundColor(from: notification)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Panel Protocol
|
// MARK: - Panel Protocol
|
||||||
|
|
@ -1337,6 +1370,7 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
navigationDelegate = nil
|
navigationDelegate = nil
|
||||||
uiDelegate = nil
|
uiDelegate = nil
|
||||||
webViewObservers.removeAll()
|
webViewObservers.removeAll()
|
||||||
|
cancellables.removeAll()
|
||||||
faviconTask?.cancel()
|
faviconTask?.cancel()
|
||||||
faviconTask = nil
|
faviconTask = nil
|
||||||
}
|
}
|
||||||
|
|
@ -1547,6 +1581,7 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
guard let url = request.url else { return }
|
guard let url = request.url else { return }
|
||||||
// Some installs can end up with a legacy Chrome UA override; keep this pinned.
|
// Some installs can end up with a legacy Chrome UA override; keep this pinned.
|
||||||
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
|
webView.customUserAgent = BrowserUserAgentSettings.safariUserAgent
|
||||||
|
shouldRenderWebView = true
|
||||||
if recordTypedNavigation {
|
if recordTypedNavigation {
|
||||||
BrowserHistoryStore.shared.recordTypedNavigation(url: url)
|
BrowserHistoryStore.shared.recordTypedNavigation(url: url)
|
||||||
}
|
}
|
||||||
|
|
@ -1649,6 +1684,7 @@ final class BrowserPanel: Panel, ObservableObject {
|
||||||
BrowserWindowPortalRegistry.detach(webView: webView)
|
BrowserWindowPortalRegistry.detach(webView: webView)
|
||||||
}
|
}
|
||||||
webViewObservers.removeAll()
|
webViewObservers.removeAll()
|
||||||
|
cancellables.removeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -548,25 +548,38 @@ struct BrowserPanelView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var webView: some View {
|
private var webView: some View {
|
||||||
WebViewRepresentable(
|
Group {
|
||||||
panel: panel,
|
if panel.shouldRenderWebView {
|
||||||
shouldAttachWebView: isVisibleInUI,
|
WebViewRepresentable(
|
||||||
shouldFocusWebView: isFocused && !addressBarFocused,
|
panel: panel,
|
||||||
isPanelFocused: isFocused,
|
shouldAttachWebView: isVisibleInUI,
|
||||||
portalZPriority: portalPriority
|
shouldFocusWebView: isFocused && !addressBarFocused,
|
||||||
)
|
isPanelFocused: isFocused,
|
||||||
// Keep the representable identity stable across bonsplit structural updates.
|
portalZPriority: portalPriority
|
||||||
// This reduces WKWebView reparenting churn (and the associated WebKit crashes).
|
)
|
||||||
.id(panel.id)
|
// Keep the representable identity stable across bonsplit structural updates.
|
||||||
.contentShape(Rectangle())
|
// This reduces WKWebView reparenting churn (and the associated WebKit crashes).
|
||||||
.simultaneousGesture(TapGesture().onEnded {
|
.id(panel.id)
|
||||||
// Chrome-like behavior: clicking web content while editing the
|
.contentShape(Rectangle())
|
||||||
// omnibar should commit blur and revert transient edits.
|
.simultaneousGesture(TapGesture().onEnded {
|
||||||
if addressBarFocused {
|
// Chrome-like behavior: clicking web content while editing the
|
||||||
addressBarFocused = false
|
// omnibar should commit blur and revert transient edits.
|
||||||
}
|
if addressBarFocused {
|
||||||
})
|
addressBarFocused = false
|
||||||
.zIndex(0)
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Color(nsColor: GhosttyApp.shared.defaultBackgroundColor)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture {
|
||||||
|
onRequestPanelFocus()
|
||||||
|
if addressBarFocused {
|
||||||
|
addressBarFocused = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.zIndex(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func triggerFocusFlashAnimation() {
|
private func triggerFocusFlashAnimation() {
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,41 @@ final class BrowserDeveloperToolsConfigurationTests: XCTestCase {
|
||||||
XCTAssertTrue(panel.webView.isInspectable)
|
XCTAssertTrue(panel.webView.isInspectable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBrowserPanelRefreshesUnderPageBackgroundColorWhenGhosttyBackgroundChanges() {
|
||||||
|
let panel = BrowserPanel(workspaceId: UUID())
|
||||||
|
let updatedColor = NSColor(srgbRed: 0.18, green: 0.29, blue: 0.44, alpha: 1.0)
|
||||||
|
let updatedOpacity = 0.57
|
||||||
|
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .ghosttyDefaultBackgroundDidChange,
|
||||||
|
object: nil,
|
||||||
|
userInfo: [
|
||||||
|
GhosttyNotificationKey.backgroundColor: updatedColor,
|
||||||
|
GhosttyNotificationKey.backgroundOpacity: updatedOpacity
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
guard let actual = panel.webView.underPageBackgroundColor?.usingColorSpace(.sRGB),
|
||||||
|
let expected = updatedColor.withAlphaComponent(updatedOpacity).usingColorSpace(.sRGB) else {
|
||||||
|
XCTFail("Expected sRGB-convertible under-page background colors")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
XCTAssertEqual(actual.redComponent, expected.redComponent, accuracy: 0.005)
|
||||||
|
XCTAssertEqual(actual.greenComponent, expected.greenComponent, accuracy: 0.005)
|
||||||
|
XCTAssertEqual(actual.blueComponent, expected.blueComponent, accuracy: 0.005)
|
||||||
|
XCTAssertEqual(actual.alphaComponent, expected.alphaComponent, accuracy: 0.005)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBrowserPanelStartsAsNewTabWithoutLoadingAboutBlank() {
|
||||||
|
let panel = BrowserPanel(workspaceId: UUID())
|
||||||
|
|
||||||
|
XCTAssertEqual(panel.displayTitle, "New tab")
|
||||||
|
XCTAssertFalse(panel.shouldRenderWebView)
|
||||||
|
XCTAssertNil(panel.webView.url)
|
||||||
|
XCTAssertNil(panel.currentURL)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue