diff --git a/Nook/Components/Peek/PeekOverlayView.swift b/Nook/Components/Peek/PeekOverlayView.swift index d89a1cc0..fdd3ec69 100644 --- a/Nook/Components/Peek/PeekOverlayView.swift +++ b/Nook/Components/Peek/PeekOverlayView.swift @@ -60,7 +60,7 @@ struct PeekOverlayView: View { } } .zIndex(9999) // Put it at the very top - .allowsHitTesting(true) // Always allow hit testing + .allowsHitTesting(isActive) // Only intercept events when Peek is active .onAppear { // Sync initial state when view appears if browserManager.peekManager.isActive { diff --git a/Nook/Components/WebsiteView/WebsiteView.swift b/Nook/Components/WebsiteView/WebsiteView.swift index a8616c43..68367fd2 100644 --- a/Nook/Components/WebsiteView/WebsiteView.swift +++ b/Nook/Components/WebsiteView/WebsiteView.swift @@ -221,7 +221,6 @@ struct WebsiteView: View { // Critical: Use allowsHitTesting to prevent SwiftUI from intercepting mouse events // This allows right-clicks to pass through to the underlying NSView (WKWebView) .allowsHitTesting(true) - .contentShape(Rectangle()) } // Removed SwiftUI contextMenu - it intercepts ALL right-clicks // WKWebView's willOpenMenu will handle context menus for images @@ -524,6 +523,7 @@ struct TabCompositorWrapper: NSViewRepresentable { context.coordinator.lastVersion != compositorVersion if needsRebuild { + let previousCurrentId = context.coordinator.lastCurrentId updateCompositor(nsView) context.coordinator.lastIsSplit = isSplit context.coordinator.lastLeftId = leftId @@ -532,6 +532,27 @@ struct TabCompositorWrapper: NSViewRepresentable { context.coordinator.lastFraction = splitFraction context.coordinator.lastSize = size context.coordinator.lastVersion = compositorVersion + + // Restore focus when tab changed (webview is now in hierarchy) + if previousCurrentId != currentId { + DispatchQueue.main.async { + guard let window = nsView.window else { return } + for subview in nsView.subviews.reversed() { + if subview is SplitDropCaptureView { continue } + if let webView = subview as? WKWebView, !webView.isHidden { + window.makeFirstResponder(webView) + return + } + // Check pane containers (split view) + for child in subview.subviews { + if let webView = child as? WKWebView, !child.isHidden { + window.makeFirstResponder(webView) + return + } + } + } + } + } } // Mark current tab as accessed (resets unload timer) @@ -822,7 +843,13 @@ private extension WebsiteView { private class ContainerView: NSView { // Don't intercept events - let them pass through to webviews override var acceptsFirstResponder: Bool { false } - + + override func resetCursorRects() { + // Empty: prevents NSHostingView and other ancestors from registering + // arrow cursor rects over the webview. WKWebView uses NSCursor.set() + // internally, which works correctly when cursor rects don't override it. + } + // Forward right-clicks to the webview below so context menus work override func rightMouseDown(with event: NSEvent) { print("🔽 [ContainerView] rightMouseDown received, forwarding to webview") diff --git a/Nook/Models/BrowserConfig/BrowserConfig.swift b/Nook/Models/BrowserConfig/BrowserConfig.swift index 6bcb1e12..507366bb 100644 --- a/Nook/Models/BrowserConfig/BrowserConfig.swift +++ b/Nook/Models/BrowserConfig/BrowserConfig.swift @@ -111,22 +111,7 @@ class BrowserConfiguration { return config } - - // MARK: - User-Agent Client Hints Configuration - - /// Configure User-Agent Client Hints for enhanced browser capability reporting - /// This ensures websites receive proper information about platform capabilities - /// including passkey/WebAuthn support - private func configureClientHints(_ config: WKWebViewConfiguration) { - // User-Agent Client Hints are automatically supported when using a Safari-compatible User-Agent - // The applicationNameForUserAgent setting enables this functionality - // Additional headers like Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform are sent automatically - - // Enable features that support Client Hints - config.preferences.setValue(true, forKey: "mediaDevicesEnabled") - config.preferences.setValue(true, forKey: "getUserMediaRequiresFocus") - } - + // MARK: - Chrome Web Store Integration /// Get the Web Store injector script diff --git a/Nook/Models/Tab/Tab.swift b/Nook/Models/Tab/Tab.swift index b6ad7ae1..37949f20 100644 --- a/Nook/Models/Tab/Tab.swift +++ b/Nook/Models/Tab/Tab.swift @@ -3421,9 +3421,9 @@ extension Tab: WKUIDelegate { completionHandler(true, nil) } - // MARK: - WebAuthn / Passkey Support + // MARK: - Media Capture Authorization - /// Handle requests for media capture authorization (including WebAuthn/passkey requests) + /// Handle requests for camera/microphone capture authorization @available(macOS 13.0, *) public func webView( _ webView: WKWebView, @@ -3436,8 +3436,6 @@ extension Tab: WKUIDelegate { "🔐 [Tab] Media capture authorization requested for type: \(type.rawValue) from origin: \(origin)" ) - // For passkeys/WebAuthn, we want to grant permission - // The system will handle the actual Touch ID/Face ID prompt decisionHandler(.grant) } } diff --git a/Nook/Nook.entitlements b/Nook/Nook.entitlements index 9c952ad1..6f3ed9f9 100644 --- a/Nook/Nook.entitlements +++ b/Nook/Nook.entitlements @@ -2,6 +2,12 @@ + com.apple.developer.aps-environment + development + com.apple.developer.authentication-services.autofill-credential-provider + + com.apple.developer.web-browser.public-key-credential + com.apple.security.automation.apple-events com.apple.security.device.bluetooth diff --git a/Nook/Utils/WebKit/FocusableWKWebView.swift b/Nook/Utils/WebKit/FocusableWKWebView.swift index 6cf8365f..d85520c5 100644 --- a/Nook/Utils/WebKit/FocusableWKWebView.swift +++ b/Nook/Utils/WebKit/FocusableWKWebView.swift @@ -46,7 +46,7 @@ final class FocusableWKWebView: WKWebView { } super.rightMouseDown(with: event) } - + override var acceptsFirstResponder: Bool { true } override func mouseUp(with event: NSEvent) { @@ -101,7 +101,7 @@ final class FocusableWKWebView: WKWebView { let applied = applyPendingContextMenuIfPossible() print("🔽 [FocusableWKWebView] prepareMenu: applyPendingContextMenuIfPossible returned \(applied)") } - + func handleImageDownload(identifier: String, promptForLocation: Bool = false) { let destinationPreference: Download.DestinationPreference = promptForLocation ? .askUser : .automaticDownloadsFolder @@ -121,7 +121,7 @@ final class FocusableWKWebView: WKWebView { } } } - + private func showSaveDialog( for localURL: URL, suggestedFilename: String, @@ -129,19 +129,19 @@ final class FocusableWKWebView: WKWebView { ) { let savePanel = NSSavePanel() savePanel.nameFieldStringValue = suggestedFilename - + // Set the default directory to Downloads if let downloads = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first { savePanel.directoryURL = downloads } - + // Set allowed file types for images savePanel.allowedContentTypes = allowedContentTypes - + // Set the title and message savePanel.title = "Save Image" savePanel.message = "Choose where to save the image" - + // Show the save dialog savePanel.begin { [weak self] result in if result == .OK, let destinationURL = savePanel.url { @@ -149,7 +149,7 @@ final class FocusableWKWebView: WKWebView { // Move the downloaded file to the chosen location try FileManager.default.moveItem(at: localURL, to: destinationURL) print("🔽 [FocusableWKWebView] Image saved to: \(destinationURL.path)") - + // Show a success notification self?.showSaveSuccessNotification(for: destinationURL) } catch { @@ -162,7 +162,7 @@ final class FocusableWKWebView: WKWebView { } } } - + private func showSaveSuccessNotification(for url: URL) { postUserNotification( title: "Image Saved", @@ -238,9 +238,9 @@ final class FocusableWKWebView: WKWebView { originalURL: URL, destinationPreference: Download.DestinationPreference ) { - guard let tab = owningTab else { + guard let tab = owningTab else { print("🔽 [FocusableWKWebView] No owning tab for download") - return + return } var enrichedRequest = request diff --git a/Onboarding/Components/TransitionView.swift b/Onboarding/Components/TransitionView.swift index adb9ddd2..eaf4fd16 100644 --- a/Onboarding/Components/TransitionView.swift +++ b/Onboarding/Components/TransitionView.swift @@ -52,7 +52,6 @@ struct TransitionView: View { .frame(width: geo.size.width, height: geo.size.height) .opacity(renderedShowB ? 1 : 0) .allowsHitTesting(renderedShowB) - .alwaysArrowCursor() if let cg = snapshot { Image(cg, scale: 1, label: Text(""))