diff --git a/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicy.swift b/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicy.swift index 6f778f20c3fe..7ae15a62b407 100644 --- a/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicy.swift +++ b/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicy.swift @@ -57,7 +57,7 @@ public struct BrowserUserAgentPolicy: Sendable { /// Resolves the browser identity policy for a top-level destination. /// - /// Google Sheets intentionally uses WebKit's default embedded identity. + /// Every web destination receives the current Safari-compatible identity. /// Non-web destinations have no applicable user-agent policy. /// /// - Parameter url: The destination of the top-level navigation. @@ -68,16 +68,7 @@ public struct BrowserUserAgentPolicy: Sendable { scheme == "http" || scheme == "https" else { return .notApplicable } - guard let host = url.host?.lowercased() else { - return .custom(safariCompatibleUserAgent) - } - let isSheetsHost = host == "sheets.google.com" || host == "spreadsheets.google.com" - let isSheetsPath = host == "docs.google.com" - && url.path.split(separator: "/", omittingEmptySubsequences: true).first? - .lowercased() == "spreadsheets" - return isSheetsHost || isSheetsPath - ? .webKitDefault - : .custom(safariCompatibleUserAgent) + return .custom(safariCompatibleUserAgent) } /// Parses a numeric dot-separated browser version. diff --git a/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicyResolution.swift b/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicyResolution.swift index 90202698ee52..a5336cd7990b 100644 --- a/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicyResolution.swift +++ b/Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/BrowserUserAgentPolicyResolution.swift @@ -3,9 +3,6 @@ public enum BrowserUserAgentPolicyResolution: Equatable, Sendable { /// Use the supplied custom user-agent identity for an HTTP or HTTPS destination. case custom(String) - /// Use WebKit's default embedded identity for an HTTP or HTTPS destination. - case webKitDefault - /// User-agent identity does not apply to this non-web destination. case notApplicable } diff --git a/Packages/macOS/CmuxBrowser/Tests/CmuxBrowserTests/BrowserUserAgentPolicyTests.swift b/Packages/macOS/CmuxBrowser/Tests/CmuxBrowserTests/BrowserUserAgentPolicyTests.swift index 389f02f21d45..5bab3b30dd91 100644 --- a/Packages/macOS/CmuxBrowser/Tests/CmuxBrowserTests/BrowserUserAgentPolicyTests.swift +++ b/Packages/macOS/CmuxBrowser/Tests/CmuxBrowserTests/BrowserUserAgentPolicyTests.swift @@ -27,14 +27,17 @@ struct BrowserUserAgentPolicyTests { #expect(newerPolicy.safariCompatibleUserAgent.contains("Version/27.0 Safari/605.1.15")) } - @Test func googleSheetsKeepsEmbeddedWebKitIdentity() { + @Test func googleSheetsReceivesCurrentSafariCompatibleIdentity() { let sheetURL = URL(string: "https://docs.google.com/spreadsheets/d/example/edit")! let sheetsRedirectURL = URL(string: "https://sheets.google.com/")! let legacyRedirectURL = URL(string: "https://spreadsheets.google.com/")! + let expectedResolution = BrowserUserAgentPolicyResolution.custom( + policy.safariCompatibleUserAgent + ) - #expect(policy.resolution(for: sheetURL) == .webKitDefault) - #expect(policy.resolution(for: sheetsRedirectURL) == .webKitDefault) - #expect(policy.resolution(for: legacyRedirectURL) == .webKitDefault) + #expect(policy.resolution(for: sheetURL) == expectedResolution) + #expect(policy.resolution(for: sheetsRedirectURL) == expectedResolution) + #expect(policy.resolution(for: legacyRedirectURL) == expectedResolution) } @Test func otherGoogleWorkspaceEditorsRemainSafariCompatible() { diff --git a/Sources/Panels/BrowserNavigationDelegate.swift b/Sources/Panels/BrowserNavigationDelegate.swift index 9d54c24c1b6c..e8d4ed0ec77d 100644 --- a/Sources/Panels/BrowserNavigationDelegate.swift +++ b/Sources/Panels/BrowserNavigationDelegate.swift @@ -576,8 +576,7 @@ import WebKit let replacedNavigation = activeMainFrameNavigation let reportReplacementWillStart = willReplaceNavigationForUserAgentPolicy return webView.restartNavigationForBrowserUserAgentPolicyIfNeeded( - for: navigationAction.request, - targetFrameIsMainFrame: navigationAction.targetFrame?.isMainFrame, + navigationAction, decisionHandler: decisionHandler, willRestart: { reportReplacementWillStart?(webView, replacedNavigation) diff --git a/Sources/Panels/BrowserPopupWindowController.swift b/Sources/Panels/BrowserPopupWindowController.swift index cf40e503ffd2..04b959802867 100644 --- a/Sources/Panels/BrowserPopupWindowController.swift +++ b/Sources/Panels/BrowserPopupWindowController.swift @@ -789,8 +789,7 @@ private class PopupUIDelegate: BrowserPDFPreviewActionUIDelegate { } return webView.restartNavigationForBrowserUserAgentPolicyIfNeeded( - for: navigationAction.request, - targetFrameIsMainFrame: navigationAction.targetFrame?.isMainFrame, + navigationAction, decisionHandler: decisionHandler, startReplacement: { restartRequest in controller.requestNavigation(restartRequest, in: webView) diff --git a/Sources/Panels/WKWebView+BrowserUserAgentPolicy.swift b/Sources/Panels/WKWebView+BrowserUserAgentPolicy.swift index 75d4c34ccddd..4517e94b7d73 100644 --- a/Sources/Panels/WKWebView+BrowserUserAgentPolicy.swift +++ b/Sources/Panels/WKWebView+BrowserUserAgentPolicy.swift @@ -12,8 +12,6 @@ extension WKWebView { switch BrowserUserAgentPolicy.system.resolution(for: url) { case .custom(let userAgent): resolvedUserAgent = userAgent - case .webKitDefault: - resolvedUserAgent = nil case .notApplicable: guard currentUserAgent != nil else { return false } customUserAgent = nil @@ -44,15 +42,14 @@ extension WKWebView { @MainActor func restartNavigationForBrowserUserAgentPolicyIfNeeded( - for request: URLRequest, - targetFrameIsMainFrame: Bool?, + _ navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void, willRestart: () -> Void = {}, startReplacement: (URLRequest) -> Void ) -> Bool { guard let restartRequest = browserUserAgentPolicyRestartRequest( - for: request, - targetFrameIsMainFrame: targetFrameIsMainFrame + for: navigationAction.request, + targetFrameIsMainFrame: navigationAction.targetFrame?.isMainFrame ) else { return false } diff --git a/cmuxTests/BrowserUserAgentPolicyWebKitTests.swift b/cmuxTests/BrowserUserAgentPolicyWebKitTests.swift index a06be4aa3555..b640268be771 100644 --- a/cmuxTests/BrowserUserAgentPolicyWebKitTests.swift +++ b/cmuxTests/BrowserUserAgentPolicyWebKitTests.swift @@ -26,9 +26,25 @@ struct BrowserUserAgentPolicyWebKitTests { #expect(webView.browserUserAgentPolicyRestartRequest(for: restartRequest) == nil) } - @Test func restartRequestCanRestoreEmbeddedIdentityForSheets() throws { + @Test func restartRequestReplacesStaleSheetsIdentity() throws { + let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) + webView.customUserAgent = "stale-agent" + var request = URLRequest( + url: URL(string: "https://docs.google.com/spreadsheets/d/example/edit")! + ) + request.setValue("stale-agent", forHTTPHeaderField: "User-Agent") + + let restartRequest = try #require( + webView.browserUserAgentPolicyRestartRequest(for: request) + ) + + #expect(restartRequest.value(forHTTPHeaderField: "User-Agent") == nil) + #expect(webView.customUserAgent == BrowserUserAgentPolicy.system.safariCompatibleUserAgent) + #expect(webView.browserUserAgentPolicyRestartRequest(for: restartRequest) == nil) + } + + @Test func sheetsRequestUsesCurrentSafariForNetworkAndNavigatorIdentity() throws { let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) - webView.customUserAgent = BrowserUserAgentPolicy.system.safariCompatibleUserAgent let request = URLRequest( url: URL(string: "https://docs.google.com/spreadsheets/d/example/edit")! ) @@ -37,18 +53,25 @@ struct BrowserUserAgentPolicyWebKitTests { webView.browserUserAgentPolicyRestartRequest(for: request) ) - #expect(webView.customUserAgent?.isEmpty != false) + #expect(restartRequest.value(forHTTPHeaderField: "User-Agent") == nil) + #expect(webView.customUserAgent == BrowserUserAgentPolicy.system.safariCompatibleUserAgent) #expect(webView.browserUserAgentPolicyRestartRequest(for: restartRequest) == nil) } - @Test func emptyCustomUserAgentIsAlreadyEmbeddedIdentityForSheets() { + @Test func emptyCustomUserAgentIsNotAcceptedAsSheetsIdentity() throws { let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) webView.customUserAgent = "" let request = URLRequest( url: URL(string: "https://docs.google.com/spreadsheets/d/example/edit")! ) - #expect(webView.browserUserAgentPolicyRestartRequest(for: request) == nil) + let restartRequest = try #require( + webView.browserUserAgentPolicyRestartRequest(for: request) + ) + + #expect(restartRequest.value(forHTTPHeaderField: "User-Agent") == nil) + #expect(webView.customUserAgent == BrowserUserAgentPolicy.system.safariCompatibleUserAgent) + #expect(webView.browserUserAgentPolicyRestartRequest(for: restartRequest) == nil) } @Test func restartRequestIgnoresSubframesAndNewWindowTargets() throws { @@ -73,37 +96,4 @@ struct BrowserUserAgentPolicyWebKitTests { #expect(webView.browserUserAgentPolicyRestartRequest(for: request) == nil) #expect(webView.customUserAgent?.isEmpty != false) } - - @Test func sheetsDestinationNeverAllowsStaleSafariIdentity() throws { - let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration()) - let request = URLRequest( - url: URL(string: "https://docs.google.com/spreadsheets/d/example/edit")! - ) - var decisions: [WKNavigationActionPolicy] = [] - var replacementRequests: [URLRequest] = [] - - webView.customUserAgent = BrowserUserAgentPolicy.system.safariCompatibleUserAgent - #expect(webView.restartNavigationForBrowserUserAgentPolicyIfNeeded( - for: request, - targetFrameIsMainFrame: true, - decisionHandler: { decisions.append($0) }, - startReplacement: { replacementRequests.append($0) } - )) - #expect(decisions == [.cancel]) - #expect((webView.customUserAgent ?? "").isEmpty) - let replacementRequest = try #require(replacementRequests.first) - - // If another load phase exposes the stale Safari identity again, the - // replacement must correct it instead of silently allowing a downgrade. - webView.customUserAgent = BrowserUserAgentPolicy.system.safariCompatibleUserAgent - #expect(webView.restartNavigationForBrowserUserAgentPolicyIfNeeded( - for: replacementRequest, - targetFrameIsMainFrame: true, - decisionHandler: { decisions.append($0) }, - startReplacement: { replacementRequests.append($0) } - )) - #expect(decisions == [.cancel, .cancel]) - #expect(replacementRequests.count == 2) - #expect((webView.customUserAgent ?? "").isEmpty) - } }