refactor(messaging): remove deprecated window.open handling
Remove WindowIntent type and handleWindowOpen() message handler. Delete deduplicator.go (pane request deduplication now handled by WebKit's native lifecycle). Window.open now handled directly by WebKit create signal, no JavaScript message passing needed.
- commit
- 9f96981f98a8981c79b6ce5559b446e2d5584327
- author
- bamen · · 2025-10-12 07:06 UTC
- parent
- 4c93f21
- signature
- unsigned
2 files · +1 -163
D internal/app/messaging/deduplicator.go +0 -113
| @@ -1,113 +0,0 @@ | |||
| 1 | - | package messaging | |
| 2 | - | ||
| 3 | - | import ( | |
| 4 | - | "crypto/sha256" | |
| 5 | - | "fmt" | |
| 6 | - | "log" | |
| 7 | - | "sync" | |
| 8 | - | "time" | |
| 9 | - | ) | |
| 10 | - | ||
| 11 | - | type RequestFingerprint struct { | |
| 12 | - | URL string | |
| 13 | - | WebViewID string | |
| 14 | - | Timestamp int64 | |
| 15 | - | Hash string | |
| 16 | - | } | |
| 17 | - | ||
| 18 | - | type PaneRequestDeduplicator struct { | |
| 19 | - | mu sync.RWMutex | |
| 20 | - | recentRequests map[string]*RequestFingerprint | |
| 21 | - | requestIDs map[string]bool | |
| 22 | - | debounceWindow time.Duration | |
| 23 | - | cleanupInterval time.Duration | |
| 24 | - | lastCleanup time.Time | |
| 25 | - | } | |
| 26 | - | ||
| 27 | - | func NewPaneRequestDeduplicator() *PaneRequestDeduplicator { | |
| 28 | - | return &PaneRequestDeduplicator{ | |
| 29 | - | recentRequests: make(map[string]*RequestFingerprint), | |
| 30 | - | requestIDs: make(map[string]bool), | |
| 31 | - | debounceWindow: 200 * time.Millisecond, | |
| 32 | - | cleanupInterval: 5 * time.Second, | |
| 33 | - | lastCleanup: time.Now(), | |
| 34 | - | } | |
| 35 | - | } | |
| 36 | - | ||
| 37 | - | func (d *PaneRequestDeduplicator) generateFingerprint(intent *WindowIntent, webViewID string) string { | |
| 38 | - | normalizedTime := intent.Timestamp / 100 | |
| 39 | - | data := fmt.Sprintf("%s:%s:%d", intent.URL, webViewID, normalizedTime) | |
| 40 | - | hash := sha256.Sum256([]byte(data)) | |
| 41 | - | return fmt.Sprintf("%x", hash[:8]) | |
| 42 | - | } | |
| 43 | - | ||
| 44 | - | func (d *PaneRequestDeduplicator) IsDuplicate(intent *WindowIntent, webViewID string) (bool, string) { | |
| 45 | - | d.mu.Lock() | |
| 46 | - | defer d.mu.Unlock() | |
| 47 | - | ||
| 48 | - | if time.Since(d.lastCleanup) > d.cleanupInterval { | |
| 49 | - | d.cleanup() | |
| 50 | - | } | |
| 51 | - | ||
| 52 | - | if intent.RequestID != "" { | |
| 53 | - | if d.requestIDs[intent.RequestID] { | |
| 54 | - | return true, fmt.Sprintf("duplicate request ID: %s", intent.RequestID) | |
| 55 | - | } | |
| 56 | - | } | |
| 57 | - | ||
| 58 | - | fingerprint := d.generateFingerprint(intent, webViewID) | |
| 59 | - | ||
| 60 | - | if existing, exists := d.recentRequests[fingerprint]; exists { | |
| 61 | - | timeDiff := time.Duration(intent.Timestamp-existing.Timestamp) * time.Millisecond | |
| 62 | - | if timeDiff < d.debounceWindow { | |
| 63 | - | return true, fmt.Sprintf("duplicate content fingerprint: %s (within %v)", fingerprint, timeDiff) | |
| 64 | - | } | |
| 65 | - | } | |
| 66 | - | ||
| 67 | - | d.recentRequests[fingerprint] = &RequestFingerprint{ | |
| 68 | - | URL: intent.URL, | |
| 69 | - | WebViewID: webViewID, | |
| 70 | - | Timestamp: intent.Timestamp, | |
| 71 | - | Hash: fingerprint, | |
| 72 | - | } | |
| 73 | - | ||
| 74 | - | if intent.RequestID != "" { | |
| 75 | - | d.requestIDs[intent.RequestID] = true | |
| 76 | - | } | |
| 77 | - | ||
| 78 | - | return false, "" | |
| 79 | - | } | |
| 80 | - | ||
| 81 | - | func (d *PaneRequestDeduplicator) cleanup() { | |
| 82 | - | now := time.Now().UnixMilli() | |
| 83 | - | cutoff := now - int64(d.debounceWindow*3/time.Millisecond) | |
| 84 | - | ||
| 85 | - | for key, req := range d.recentRequests { | |
| 86 | - | if now-req.Timestamp > cutoff { | |
| 87 | - | delete(d.recentRequests, key) | |
| 88 | - | } | |
| 89 | - | } | |
| 90 | - | ||
| 91 | - | if len(d.requestIDs) > 100 { | |
| 92 | - | d.requestIDs = make(map[string]bool) | |
| 93 | - | } | |
| 94 | - | ||
| 95 | - | d.lastCleanup = time.Now() | |
| 96 | - | log.Printf("[deduplicator] Cleanup completed: %d fingerprints, %d request IDs", len(d.recentRequests), len(d.requestIDs)) | |
| 97 | - | } | |
| 98 | - | ||
| 99 | - | // ClearRequestID removes a specific request ID from the deduplication cache | |
| 100 | - | // This allows the same RequestID to be used again (e.g., for OAuth verification -> real popup flow) | |
| 101 | - | func (d *PaneRequestDeduplicator) ClearRequestID(requestID string) { | |
| 102 | - | if requestID == "" { | |
| 103 | - | return | |
| 104 | - | } | |
| 105 | - | ||
| 106 | - | d.mu.Lock() | |
| 107 | - | defer d.mu.Unlock() | |
| 108 | - | ||
| 109 | - | if d.requestIDs[requestID] { | |
| 110 | - | delete(d.requestIDs, requestID) | |
| 111 | - | log.Printf("[deduplicator] Cleared request ID: %s", requestID) | |
| 112 | - | } | |
| 113 | - | } | |
| @@ -1,113 +0,0 @@ | |||||
| 1 | - | package messaging | |||
| 2 | - | ||||
| 3 | - | import ( | |||
| 4 | - | "crypto/sha256" | |||
| 5 | - | "fmt" | |||
| 6 | - | "log" | |||
| 7 | - | "sync" | |||
| 8 | - | "time" | |||
| 9 | - | ) | |||
| 10 | - | ||||
| 11 | - | type RequestFingerprint struct { | |||
| 12 | - | URL string | |||
| 13 | - | WebViewID string | |||
| 14 | - | Timestamp int64 | |||
| 15 | - | Hash string | |||
| 16 | - | } | |||
| 17 | - | ||||
| 18 | - | type PaneRequestDeduplicator struct { | |||
| 19 | - | mu sync.RWMutex | |||
| 20 | - | recentRequests map[string]*RequestFingerprint | |||
| 21 | - | requestIDs map[string]bool | |||
| 22 | - | debounceWindow time.Duration | |||
| 23 | - | cleanupInterval time.Duration | |||
| 24 | - | lastCleanup time.Time | |||
| 25 | - | } | |||
| 26 | - | ||||
| 27 | - | func NewPaneRequestDeduplicator() *PaneRequestDeduplicator { | |||
| 28 | - | return &PaneRequestDeduplicator{ | |||
| 29 | - | recentRequests: make(map[string]*RequestFingerprint), | |||
| 30 | - | requestIDs: make(map[string]bool), | |||
| 31 | - | debounceWindow: 200 * time.Millisecond, | |||
| 32 | - | cleanupInterval: 5 * time.Second, | |||
| 33 | - | lastCleanup: time.Now(), | |||
| 34 | - | } | |||
| 35 | - | } | |||
| 36 | - | ||||
| 37 | - | func (d *PaneRequestDeduplicator) generateFingerprint(intent *WindowIntent, webViewID string) string { | |||
| 38 | - | normalizedTime := intent.Timestamp / 100 | |||
| 39 | - | data := fmt.Sprintf("%s:%s:%d", intent.URL, webViewID, normalizedTime) | |||
| 40 | - | hash := sha256.Sum256([]byte(data)) | |||
| 41 | - | return fmt.Sprintf("%x", hash[:8]) | |||
| 42 | - | } | |||
| 43 | - | ||||
| 44 | - | func (d *PaneRequestDeduplicator) IsDuplicate(intent *WindowIntent, webViewID string) (bool, string) { | |||
| 45 | - | d.mu.Lock() | |||
| 46 | - | defer d.mu.Unlock() | |||
| 47 | - | ||||
| 48 | - | if time.Since(d.lastCleanup) > d.cleanupInterval { | |||
| 49 | - | d.cleanup() | |||
| 50 | - | } | |||
| 51 | - | ||||
| 52 | - | if intent.RequestID != "" { | |||
| 53 | - | if d.requestIDs[intent.RequestID] { | |||
| 54 | - | return true, fmt.Sprintf("duplicate request ID: %s", intent.RequestID) | |||
| 55 | - | } | |||
| 56 | - | } | |||
| 57 | - | ||||
| 58 | - | fingerprint := d.generateFingerprint(intent, webViewID) | |||
| 59 | - | ||||
| 60 | - | if existing, exists := d.recentRequests[fingerprint]; exists { | |||
| 61 | - | timeDiff := time.Duration(intent.Timestamp-existing.Timestamp) * time.Millisecond | |||
| 62 | - | if timeDiff < d.debounceWindow { | |||
| 63 | - | return true, fmt.Sprintf("duplicate content fingerprint: %s (within %v)", fingerprint, timeDiff) | |||
| 64 | - | } | |||
| 65 | - | } | |||
| 66 | - | ||||
| 67 | - | d.recentRequests[fingerprint] = &RequestFingerprint{ | |||
| 68 | - | URL: intent.URL, | |||
| 69 | - | WebViewID: webViewID, | |||
| 70 | - | Timestamp: intent.Timestamp, | |||
| 71 | - | Hash: fingerprint, | |||
| 72 | - | } | |||
| 73 | - | ||||
| 74 | - | if intent.RequestID != "" { | |||
| 75 | - | d.requestIDs[intent.RequestID] = true | |||
| 76 | - | } | |||
| 77 | - | ||||
| 78 | - | return false, "" | |||
| 79 | - | } | |||
| 80 | - | ||||
| 81 | - | func (d *PaneRequestDeduplicator) cleanup() { | |||
| 82 | - | now := time.Now().UnixMilli() | |||
| 83 | - | cutoff := now - int64(d.debounceWindow*3/time.Millisecond) | |||
| 84 | - | ||||
| 85 | - | for key, req := range d.recentRequests { | |||
| 86 | - | if now-req.Timestamp > cutoff { | |||
| 87 | - | delete(d.recentRequests, key) | |||
| 88 | - | } | |||
| 89 | - | } | |||
| 90 | - | ||||
| 91 | - | if len(d.requestIDs) > 100 { | |||
| 92 | - | d.requestIDs = make(map[string]bool) | |||
| 93 | - | } | |||
| 94 | - | ||||
| 95 | - | d.lastCleanup = time.Now() | |||
| 96 | - | log.Printf("[deduplicator] Cleanup completed: %d fingerprints, %d request IDs", len(d.recentRequests), len(d.requestIDs)) | |||
| 97 | - | } | |||
| 98 | - | ||||
| 99 | - | // ClearRequestID removes a specific request ID from the deduplication cache | |||
| 100 | - | // This allows the same RequestID to be used again (e.g., for OAuth verification -> real popup flow) | |||
| 101 | - | func (d *PaneRequestDeduplicator) ClearRequestID(requestID string) { | |||
| 102 | - | if requestID == "" { | |||
| 103 | - | return | |||
| 104 | - | } | |||
| 105 | - | ||||
| 106 | - | d.mu.Lock() | |||
| 107 | - | defer d.mu.Unlock() | |||
| 108 | - | ||||
| 109 | - | if d.requestIDs[requestID] { | |||
| 110 | - | delete(d.requestIDs, requestID) | |||
| 111 | - | log.Printf("[deduplicator] Cleared request ID: %s", requestID) | |||
| 112 | - | } | |||
| 113 | - | } | |||
M internal/app/messaging/handler.go +1 -50
| @@ -53,26 +53,6 @@ type Message struct { | |||
| 53 | 53 | Payload json.RawMessage `json:"payload"` | |
| 54 | 54 | } | |
| 55 | 55 | ||
| 56 | - | // WindowIntent represents an intercepted window.open call from JavaScript | |
| 57 | - | type WindowIntent struct { | |
| 58 | - | URL string `json:"url"` | |
| 59 | - | Target string `json:"target"` | |
| 60 | - | Features string `json:"features"` | |
| 61 | - | Timestamp int64 `json:"timestamp"` | |
| 62 | - | WindowType string `json:"windowType"` // "tab", "popup", "unknown" | |
| 63 | - | IsPopup bool `json:"isPopup"` | |
| 64 | - | IsTab bool `json:"isTab"` | |
| 65 | - | RequestID string `json:"requestId"` // NEW: Unique request identifier | |
| 66 | - | UserTriggered bool `json:"userTriggered"` // NEW: Whether this was user-initiated | |
| 67 | - | // Parsed features | |
| 68 | - | Width *int `json:"width,omitempty"` | |
| 69 | - | Height *int `json:"height,omitempty"` | |
| 70 | - | Toolbar *bool `json:"toolbar,omitempty"` | |
| 71 | - | Location *bool `json:"location,omitempty"` | |
| 72 | - | Menubar *bool `json:"menubar,omitempty"` | |
| 73 | - | Resizable *bool `json:"resizable,omitempty"` | |
| 74 | - | } | |
| 75 | - | ||
| 76 | 56 | // NewHandler creates a new message handler | |
| 77 | 57 | func NewHandler(parserService *services.ParserService, browserService *services.BrowserService) *Handler { | |
| 78 | 58 | return &Handler{ | |
| @@ -123,8 +103,6 @@ func (h *Handler) Handle(payload string) { | |||
| 123 | 103 | h.handleHistoryDelete(msg) | |
| 124 | 104 | case "workspace": | |
| 125 | 105 | h.handleWorkspace(msg) | |
| 126 | - | case "handle-window-open": | |
| 127 | - | h.handleWindowOpen(msg) | |
| 128 | 106 | case "close-popup": | |
| 129 | 107 | h.handleClosePopup(msg) | |
| 130 | 108 | case "console-message": | |
| @@ -530,33 +508,6 @@ func (h *Handler) handleHistoryDelete(msg Message) { | |||
| 530 | 508 | _ = h.webView.InjectScript("window.__dumber_history_deleted && window.__dumber_history_deleted(" + string(b) + ")") | |
| 531 | 509 | } | |
| 532 | 510 | ||
| 533 | - | // handleWindowOpen processes handle-window-open messages to create panes directly | |
| 534 | - | func (h *Handler) handleWindowOpen(msg Message) { | |
| 535 | - | var intent WindowIntent | |
| 536 | - | if err := json.Unmarshal(msg.Payload, &intent); err != nil { | |
| 537 | - | log.Printf("[messaging] Failed to unmarshal handle-window-open: %v", err) | |
| 538 | - | return | |
| 539 | - | } | |
| 540 | - | ||
| 541 | - | log.Printf("[messaging] Handling direct window.open request: url=%s type=%s requestId=%s", intent.URL, intent.WindowType, intent.RequestID) | |
| 542 | - | ||
| 543 | - | // Forward to workspace observer if available | |
| 544 | - | if h.workspaceObserver != nil && h.webView != nil { | |
| 545 | - | // Create a workspace message to trigger pane creation | |
| 546 | - | workspaceMsg := Message{ | |
| 547 | - | Type: "workspace", | |
| 548 | - | Event: "create-pane", | |
| 549 | - | URL: intent.URL, | |
| 550 | - | Action: intent.WindowType, // "tab" or "popup" | |
| 551 | - | RequestID: intent.RequestID, // NEW: Pass through request ID | |
| 552 | - | } | |
| 553 | - | log.Printf("[messaging] Forwarding to workspace: %+v", workspaceMsg) | |
| 554 | - | h.workspaceObserver.OnWorkspaceMessage(h.webView, workspaceMsg) | |
| 555 | - | } else { | |
| 556 | - | log.Printf("[messaging] No workspace observer available for direct window.open") | |
| 557 | - | } | |
| 558 | - | } | |
| 559 | - | ||
| 560 | 511 | // handleConsoleMessage processes console-message from JavaScript | |
| 561 | 512 | func (h *Handler) handleConsoleMessage(msg Message) { | |
| 562 | 513 | // Check if console capture is enabled | |
| @@ -595,7 +546,7 @@ func (h *Handler) handleWebViewIDRequest(msg Message) { | |||
| 595 | 546 | } | |
| 596 | 547 | ||
| 597 | 548 | webViewID := h.webView.ID() | |
| 598 | - | log.Printf("[messaging] Sending webview ID %s to JavaScript", webViewID) | |
| 549 | + | log.Printf("[messaging] Sending webview ID %d to JavaScript", webViewID) | |
| 599 | 550 | ||
| 600 | 551 | // Send the webview ID back to JavaScript via custom event | |
| 601 | 552 | if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{ | |
| @@ -53,26 +53,6 @@ type Message struct { | |||||
| 53 | Payload json.RawMessage `json:"payload"` | 53 | Payload json.RawMessage `json:"payload"` | ||
| 54 | } | 54 | } | ||
| 55 | 55 | ||||
| 56 | - | // WindowIntent represents an intercepted window.open call from JavaScript | |||
| 57 | - | type WindowIntent struct { | |||
| 58 | - | URL string `json:"url"` | |||
| 59 | - | Target string `json:"target"` | |||
| 60 | - | Features string `json:"features"` | |||
| 61 | - | Timestamp int64 `json:"timestamp"` | |||
| 62 | - | WindowType string `json:"windowType"` // "tab", "popup", "unknown" | |||
| 63 | - | IsPopup bool `json:"isPopup"` | |||
| 64 | - | IsTab bool `json:"isTab"` | |||
| 65 | - | RequestID string `json:"requestId"` // NEW: Unique request identifier | |||
| 66 | - | UserTriggered bool `json:"userTriggered"` // NEW: Whether this was user-initiated | |||
| 67 | - | // Parsed features | |||
| 68 | - | Width *int `json:"width,omitempty"` | |||
| 69 | - | Height *int `json:"height,omitempty"` | |||
| 70 | - | Toolbar *bool `json:"toolbar,omitempty"` | |||
| 71 | - | Location *bool `json:"location,omitempty"` | |||
| 72 | - | Menubar *bool `json:"menubar,omitempty"` | |||
| 73 | - | Resizable *bool `json:"resizable,omitempty"` | |||
| 74 | - | } | |||
| 75 | - | ||||
| 76 | // NewHandler creates a new message handler | 56 | // NewHandler creates a new message handler | ||
| 77 | func NewHandler(parserService *services.ParserService, browserService *services.BrowserService) *Handler { | 57 | func NewHandler(parserService *services.ParserService, browserService *services.BrowserService) *Handler { | ||
| 78 | return &Handler{ | 58 | return &Handler{ | ||
| @@ -123,8 +103,6 @@ func (h *Handler) Handle(payload string) { | |||||
| 123 | h.handleHistoryDelete(msg) | 103 | h.handleHistoryDelete(msg) | ||
| 124 | case "workspace": | 104 | case "workspace": | ||
| 125 | h.handleWorkspace(msg) | 105 | h.handleWorkspace(msg) | ||
| 126 | - | case "handle-window-open": | |||
| 127 | - | h.handleWindowOpen(msg) | |||
| 128 | case "close-popup": | 106 | case "close-popup": | ||
| 129 | h.handleClosePopup(msg) | 107 | h.handleClosePopup(msg) | ||
| 130 | case "console-message": | 108 | case "console-message": | ||
| @@ -530,33 +508,6 @@ func (h *Handler) handleHistoryDelete(msg Message) { | |||||
| 530 | _ = h.webView.InjectScript("window.__dumber_history_deleted && window.__dumber_history_deleted(" + string(b) + ")") | 508 | _ = h.webView.InjectScript("window.__dumber_history_deleted && window.__dumber_history_deleted(" + string(b) + ")") | ||
| 531 | } | 509 | } | ||
| 532 | 510 | ||||
| 533 | - | // handleWindowOpen processes handle-window-open messages to create panes directly | |||
| 534 | - | func (h *Handler) handleWindowOpen(msg Message) { | |||
| 535 | - | var intent WindowIntent | |||
| 536 | - | if err := json.Unmarshal(msg.Payload, &intent); err != nil { | |||
| 537 | - | log.Printf("[messaging] Failed to unmarshal handle-window-open: %v", err) | |||
| 538 | - | return | |||
| 539 | - | } | |||
| 540 | - | ||||
| 541 | - | log.Printf("[messaging] Handling direct window.open request: url=%s type=%s requestId=%s", intent.URL, intent.WindowType, intent.RequestID) | |||
| 542 | - | ||||
| 543 | - | // Forward to workspace observer if available | |||
| 544 | - | if h.workspaceObserver != nil && h.webView != nil { | |||
| 545 | - | // Create a workspace message to trigger pane creation | |||
| 546 | - | workspaceMsg := Message{ | |||
| 547 | - | Type: "workspace", | |||
| 548 | - | Event: "create-pane", | |||
| 549 | - | URL: intent.URL, | |||
| 550 | - | Action: intent.WindowType, // "tab" or "popup" | |||
| 551 | - | RequestID: intent.RequestID, // NEW: Pass through request ID | |||
| 552 | - | } | |||
| 553 | - | log.Printf("[messaging] Forwarding to workspace: %+v", workspaceMsg) | |||
| 554 | - | h.workspaceObserver.OnWorkspaceMessage(h.webView, workspaceMsg) | |||
| 555 | - | } else { | |||
| 556 | - | log.Printf("[messaging] No workspace observer available for direct window.open") | |||
| 557 | - | } | |||
| 558 | - | } | |||
| 559 | - | ||||
| 560 | // handleConsoleMessage processes console-message from JavaScript | 511 | // handleConsoleMessage processes console-message from JavaScript | ||
| 561 | func (h *Handler) handleConsoleMessage(msg Message) { | 512 | func (h *Handler) handleConsoleMessage(msg Message) { | ||
| 562 | // Check if console capture is enabled | 513 | // Check if console capture is enabled | ||
| @@ -595,7 +546,7 @@ func (h *Handler) handleWebViewIDRequest(msg Message) { | |||||
| 595 | } | 546 | } | ||
| 596 | 547 | ||||
| 597 | webViewID := h.webView.ID() | 548 | webViewID := h.webView.ID() | ||
| 598 | - | log.Printf("[messaging] Sending webview ID %s to JavaScript", webViewID) | 549 | + | log.Printf("[messaging] Sending webview ID %d to JavaScript", webViewID) |
| 599 | 550 | ||||
| 600 | // Send the webview ID back to JavaScript via custom event | 551 | // Send the webview ID back to JavaScript via custom event | ||
| 601 | if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{ | 552 | if err := h.webView.DispatchCustomEvent("dumber:webview-id", map[string]any{ | ||