webview_popup_proxy.go RawHistoryFolder
1package cef
2
3import (
4 "context"
5 "strings"
6
7 "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/logging"
9)
10
11type syntheticPopupState struct {
12 WebView port.WebView
13 PendingURI string
14 Closed bool
15}
16
17// syntheticPopupStateLocked returns the synthetic popup state for proxyID.
18// Caller must hold wv.syntheticPopupMu.
19func (wv *WebView) syntheticPopupStateLocked(proxyID string) *syntheticPopupState {
20 if wv == nil || proxyID == "" {
21 return nil
22 }
23 if wv.syntheticPopups == nil {
24 wv.syntheticPopups = make(map[string]*syntheticPopupState)
25 }
26 state := wv.syntheticPopups[proxyID]
27 if state == nil {
28 state = &syntheticPopupState{}
29 wv.syntheticPopups[proxyID] = state
30 }
31 return state
32}
33
34func (wv *WebView) deleteSyntheticPopupState(proxyID string) {
35 if wv == nil || proxyID == "" {
36 return
37 }
38
39 wv.syntheticPopupMu.Lock()
40 defer wv.syntheticPopupMu.Unlock()
41 delete(wv.syntheticPopups, proxyID)
42}
43
44func (wv *WebView) handleSyntheticPopupOpen(targetURL, frameName, proxyID string, userGesture, noJavaScriptAccess bool) {
45 if wv == nil || proxyID == "" || wv.destroyed.Load() {
46 return
47 }
48
49 wv.syntheticPopupMu.Lock()
50 state := wv.syntheticPopupStateLocked(proxyID)
51 if state.Closed {
52 delete(wv.syntheticPopups, proxyID)
53 wv.syntheticPopupMu.Unlock()
54 return
55 }
56 wv.syntheticPopupMu.Unlock()
57
58 wv.runOnGTK(func() {
59 if wv.destroyed.Load() {
60 return
61 }
62
63 wv.syntheticPopupMu.Lock()
64 if state := wv.syntheticPopups[proxyID]; state != nil && state.Closed {
65 delete(wv.syntheticPopups, proxyID)
66 wv.syntheticPopupMu.Unlock()
67 return
68 }
69 wv.syntheticPopupMu.Unlock()
70
71 wv.mu.RLock()
72 cb := wv.callbacks
73 wv.mu.RUnlock()
74 if cb == nil || cb.OnCreate == nil {
75 wv.deleteSyntheticPopupState(proxyID)
76 return
77 }
78
79 popupWV := cb.OnCreate(port.PopupRequest{
80 TargetURI: targetURL,
81 FrameName: frameName,
82 IsUserGesture: userGesture,
83 NoJavaScriptAccess: noJavaScriptAccess,
84 ParentViewID: wv.id,
85 })
86 if popupWV == nil {
87 wv.deleteSyntheticPopupState(proxyID)
88 return
89 }
90 if cefPopup, ok := popupWV.(*WebView); ok {
91 cefPopup.setPopupNoJavaScriptAccess(noJavaScriptAccess)
92 }
93
94 pendingURI := ""
95 wv.syntheticPopupMu.Lock()
96 state := wv.syntheticPopupStateLocked(proxyID)
97 if state.Closed {
98 delete(wv.syntheticPopups, proxyID)
99 wv.syntheticPopupMu.Unlock()
100 closeSyntheticPopupWebView(popupWV)
101 return
102 }
103 state.WebView = popupWV
104 pendingURI = strings.TrimSpace(state.PendingURI)
105 wv.syntheticPopupMu.Unlock()
106
107 if pendingURI == "" || pendingURI == strings.TrimSpace(targetURL) || popupWV.IsDestroyed() {
108 return
109 }
110 if err := popupWV.LoadURI(context.Background(), pendingURI); err != nil {
111 logging.FromContext(wv.ctx).Warn().
112 Err(err).
113 Str("proxy_id", proxyID).
114 Str("uri", logging.TruncateURL(pendingURI, logging.PermissionLogURLMaxLen)).
115 Msg("cef: failed to replay synthetic popup navigation")
116 }
117 })
118}
119
120func (wv *WebView) handleSyntheticPopupNavigate(proxyID, targetURL string) {
121 if wv == nil || proxyID == "" || wv.destroyed.Load() {
122 return
123 }
124
125 trimmedURI := strings.TrimSpace(targetURL)
126 wv.syntheticPopupMu.Lock()
127 state := wv.syntheticPopupStateLocked(proxyID)
128 if state.Closed {
129 wv.syntheticPopupMu.Unlock()
130 return
131 }
132 state.PendingURI = trimmedURI
133 popupWV := state.WebView
134 wv.syntheticPopupMu.Unlock()
135
136 if trimmedURI == "" || popupWV == nil {
137 return
138 }
139
140 wv.runOnGTK(func() {
141 if popupWV.IsDestroyed() {
142 wv.syntheticPopupMu.Lock()
143 delete(wv.syntheticPopups, proxyID)
144 wv.syntheticPopupMu.Unlock()
145 return
146 }
147 if err := popupWV.LoadURI(context.Background(), trimmedURI); err != nil {
148 logging.FromContext(wv.ctx).Warn().
149 Err(err).
150 Str("proxy_id", proxyID).
151 Str("uri", logging.TruncateURL(trimmedURI, logging.PermissionLogURLMaxLen)).
152 Msg("cef: failed to navigate synthetic popup")
153 }
154 })
155}
156
157func (wv *WebView) handleSyntheticPopupClose(proxyID string) {
158 if wv == nil || proxyID == "" || wv.destroyed.Load() {
159 return
160 }
161
162 wv.syntheticPopupMu.Lock()
163 if wv.syntheticPopups == nil {
164 wv.syntheticPopupMu.Unlock()
165 return
166 }
167 state := wv.syntheticPopups[proxyID]
168 if state == nil {
169 wv.syntheticPopupMu.Unlock()
170 return
171 }
172 state.Closed = true
173 popupWV := state.WebView
174 delete(wv.syntheticPopups, proxyID)
175 wv.syntheticPopupMu.Unlock()
176 if popupWV == nil {
177 return
178 }
179
180 wv.runOnGTK(func() {
181 closeSyntheticPopupWebView(popupWV)
182 })
183}
184
185func closeSyntheticPopupWebView(popupWV port.WebView) {
186 if popupWV == nil {
187 return
188 }
189 if closeable, ok := popupWV.(port.OAuthCallbackCapable); ok {
190 closeable.Close()
191 return
192 }
193 popupWV.Destroy()
194}