1package cef
2
3import (
4 "context"
5 "sync"
6
7 purecef "github.com/bnema/purego-cef/cef"
8
9 "github.com/bnema/dumber/internal/logging"
10)
11
12const (
13 popupOpenerExtraInfoEnabledKey = "dumber.popup_opener.enabled"
14 popupOpenerExtraInfoParentURIKey = "dumber.popup_opener.parent_uri"
15 popupOpenerExtraInfoBridgeNonceKey = "dumber.popup_opener.bridge_nonce"
16 popupOpenerRenderScriptURL = "dumber://popup-opener-renderer"
17)
18
19type popupOpenerRenderMetadata struct {
20 ParentURI string
21 BridgeNonce string
22}
23
24var popupOpenerRenderExtraInfoBuilder = buildPopupOpenerRenderExtraInfo
25
26func buildPopupOpenerRenderExtraInfo(parentURI, bridgeNonce string) purecef.DictionaryValue {
27 if parentURI == "" || bridgeNonce == "" {
28 return nil
29 }
30 extraInfo := purecef.DictionaryValueCreate()
31 if extraInfo == nil {
32 return nil
33 }
34 extraInfo.SetBool(popupOpenerExtraInfoEnabledKey, 1)
35 extraInfo.SetString(popupOpenerExtraInfoParentURIKey, parentURI)
36 extraInfo.SetString(popupOpenerExtraInfoBridgeNonceKey, bridgeNonce)
37 return extraInfo
38}
39
40func decodePopupOpenerRenderMetadata(extraInfo purecef.DictionaryValue) (popupOpenerRenderMetadata, bool) {
41 var metadata popupOpenerRenderMetadata
42 if extraInfo == nil || extraInfo.GetBool(popupOpenerExtraInfoEnabledKey) != 1 {
43 return metadata, false
44 }
45 metadata.ParentURI = extraInfo.GetString(popupOpenerExtraInfoParentURIKey)
46 metadata.BridgeNonce = extraInfo.GetString(popupOpenerExtraInfoBridgeNonceKey)
47 if metadata.ParentURI == "" || metadata.BridgeNonce == "" {
48 return popupOpenerRenderMetadata{}, false
49 }
50 return metadata, true
51}
52
53// composedRenderProcessHandler keeps the popup-opener handler as the primary
54// CEF renderer handler and adds only editable-focus reporting from the legacy
55// renderer bridge. Embedding preserves every popup callback without enabling
56// the bridge's V8 extension in OnWebKitInitialized.
57type composedRenderProcessHandler struct {
58 *popupOpenerRenderProcessHandler
59 editableFocusHandler *rendererBridgeProcessHandler
60}
61
62func newComposedRenderProcessHandler() *composedRenderProcessHandler {
63 return &composedRenderProcessHandler{
64 popupOpenerRenderProcessHandler: newPopupOpenerRenderProcessHandler(),
65 editableFocusHandler: &rendererBridgeProcessHandler{},
66 }
67}
68
69func (h *composedRenderProcessHandler) OnFocusedNodeChanged(
70 browser purecef.Browser,
71 frame purecef.Frame,
72 node purecef.Domnode,
73) {
74 h.popupOpenerRenderProcessHandler.OnFocusedNodeChanged(browser, frame, node)
75 h.editableFocusHandler.OnFocusedNodeChanged(browser, frame, node)
76}
77
78type popupOpenerRenderProcessHandler struct {
79 mu sync.RWMutex
80 byBrowserID map[int32]popupOpenerRenderMetadata
81}
82
83func newPopupOpenerRenderProcessHandler() *popupOpenerRenderProcessHandler {
84 return &popupOpenerRenderProcessHandler{byBrowserID: make(map[int32]popupOpenerRenderMetadata)}
85}
86
87func (*popupOpenerRenderProcessHandler) OnWebKitInitialized() {}
88
89func (h *popupOpenerRenderProcessHandler) OnBrowserCreated(browser purecef.Browser, extraInfo purecef.DictionaryValue) {
90 if h == nil || browser == nil {
91 return
92 }
93 metadata, ok := decodePopupOpenerRenderMetadata(extraInfo)
94 if !ok {
95 return
96 }
97 browserID := browser.GetIdentifier()
98 h.mu.Lock()
99 h.byBrowserID[browserID] = metadata
100 h.mu.Unlock()
101}
102
103func (h *popupOpenerRenderProcessHandler) OnBrowserDestroyed(browser purecef.Browser) {
104 if h == nil || browser == nil {
105 return
106 }
107 browserID := browser.GetIdentifier()
108 h.mu.Lock()
109 delete(h.byBrowserID, browserID)
110 h.mu.Unlock()
111}
112
113func (*popupOpenerRenderProcessHandler) GetLoadHandler() purecef.LoadHandler { return nil }
114
115func (h *popupOpenerRenderProcessHandler) OnContextCreated(browser purecef.Browser, frame purecef.Frame, ctx purecef.V8Context) {
116 if h == nil || browser == nil || frame == nil || !frame.IsMain() || ctx == nil || !ctx.IsValid() {
117 return
118 }
119 browserID := browser.GetIdentifier()
120 h.mu.RLock()
121 metadata, ok := h.byBrowserID[browserID]
122 h.mu.RUnlock()
123 if !ok {
124 return
125 }
126
127 script := buildPopupOpenerBridgeJS(metadata.BridgeNonce, metadata.ParentURI)
128 if script == "" {
129 return
130 }
131 if ctx.Enter() != 1 {
132 logging.FromContext(context.Background()).Warn().
133 Int32("browser_id", browserID).
134 Msg("cef: popup opener renderer bridge could not enter V8 context")
135 return
136 }
137 defer ctx.Exit()
138 if ctx.Eval(script, popupOpenerRenderScriptURL, 0, nil, nil) != 1 {
139 logging.FromContext(context.Background()).Warn().
140 Int32("browser_id", browserID).
141 Msg("cef: popup opener renderer bridge eval failed")
142 }
143}
144
145func (*popupOpenerRenderProcessHandler) OnContextReleased(purecef.Browser, purecef.Frame, purecef.V8Context) {
146}
147
148func (*popupOpenerRenderProcessHandler) OnUncaughtException(
149 purecef.Browser,
150 purecef.Frame,
151 purecef.V8Context,
152 purecef.V8Exception,
153 purecef.V8StackTrace,
154) {
155}
156
157func (*popupOpenerRenderProcessHandler) OnFocusedNodeChanged(purecef.Browser, purecef.Frame, purecef.Domnode) {
158}
159
160func (*popupOpenerRenderProcessHandler) OnProcessMessageReceived(
161 purecef.Browser,
162 purecef.Frame,
163 purecef.ProcessID,
164 purecef.ProcessMessage,
165) int32 {
166 return 0
167}