detached 9f96981

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 {
5353 Payload json.RawMessage `json:"payload"`
5454 }
5555
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-
7656 // NewHandler creates a new message handler
7757 func NewHandler(parserService *services.ParserService, browserService *services.BrowserService) *Handler {
7858 return &Handler{
@@ -123,8 +103,6 @@ func (h *Handler) Handle(payload string) {
123103 h.handleHistoryDelete(msg)
124104 case "workspace":
125105 h.handleWorkspace(msg)
126- case "handle-window-open":
127- h.handleWindowOpen(msg)
128106 case "close-popup":
129107 h.handleClosePopup(msg)
130108 case "console-message":
@@ -530,33 +508,6 @@ func (h *Handler) handleHistoryDelete(msg Message) {
530508 _ = h.webView.InjectScript("window.__dumber_history_deleted && window.__dumber_history_deleted(" + string(b) + ")")
531509 }
532510
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-
560511 // handleConsoleMessage processes console-message from JavaScript
561512 func (h *Handler) handleConsoleMessage(msg Message) {
562513 // Check if console capture is enabled
@@ -595,7 +546,7 @@ func (h *Handler) handleWebViewIDRequest(msg Message) {
595546 }
596547
597548 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)
599550
600551 // Send the webview ID back to JavaScript via custom event
601552 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 handler56 // 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 JavaScript511 // 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 enabled513 // 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 event551 // 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{

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close