소스 검색

Fix Reddit post share button in the embedded WebView.

Bridge navigator.share to the native macOS share sheet and stop overlay/post CSS from blocking share menus in WKWebView.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 2 주 전
부모
커밋
56f5cd20ce

+ 70 - 0
App AI for Reddit/Managers/RedditShareScriptBridge.swift

@@ -0,0 +1,70 @@
+import AppKit
+import WebKit
+
+@MainActor
+final class RedditShareScriptBridge: NSObject, WKScriptMessageHandler {
+    static let shared = RedditShareScriptBridge()
+
+    weak var webView: WKWebView?
+
+    private override init() {
+        super.init()
+    }
+
+    nonisolated func userContentController(
+        _ userContentController: WKUserContentController,
+        didReceive message: WKScriptMessage
+    ) {
+        guard message.name == RedditShareBridgeStyle.messageHandlerName else { return }
+        Task { @MainActor in
+            handleShareMessage(message)
+        }
+    }
+
+    private func handleShareMessage(_ message: WKScriptMessage) {
+        let targetWebView = webView ?? message.webView
+        guard let targetWebView else {
+            return
+        }
+
+        var items: [Any] = []
+        if let payload = message.body as? [String: Any] {
+            if let urlString = payload["url"] as? String,
+               !urlString.isEmpty,
+               let url = URL(string: urlString) {
+                items.append(url)
+            }
+            if let text = payload["text"] as? String, !text.isEmpty {
+                items.append(text)
+            }
+            if let title = payload["title"] as? String, !title.isEmpty {
+                items.append(title)
+            }
+        } else if let urlString = message.body as? String,
+                  let url = URL(string: urlString) {
+            items.append(url)
+        }
+
+        guard !items.isEmpty else {
+            resolveShare(on: targetWebView, success: false)
+            return
+        }
+
+        let picker = NSSharingServicePicker(items: items)
+        let anchor = NSRect(
+            x: targetWebView.bounds.midX,
+            y: targetWebView.bounds.midY,
+            width: 1,
+            height: 1
+        )
+        picker.show(relativeTo: anchor, of: targetWebView, preferredEdge: .minY)
+        resolveShare(on: targetWebView, success: true)
+    }
+
+    private func resolveShare(on webView: WKWebView, success: Bool) {
+        webView.evaluateJavaScript(
+            "window.__reddoraShareCallback && window.__reddoraShareCallback(\(success));",
+            completionHandler: nil
+        )
+    }
+}

+ 11 - 0
App AI for Reddit/Managers/RedditWebViewManager.swift

@@ -41,6 +41,17 @@ enum RedditWebViewManager {
         )
         configuration.userContentController.addUserScript(authChromeScript)
 
+        let shareBridgeScript = WKUserScript(
+            source: RedditShareBridgeStyle.injectionScript,
+            injectionTime: .atDocumentStart,
+            forMainFrameOnly: true
+        )
+        configuration.userContentController.addUserScript(shareBridgeScript)
+        configuration.userContentController.add(
+            RedditShareScriptBridge.shared,
+            name: RedditShareBridgeStyle.messageHandlerName
+        )
+
         if includeLoginBridge {
             let loginBridgeScript = WKUserScript(
                 source: RedditLoginBridgeStyle.injectionScript,

+ 13 - 9
App AI for Reddit/Utilities/RedditHomeChromeStyle.swift

@@ -84,18 +84,22 @@ enum RedditHomeChromeStyle {
           touch-action: manipulation !important;
         }
 
-        shreddit-overlay-display {
+        /* Only suppress inert overlay layers that block header clicks. */
+        shreddit-overlay-display:not(:has(
+          button,
+          a,
+          [role="button"],
+          [role="menu"],
+          [role="menuitem"],
+          [role="dialog"],
+          [role="listbox"],
+          faceplate-menu,
+          input:not([type="hidden"]),
+          textarea
+        )) {
           pointer-events: none !important;
         }
 
-        shreddit-overlay-display:has(
-          shreddit-signup-drawer,
-          auth-flow-modal,
-          input[type="password"]
-        ) {
-          pointer-events: auto !important;
-        }
-
         /* Feed sort row sits below the header; keep it from overlapping action buttons. */
         shreddit-sort-dropdown,
         [data-testid="sort-dropdown"],

+ 1 - 1
App AI for Reddit/Utilities/RedditPostBoundaryStyle.swift

@@ -17,7 +17,7 @@ enum RedditPostBoundaryStyle {
           background-color: rgba(255, 255, 255, 0.02) !important;
           box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.03) !important;
           margin-block: 10px !important;
-          overflow: clip !important;
+          overflow: visible !important;
         }
 
         shreddit-post:not([promoted]):not([data-promoted="true"])::part(post),

+ 75 - 0
App AI for Reddit/Utilities/RedditShareBridgeStyle.swift

@@ -0,0 +1,75 @@
+import Foundation
+
+enum RedditShareBridgeStyle {
+    static let messageHandlerName = "reddoraShare"
+
+    /// WKWebView does not implement navigator.share. Reddit's share button relies on it
+    /// in Safari, so we bridge to the native macOS share sheet.
+    static let injectionScript = """
+    (function() {
+      if (window.__reddoraShareBridgeInstalled) return;
+      window.__reddoraShareBridgeInstalled = true;
+
+      function hasBridge() {
+        return !!(
+          window.webkit &&
+          window.webkit.messageHandlers &&
+          window.webkit.messageHandlers.reddoraShare
+        );
+      }
+
+      window.__reddoraShareCallback = function(success) {
+        const resolve = window.__reddoraShareResolve;
+        const reject = window.__reddoraShareReject;
+        window.__reddoraShareResolve = null;
+        window.__reddoraShareReject = null;
+
+        if (success && resolve) {
+          resolve();
+        } else if (!success && reject) {
+          reject(new DOMException('Share canceled', 'AbortError'));
+        }
+      };
+
+      function nativeShare(data) {
+        return new Promise(function(resolve, reject) {
+          if (!hasBridge()) {
+            reject(new DOMException('Share unavailable', 'NotSupportedError'));
+            return;
+          }
+
+          window.__reddoraShareResolve = resolve;
+          window.__reddoraShareReject = reject;
+          window.webkit.messageHandlers.reddoraShare.postMessage({
+            title: data && data.title ? String(data.title) : '',
+            text: data && data.text ? String(data.text) : '',
+            url: data && data.url ? String(data.url) : ''
+          });
+        });
+      }
+
+      function canShare(data) {
+        if (!hasBridge()) return false;
+        if (!data) return true;
+        if (data.files && data.files.length) return false;
+        return !!(data.url || data.text || data.title);
+      }
+
+      try {
+        Object.defineProperty(navigator, 'share', {
+          value: nativeShare,
+          configurable: true,
+          writable: true
+        });
+        Object.defineProperty(navigator, 'canShare', {
+          value: canShare,
+          configurable: true,
+          writable: true
+        });
+      } catch (error) {
+        navigator.share = nativeShare;
+        navigator.canShare = canShare;
+      }
+    })();
+    """
+}

+ 7 - 0
App AI for Reddit/Views/RedditWebView.swift

@@ -26,6 +26,7 @@ struct RedditWebView: NSViewRepresentable {
         context.coordinator.lastReloadTrigger = reloadTrigger
         context.coordinator.lastAppliedDarkMode = isDarkMode
         context.coordinator.installLoginBridge()
+        context.coordinator.attachShareBridge(to: container.webView)
         context.coordinator.startObservingNavigation(in: container.webView)
         container.applyTheme(isDarkMode: isDarkMode)
 
@@ -49,6 +50,7 @@ struct RedditWebView: NSViewRepresentable {
     func updateNSView(_ container: RedditWebViewContainer, context: Context) {
         context.coordinator.onCanGoBackChange = onCanGoBackChange
         context.coordinator.installLoginBridge()
+        context.coordinator.attachShareBridge(to: container.webView)
 
         if context.coordinator.lastAppliedDarkMode != isDarkMode {
             context.coordinator.lastAppliedDarkMode = isDarkMode
@@ -128,9 +130,14 @@ struct RedditWebView: NSViewRepresentable {
             }
         }
 
+        func attachShareBridge(to webView: WKWebView) {
+            RedditShareScriptBridge.shared.webView = webView
+        }
+
         func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
             startObservingNavigation(in: webView)
             reportBackButtonVisibility(for: webView)
+            webView.evaluateJavaScript(RedditShareBridgeStyle.injectionScript, completionHandler: nil)
             webView.evaluateJavaScript(RedditPostBoundaryStyle.injectionScript, completionHandler: nil)
             webView.evaluateJavaScript(RedditHomeChromeStyle.injectionScript, completionHandler: nil)
             webView.evaluateJavaScript(RedditAuthChromeStyle.injectionScript, completionHandler: nil)