chore: fix branch lint issues
- commit
- 314ad3ddaa85393a88f1b9f3ed338fa8194b690c
- author
- brice · · 2026-04-23 06:46 UTC
- parent
- 292d988
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
11 files · +317 -309
M internal/infrastructure/cef/engine_init.go +100 -64
| @@ -179,51 +179,80 @@ func wireEngine( | |||
| 179 | 179 | return nil, fmt.Errorf("GL loader: %w", err) | |
| 180 | 180 | } | |
| 181 | 181 | ||
| 182 | - | scale := detectHiDPIScale(logger) | |
| 183 | - | ||
| 184 | - | // Initialize the GPU transcoder when enabled in the loaded app config. | |
| 185 | - | var mediaTranscoder port.MediaTranscoder | |
| 186 | - | transcoderState := buildTranscoderStartupState(transcodingCfg) | |
| 187 | - | if transcodingCfg.Enabled { | |
| 188 | - | transcoderState.ProbeAttempted = true | |
| 189 | - | tc := transcoderpkg.New(config.TranscodingConfig{ | |
| 190 | - | Enabled: transcodingCfg.Enabled, | |
| 191 | - | HWAccel: transcodingCfg.HWAccel, | |
| 192 | - | MaxConcurrent: transcodingCfg.MaxConcurrent, | |
| 193 | - | Quality: transcodingCfg.Quality, | |
| 194 | - | }, logger) | |
| 195 | - | caps := tc.Capabilities() | |
| 196 | - | transcoderState.API = caps.API | |
| 197 | - | transcoderState.Encoders = append([]string(nil), caps.Encoders...) | |
| 198 | - | transcoderState.Decoders = append([]string(nil), caps.Decoders...) | |
| 199 | - | if tc.Available() { | |
| 200 | - | mediaTranscoder = tc | |
| 201 | - | transcoderState.Status = "available" | |
| 202 | - | logger.Info(). | |
| 203 | - | Str("api", tc.Capabilities().API). | |
| 204 | - | Strs("encoders", tc.Capabilities().Encoders). | |
| 205 | - | Msg("cef: GPU transcoding available") | |
| 206 | - | } else { | |
| 207 | - | transcoderState.Status = "unavailable_no_compatible_gpu" | |
| 208 | - | logger.Warn().Msg("cef: GPU transcoding enabled but no compatible GPU found — feature disabled") | |
| 209 | - | } | |
| 210 | - | } | |
| 211 | - | ||
| 212 | - | factory := newWebViewFactory(eng, gl, webViewFactoryOptions{ | |
| 213 | - | scale: scale, | |
| 182 | + | mediaTranscoder, transcoderState := initializeEngineTranscoder(transcodingCfg, logger) | |
| 183 | + | eng.gl = gl | |
| 184 | + | eng.factory = newWebViewFactory(eng, gl, webViewFactoryOptions{ | |
| 185 | + | scale: detectHiDPIScale(logger), | |
| 214 | 186 | windowlessFrameRate: windowlessFrameRate, | |
| 215 | 187 | transcoder: mediaTranscoder, | |
| 216 | 188 | mediaClassifier: mediaClassifier, | |
| 217 | 189 | audioOutputFactory: audioFactory, | |
| 218 | 190 | }) | |
| 219 | - | pool := newWebViewPool(factory) | |
| 220 | - | ||
| 221 | - | eng.gl = gl | |
| 222 | - | eng.factory = factory | |
| 223 | - | eng.pool = pool | |
| 191 | + | eng.pool = newWebViewPool(eng.factory) | |
| 224 | 192 | eng.contentInj = newContentInjector(eng, nil) | |
| 225 | 193 | eng.transcoderState = transcoderState | |
| 226 | 194 | ||
| 195 | + | messageRouter, schemeHandler, err := newEngineSchemeHandler( | |
| 196 | + | ctx, | |
| 197 | + | eng, | |
| 198 | + | logger, | |
| 199 | + | mediaTranscoder, | |
| 200 | + | currentConfigPayload, | |
| 201 | + | defaultConfigPayload, | |
| 202 | + | ) | |
| 203 | + | if err != nil { | |
| 204 | + | purecef.Shutdown() | |
| 205 | + | return nil, err | |
| 206 | + | } | |
| 207 | + | eng.messageRouter = messageRouter | |
| 208 | + | eng.schemeHandler = schemeHandler | |
| 209 | + | ||
| 210 | + | registerEngineSchemeFactories(logger, purecef.NewSchemeHandlerFactory(schemeHandler)) | |
| 211 | + | return eng, nil | |
| 212 | + | } | |
| 213 | + | ||
| 214 | + | func initializeEngineTranscoder( | |
| 215 | + | transcodingCfg TranscodingRuntimeConfig, | |
| 216 | + | logger *zerolog.Logger, | |
| 217 | + | ) (port.MediaTranscoder, transcoderStartupState) { | |
| 218 | + | transcoderState := buildTranscoderStartupState(transcodingCfg) | |
| 219 | + | if !transcodingCfg.Enabled { | |
| 220 | + | return nil, transcoderState | |
| 221 | + | } | |
| 222 | + | ||
| 223 | + | transcoderState.ProbeAttempted = true | |
| 224 | + | tc := transcoderpkg.New(config.TranscodingConfig{ | |
| 225 | + | Enabled: transcodingCfg.Enabled, | |
| 226 | + | HWAccel: transcodingCfg.HWAccel, | |
| 227 | + | MaxConcurrent: transcodingCfg.MaxConcurrent, | |
| 228 | + | Quality: transcodingCfg.Quality, | |
| 229 | + | }, logger) | |
| 230 | + | caps := tc.Capabilities() | |
| 231 | + | transcoderState.API = caps.API | |
| 232 | + | transcoderState.Encoders = append([]string(nil), caps.Encoders...) | |
| 233 | + | transcoderState.Decoders = append([]string(nil), caps.Decoders...) | |
| 234 | + | if !tc.Available() { | |
| 235 | + | transcoderState.Status = "unavailable_no_compatible_gpu" | |
| 236 | + | logger.Warn().Msg("cef: GPU transcoding enabled but no compatible GPU found — feature disabled") | |
| 237 | + | return nil, transcoderState | |
| 238 | + | } | |
| 239 | + | ||
| 240 | + | transcoderState.Status = "available" | |
| 241 | + | logger.Info(). | |
| 242 | + | Str("api", caps.API). | |
| 243 | + | Strs("encoders", caps.Encoders). | |
| 244 | + | Msg("cef: GPU transcoding available") | |
| 245 | + | return tc, transcoderState | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | func newEngineSchemeHandler( | |
| 249 | + | ctx context.Context, | |
| 250 | + | eng *Engine, | |
| 251 | + | logger *zerolog.Logger, | |
| 252 | + | mediaTranscoder port.MediaTranscoder, | |
| 253 | + | currentConfigPayload func() ([]byte, error), | |
| 254 | + | defaultConfigPayload func() ([]byte, error), | |
| 255 | + | ) (*MessageRouter, *dumbSchemeHandler, error) { | |
| 227 | 256 | messageRouter := NewMessageRouter(ctx) | |
| 228 | 257 | schemeHandler, err := newDumbSchemeHandler( | |
| 229 | 258 | ctx, | |
| @@ -233,15 +262,25 @@ func wireEngine( | |||
| 233 | 262 | defaultConfigPayload, | |
| 234 | 263 | ) | |
| 235 | 264 | if err != nil { | |
| 236 | - | purecef.Shutdown() | |
| 237 | - | return nil, err | |
| 265 | + | return nil, nil, err | |
| 238 | 266 | } | |
| 239 | 267 | schemeHandler.setAssets(assets.WebUIAssets) | |
| 268 | + | schemeHandler.onClipboardSet = makeGTKClipboardSetter(logger) | |
| 269 | + | schemeHandler.onEditableFocus = eng.handleEditableFocusBridge | |
| 270 | + | schemeHandler.onPopupOpen = eng.handlePopupBridgeOpen | |
| 271 | + | schemeHandler.onPopupNavigate = eng.handlePopupBridgeNavigate | |
| 272 | + | schemeHandler.onPopupClose = eng.handlePopupBridgeClose | |
| 273 | + | schemeHandler.onPopupOpenerNavigate = eng.handlePopupOpenerNavigate | |
| 274 | + | schemeHandler.onPopupOpenerPostMessage = eng.handlePopupOpenerPostMessage | |
| 275 | + | schemeHandler.bridgeNonceValidator = eng.validateBridgeRequest | |
| 276 | + | return messageRouter, schemeHandler, nil | |
| 277 | + | } | |
| 240 | 278 | ||
| 279 | + | func makeGTKClipboardSetter(logger *zerolog.Logger) func(string) { | |
| 241 | 280 | // Bridge clipboard writes from page JS → GDK system clipboard. | |
| 242 | 281 | // The callback is invoked on the CEF IO thread, so schedule the actual GDK | |
| 243 | 282 | // write on the GTK main loop. | |
| 244 | - | schemeHandler.onClipboardSet = func(text string) { | |
| 283 | + | return func(text string) { | |
| 245 | 284 | fn := glib.SourceOnceFunc(func(_ uintptr) { | |
| 246 | 285 | if display := gdk.DisplayGetDefault(); display != nil { | |
| 247 | 286 | if cb := display.GetClipboard(); cb != nil { | |
| @@ -256,40 +295,37 @@ func wireEngine( | |||
| 256 | 295 | }) | |
| 257 | 296 | glib.IdleAddOnce(&fn, 0) | |
| 258 | 297 | } | |
| 298 | + | } | |
| 259 | 299 | ||
| 260 | - | schemeHandler.onEditableFocus = eng.handleEditableFocusBridge | |
| 261 | - | schemeHandler.onPopupOpen = eng.handlePopupBridgeOpen | |
| 262 | - | schemeHandler.onPopupNavigate = eng.handlePopupBridgeNavigate | |
| 263 | - | schemeHandler.onPopupClose = eng.handlePopupBridgeClose | |
| 264 | - | schemeHandler.onPopupOpenerNavigate = eng.handlePopupOpenerNavigate | |
| 265 | - | schemeHandler.onPopupOpenerPostMessage = eng.handlePopupOpenerPostMessage | |
| 266 | - | schemeHandler.bridgeNonceValidator = eng.validateBridgeRequest | |
| 267 | - | ||
| 268 | - | schemeFactory := purecef.NewSchemeHandlerFactory(schemeHandler) | |
| 300 | + | func registerEngineSchemeFactories(logger *zerolog.Logger, schemeFactory purecef.SchemeHandlerFactory) { | |
| 301 | + | registerEngineSchemeFactory(logger, "dumb", "", schemeFactory) | |
| 302 | + | registerEngineSchemeFactory(logger, actualInternalScheme, actualInternalHost, schemeFactory) | |
| 303 | + | } | |
| 269 | 304 | ||
| 270 | - | result := purecef.RegisterSchemeHandlerFactory("dumb", "", schemeFactory) | |
| 271 | - | if result != 1 { | |
| 272 | - | logger.Error().Int32("result", result).Msg("cef: failed to register dumb:// scheme handler factory") | |
| 273 | - | } else { | |
| 305 | + | func registerEngineSchemeFactory( | |
| 306 | + | logger *zerolog.Logger, | |
| 307 | + | scheme, host string, | |
| 308 | + | schemeFactory purecef.SchemeHandlerFactory, | |
| 309 | + | ) { | |
| 310 | + | result := purecef.RegisterSchemeHandlerFactory(scheme, host, schemeFactory) | |
| 311 | + | if scheme == "dumb" && host == "" { | |
| 312 | + | if result != 1 { | |
| 313 | + | logger.Error().Int32("result", result).Msg("cef: failed to register dumb:// scheme handler factory") | |
| 314 | + | return | |
| 315 | + | } | |
| 274 | 316 | logger.Info().Msg("cef: registered dumb:// scheme handler factory") | |
| 317 | + | return | |
| 275 | 318 | } | |
| 276 | - | ||
| 277 | - | result = purecef.RegisterSchemeHandlerFactory(actualInternalScheme, actualInternalHost, schemeFactory) | |
| 278 | 319 | if result != 1 { | |
| 279 | 320 | logger.Error(). | |
| 280 | 321 | Int32("result", result). | |
| 281 | 322 | Str("origin", actualInternalOrigin). | |
| 282 | 323 | Msg("cef: failed to register internal https handler factory") | |
| 283 | - | } else { | |
| 284 | - | logger.Info(). | |
| 285 | - | Str("origin", actualInternalOrigin). | |
| 286 | - | Msg("cef: registered internal https handler factory") | |
| 324 | + | return | |
| 287 | 325 | } | |
| 288 | - | ||
| 289 | - | eng.messageRouter = messageRouter | |
| 290 | - | eng.schemeHandler = schemeHandler | |
| 291 | - | ||
| 292 | - | return eng, nil | |
| 326 | + | logger.Info(). | |
| 327 | + | Str("origin", actualInternalOrigin). | |
| 328 | + | Msg("cef: registered internal https handler factory") | |
| 293 | 329 | } | |
| 294 | 330 | ||
| 295 | 331 | func buildTranscoderStartupState(cfg TranscodingRuntimeConfig) transcoderStartupState { | |
| @@ -179,51 +179,80 @@ func wireEngine( | |||||
| 179 | return nil, fmt.Errorf("GL loader: %w", err) | 179 | return nil, fmt.Errorf("GL loader: %w", err) | ||
| 180 | } | 180 | } | ||
| 181 | 181 | ||||
| 182 | - | scale := detectHiDPIScale(logger) | 182 | + | mediaTranscoder, transcoderState := initializeEngineTranscoder(transcodingCfg, logger) |
| 183 | - | 183 | + | eng.gl = gl | |
| 184 | - | // Initialize the GPU transcoder when enabled in the loaded app config. | 184 | + | eng.factory = newWebViewFactory(eng, gl, webViewFactoryOptions{ |
| 185 | - | var mediaTranscoder port.MediaTranscoder | 185 | + | scale: detectHiDPIScale(logger), |
| 186 | - | transcoderState := buildTranscoderStartupState(transcodingCfg) | |||
| 187 | - | if transcodingCfg.Enabled { | |||
| 188 | - | transcoderState.ProbeAttempted = true | |||
| 189 | - | tc := transcoderpkg.New(config.TranscodingConfig{ | |||
| 190 | - | Enabled: transcodingCfg.Enabled, | |||
| 191 | - | HWAccel: transcodingCfg.HWAccel, | |||
| 192 | - | MaxConcurrent: transcodingCfg.MaxConcurrent, | |||
| 193 | - | Quality: transcodingCfg.Quality, | |||
| 194 | - | }, logger) | |||
| 195 | - | caps := tc.Capabilities() | |||
| 196 | - | transcoderState.API = caps.API | |||
| 197 | - | transcoderState.Encoders = append([]string(nil), caps.Encoders...) | |||
| 198 | - | transcoderState.Decoders = append([]string(nil), caps.Decoders...) | |||
| 199 | - | if tc.Available() { | |||
| 200 | - | mediaTranscoder = tc | |||
| 201 | - | transcoderState.Status = "available" | |||
| 202 | - | logger.Info(). | |||
| 203 | - | Str("api", tc.Capabilities().API). | |||
| 204 | - | Strs("encoders", tc.Capabilities().Encoders). | |||
| 205 | - | Msg("cef: GPU transcoding available") | |||
| 206 | - | } else { | |||
| 207 | - | transcoderState.Status = "unavailable_no_compatible_gpu" | |||
| 208 | - | logger.Warn().Msg("cef: GPU transcoding enabled but no compatible GPU found — feature disabled") | |||
| 209 | - | } | |||
| 210 | - | } | |||
| 211 | - | ||||
| 212 | - | factory := newWebViewFactory(eng, gl, webViewFactoryOptions{ | |||
| 213 | - | scale: scale, | |||
| 214 | windowlessFrameRate: windowlessFrameRate, | 186 | windowlessFrameRate: windowlessFrameRate, | ||
| 215 | transcoder: mediaTranscoder, | 187 | transcoder: mediaTranscoder, | ||
| 216 | mediaClassifier: mediaClassifier, | 188 | mediaClassifier: mediaClassifier, | ||
| 217 | audioOutputFactory: audioFactory, | 189 | audioOutputFactory: audioFactory, | ||
| 218 | }) | 190 | }) | ||
| 219 | - | pool := newWebViewPool(factory) | 191 | + | eng.pool = newWebViewPool(eng.factory) |
| 220 | - | ||||
| 221 | - | eng.gl = gl | |||
| 222 | - | eng.factory = factory | |||
| 223 | - | eng.pool = pool | |||
| 224 | eng.contentInj = newContentInjector(eng, nil) | 192 | eng.contentInj = newContentInjector(eng, nil) | ||
| 225 | eng.transcoderState = transcoderState | 193 | eng.transcoderState = transcoderState | ||
| 226 | 194 | ||||
| 195 | + | messageRouter, schemeHandler, err := newEngineSchemeHandler( | |||
| 196 | + | ctx, | |||
| 197 | + | eng, | |||
| 198 | + | logger, | |||
| 199 | + | mediaTranscoder, | |||
| 200 | + | currentConfigPayload, | |||
| 201 | + | defaultConfigPayload, | |||
| 202 | + | ) | |||
| 203 | + | if err != nil { | |||
| 204 | + | purecef.Shutdown() | |||
| 205 | + | return nil, err | |||
| 206 | + | } | |||
| 207 | + | eng.messageRouter = messageRouter | |||
| 208 | + | eng.schemeHandler = schemeHandler | |||
| 209 | + | ||||
| 210 | + | registerEngineSchemeFactories(logger, purecef.NewSchemeHandlerFactory(schemeHandler)) | |||
| 211 | + | return eng, nil | |||
| 212 | + | } | |||
| 213 | + | ||||
| 214 | + | func initializeEngineTranscoder( | |||
| 215 | + | transcodingCfg TranscodingRuntimeConfig, | |||
| 216 | + | logger *zerolog.Logger, | |||
| 217 | + | ) (port.MediaTranscoder, transcoderStartupState) { | |||
| 218 | + | transcoderState := buildTranscoderStartupState(transcodingCfg) | |||
| 219 | + | if !transcodingCfg.Enabled { | |||
| 220 | + | return nil, transcoderState | |||
| 221 | + | } | |||
| 222 | + | ||||
| 223 | + | transcoderState.ProbeAttempted = true | |||
| 224 | + | tc := transcoderpkg.New(config.TranscodingConfig{ | |||
| 225 | + | Enabled: transcodingCfg.Enabled, | |||
| 226 | + | HWAccel: transcodingCfg.HWAccel, | |||
| 227 | + | MaxConcurrent: transcodingCfg.MaxConcurrent, | |||
| 228 | + | Quality: transcodingCfg.Quality, | |||
| 229 | + | }, logger) | |||
| 230 | + | caps := tc.Capabilities() | |||
| 231 | + | transcoderState.API = caps.API | |||
| 232 | + | transcoderState.Encoders = append([]string(nil), caps.Encoders...) | |||
| 233 | + | transcoderState.Decoders = append([]string(nil), caps.Decoders...) | |||
| 234 | + | if !tc.Available() { | |||
| 235 | + | transcoderState.Status = "unavailable_no_compatible_gpu" | |||
| 236 | + | logger.Warn().Msg("cef: GPU transcoding enabled but no compatible GPU found — feature disabled") | |||
| 237 | + | return nil, transcoderState | |||
| 238 | + | } | |||
| 239 | + | ||||
| 240 | + | transcoderState.Status = "available" | |||
| 241 | + | logger.Info(). | |||
| 242 | + | Str("api", caps.API). | |||
| 243 | + | Strs("encoders", caps.Encoders). | |||
| 244 | + | Msg("cef: GPU transcoding available") | |||
| 245 | + | return tc, transcoderState | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | func newEngineSchemeHandler( | |||
| 249 | + | ctx context.Context, | |||
| 250 | + | eng *Engine, | |||
| 251 | + | logger *zerolog.Logger, | |||
| 252 | + | mediaTranscoder port.MediaTranscoder, | |||
| 253 | + | currentConfigPayload func() ([]byte, error), | |||
| 254 | + | defaultConfigPayload func() ([]byte, error), | |||
| 255 | + | ) (*MessageRouter, *dumbSchemeHandler, error) { | |||
| 227 | messageRouter := NewMessageRouter(ctx) | 256 | messageRouter := NewMessageRouter(ctx) | ||
| 228 | schemeHandler, err := newDumbSchemeHandler( | 257 | schemeHandler, err := newDumbSchemeHandler( | ||
| 229 | ctx, | 258 | ctx, | ||
| @@ -233,15 +262,25 @@ func wireEngine( | |||||
| 233 | defaultConfigPayload, | 262 | defaultConfigPayload, | ||
| 234 | ) | 263 | ) | ||
| 235 | if err != nil { | 264 | if err != nil { | ||
| 236 | - | purecef.Shutdown() | 265 | + | return nil, nil, err |
| 237 | - | return nil, err | |||
| 238 | } | 266 | } | ||
| 239 | schemeHandler.setAssets(assets.WebUIAssets) | 267 | schemeHandler.setAssets(assets.WebUIAssets) | ||
| 268 | + | schemeHandler.onClipboardSet = makeGTKClipboardSetter(logger) | |||
| 269 | + | schemeHandler.onEditableFocus = eng.handleEditableFocusBridge | |||
| 270 | + | schemeHandler.onPopupOpen = eng.handlePopupBridgeOpen | |||
| 271 | + | schemeHandler.onPopupNavigate = eng.handlePopupBridgeNavigate | |||
| 272 | + | schemeHandler.onPopupClose = eng.handlePopupBridgeClose | |||
| 273 | + | schemeHandler.onPopupOpenerNavigate = eng.handlePopupOpenerNavigate | |||
| 274 | + | schemeHandler.onPopupOpenerPostMessage = eng.handlePopupOpenerPostMessage | |||
| 275 | + | schemeHandler.bridgeNonceValidator = eng.validateBridgeRequest | |||
| 276 | + | return messageRouter, schemeHandler, nil | |||
| 277 | + | } | |||
| 240 | 278 | ||||
| 279 | + | func makeGTKClipboardSetter(logger *zerolog.Logger) func(string) { | |||
| 241 | // Bridge clipboard writes from page JS → GDK system clipboard. | 280 | // Bridge clipboard writes from page JS → GDK system clipboard. | ||
| 242 | // The callback is invoked on the CEF IO thread, so schedule the actual GDK | 281 | // The callback is invoked on the CEF IO thread, so schedule the actual GDK | ||
| 243 | // write on the GTK main loop. | 282 | // write on the GTK main loop. | ||
| 244 | - | schemeHandler.onClipboardSet = func(text string) { | 283 | + | return func(text string) { |
| 245 | fn := glib.SourceOnceFunc(func(_ uintptr) { | 284 | fn := glib.SourceOnceFunc(func(_ uintptr) { | ||
| 246 | if display := gdk.DisplayGetDefault(); display != nil { | 285 | if display := gdk.DisplayGetDefault(); display != nil { | ||
| 247 | if cb := display.GetClipboard(); cb != nil { | 286 | if cb := display.GetClipboard(); cb != nil { | ||
| @@ -256,40 +295,37 @@ func wireEngine( | |||||
| 256 | }) | 295 | }) | ||
| 257 | glib.IdleAddOnce(&fn, 0) | 296 | glib.IdleAddOnce(&fn, 0) | ||
| 258 | } | 297 | } | ||
| 298 | + | } | |||
| 259 | 299 | ||||
| 260 | - | schemeHandler.onEditableFocus = eng.handleEditableFocusBridge | 300 | + | func registerEngineSchemeFactories(logger *zerolog.Logger, schemeFactory purecef.SchemeHandlerFactory) { |
| 261 | - | schemeHandler.onPopupOpen = eng.handlePopupBridgeOpen | 301 | + | registerEngineSchemeFactory(logger, "dumb", "", schemeFactory) |
| 262 | - | schemeHandler.onPopupNavigate = eng.handlePopupBridgeNavigate | 302 | + | registerEngineSchemeFactory(logger, actualInternalScheme, actualInternalHost, schemeFactory) |
| 263 | - | schemeHandler.onPopupClose = eng.handlePopupBridgeClose | 303 | + | } |
| 264 | - | schemeHandler.onPopupOpenerNavigate = eng.handlePopupOpenerNavigate | |||
| 265 | - | schemeHandler.onPopupOpenerPostMessage = eng.handlePopupOpenerPostMessage | |||
| 266 | - | schemeHandler.bridgeNonceValidator = eng.validateBridgeRequest | |||
| 267 | - | ||||
| 268 | - | schemeFactory := purecef.NewSchemeHandlerFactory(schemeHandler) | |||
| 269 | 304 | ||||
| 270 | - | result := purecef.RegisterSchemeHandlerFactory("dumb", "", schemeFactory) | 305 | + | func registerEngineSchemeFactory( |
| 271 | - | if result != 1 { | 306 | + | logger *zerolog.Logger, |
| 272 | - | logger.Error().Int32("result", result).Msg("cef: failed to register dumb:// scheme handler factory") | 307 | + | scheme, host string, |
| 273 | - | } else { | 308 | + | schemeFactory purecef.SchemeHandlerFactory, |
| 309 | + | ) { | |||
| 310 | + | result := purecef.RegisterSchemeHandlerFactory(scheme, host, schemeFactory) | |||
| 311 | + | if scheme == "dumb" && host == "" { | |||
| 312 | + | if result != 1 { | |||
| 313 | + | logger.Error().Int32("result", result).Msg("cef: failed to register dumb:// scheme handler factory") | |||
| 314 | + | return | |||
| 315 | + | } | |||
| 274 | logger.Info().Msg("cef: registered dumb:// scheme handler factory") | 316 | logger.Info().Msg("cef: registered dumb:// scheme handler factory") | ||
| 317 | + | return | |||
| 275 | } | 318 | } | ||
| 276 | - | ||||
| 277 | - | result = purecef.RegisterSchemeHandlerFactory(actualInternalScheme, actualInternalHost, schemeFactory) | |||
| 278 | if result != 1 { | 319 | if result != 1 { | ||
| 279 | logger.Error(). | 320 | logger.Error(). | ||
| 280 | Int32("result", result). | 321 | Int32("result", result). | ||
| 281 | Str("origin", actualInternalOrigin). | 322 | Str("origin", actualInternalOrigin). | ||
| 282 | Msg("cef: failed to register internal https handler factory") | 323 | Msg("cef: failed to register internal https handler factory") | ||
| 283 | - | } else { | 324 | + | return |
| 284 | - | logger.Info(). | |||
| 285 | - | Str("origin", actualInternalOrigin). | |||
| 286 | - | Msg("cef: registered internal https handler factory") | |||
| 287 | } | 325 | } | ||
| 288 | - | 326 | + | logger.Info(). | |
| 289 | - | eng.messageRouter = messageRouter | 327 | + | Str("origin", actualInternalOrigin). |
| 290 | - | eng.schemeHandler = schemeHandler | 328 | + | Msg("cef: registered internal https handler factory") |
| 291 | - | ||||
| 292 | - | return eng, nil | |||
| 293 | } | 329 | } | ||
| 294 | 330 | ||||
| 295 | func buildTranscoderStartupState(cfg TranscodingRuntimeConfig) transcoderStartupState { | 331 | func buildTranscoderStartupState(cfg TranscodingRuntimeConfig) transcoderStartupState { | ||
M internal/infrastructure/cef/popup_opener_bridge.go +14 -12
| @@ -23,6 +23,8 @@ type popupOpenerNavigatePayload struct { | |||
| 23 | 23 | URL string `json:"url"` | |
| 24 | 24 | } | |
| 25 | 25 | ||
| 26 | + | const popupOpenerHTTPScheme = "http" | |
| 27 | + | ||
| 26 | 28 | func (wv *WebView) setPopupNoJavaScriptAccess(noJavaScriptAccess bool) { | |
| 27 | 29 | if wv == nil { | |
| 28 | 30 | return | |
| @@ -96,7 +98,7 @@ func (wv *WebView) HasActivePopupOpenerBridge() bool { | |||
| 96 | 98 | return active | |
| 97 | 99 | } | |
| 98 | 100 | ||
| 99 | - | func (wv *WebView) popupOpenerBridgeState() (parentURI string, active bool, blocked bool) { | |
| 101 | + | func (wv *WebView) popupOpenerBridgeState() (parentURI string, active, blocked bool) { | |
| 100 | 102 | if wv == nil { | |
| 101 | 103 | return "", false, false | |
| 102 | 104 | } | |
| @@ -301,8 +303,8 @@ func sanitizePopupOpenerNavigationTarget(target *neturl.URL) string { | |||
| 301 | 303 | if target == nil { | |
| 302 | 304 | return "" | |
| 303 | 305 | } | |
| 304 | - | switch strings.ToLower(strings.TrimSpace(target.Scheme)) { | |
| 305 | - | case "http", "https": | |
| 306 | + | switch scheme := strings.ToLower(strings.TrimSpace(target.Scheme)); scheme { | |
| 307 | + | case popupOpenerHTTPScheme, actualInternalScheme: | |
| 306 | 308 | return target.String() | |
| 307 | 309 | default: | |
| 308 | 310 | return "" | |
| @@ -346,7 +348,7 @@ func (wv *WebView) handlePopupOpenerPostMessage(payload popupOpenerPostMessagePa | |||
| 346 | 348 | } | |
| 347 | 349 | ||
| 348 | 350 | sourceOrigin := popupSourceOrigin(payload) | |
| 349 | - | dataExpr := "undefined" | |
| 351 | + | var dataExpr string | |
| 350 | 352 | switch strings.ToLower(strings.TrimSpace(payload.DataKind)) { | |
| 351 | 353 | case "json": | |
| 352 | 354 | dataExpr = fmt.Sprintf("JSON.parse('%s')", webutil.EscapeForJSString(payload.Data)) | |
| @@ -430,17 +432,17 @@ func canonicalOrigin(rawURL string) string { | |||
| 430 | 432 | if host == "" { | |
| 431 | 433 | return "" | |
| 432 | 434 | } | |
| 433 | - | port := parsed.Port() | |
| 434 | - | if port == "" { | |
| 435 | + | portStr := parsed.Port() | |
| 436 | + | if portStr == "" { | |
| 435 | 437 | switch scheme { | |
| 436 | - | case "http": | |
| 437 | - | port = "80" | |
| 438 | - | case "https": | |
| 439 | - | port = "443" | |
| 438 | + | case popupOpenerHTTPScheme: | |
| 439 | + | portStr = "80" | |
| 440 | + | case actualInternalScheme: | |
| 441 | + | portStr = "443" | |
| 440 | 442 | } | |
| 441 | 443 | } | |
| 442 | - | if port == "" { | |
| 444 | + | if portStr == "" { | |
| 443 | 445 | return scheme + "://" + host | |
| 444 | 446 | } | |
| 445 | - | return scheme + "://" + host + ":" + port | |
| 447 | + | return scheme + "://" + host + ":" + portStr | |
| 446 | 448 | } | |
| @@ -23,6 +23,8 @@ type popupOpenerNavigatePayload struct { | |||||
| 23 | URL string `json:"url"` | 23 | URL string `json:"url"` | ||
| 24 | } | 24 | } | ||
| 25 | 25 | ||||
| 26 | + | const popupOpenerHTTPScheme = "http" | |||
| 27 | + | ||||
| 26 | func (wv *WebView) setPopupNoJavaScriptAccess(noJavaScriptAccess bool) { | 28 | func (wv *WebView) setPopupNoJavaScriptAccess(noJavaScriptAccess bool) { | ||
| 27 | if wv == nil { | 29 | if wv == nil { | ||
| 28 | return | 30 | return | ||
| @@ -96,7 +98,7 @@ func (wv *WebView) HasActivePopupOpenerBridge() bool { | |||||
| 96 | return active | 98 | return active | ||
| 97 | } | 99 | } | ||
| 98 | 100 | ||||
| 99 | - | func (wv *WebView) popupOpenerBridgeState() (parentURI string, active bool, blocked bool) { | 101 | + | func (wv *WebView) popupOpenerBridgeState() (parentURI string, active, blocked bool) { |
| 100 | if wv == nil { | 102 | if wv == nil { | ||
| 101 | return "", false, false | 103 | return "", false, false | ||
| 102 | } | 104 | } | ||
| @@ -301,8 +303,8 @@ func sanitizePopupOpenerNavigationTarget(target *neturl.URL) string { | |||||
| 301 | if target == nil { | 303 | if target == nil { | ||
| 302 | return "" | 304 | return "" | ||
| 303 | } | 305 | } | ||
| 304 | - | switch strings.ToLower(strings.TrimSpace(target.Scheme)) { | 306 | + | switch scheme := strings.ToLower(strings.TrimSpace(target.Scheme)); scheme { |
| 305 | - | case "http", "https": | 307 | + | case popupOpenerHTTPScheme, actualInternalScheme: |
| 306 | return target.String() | 308 | return target.String() | ||
| 307 | default: | 309 | default: | ||
| 308 | return "" | 310 | return "" | ||
| @@ -346,7 +348,7 @@ func (wv *WebView) handlePopupOpenerPostMessage(payload popupOpenerPostMessagePa | |||||
| 346 | } | 348 | } | ||
| 347 | 349 | ||||
| 348 | sourceOrigin := popupSourceOrigin(payload) | 350 | sourceOrigin := popupSourceOrigin(payload) | ||
| 349 | - | dataExpr := "undefined" | 351 | + | var dataExpr string |
| 350 | switch strings.ToLower(strings.TrimSpace(payload.DataKind)) { | 352 | switch strings.ToLower(strings.TrimSpace(payload.DataKind)) { | ||
| 351 | case "json": | 353 | case "json": | ||
| 352 | dataExpr = fmt.Sprintf("JSON.parse('%s')", webutil.EscapeForJSString(payload.Data)) | 354 | dataExpr = fmt.Sprintf("JSON.parse('%s')", webutil.EscapeForJSString(payload.Data)) | ||
| @@ -430,17 +432,17 @@ func canonicalOrigin(rawURL string) string { | |||||
| 430 | if host == "" { | 432 | if host == "" { | ||
| 431 | return "" | 433 | return "" | ||
| 432 | } | 434 | } | ||
| 433 | - | port := parsed.Port() | 435 | + | portStr := parsed.Port() |
| 434 | - | if port == "" { | 436 | + | if portStr == "" { |
| 435 | switch scheme { | 437 | switch scheme { | ||
| 436 | - | case "http": | 438 | + | case popupOpenerHTTPScheme: |
| 437 | - | port = "80" | 439 | + | portStr = "80" |
| 438 | - | case "https": | 440 | + | case actualInternalScheme: |
| 439 | - | port = "443" | 441 | + | portStr = "443" |
| 440 | } | 442 | } | ||
| 441 | } | 443 | } | ||
| 442 | - | if port == "" { | 444 | + | if portStr == "" { |
| 443 | return scheme + "://" + host | 445 | return scheme + "://" + host | ||
| 444 | } | 446 | } | ||
| 445 | - | return scheme + "://" + host + ":" + port | 447 | + | return scheme + "://" + host + ":" + portStr |
| 446 | } | 448 | } | ||
M internal/infrastructure/cef/popup_opener_renderer_test.go +1 -1
| @@ -75,7 +75,7 @@ func (c *testPopupOpenerV8Context) Exit() int32 { | |||
| 75 | 75 | return 1 | |
| 76 | 76 | } | |
| 77 | 77 | func (c *testPopupOpenerV8Context) IsSame(that purecef.V8Context) bool { return c == that } | |
| 78 | - | func (c *testPopupOpenerV8Context) Eval(code string, scriptURL string, _ int32, _, _ unsafe.Pointer) int32 { | |
| 78 | + | func (c *testPopupOpenerV8Context) Eval(code, scriptURL string, _ int32, _, _ unsafe.Pointer) int32 { | |
| 79 | 79 | c.evalCode = code | |
| 80 | 80 | c.evalURL = scriptURL | |
| 81 | 81 | return 1 | |
| @@ -75,7 +75,7 @@ func (c *testPopupOpenerV8Context) Exit() int32 { | |||||
| 75 | return 1 | 75 | return 1 | ||
| 76 | } | 76 | } | ||
| 77 | func (c *testPopupOpenerV8Context) IsSame(that purecef.V8Context) bool { return c == that } | 77 | func (c *testPopupOpenerV8Context) IsSame(that purecef.V8Context) bool { return c == that } | ||
| 78 | - | func (c *testPopupOpenerV8Context) Eval(code string, scriptURL string, _ int32, _, _ unsafe.Pointer) int32 { | 78 | + | func (c *testPopupOpenerV8Context) Eval(code, scriptURL string, _ int32, _, _ unsafe.Pointer) int32 { |
| 79 | c.evalCode = code | 79 | c.evalCode = code | ||
| 80 | c.evalURL = scriptURL | 80 | c.evalURL = scriptURL | ||
| 81 | return 1 | 81 | return 1 | ||
M internal/infrastructure/cef/scheme_handler.go +9 -2
| @@ -252,7 +252,7 @@ func (h *dumbSchemeHandler) handleTranscodeAPI(request purecef.Request) purecef. | |||
| 252 | 252 | } | |
| 253 | 253 | ||
| 254 | 254 | sourceParsed, err := url.Parse(sourceURL) | |
| 255 | - | if err != nil || sourceParsed.Host == "" || (sourceParsed.Scheme != "http" && sourceParsed.Scheme != "https") { | |
| 255 | + | if err != nil || sourceParsed.Host == "" || (sourceParsed.Scheme != "http" && sourceParsed.Scheme != actualInternalScheme) { | |
| 256 | 256 | return h.newAPIJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "invalid src"}) | |
| 257 | 257 | } | |
| 258 | 258 | ||
| @@ -453,7 +453,14 @@ func (h *dumbSchemeHandler) handlePopupOpenerNavigate(request purecef.Request, b | |||
| 453 | 453 | } | |
| 454 | 454 | ||
| 455 | 455 | func (h *dumbSchemeHandler) handlePopupOpenerPostMessage(request purecef.Request, browser purecef.Browser) purecef.ResourceHandler { | |
| 456 | - | return handlePopupBridgeRequest(h, request, browser, "popup-opener-post-message", decodePopupOpenerPostMessagePayload, h.onPopupOpenerPostMessage) | |
| 456 | + | return handlePopupBridgeRequest( | |
| 457 | + | h, | |
| 458 | + | request, | |
| 459 | + | browser, | |
| 460 | + | "popup-opener-post-message", | |
| 461 | + | decodePopupOpenerPostMessagePayload, | |
| 462 | + | h.onPopupOpenerPostMessage, | |
| 463 | + | ) | |
| 457 | 464 | } | |
| 458 | 465 | ||
| 459 | 466 | func (h *dumbSchemeHandler) hasTrustedBridgeNonce(request purecef.Request, browser purecef.Browser) bool { | |
| @@ -252,7 +252,7 @@ func (h *dumbSchemeHandler) handleTranscodeAPI(request purecef.Request) purecef. | |||||
| 252 | } | 252 | } | ||
| 253 | 253 | ||||
| 254 | sourceParsed, err := url.Parse(sourceURL) | 254 | sourceParsed, err := url.Parse(sourceURL) | ||
| 255 | - | if err != nil || sourceParsed.Host == "" || (sourceParsed.Scheme != "http" && sourceParsed.Scheme != "https") { | 255 | + | if err != nil || sourceParsed.Host == "" || (sourceParsed.Scheme != "http" && sourceParsed.Scheme != actualInternalScheme) { |
| 256 | return h.newAPIJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "invalid src"}) | 256 | return h.newAPIJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "invalid src"}) | ||
| 257 | } | 257 | } | ||
| 258 | 258 | ||||
| @@ -453,7 +453,14 @@ func (h *dumbSchemeHandler) handlePopupOpenerNavigate(request purecef.Request, b | |||||
| 453 | } | 453 | } | ||
| 454 | 454 | ||||
| 455 | func (h *dumbSchemeHandler) handlePopupOpenerPostMessage(request purecef.Request, browser purecef.Browser) purecef.ResourceHandler { | 455 | func (h *dumbSchemeHandler) handlePopupOpenerPostMessage(request purecef.Request, browser purecef.Browser) purecef.ResourceHandler { | ||
| 456 | - | return handlePopupBridgeRequest(h, request, browser, "popup-opener-post-message", decodePopupOpenerPostMessagePayload, h.onPopupOpenerPostMessage) | 456 | + | return handlePopupBridgeRequest( |
| 457 | + | h, | |||
| 458 | + | request, | |||
| 459 | + | browser, | |||
| 460 | + | "popup-opener-post-message", | |||
| 461 | + | decodePopupOpenerPostMessagePayload, | |||
| 462 | + | h.onPopupOpenerPostMessage, | |||
| 463 | + | ) | |||
| 457 | } | 464 | } | ||
| 458 | 465 | ||||
| 459 | func (h *dumbSchemeHandler) hasTrustedBridgeNonce(request purecef.Request, browser purecef.Browser) bool { | 466 | func (h *dumbSchemeHandler) hasTrustedBridgeNonce(request purecef.Request, browser purecef.Browser) bool { | ||
M internal/infrastructure/cef/webview.go +65 -27
| @@ -794,25 +794,51 @@ func (wv *WebView) prepareNativePopup( | |||
| 794 | 794 | return false | |
| 795 | 795 | } | |
| 796 | 796 | ||
| 797 | - | wv.mu.RLock() | |
| 798 | - | if !wv.nativePopupCandidate || wv.client == nil || wv.nativePopupFallbackStarted || wv.browser != nil { | |
| 799 | - | wv.mu.RUnlock() | |
| 797 | + | prep, ok := wv.nativePopupPreparationSnapshot() | |
| 798 | + | if !ok { | |
| 800 | 799 | return false | |
| 801 | 800 | } | |
| 802 | - | parent := wv.nativePopupParent | |
| 803 | - | frameRate := wv.windowlessFrameRate | |
| 804 | - | backgroundColor := wv.backgroundColor | |
| 805 | - | wv.mu.RUnlock() | |
| 806 | - | ||
| 807 | - | parentWindowHandle := popupParentWindowHandle(parent) | |
| 801 | + | parentWindowHandle := popupParentWindowHandle(prep.parent) | |
| 808 | 802 | if parentWindowHandle == 0 { | |
| 809 | 803 | return false | |
| 810 | 804 | } | |
| 805 | + | client, ok := wv.activateNativePopup(popupID, targetURL) | |
| 806 | + | if !ok { | |
| 807 | + | return false | |
| 808 | + | } | |
| 809 | + | if prep.parent != nil { | |
| 810 | + | prep.parent.trackPendingNativePopup(popupID, wv) | |
| 811 | + | } | |
| 812 | + | ||
| 813 | + | configureNativePopupWindow(windowInfo, settings, parentWindowHandle, prep.frameRate, prep.backgroundColor) | |
| 814 | + | clientSlot.Set(client) | |
| 815 | + | return true | |
| 816 | + | } | |
| 817 | + | ||
| 818 | + | func (wv *WebView) nativePopupPreparationSnapshot() (nativePopupPreparation, bool) { | |
| 819 | + | if wv == nil { | |
| 820 | + | return nativePopupPreparation{}, false | |
| 821 | + | } | |
| 822 | + | wv.mu.RLock() | |
| 823 | + | defer wv.mu.RUnlock() | |
| 824 | + | if !wv.canPrepareNativePopupLocked() { | |
| 825 | + | return nativePopupPreparation{}, false | |
| 826 | + | } | |
| 827 | + | return nativePopupPreparation{ | |
| 828 | + | parent: wv.nativePopupParent, | |
| 829 | + | frameRate: wv.windowlessFrameRate, | |
| 830 | + | backgroundColor: wv.backgroundColor, | |
| 831 | + | }, true | |
| 832 | + | } | |
| 811 | 833 | ||
| 834 | + | func (wv *WebView) activateNativePopup(popupID int32, targetURL string) (purecef.RawClient, bool) { | |
| 835 | + | if wv == nil { | |
| 836 | + | return nil, false | |
| 837 | + | } | |
| 812 | 838 | wv.mu.Lock() | |
| 813 | - | if !wv.nativePopupCandidate || wv.client == nil || wv.nativePopupFallbackStarted || wv.browser != nil { | |
| 814 | - | wv.mu.Unlock() | |
| 815 | - | return false | |
| 839 | + | defer wv.mu.Unlock() | |
| 840 | + | if !wv.canPrepareNativePopupLocked() { | |
| 841 | + | return nil, false | |
| 816 | 842 | } | |
| 817 | 843 | wv.nativePopupCandidate = false | |
| 818 | 844 | wv.nativePopupID = popupID | |
| @@ -826,28 +852,40 @@ func (wv *WebView) prepareNativePopup( | |||
| 826 | 852 | wv.uri = toConceptualInternalURL(targetURL) | |
| 827 | 853 | } | |
| 828 | 854 | wv.isLoading = true | |
| 829 | - | client := wv.client | |
| 830 | - | wv.mu.Unlock() | |
| 855 | + | return wv.client, true | |
| 856 | + | } | |
| 831 | 857 | ||
| 832 | - | if parent != nil { | |
| 833 | - | parent.trackPendingNativePopup(popupID, wv) | |
| 834 | - | } | |
| 858 | + | func (wv *WebView) canPrepareNativePopupLocked() bool { | |
| 859 | + | return wv.nativePopupCandidate && wv.client != nil && !wv.nativePopupFallbackStarted && wv.browser == nil | |
| 860 | + | } | |
| 835 | 861 | ||
| 862 | + | func configureNativePopupWindow( | |
| 863 | + | windowInfo *purecef.WindowInfo, | |
| 864 | + | settings *purecef.BrowserSettings, | |
| 865 | + | parentWindowHandle uintptr, | |
| 866 | + | frameRate int32, | |
| 867 | + | backgroundColor uint32, | |
| 868 | + | ) { | |
| 836 | 869 | purecef.SetAsWindowless(windowInfo, purecef.WindowHandle(parentWindowHandle), false) | |
| 837 | 870 | if externalBeginFrameEnabled() { | |
| 838 | 871 | windowInfo.ExternalBeginFrameEnabled = 1 | |
| 839 | 872 | } | |
| 840 | - | if settings != nil { | |
| 841 | - | if frameRate > 0 { | |
| 842 | - | settings.WindowlessFrameRate = frameRate | |
| 843 | - | } | |
| 844 | - | settings.LocalStorage = 1 | |
| 845 | - | if backgroundColor != 0 { | |
| 846 | - | settings.BackgroundColor = backgroundColor | |
| 847 | - | } | |
| 873 | + | if settings == nil { | |
| 874 | + | return | |
| 848 | 875 | } | |
| 849 | - | clientSlot.Set(client) | |
| 850 | - | return true | |
| 876 | + | if frameRate > 0 { | |
| 877 | + | settings.WindowlessFrameRate = frameRate | |
| 878 | + | } | |
| 879 | + | settings.LocalStorage = 1 | |
| 880 | + | if backgroundColor != 0 { | |
| 881 | + | settings.BackgroundColor = backgroundColor | |
| 882 | + | } | |
| 883 | + | } | |
| 884 | + | ||
| 885 | + | type nativePopupPreparation struct { | |
| 886 | + | parent *WebView | |
| 887 | + | frameRate int32 | |
| 888 | + | backgroundColor uint32 | |
| 851 | 889 | } | |
| 852 | 890 | ||
| 853 | 891 | func (wv *WebView) handleNativePopupAborted() { | |
| @@ -794,25 +794,51 @@ func (wv *WebView) prepareNativePopup( | |||||
| 794 | return false | 794 | return false | ||
| 795 | } | 795 | } | ||
| 796 | 796 | ||||
| 797 | - | wv.mu.RLock() | 797 | + | prep, ok := wv.nativePopupPreparationSnapshot() |
| 798 | - | if !wv.nativePopupCandidate || wv.client == nil || wv.nativePopupFallbackStarted || wv.browser != nil { | 798 | + | if !ok { |
| 799 | - | wv.mu.RUnlock() | |||
| 800 | return false | 799 | return false | ||
| 801 | } | 800 | } | ||
| 802 | - | parent := wv.nativePopupParent | 801 | + | parentWindowHandle := popupParentWindowHandle(prep.parent) |
| 803 | - | frameRate := wv.windowlessFrameRate | |||
| 804 | - | backgroundColor := wv.backgroundColor | |||
| 805 | - | wv.mu.RUnlock() | |||
| 806 | - | ||||
| 807 | - | parentWindowHandle := popupParentWindowHandle(parent) | |||
| 808 | if parentWindowHandle == 0 { | 802 | if parentWindowHandle == 0 { | ||
| 809 | return false | 803 | return false | ||
| 810 | } | 804 | } | ||
| 805 | + | client, ok := wv.activateNativePopup(popupID, targetURL) | |||
| 806 | + | if !ok { | |||
| 807 | + | return false | |||
| 808 | + | } | |||
| 809 | + | if prep.parent != nil { | |||
| 810 | + | prep.parent.trackPendingNativePopup(popupID, wv) | |||
| 811 | + | } | |||
| 812 | + | ||||
| 813 | + | configureNativePopupWindow(windowInfo, settings, parentWindowHandle, prep.frameRate, prep.backgroundColor) | |||
| 814 | + | clientSlot.Set(client) | |||
| 815 | + | return true | |||
| 816 | + | } | |||
| 817 | + | ||||
| 818 | + | func (wv *WebView) nativePopupPreparationSnapshot() (nativePopupPreparation, bool) { | |||
| 819 | + | if wv == nil { | |||
| 820 | + | return nativePopupPreparation{}, false | |||
| 821 | + | } | |||
| 822 | + | wv.mu.RLock() | |||
| 823 | + | defer wv.mu.RUnlock() | |||
| 824 | + | if !wv.canPrepareNativePopupLocked() { | |||
| 825 | + | return nativePopupPreparation{}, false | |||
| 826 | + | } | |||
| 827 | + | return nativePopupPreparation{ | |||
| 828 | + | parent: wv.nativePopupParent, | |||
| 829 | + | frameRate: wv.windowlessFrameRate, | |||
| 830 | + | backgroundColor: wv.backgroundColor, | |||
| 831 | + | }, true | |||
| 832 | + | } | |||
| 811 | 833 | ||||
| 834 | + | func (wv *WebView) activateNativePopup(popupID int32, targetURL string) (purecef.RawClient, bool) { | |||
| 835 | + | if wv == nil { | |||
| 836 | + | return nil, false | |||
| 837 | + | } | |||
| 812 | wv.mu.Lock() | 838 | wv.mu.Lock() | ||
| 813 | - | if !wv.nativePopupCandidate || wv.client == nil || wv.nativePopupFallbackStarted || wv.browser != nil { | 839 | + | defer wv.mu.Unlock() |
| 814 | - | wv.mu.Unlock() | 840 | + | if !wv.canPrepareNativePopupLocked() { |
| 815 | - | return false | 841 | + | return nil, false |
| 816 | } | 842 | } | ||
| 817 | wv.nativePopupCandidate = false | 843 | wv.nativePopupCandidate = false | ||
| 818 | wv.nativePopupID = popupID | 844 | wv.nativePopupID = popupID | ||
| @@ -826,28 +852,40 @@ func (wv *WebView) prepareNativePopup( | |||||
| 826 | wv.uri = toConceptualInternalURL(targetURL) | 852 | wv.uri = toConceptualInternalURL(targetURL) | ||
| 827 | } | 853 | } | ||
| 828 | wv.isLoading = true | 854 | wv.isLoading = true | ||
| 829 | - | client := wv.client | 855 | + | return wv.client, true |
| 830 | - | wv.mu.Unlock() | 856 | + | } |
| 831 | 857 | ||||
| 832 | - | if parent != nil { | 858 | + | func (wv *WebView) canPrepareNativePopupLocked() bool { |
| 833 | - | parent.trackPendingNativePopup(popupID, wv) | 859 | + | return wv.nativePopupCandidate && wv.client != nil && !wv.nativePopupFallbackStarted && wv.browser == nil |
| 834 | - | } | 860 | + | } |
| 835 | 861 | ||||
| 862 | + | func configureNativePopupWindow( | |||
| 863 | + | windowInfo *purecef.WindowInfo, | |||
| 864 | + | settings *purecef.BrowserSettings, | |||
| 865 | + | parentWindowHandle uintptr, | |||
| 866 | + | frameRate int32, | |||
| 867 | + | backgroundColor uint32, | |||
| 868 | + | ) { | |||
| 836 | purecef.SetAsWindowless(windowInfo, purecef.WindowHandle(parentWindowHandle), false) | 869 | purecef.SetAsWindowless(windowInfo, purecef.WindowHandle(parentWindowHandle), false) | ||
| 837 | if externalBeginFrameEnabled() { | 870 | if externalBeginFrameEnabled() { | ||
| 838 | windowInfo.ExternalBeginFrameEnabled = 1 | 871 | windowInfo.ExternalBeginFrameEnabled = 1 | ||
| 839 | } | 872 | } | ||
| 840 | - | if settings != nil { | 873 | + | if settings == nil { |
| 841 | - | if frameRate > 0 { | 874 | + | return |
| 842 | - | settings.WindowlessFrameRate = frameRate | |||
| 843 | - | } | |||
| 844 | - | settings.LocalStorage = 1 | |||
| 845 | - | if backgroundColor != 0 { | |||
| 846 | - | settings.BackgroundColor = backgroundColor | |||
| 847 | - | } | |||
| 848 | } | 875 | } | ||
| 849 | - | clientSlot.Set(client) | 876 | + | if frameRate > 0 { |
| 850 | - | return true | 877 | + | settings.WindowlessFrameRate = frameRate |
| 878 | + | } | |||
| 879 | + | settings.LocalStorage = 1 | |||
| 880 | + | if backgroundColor != 0 { | |||
| 881 | + | settings.BackgroundColor = backgroundColor | |||
| 882 | + | } | |||
| 883 | + | } | |||
| 884 | + | ||||
| 885 | + | type nativePopupPreparation struct { | |||
| 886 | + | parent *WebView | |||
| 887 | + | frameRate int32 | |||
| 888 | + | backgroundColor uint32 | |||
| 851 | } | 889 | } | ||
| 852 | 890 | ||||
| 853 | func (wv *WebView) handleNativePopupAborted() { | 891 | func (wv *WebView) handleNativePopupAborted() { | ||
M internal/infrastructure/cef/webview_handlers.go +74 -51
| @@ -759,73 +759,88 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||
| 759 | 759 | return | |
| 760 | 760 | } | |
| 761 | 761 | browserID := browser.GetIdentifier() | |
| 762 | - | log.Debug(). | |
| 763 | - | Int32("browser_id", browserID). | |
| 764 | - | Msg("cef: OnAfterCreated") | |
| 762 | + | log.Debug().Int32("browser_id", browserID).Msg("cef: OnAfterCreated") | |
| 763 | + | ||
| 765 | 764 | host := browser.GetHost() | |
| 766 | 765 | if host == nil { | |
| 767 | 766 | log.Warn().Msg("cef: OnAfterCreated returned nil host") | |
| 768 | 767 | return | |
| 769 | 768 | } | |
| 770 | 769 | ||
| 771 | - | shouldSyncFocus := false | |
| 772 | - | hasPendingNavigation := false | |
| 773 | - | nativePopupParent := (*WebView)(nil) | |
| 774 | - | nativePopupID := int32(0) | |
| 775 | - | duplicateBrowserID := int32(0) | |
| 776 | - | closeDuplicate := false | |
| 770 | + | state := h.attachAfterCreatedBrowser(browser, host, browserID) | |
| 771 | + | if state.closeDuplicate { | |
| 772 | + | log.Warn(). | |
| 773 | + | Int32("browser_id", browserID). | |
| 774 | + | Int32("existing_browser_id", state.duplicateBrowserID). | |
| 775 | + | Msg("cef: duplicate popup browser attached after shell already had a browser; closing duplicate") | |
| 776 | + | host.CloseBrowser(1) | |
| 777 | + | return | |
| 778 | + | } | |
| 779 | + | ||
| 780 | + | h.finishAfterCreated(browser, host, state) | |
| 781 | + | } | |
| 782 | + | ||
| 783 | + | func (h *handlerSet) attachAfterCreatedBrowser( | |
| 784 | + | browser purecef.Browser, | |
| 785 | + | host purecef.BrowserHost, | |
| 786 | + | browserID int32, | |
| 787 | + | ) afterCreatedState { | |
| 788 | + | state := afterCreatedState{} | |
| 777 | 789 | h.wv.mu.Lock() | |
| 790 | + | defer h.wv.mu.Unlock() | |
| 791 | + | ||
| 778 | 792 | if existing := h.wv.browser; existing != nil { | |
| 779 | 793 | existingID := existing.GetIdentifier() | |
| 780 | 794 | if existingID != 0 && existingID != browserID { | |
| 781 | - | duplicateBrowserID = existingID | |
| 782 | - | closeDuplicate = true | |
| 795 | + | state.closeDuplicate = true | |
| 796 | + | state.duplicateBrowserID = existingID | |
| 797 | + | return state | |
| 783 | 798 | } | |
| 784 | 799 | } | |
| 785 | - | if !closeDuplicate { | |
| 786 | - | h.wv.browser = browser | |
| 787 | - | h.wv.host = host | |
| 788 | - | h.wv.pendingCreate = nil | |
| 789 | - | h.wv.input.setHost(host) | |
| 790 | - | if h.wv.findCtrl != nil { | |
| 791 | - | h.wv.findCtrl.setHost(host) | |
| 792 | - | } | |
| 793 | - | nativePopupParent = h.wv.nativePopupParent | |
| 794 | - | nativePopupID = h.wv.nativePopupID | |
| 795 | - | h.wv.nativePopupParent = nil | |
| 796 | - | h.wv.nativePopupID = 0 | |
| 797 | - | h.wv.nativePopupFallbackStarted = false | |
| 798 | - | ||
| 799 | - | // Mark browser as visible — CEF OSR starts in hidden state and suppresses | |
| 800 | - | // painting/caret updates until explicitly told the browser is shown. | |
| 801 | - | host.WasHidden(0) | |
| 802 | - | ||
| 803 | - | // If GTK focus entered before the browser existed, reassert browser focus | |
| 804 | - | // now. Otherwise CEF may stay internally unfocused and suppress caret paint. | |
| 805 | - | if h.wv.input != nil { | |
| 806 | - | shouldSyncFocus = h.wv.input.hasGTKFocus() | |
| 807 | - | if !shouldSyncFocus && h.wv.input.glArea != nil { | |
| 808 | - | shouldSyncFocus = h.wv.input.glArea.HasFocus() | |
| 809 | - | } | |
| 810 | - | } | |
| 811 | - | hasPendingNavigation = strings.TrimSpace(h.wv.pendingURI) != "" | |
| 800 | + | ||
| 801 | + | h.wv.browser = browser | |
| 802 | + | h.wv.host = host | |
| 803 | + | h.wv.pendingCreate = nil | |
| 804 | + | h.wv.input.setHost(host) | |
| 805 | + | if h.wv.findCtrl != nil { | |
| 806 | + | h.wv.findCtrl.setHost(host) | |
| 807 | + | } | |
| 808 | + | state.nativePopupParent = h.wv.nativePopupParent | |
| 809 | + | state.nativePopupID = h.wv.nativePopupID | |
| 810 | + | h.wv.nativePopupParent = nil | |
| 811 | + | h.wv.nativePopupID = 0 | |
| 812 | + | h.wv.nativePopupFallbackStarted = false | |
| 813 | + | ||
| 814 | + | // Mark browser as visible — CEF OSR starts in hidden state and suppresses | |
| 815 | + | // painting/caret updates until explicitly told the browser is shown. | |
| 816 | + | host.WasHidden(0) | |
| 817 | + | state.shouldSyncFocus = h.afterCreatedShouldSyncFocus() | |
| 818 | + | state.hasPendingNavigation = strings.TrimSpace(h.wv.pendingURI) != "" | |
| 819 | + | return state | |
| 820 | + | } | |
| 821 | + | ||
| 822 | + | func (h *handlerSet) afterCreatedShouldSyncFocus() bool { | |
| 823 | + | if h.wv.input == nil { | |
| 824 | + | return false | |
| 812 | 825 | } | |
| 813 | - | h.wv.mu.Unlock() | |
| 814 | - | if closeDuplicate { | |
| 815 | - | log.Warn(). | |
| 816 | - | Int32("browser_id", browserID). | |
| 817 | - | Int32("existing_browser_id", duplicateBrowserID). | |
| 818 | - | Msg("cef: duplicate popup browser attached after shell already had a browser; closing duplicate") | |
| 819 | - | host.CloseBrowser(1) | |
| 820 | - | return | |
| 826 | + | if h.wv.input.hasGTKFocus() { | |
| 827 | + | return true | |
| 821 | 828 | } | |
| 829 | + | return h.wv.input.glArea != nil && h.wv.input.glArea.HasFocus() | |
| 830 | + | } | |
| 831 | + | ||
| 832 | + | func (h *handlerSet) finishAfterCreated( | |
| 833 | + | browser purecef.Browser, | |
| 834 | + | host purecef.BrowserHost, | |
| 835 | + | state afterCreatedState, | |
| 836 | + | ) { | |
| 822 | 837 | if h.wv.engine != nil { | |
| 823 | 838 | h.wv.engine.recordBrowserAfterCreated(browser) | |
| 824 | 839 | h.wv.engine.registerWebView(h.wv) | |
| 825 | 840 | h.wv.engine.bindBrowserWebView(browser, h.wv) | |
| 826 | 841 | } | |
| 827 | 842 | h.wv.stopNativePopupFallbackTimer() | |
| 828 | - | if shouldSyncFocus { | |
| 843 | + | if state.shouldSyncFocus { | |
| 829 | 844 | syncWindowlessBrowserFocus(host) | |
| 830 | 845 | } else { | |
| 831 | 846 | // Request an initial OSR frame even before the first real navigation | |
| @@ -833,17 +848,25 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||
| 833 | 848 | // startup path relied on and prevents the render pipeline from staying idle. | |
| 834 | 849 | host.Invalidate(purecef.PaintElementTypePetView) | |
| 835 | 850 | } | |
| 836 | - | if hasPendingNavigation { | |
| 851 | + | if state.hasPendingNavigation { | |
| 837 | 852 | h.wv.schedulePendingNavigationReplay(0) | |
| 838 | 853 | } | |
| 839 | - | if nativePopupParent != nil && nativePopupID != 0 { | |
| 840 | - | nativePopupParent.clearPendingNativePopup(nativePopupID, h.wv) | |
| 854 | + | if state.nativePopupParent != nil && state.nativePopupID != 0 { | |
| 855 | + | state.nativePopupParent.clearPendingNativePopup(state.nativePopupID, h.wv) | |
| 841 | 856 | } | |
| 842 | - | ||
| 843 | 857 | h.wv.scheduleStartBeginFrameLoop() | |
| 844 | 858 | h.wv.fireReadyToShow() | |
| 845 | 859 | } | |
| 846 | 860 | ||
| 861 | + | type afterCreatedState struct { | |
| 862 | + | shouldSyncFocus bool | |
| 863 | + | hasPendingNavigation bool | |
| 864 | + | nativePopupParent *WebView | |
| 865 | + | nativePopupID int32 | |
| 866 | + | duplicateBrowserID int32 | |
| 867 | + | closeDuplicate bool | |
| 868 | + | } | |
| 869 | + | ||
| 847 | 870 | // DoClose returns false to allow the default close behavior. | |
| 848 | 871 | func (h *handlerSet) DoClose(_ purecef.Browser) bool { | |
| 849 | 872 | return false | |
| @@ -759,73 +759,88 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||||
| 759 | return | 759 | return | ||
| 760 | } | 760 | } | ||
| 761 | browserID := browser.GetIdentifier() | 761 | browserID := browser.GetIdentifier() | ||
| 762 | - | log.Debug(). | 762 | + | log.Debug().Int32("browser_id", browserID).Msg("cef: OnAfterCreated") |
| 763 | - | Int32("browser_id", browserID). | 763 | + | |
| 764 | - | Msg("cef: OnAfterCreated") | |||
| 765 | host := browser.GetHost() | 764 | host := browser.GetHost() | ||
| 766 | if host == nil { | 765 | if host == nil { | ||
| 767 | log.Warn().Msg("cef: OnAfterCreated returned nil host") | 766 | log.Warn().Msg("cef: OnAfterCreated returned nil host") | ||
| 768 | return | 767 | return | ||
| 769 | } | 768 | } | ||
| 770 | 769 | ||||
| 771 | - | shouldSyncFocus := false | 770 | + | state := h.attachAfterCreatedBrowser(browser, host, browserID) |
| 772 | - | hasPendingNavigation := false | 771 | + | if state.closeDuplicate { |
| 773 | - | nativePopupParent := (*WebView)(nil) | 772 | + | log.Warn(). |
| 774 | - | nativePopupID := int32(0) | 773 | + | Int32("browser_id", browserID). |
| 775 | - | duplicateBrowserID := int32(0) | 774 | + | Int32("existing_browser_id", state.duplicateBrowserID). |
| 776 | - | closeDuplicate := false | 775 | + | Msg("cef: duplicate popup browser attached after shell already had a browser; closing duplicate") |
| 776 | + | host.CloseBrowser(1) | |||
| 777 | + | return | |||
| 778 | + | } | |||
| 779 | + | ||||
| 780 | + | h.finishAfterCreated(browser, host, state) | |||
| 781 | + | } | |||
| 782 | + | ||||
| 783 | + | func (h *handlerSet) attachAfterCreatedBrowser( | |||
| 784 | + | browser purecef.Browser, | |||
| 785 | + | host purecef.BrowserHost, | |||
| 786 | + | browserID int32, | |||
| 787 | + | ) afterCreatedState { | |||
| 788 | + | state := afterCreatedState{} | |||
| 777 | h.wv.mu.Lock() | 789 | h.wv.mu.Lock() | ||
| 790 | + | defer h.wv.mu.Unlock() | |||
| 791 | + | ||||
| 778 | if existing := h.wv.browser; existing != nil { | 792 | if existing := h.wv.browser; existing != nil { | ||
| 779 | existingID := existing.GetIdentifier() | 793 | existingID := existing.GetIdentifier() | ||
| 780 | if existingID != 0 && existingID != browserID { | 794 | if existingID != 0 && existingID != browserID { | ||
| 781 | - | duplicateBrowserID = existingID | 795 | + | state.closeDuplicate = true |
| 782 | - | closeDuplicate = true | 796 | + | state.duplicateBrowserID = existingID |
| 797 | + | return state | |||
| 783 | } | 798 | } | ||
| 784 | } | 799 | } | ||
| 785 | - | if !closeDuplicate { | 800 | + | |
| 786 | - | h.wv.browser = browser | 801 | + | h.wv.browser = browser |
| 787 | - | h.wv.host = host | 802 | + | h.wv.host = host |
| 788 | - | h.wv.pendingCreate = nil | 803 | + | h.wv.pendingCreate = nil |
| 789 | - | h.wv.input.setHost(host) | 804 | + | h.wv.input.setHost(host) |
| 790 | - | if h.wv.findCtrl != nil { | 805 | + | if h.wv.findCtrl != nil { |
| 791 | - | h.wv.findCtrl.setHost(host) | 806 | + | h.wv.findCtrl.setHost(host) |
| 792 | - | } | 807 | + | } |
| 793 | - | nativePopupParent = h.wv.nativePopupParent | 808 | + | state.nativePopupParent = h.wv.nativePopupParent |
| 794 | - | nativePopupID = h.wv.nativePopupID | 809 | + | state.nativePopupID = h.wv.nativePopupID |
| 795 | - | h.wv.nativePopupParent = nil | 810 | + | h.wv.nativePopupParent = nil |
| 796 | - | h.wv.nativePopupID = 0 | 811 | + | h.wv.nativePopupID = 0 |
| 797 | - | h.wv.nativePopupFallbackStarted = false | 812 | + | h.wv.nativePopupFallbackStarted = false |
| 798 | - | 813 | + | ||
| 799 | - | // Mark browser as visible — CEF OSR starts in hidden state and suppresses | 814 | + | // Mark browser as visible — CEF OSR starts in hidden state and suppresses |
| 800 | - | // painting/caret updates until explicitly told the browser is shown. | 815 | + | // painting/caret updates until explicitly told the browser is shown. |
| 801 | - | host.WasHidden(0) | 816 | + | host.WasHidden(0) |
| 802 | - | 817 | + | state.shouldSyncFocus = h.afterCreatedShouldSyncFocus() | |
| 803 | - | // If GTK focus entered before the browser existed, reassert browser focus | 818 | + | state.hasPendingNavigation = strings.TrimSpace(h.wv.pendingURI) != "" |
| 804 | - | // now. Otherwise CEF may stay internally unfocused and suppress caret paint. | 819 | + | return state |
| 805 | - | if h.wv.input != nil { | 820 | + | } |
| 806 | - | shouldSyncFocus = h.wv.input.hasGTKFocus() | 821 | + | |
| 807 | - | if !shouldSyncFocus && h.wv.input.glArea != nil { | 822 | + | func (h *handlerSet) afterCreatedShouldSyncFocus() bool { |
| 808 | - | shouldSyncFocus = h.wv.input.glArea.HasFocus() | 823 | + | if h.wv.input == nil { |
| 809 | - | } | 824 | + | return false |
| 810 | - | } | |||
| 811 | - | hasPendingNavigation = strings.TrimSpace(h.wv.pendingURI) != "" | |||
| 812 | } | 825 | } | ||
| 813 | - | h.wv.mu.Unlock() | 826 | + | if h.wv.input.hasGTKFocus() { |
| 814 | - | if closeDuplicate { | 827 | + | return true |
| 815 | - | log.Warn(). | |||
| 816 | - | Int32("browser_id", browserID). | |||
| 817 | - | Int32("existing_browser_id", duplicateBrowserID). | |||
| 818 | - | Msg("cef: duplicate popup browser attached after shell already had a browser; closing duplicate") | |||
| 819 | - | host.CloseBrowser(1) | |||
| 820 | - | return | |||
| 821 | } | 828 | } | ||
| 829 | + | return h.wv.input.glArea != nil && h.wv.input.glArea.HasFocus() | |||
| 830 | + | } | |||
| 831 | + | ||||
| 832 | + | func (h *handlerSet) finishAfterCreated( | |||
| 833 | + | browser purecef.Browser, | |||
| 834 | + | host purecef.BrowserHost, | |||
| 835 | + | state afterCreatedState, | |||
| 836 | + | ) { | |||
| 822 | if h.wv.engine != nil { | 837 | if h.wv.engine != nil { | ||
| 823 | h.wv.engine.recordBrowserAfterCreated(browser) | 838 | h.wv.engine.recordBrowserAfterCreated(browser) | ||
| 824 | h.wv.engine.registerWebView(h.wv) | 839 | h.wv.engine.registerWebView(h.wv) | ||
| 825 | h.wv.engine.bindBrowserWebView(browser, h.wv) | 840 | h.wv.engine.bindBrowserWebView(browser, h.wv) | ||
| 826 | } | 841 | } | ||
| 827 | h.wv.stopNativePopupFallbackTimer() | 842 | h.wv.stopNativePopupFallbackTimer() | ||
| 828 | - | if shouldSyncFocus { | 843 | + | if state.shouldSyncFocus { |
| 829 | syncWindowlessBrowserFocus(host) | 844 | syncWindowlessBrowserFocus(host) | ||
| 830 | } else { | 845 | } else { | ||
| 831 | // Request an initial OSR frame even before the first real navigation | 846 | // Request an initial OSR frame even before the first real navigation | ||
| @@ -833,17 +848,25 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||||
| 833 | // startup path relied on and prevents the render pipeline from staying idle. | 848 | // startup path relied on and prevents the render pipeline from staying idle. | ||
| 834 | host.Invalidate(purecef.PaintElementTypePetView) | 849 | host.Invalidate(purecef.PaintElementTypePetView) | ||
| 835 | } | 850 | } | ||
| 836 | - | if hasPendingNavigation { | 851 | + | if state.hasPendingNavigation { |
| 837 | h.wv.schedulePendingNavigationReplay(0) | 852 | h.wv.schedulePendingNavigationReplay(0) | ||
| 838 | } | 853 | } | ||
| 839 | - | if nativePopupParent != nil && nativePopupID != 0 { | 854 | + | if state.nativePopupParent != nil && state.nativePopupID != 0 { |
| 840 | - | nativePopupParent.clearPendingNativePopup(nativePopupID, h.wv) | 855 | + | state.nativePopupParent.clearPendingNativePopup(state.nativePopupID, h.wv) |
| 841 | } | 856 | } | ||
| 842 | - | ||||
| 843 | h.wv.scheduleStartBeginFrameLoop() | 857 | h.wv.scheduleStartBeginFrameLoop() | ||
| 844 | h.wv.fireReadyToShow() | 858 | h.wv.fireReadyToShow() | ||
| 845 | } | 859 | } | ||
| 846 | 860 | ||||
| 861 | + | type afterCreatedState struct { | |||
| 862 | + | shouldSyncFocus bool | |||
| 863 | + | hasPendingNavigation bool | |||
| 864 | + | nativePopupParent *WebView | |||
| 865 | + | nativePopupID int32 | |||
| 866 | + | duplicateBrowserID int32 | |||
| 867 | + | closeDuplicate bool | |||
| 868 | + | } | |||
| 869 | + | ||||
| 847 | // DoClose returns false to allow the default close behavior. | 870 | // DoClose returns false to allow the default close behavior. | ||
| 848 | func (h *handlerSet) DoClose(_ purecef.Browser) bool { | 871 | func (h *handlerSet) DoClose(_ purecef.Browser) bool { | ||
| 849 | return false | 872 | return false | ||
M internal/infrastructure/cef/webview_popup_proxy.go +0 -13
| @@ -14,19 +14,6 @@ type syntheticPopupState struct { | |||
| 14 | 14 | Closed bool | |
| 15 | 15 | } | |
| 16 | 16 | ||
| 17 | - | func (wv *WebView) syntheticPopupState(proxyID string) *syntheticPopupState { | |
| 18 | - | if wv == nil || proxyID == "" { | |
| 19 | - | return nil | |
| 20 | - | } | |
| 21 | - | ||
| 22 | - | wv.syntheticPopupMu.Lock() | |
| 23 | - | defer wv.syntheticPopupMu.Unlock() | |
| 24 | - | if wv.syntheticPopups == nil { | |
| 25 | - | return nil | |
| 26 | - | } | |
| 27 | - | return wv.syntheticPopups[proxyID] | |
| 28 | - | } | |
| 29 | - | ||
| 30 | 17 | // syntheticPopupStateLocked returns the synthetic popup state for proxyID. | |
| 31 | 18 | // Caller must hold wv.syntheticPopupMu. | |
| 32 | 19 | func (wv *WebView) syntheticPopupStateLocked(proxyID string) *syntheticPopupState { | |
| @@ -14,19 +14,6 @@ type syntheticPopupState struct { | |||||
| 14 | Closed bool | 14 | Closed bool | ||
| 15 | } | 15 | } | ||
| 16 | 16 | ||||
| 17 | - | func (wv *WebView) syntheticPopupState(proxyID string) *syntheticPopupState { | |||
| 18 | - | if wv == nil || proxyID == "" { | |||
| 19 | - | return nil | |||
| 20 | - | } | |||
| 21 | - | ||||
| 22 | - | wv.syntheticPopupMu.Lock() | |||
| 23 | - | defer wv.syntheticPopupMu.Unlock() | |||
| 24 | - | if wv.syntheticPopups == nil { | |||
| 25 | - | return nil | |||
| 26 | - | } | |||
| 27 | - | return wv.syntheticPopups[proxyID] | |||
| 28 | - | } | |||
| 29 | - | ||||
| 30 | // syntheticPopupStateLocked returns the synthetic popup state for proxyID. | 17 | // syntheticPopupStateLocked returns the synthetic popup state for proxyID. | ||
| 31 | // Caller must hold wv.syntheticPopupMu. | 18 | // Caller must hold wv.syntheticPopupMu. | ||
| 32 | func (wv *WebView) syntheticPopupStateLocked(proxyID string) *syntheticPopupState { | 19 | func (wv *WebView) syntheticPopupStateLocked(proxyID string) *syntheticPopupState { | ||
M internal/ui/coordinator/content/coordinator.go +0 -6
| @@ -322,12 +322,6 @@ func (c *Coordinator) deleteWebViewLocked(paneID entity.PaneID) port.WebView { | |||
| 322 | 322 | return wv | |
| 323 | 323 | } | |
| 324 | 324 | ||
| 325 | - | func (c *Coordinator) paneIDByWebViewID(webViewID port.WebViewID) (entity.PaneID, bool) { | |
| 326 | - | c.webViewsMu.RLock() | |
| 327 | - | defer c.webViewsMu.RUnlock() | |
| 328 | - | return c.paneIDByWebViewIDLocked(webViewID) | |
| 329 | - | } | |
| 330 | - | ||
| 331 | 325 | func (c *Coordinator) paneIDByWebViewIDLocked(webViewID port.WebViewID) (entity.PaneID, bool) { | |
| 332 | 326 | paneID, ok := c.webViewPaneIDs[webViewID] | |
| 333 | 327 | return paneID, ok | |
| @@ -322,12 +322,6 @@ func (c *Coordinator) deleteWebViewLocked(paneID entity.PaneID) port.WebView { | |||||
| 322 | return wv | 322 | return wv | ||
| 323 | } | 323 | } | ||
| 324 | 324 | ||||
| 325 | - | func (c *Coordinator) paneIDByWebViewID(webViewID port.WebViewID) (entity.PaneID, bool) { | |||
| 326 | - | c.webViewsMu.RLock() | |||
| 327 | - | defer c.webViewsMu.RUnlock() | |||
| 328 | - | return c.paneIDByWebViewIDLocked(webViewID) | |||
| 329 | - | } | |||
| 330 | - | ||||
| 331 | func (c *Coordinator) paneIDByWebViewIDLocked(webViewID port.WebViewID) (entity.PaneID, bool) { | 325 | func (c *Coordinator) paneIDByWebViewIDLocked(webViewID port.WebViewID) (entity.PaneID, bool) { | ||
| 332 | paneID, ok := c.webViewPaneIDs[webViewID] | 326 | paneID, ok := c.webViewPaneIDs[webViewID] | ||
| 333 | return paneID, ok | 327 | return paneID, ok | ||
M internal/ui/coordinator/content/oauth.go +7 -16
| @@ -206,10 +206,7 @@ func composeOnURIChanged(existing, next func(string)) func(string) { | |||
| 206 | 206 | } | |
| 207 | 207 | } | |
| 208 | 208 | ||
| 209 | - | func composeOnLoadChanged( | |
| 210 | - | existing func(port.LoadEvent), | |
| 211 | - | next func(port.LoadEvent), | |
| 212 | - | ) func(port.LoadEvent) { | |
| 209 | + | func composeOnLoadChanged(existing, next func(port.LoadEvent)) func(port.LoadEvent) { | |
| 213 | 210 | if existing == nil { | |
| 214 | 211 | return next | |
| 215 | 212 | } | |
| @@ -402,8 +399,7 @@ func (c *Coordinator) scheduleParentPaneOAuthResume( | |||
| 402 | 399 | ctx context.Context, | |
| 403 | 400 | parentPaneID entity.PaneID, | |
| 404 | 401 | popupID port.WebViewID, | |
| 405 | - | parentURIAtOpen string, | |
| 406 | - | callbackURI string, | |
| 402 | + | parentURIAtOpen, callbackURI string, | |
| 407 | 403 | ) { | |
| 408 | 404 | c.scheduleParentPaneOAuthResumeAttempt( | |
| 409 | 405 | ctx, | |
| @@ -419,8 +415,7 @@ func (c *Coordinator) scheduleParentPaneOAuthResumeAttempt( | |||
| 419 | 415 | ctx context.Context, | |
| 420 | 416 | parentPaneID entity.PaneID, | |
| 421 | 417 | popupID port.WebViewID, | |
| 422 | - | parentURIAtOpen string, | |
| 423 | - | callbackURI string, | |
| 418 | + | parentURIAtOpen, callbackURI string, | |
| 424 | 419 | remainingGrace int, | |
| 425 | 420 | ) { | |
| 426 | 421 | c.ensurePopupManager().schedulePopupRefresh(parentPaneID, oauthParentRefreshDebounce, func() { | |
| @@ -436,8 +431,7 @@ func (c *Coordinator) resumeParentPaneAfterOAuth( | |||
| 436 | 431 | ctx context.Context, | |
| 437 | 432 | parentPaneID entity.PaneID, | |
| 438 | 433 | popupID port.WebViewID, | |
| 439 | - | parentURIAtOpen string, | |
| 440 | - | callbackURI string, | |
| 434 | + | parentURIAtOpen, callbackURI string, | |
| 441 | 435 | ) { | |
| 442 | 436 | c.resumeParentPaneAfterOAuthAttempt( | |
| 443 | 437 | ctx, | |
| @@ -453,8 +447,7 @@ func (c *Coordinator) resumeParentPaneAfterOAuthAttempt( | |||
| 453 | 447 | ctx context.Context, | |
| 454 | 448 | parentPaneID entity.PaneID, | |
| 455 | 449 | popupID port.WebViewID, | |
| 456 | - | parentURIAtOpen string, | |
| 457 | - | callbackURI string, | |
| 450 | + | parentURIAtOpen, callbackURI string, | |
| 458 | 451 | remainingGrace int, | |
| 459 | 452 | ) { | |
| 460 | 453 | log := logging.FromContext(ctx) | |
| @@ -515,9 +508,7 @@ func (c *Coordinator) resumeParentPaneAfterOAuthAttempt( | |||
| 515 | 508 | } | |
| 516 | 509 | ||
| 517 | 510 | func shouldGraceWaitForParentPaneOAuthResume( | |
| 518 | - | parentURIAtOpen string, | |
| 519 | - | currentParentURI string, | |
| 520 | - | callbackURI string, | |
| 511 | + | parentURIAtOpen, currentParentURI, callbackURI string, | |
| 521 | 512 | remainingGrace int, | |
| 522 | 513 | ) bool { | |
| 523 | 514 | if remainingGrace <= 0 { | |
| @@ -532,7 +523,7 @@ func shouldGraceWaitForParentPaneOAuthResume( | |||
| 532 | 523 | return sameOAuthResumeSite(parentURIAtOpen, callbackURI) | |
| 533 | 524 | } | |
| 534 | 525 | ||
| 535 | - | func sameOAuthResumeSite(a string, b string) bool { | |
| 526 | + | func sameOAuthResumeSite(a, b string) bool { | |
| 536 | 527 | hostA := normalizeOAuthResumeHost(a) | |
| 537 | 528 | hostB := normalizeOAuthResumeHost(b) | |
| 538 | 529 | return hostA != "" && hostA == hostB | |
| @@ -206,10 +206,7 @@ func composeOnURIChanged(existing, next func(string)) func(string) { | |||||
| 206 | } | 206 | } | ||
| 207 | } | 207 | } | ||
| 208 | 208 | ||||
| 209 | - | func composeOnLoadChanged( | 209 | + | func composeOnLoadChanged(existing, next func(port.LoadEvent)) func(port.LoadEvent) { |
| 210 | - | existing func(port.LoadEvent), | |||
| 211 | - | next func(port.LoadEvent), | |||
| 212 | - | ) func(port.LoadEvent) { | |||
| 213 | if existing == nil { | 210 | if existing == nil { | ||
| 214 | return next | 211 | return next | ||
| 215 | } | 212 | } | ||
| @@ -402,8 +399,7 @@ func (c *Coordinator) scheduleParentPaneOAuthResume( | |||||
| 402 | ctx context.Context, | 399 | ctx context.Context, | ||
| 403 | parentPaneID entity.PaneID, | 400 | parentPaneID entity.PaneID, | ||
| 404 | popupID port.WebViewID, | 401 | popupID port.WebViewID, | ||
| 405 | - | parentURIAtOpen string, | 402 | + | parentURIAtOpen, callbackURI string, |
| 406 | - | callbackURI string, | |||
| 407 | ) { | 403 | ) { | ||
| 408 | c.scheduleParentPaneOAuthResumeAttempt( | 404 | c.scheduleParentPaneOAuthResumeAttempt( | ||
| 409 | ctx, | 405 | ctx, | ||
| @@ -419,8 +415,7 @@ func (c *Coordinator) scheduleParentPaneOAuthResumeAttempt( | |||||
| 419 | ctx context.Context, | 415 | ctx context.Context, | ||
| 420 | parentPaneID entity.PaneID, | 416 | parentPaneID entity.PaneID, | ||
| 421 | popupID port.WebViewID, | 417 | popupID port.WebViewID, | ||
| 422 | - | parentURIAtOpen string, | 418 | + | parentURIAtOpen, callbackURI string, |
| 423 | - | callbackURI string, | |||
| 424 | remainingGrace int, | 419 | remainingGrace int, | ||
| 425 | ) { | 420 | ) { | ||
| 426 | c.ensurePopupManager().schedulePopupRefresh(parentPaneID, oauthParentRefreshDebounce, func() { | 421 | c.ensurePopupManager().schedulePopupRefresh(parentPaneID, oauthParentRefreshDebounce, func() { | ||
| @@ -436,8 +431,7 @@ func (c *Coordinator) resumeParentPaneAfterOAuth( | |||||
| 436 | ctx context.Context, | 431 | ctx context.Context, | ||
| 437 | parentPaneID entity.PaneID, | 432 | parentPaneID entity.PaneID, | ||
| 438 | popupID port.WebViewID, | 433 | popupID port.WebViewID, | ||
| 439 | - | parentURIAtOpen string, | 434 | + | parentURIAtOpen, callbackURI string, |
| 440 | - | callbackURI string, | |||
| 441 | ) { | 435 | ) { | ||
| 442 | c.resumeParentPaneAfterOAuthAttempt( | 436 | c.resumeParentPaneAfterOAuthAttempt( | ||
| 443 | ctx, | 437 | ctx, | ||
| @@ -453,8 +447,7 @@ func (c *Coordinator) resumeParentPaneAfterOAuthAttempt( | |||||
| 453 | ctx context.Context, | 447 | ctx context.Context, | ||
| 454 | parentPaneID entity.PaneID, | 448 | parentPaneID entity.PaneID, | ||
| 455 | popupID port.WebViewID, | 449 | popupID port.WebViewID, | ||
| 456 | - | parentURIAtOpen string, | 450 | + | parentURIAtOpen, callbackURI string, |
| 457 | - | callbackURI string, | |||
| 458 | remainingGrace int, | 451 | remainingGrace int, | ||
| 459 | ) { | 452 | ) { | ||
| 460 | log := logging.FromContext(ctx) | 453 | log := logging.FromContext(ctx) | ||
| @@ -515,9 +508,7 @@ func (c *Coordinator) resumeParentPaneAfterOAuthAttempt( | |||||
| 515 | } | 508 | } | ||
| 516 | 509 | ||||
| 517 | func shouldGraceWaitForParentPaneOAuthResume( | 510 | func shouldGraceWaitForParentPaneOAuthResume( | ||
| 518 | - | parentURIAtOpen string, | 511 | + | parentURIAtOpen, currentParentURI, callbackURI string, |
| 519 | - | currentParentURI string, | |||
| 520 | - | callbackURI string, | |||
| 521 | remainingGrace int, | 512 | remainingGrace int, | ||
| 522 | ) bool { | 513 | ) bool { | ||
| 523 | if remainingGrace <= 0 { | 514 | if remainingGrace <= 0 { | ||
| @@ -532,7 +523,7 @@ func shouldGraceWaitForParentPaneOAuthResume( | |||||
| 532 | return sameOAuthResumeSite(parentURIAtOpen, callbackURI) | 523 | return sameOAuthResumeSite(parentURIAtOpen, callbackURI) | ||
| 533 | } | 524 | } | ||
| 534 | 525 | ||||
| 535 | - | func sameOAuthResumeSite(a string, b string) bool { | 526 | + | func sameOAuthResumeSite(a, b string) bool { |
| 536 | hostA := normalizeOAuthResumeHost(a) | 527 | hostA := normalizeOAuthResumeHost(a) | ||
| 537 | hostB := normalizeOAuthResumeHost(b) | 528 | hostB := normalizeOAuthResumeHost(b) | ||
| 538 | return hostA != "" && hostA == hostB | 529 | return hostA != "" && hostA == hostB | ||
M internal/ui/coordinator/content/popup.go +0 -79
| @@ -91,26 +91,6 @@ func isReusableNamedPopupFrame(frameName string) bool { | |||
| 91 | 91 | return frameName != "" && frameName != "_blank" | |
| 92 | 92 | } | |
| 93 | 93 | ||
| 94 | - | func (c *Coordinator) lookupReusableNamedPopup(parentPaneID entity.PaneID, frameName string) (*namedPopupState, bool) { | |
| 95 | - | return c.ensurePopupManager().lookupReusableNamedPopup(parentPaneID, frameName) | |
| 96 | - | } | |
| 97 | - | ||
| 98 | - | func (c *Coordinator) storeReusableNamedPopup( | |
| 99 | - | parentPaneID entity.PaneID, | |
| 100 | - | frameName string, | |
| 101 | - | wv port.WebView, | |
| 102 | - | ) { | |
| 103 | - | c.ensurePopupManager().storeReusableNamedPopup(parentPaneID, frameName, wv) | |
| 104 | - | } | |
| 105 | - | ||
| 106 | - | func (c *Coordinator) updatePendingPopupTarget(popupID port.WebViewID, targetURI string) { | |
| 107 | - | c.ensurePopupManager().updatePendingPopupTarget(popupID, targetURI) | |
| 108 | - | } | |
| 109 | - | ||
| 110 | - | func (c *Coordinator) clearReusableNamedPopupByWebViewID(popupID port.WebViewID) { | |
| 111 | - | c.ensurePopupManager().clearReusableNamedPopupByWebViewID(popupID) | |
| 112 | - | } | |
| 113 | - | ||
| 114 | 94 | // InsertPopupInput contains the data needed to insert a popup into the workspace. | |
| 115 | 95 | type InsertPopupInput struct { | |
| 116 | 96 | // ParentPaneID is the pane that spawned this popup. | |
| @@ -199,40 +179,6 @@ func (c *Coordinator) buildPopupCreateHandler( | |||
| 199 | 179 | } | |
| 200 | 180 | } | |
| 201 | 181 | ||
| 202 | - | // createPopupPane creates a new pane entity for a popup window. | |
| 203 | - | func (c *Coordinator) createPopupPane( | |
| 204 | - | popupID port.WebViewID, | |
| 205 | - | parentPaneID entity.PaneID, | |
| 206 | - | targetURI string, | |
| 207 | - | ) (entity.PaneID, *entity.Pane) { | |
| 208 | - | return c.ensurePopupManager().createPopupPane(popupID, parentPaneID, targetURI) | |
| 209 | - | } | |
| 210 | - | ||
| 211 | - | func (c *Coordinator) relatedPopupSupportDisabled() bool { | |
| 212 | - | return c.ensurePopupManager().relatedPopupSupportDisabled() | |
| 213 | - | } | |
| 214 | - | ||
| 215 | - | func (c *Coordinator) markRelatedPopupUnsupported() { | |
| 216 | - | c.ensurePopupManager().markRelatedPopupUnsupported() | |
| 217 | - | } | |
| 218 | - | ||
| 219 | - | func (c *Coordinator) markRelatedPopupSupported() { | |
| 220 | - | c.ensurePopupManager().markRelatedPopupSupported() | |
| 221 | - | } | |
| 222 | - | ||
| 223 | - | // createPopupWebView prefers a related WebView so popups can share the | |
| 224 | - | // parent session/context. If the engine does not support related popup views, | |
| 225 | - | // it gracefully falls back to a regular WebView so target="_blank" and | |
| 226 | - | // window.open() still open in a workspace pane. | |
| 227 | - | func (c *Coordinator) createPopupWebView( | |
| 228 | - | ctx context.Context, | |
| 229 | - | parentID port.WebViewID, | |
| 230 | - | targetURI string, | |
| 231 | - | noJavaScriptAccess bool, | |
| 232 | - | ) (port.WebView, bool, error) { | |
| 233 | - | return c.ensurePopupManager().createPopupWebView(ctx, parentID, targetURI, noJavaScriptAccess) | |
| 234 | - | } | |
| 235 | - | ||
| 236 | 182 | // handlePopupCreate handles a popup request from the current WebView. | |
| 237 | 183 | // Returns a WebView if popup handling is allowed, nil to block. | |
| 238 | 184 | // | |
| @@ -248,31 +194,6 @@ func (c *Coordinator) handlePopupCreate( | |||
| 248 | 194 | return c.ensurePopupManager().handlePopupCreate(ctx, c.popupHooks(), parentPaneID, parentWV, req) | |
| 249 | 195 | } | |
| 250 | 196 | ||
| 251 | - | func (c *Coordinator) reuseNamedPopup( | |
| 252 | - | ctx context.Context, | |
| 253 | - | parentPaneID entity.PaneID, | |
| 254 | - | frameName string, | |
| 255 | - | targetURI string, | |
| 256 | - | ) (port.WebView, bool) { | |
| 257 | - | return c.ensurePopupManager().reuseNamedPopup(ctx, parentPaneID, frameName, targetURI) | |
| 258 | - | } | |
| 259 | - | ||
| 260 | - | func (c *Coordinator) finishPopupCreate(ctx context.Context, create popupCreateContext) port.WebView { | |
| 261 | - | return c.ensurePopupManager().finishPopupCreate(ctx, c.popupHooks(), create) | |
| 262 | - | } | |
| 263 | - | ||
| 264 | - | // handlePopupReadyToShow handles the WebKit "ready-to-show" signal. | |
| 265 | - | // The popup WebView was already inserted into the workspace (hidden) during | |
| 266 | - | // the create signal. This handler just makes it visible. | |
| 267 | - | func (c *Coordinator) handlePopupReadyToShow(ctx context.Context, popupID port.WebViewID) { | |
| 268 | - | c.ensurePopupManager().handlePopupReadyToShow(ctx, popupID) | |
| 269 | - | } | |
| 270 | - | ||
| 271 | - | // handlePopupClose handles the WebKit "close" signal for popup windows. | |
| 272 | - | func (c *Coordinator) handlePopupClose(ctx context.Context, popupID port.WebViewID) { | |
| 273 | - | c.ensurePopupManager().handlePopupClose(ctx, c.popupHooks(), popupID) | |
| 274 | - | } | |
| 275 | - | ||
| 276 | 197 | // handleLinkMiddleClick handles middle-click / Ctrl+click on links. | |
| 277 | 198 | // Opens the link in a new pane using blank_target_behavior config. | |
| 278 | 199 | func (c *Coordinator) handleLinkMiddleClick(ctx context.Context, parentPaneID entity.PaneID, uri string) bool { | |
| @@ -91,26 +91,6 @@ func isReusableNamedPopupFrame(frameName string) bool { | |||||
| 91 | return frameName != "" && frameName != "_blank" | 91 | return frameName != "" && frameName != "_blank" | ||
| 92 | } | 92 | } | ||
| 93 | 93 | ||||
| 94 | - | func (c *Coordinator) lookupReusableNamedPopup(parentPaneID entity.PaneID, frameName string) (*namedPopupState, bool) { | |||
| 95 | - | return c.ensurePopupManager().lookupReusableNamedPopup(parentPaneID, frameName) | |||
| 96 | - | } | |||
| 97 | - | ||||
| 98 | - | func (c *Coordinator) storeReusableNamedPopup( | |||
| 99 | - | parentPaneID entity.PaneID, | |||
| 100 | - | frameName string, | |||
| 101 | - | wv port.WebView, | |||
| 102 | - | ) { | |||
| 103 | - | c.ensurePopupManager().storeReusableNamedPopup(parentPaneID, frameName, wv) | |||
| 104 | - | } | |||
| 105 | - | ||||
| 106 | - | func (c *Coordinator) updatePendingPopupTarget(popupID port.WebViewID, targetURI string) { | |||
| 107 | - | c.ensurePopupManager().updatePendingPopupTarget(popupID, targetURI) | |||
| 108 | - | } | |||
| 109 | - | ||||
| 110 | - | func (c *Coordinator) clearReusableNamedPopupByWebViewID(popupID port.WebViewID) { | |||
| 111 | - | c.ensurePopupManager().clearReusableNamedPopupByWebViewID(popupID) | |||
| 112 | - | } | |||
| 113 | - | ||||
| 114 | // InsertPopupInput contains the data needed to insert a popup into the workspace. | 94 | // InsertPopupInput contains the data needed to insert a popup into the workspace. | ||
| 115 | type InsertPopupInput struct { | 95 | type InsertPopupInput struct { | ||
| 116 | // ParentPaneID is the pane that spawned this popup. | 96 | // ParentPaneID is the pane that spawned this popup. | ||
| @@ -199,40 +179,6 @@ func (c *Coordinator) buildPopupCreateHandler( | |||||
| 199 | } | 179 | } | ||
| 200 | } | 180 | } | ||
| 201 | 181 | ||||
| 202 | - | // createPopupPane creates a new pane entity for a popup window. | |||
| 203 | - | func (c *Coordinator) createPopupPane( | |||
| 204 | - | popupID port.WebViewID, | |||
| 205 | - | parentPaneID entity.PaneID, | |||
| 206 | - | targetURI string, | |||
| 207 | - | ) (entity.PaneID, *entity.Pane) { | |||
| 208 | - | return c.ensurePopupManager().createPopupPane(popupID, parentPaneID, targetURI) | |||
| 209 | - | } | |||
| 210 | - | ||||
| 211 | - | func (c *Coordinator) relatedPopupSupportDisabled() bool { | |||
| 212 | - | return c.ensurePopupManager().relatedPopupSupportDisabled() | |||
| 213 | - | } | |||
| 214 | - | ||||
| 215 | - | func (c *Coordinator) markRelatedPopupUnsupported() { | |||
| 216 | - | c.ensurePopupManager().markRelatedPopupUnsupported() | |||
| 217 | - | } | |||
| 218 | - | ||||
| 219 | - | func (c *Coordinator) markRelatedPopupSupported() { | |||
| 220 | - | c.ensurePopupManager().markRelatedPopupSupported() | |||
| 221 | - | } | |||
| 222 | - | ||||
| 223 | - | // createPopupWebView prefers a related WebView so popups can share the | |||
| 224 | - | // parent session/context. If the engine does not support related popup views, | |||
| 225 | - | // it gracefully falls back to a regular WebView so target="_blank" and | |||
| 226 | - | // window.open() still open in a workspace pane. | |||
| 227 | - | func (c *Coordinator) createPopupWebView( | |||
| 228 | - | ctx context.Context, | |||
| 229 | - | parentID port.WebViewID, | |||
| 230 | - | targetURI string, | |||
| 231 | - | noJavaScriptAccess bool, | |||
| 232 | - | ) (port.WebView, bool, error) { | |||
| 233 | - | return c.ensurePopupManager().createPopupWebView(ctx, parentID, targetURI, noJavaScriptAccess) | |||
| 234 | - | } | |||
| 235 | - | ||||
| 236 | // handlePopupCreate handles a popup request from the current WebView. | 182 | // handlePopupCreate handles a popup request from the current WebView. | ||
| 237 | // Returns a WebView if popup handling is allowed, nil to block. | 183 | // Returns a WebView if popup handling is allowed, nil to block. | ||
| 238 | // | 184 | // | ||
| @@ -248,31 +194,6 @@ func (c *Coordinator) handlePopupCreate( | |||||
| 248 | return c.ensurePopupManager().handlePopupCreate(ctx, c.popupHooks(), parentPaneID, parentWV, req) | 194 | return c.ensurePopupManager().handlePopupCreate(ctx, c.popupHooks(), parentPaneID, parentWV, req) | ||
| 249 | } | 195 | } | ||
| 250 | 196 | ||||
| 251 | - | func (c *Coordinator) reuseNamedPopup( | |||
| 252 | - | ctx context.Context, | |||
| 253 | - | parentPaneID entity.PaneID, | |||
| 254 | - | frameName string, | |||
| 255 | - | targetURI string, | |||
| 256 | - | ) (port.WebView, bool) { | |||
| 257 | - | return c.ensurePopupManager().reuseNamedPopup(ctx, parentPaneID, frameName, targetURI) | |||
| 258 | - | } | |||
| 259 | - | ||||
| 260 | - | func (c *Coordinator) finishPopupCreate(ctx context.Context, create popupCreateContext) port.WebView { | |||
| 261 | - | return c.ensurePopupManager().finishPopupCreate(ctx, c.popupHooks(), create) | |||
| 262 | - | } | |||
| 263 | - | ||||
| 264 | - | // handlePopupReadyToShow handles the WebKit "ready-to-show" signal. | |||
| 265 | - | // The popup WebView was already inserted into the workspace (hidden) during | |||
| 266 | - | // the create signal. This handler just makes it visible. | |||
| 267 | - | func (c *Coordinator) handlePopupReadyToShow(ctx context.Context, popupID port.WebViewID) { | |||
| 268 | - | c.ensurePopupManager().handlePopupReadyToShow(ctx, popupID) | |||
| 269 | - | } | |||
| 270 | - | ||||
| 271 | - | // handlePopupClose handles the WebKit "close" signal for popup windows. | |||
| 272 | - | func (c *Coordinator) handlePopupClose(ctx context.Context, popupID port.WebViewID) { | |||
| 273 | - | c.ensurePopupManager().handlePopupClose(ctx, c.popupHooks(), popupID) | |||
| 274 | - | } | |||
| 275 | - | ||||
| 276 | // handleLinkMiddleClick handles middle-click / Ctrl+click on links. | 197 | // handleLinkMiddleClick handles middle-click / Ctrl+click on links. | ||
| 277 | // Opens the link in a new pane using blank_target_behavior config. | 198 | // Opens the link in a new pane using blank_target_behavior config. | ||
| 278 | func (c *Coordinator) handleLinkMiddleClick(ctx context.Context, parentPaneID entity.PaneID, uri string) bool { | 199 | func (c *Coordinator) handleLinkMiddleClick(ctx context.Context, parentPaneID entity.PaneID, uri string) bool { | ||
M internal/ui/coordinator/content/popup_manager.go +47 -38
| @@ -665,69 +665,78 @@ func (pm *popupManager) handlePopupReadyToShow(ctx context.Context, popupID port | |||
| 665 | 665 | Msg("popup now visible") | |
| 666 | 666 | } | |
| 667 | 667 | ||
| 668 | - | func (pm *popupManager) handlePopupClose(ctx context.Context, hooks popupCoordinatorHooks, popupID port.WebViewID) { | |
| 669 | - | log := logging.FromContext(ctx) | |
| 670 | - | fields := log.Debug().Uint64("popup_id", uint64(popupID)) | |
| 668 | + | func (pm *popupManager) logPopupCloseSignal(ctx context.Context, hooks popupCoordinatorHooks, popupID port.WebViewID) { | |
| 669 | + | fields := logging.FromContext(ctx).Debug().Uint64("popup_id", uint64(popupID)) | |
| 671 | 670 | if hooks.findPaneByWebViewID != nil && hooks.getWebView != nil { | |
| 672 | 671 | if paneID, ok := hooks.findPaneByWebViewID(popupID); ok && paneID != "" { | |
| 673 | 672 | fields = fields.Str("pane_id", string(paneID)) | |
| 674 | 673 | if wv := hooks.getWebView(paneID); wv != nil { | |
| 675 | 674 | fields = fields. | |
| 676 | 675 | Str("current_uri", logging.TruncateURL(wv.URI(), logURLMaxLen)). | |
| 677 | - | Bool("is_loading", wv.IsLoading()) | |
| 678 | - | fields = fields.Bool("synthetic_opener_active", popupUsesSyntheticOpenerSignals(wv)) | |
| 676 | + | Bool("is_loading", wv.IsLoading()). | |
| 677 | + | Bool("synthetic_opener_active", popupUsesSyntheticOpenerSignals(wv)) | |
| 679 | 678 | } | |
| 680 | 679 | } | |
| 681 | 680 | } | |
| 682 | 681 | fields.Msg("popup close signal received") | |
| 682 | + | } | |
| 683 | 683 | ||
| 684 | - | pending, wasPending := pm.takePendingPopup(popupID) | |
| 685 | - | if wasPending && pending != nil { | |
| 686 | - | if hooks.handlePopupOAuthClose != nil { | |
| 687 | - | hooks.handlePopupOAuthClose(ctx, popupID) | |
| 688 | - | } | |
| 689 | - | if pm.onClosePane != nil { | |
| 690 | - | if err := pm.onClosePane(ctx, pending.PaneID); err != nil { | |
| 691 | - | log.Warn().Err(err).Str("pane_id", string(pending.PaneID)).Msg("failed to close pending popup pane") | |
| 692 | - | } | |
| 693 | - | } | |
| 694 | - | pm.clearReusableNamedPopupByWebViewID(popupID) | |
| 695 | - | if hooks.getWebView != nil && hooks.releaseWebView != nil && hooks.getWebView(pending.PaneID) != nil { | |
| 696 | - | hooks.releaseWebView(ctx, pending.PaneID) | |
| 697 | - | } | |
| 698 | - | log.Debug().Str("pane_id", string(pending.PaneID)).Msg("cleaned up pending popup that was never shown") | |
| 699 | - | return | |
| 684 | + | func (pm *popupManager) findPopupPaneForClose(hooks popupCoordinatorHooks, popupID port.WebViewID) (entity.PaneID, bool) { | |
| 685 | + | if hooks.findPaneByWebViewID == nil { | |
| 686 | + | return "", false | |
| 700 | 687 | } | |
| 688 | + | paneID, ok := hooks.findPaneByWebViewID(popupID) | |
| 689 | + | return paneID, ok && paneID != "" | |
| 690 | + | } | |
| 701 | 691 | ||
| 702 | - | var ( | |
| 703 | - | paneID entity.PaneID | |
| 704 | - | ok bool | |
| 705 | - | ) | |
| 706 | - | if hooks.findPaneByWebViewID != nil { | |
| 707 | - | paneID, ok = hooks.findPaneByWebViewID(popupID) | |
| 708 | - | } | |
| 709 | - | if !ok || paneID == "" { | |
| 710 | - | if hooks.handlePopupOAuthClose != nil { | |
| 711 | - | hooks.handlePopupOAuthClose(ctx, popupID) | |
| 712 | - | } | |
| 713 | - | pm.clearReusableNamedPopupByWebViewID(popupID) | |
| 714 | - | log.Warn().Msg("popup close: could not find pane for webview") | |
| 692 | + | func (pm *popupManager) releasePopupWebView(ctx context.Context, hooks popupCoordinatorHooks, paneID entity.PaneID) { | |
| 693 | + | if paneID == "" || hooks.getWebView == nil || hooks.releaseWebView == nil { | |
| 715 | 694 | return | |
| 716 | 695 | } | |
| 696 | + | if hooks.getWebView(paneID) != nil { | |
| 697 | + | hooks.releaseWebView(ctx, paneID) | |
| 698 | + | } | |
| 699 | + | } | |
| 717 | 700 | ||
| 701 | + | func (pm *popupManager) cleanupPopupClose( | |
| 702 | + | ctx context.Context, | |
| 703 | + | hooks popupCoordinatorHooks, | |
| 704 | + | popupID port.WebViewID, | |
| 705 | + | paneID entity.PaneID, | |
| 706 | + | closeErrMsg string, | |
| 707 | + | ) { | |
| 708 | + | log := logging.FromContext(ctx) | |
| 718 | 709 | if hooks.handlePopupOAuthClose != nil { | |
| 719 | 710 | hooks.handlePopupOAuthClose(ctx, popupID) | |
| 720 | 711 | } | |
| 721 | - | if pm.onClosePane != nil { | |
| 712 | + | if paneID != "" && pm.onClosePane != nil { | |
| 722 | 713 | if err := pm.onClosePane(ctx, paneID); err != nil { | |
| 723 | - | log.Warn().Err(err).Str("pane_id", string(paneID)).Msg("failed to close popup pane") | |
| 714 | + | log.Warn().Err(err).Str("pane_id", string(paneID)).Msg(closeErrMsg) | |
| 724 | 715 | } | |
| 725 | 716 | } | |
| 726 | 717 | pm.clearReusableNamedPopupByWebViewID(popupID) | |
| 727 | - | if hooks.getWebView != nil && hooks.releaseWebView != nil && hooks.getWebView(paneID) != nil { | |
| 728 | - | hooks.releaseWebView(ctx, paneID) | |
| 718 | + | pm.releasePopupWebView(ctx, hooks, paneID) | |
| 719 | + | } | |
| 720 | + | ||
| 721 | + | func (pm *popupManager) handlePopupClose(ctx context.Context, hooks popupCoordinatorHooks, popupID port.WebViewID) { | |
| 722 | + | log := logging.FromContext(ctx) | |
| 723 | + | pm.logPopupCloseSignal(ctx, hooks, popupID) | |
| 724 | + | ||
| 725 | + | pending, wasPending := pm.takePendingPopup(popupID) | |
| 726 | + | if wasPending && pending != nil { | |
| 727 | + | pm.cleanupPopupClose(ctx, hooks, popupID, pending.PaneID, "failed to close pending popup pane") | |
| 728 | + | log.Debug().Str("pane_id", string(pending.PaneID)).Msg("cleaned up pending popup that was never shown") | |
| 729 | + | return | |
| 730 | + | } | |
| 731 | + | ||
| 732 | + | paneID, ok := pm.findPopupPaneForClose(hooks, popupID) | |
| 733 | + | if !ok { | |
| 734 | + | pm.cleanupPopupClose(ctx, hooks, popupID, "", "") | |
| 735 | + | log.Warn().Msg("popup close: could not find pane for webview") | |
| 736 | + | return | |
| 729 | 737 | } | |
| 730 | 738 | ||
| 739 | + | pm.cleanupPopupClose(ctx, hooks, popupID, paneID, "failed to close popup pane") | |
| 731 | 740 | log.Info().Str("pane_id", string(paneID)).Msg("popup closed") | |
| 732 | 741 | } | |
| 733 | 742 | ||
| @@ -665,69 +665,78 @@ func (pm *popupManager) handlePopupReadyToShow(ctx context.Context, popupID port | |||||
| 665 | Msg("popup now visible") | 665 | Msg("popup now visible") | ||
| 666 | } | 666 | } | ||
| 667 | 667 | ||||
| 668 | - | func (pm *popupManager) handlePopupClose(ctx context.Context, hooks popupCoordinatorHooks, popupID port.WebViewID) { | 668 | + | func (pm *popupManager) logPopupCloseSignal(ctx context.Context, hooks popupCoordinatorHooks, popupID port.WebViewID) { |
| 669 | - | log := logging.FromContext(ctx) | 669 | + | fields := logging.FromContext(ctx).Debug().Uint64("popup_id", uint64(popupID)) |
| 670 | - | fields := log.Debug().Uint64("popup_id", uint64(popupID)) | |||
| 671 | if hooks.findPaneByWebViewID != nil && hooks.getWebView != nil { | 670 | if hooks.findPaneByWebViewID != nil && hooks.getWebView != nil { | ||
| 672 | if paneID, ok := hooks.findPaneByWebViewID(popupID); ok && paneID != "" { | 671 | if paneID, ok := hooks.findPaneByWebViewID(popupID); ok && paneID != "" { | ||
| 673 | fields = fields.Str("pane_id", string(paneID)) | 672 | fields = fields.Str("pane_id", string(paneID)) | ||
| 674 | if wv := hooks.getWebView(paneID); wv != nil { | 673 | if wv := hooks.getWebView(paneID); wv != nil { | ||
| 675 | fields = fields. | 674 | fields = fields. | ||
| 676 | Str("current_uri", logging.TruncateURL(wv.URI(), logURLMaxLen)). | 675 | Str("current_uri", logging.TruncateURL(wv.URI(), logURLMaxLen)). | ||
| 677 | - | Bool("is_loading", wv.IsLoading()) | 676 | + | Bool("is_loading", wv.IsLoading()). |
| 678 | - | fields = fields.Bool("synthetic_opener_active", popupUsesSyntheticOpenerSignals(wv)) | 677 | + | Bool("synthetic_opener_active", popupUsesSyntheticOpenerSignals(wv)) |
| 679 | } | 678 | } | ||
| 680 | } | 679 | } | ||
| 681 | } | 680 | } | ||
| 682 | fields.Msg("popup close signal received") | 681 | fields.Msg("popup close signal received") | ||
| 682 | + | } | |||
| 683 | 683 | ||||
| 684 | - | pending, wasPending := pm.takePendingPopup(popupID) | 684 | + | func (pm *popupManager) findPopupPaneForClose(hooks popupCoordinatorHooks, popupID port.WebViewID) (entity.PaneID, bool) { |
| 685 | - | if wasPending && pending != nil { | 685 | + | if hooks.findPaneByWebViewID == nil { |
| 686 | - | if hooks.handlePopupOAuthClose != nil { | 686 | + | return "", false |
| 687 | - | hooks.handlePopupOAuthClose(ctx, popupID) | |||
| 688 | - | } | |||
| 689 | - | if pm.onClosePane != nil { | |||
| 690 | - | if err := pm.onClosePane(ctx, pending.PaneID); err != nil { | |||
| 691 | - | log.Warn().Err(err).Str("pane_id", string(pending.PaneID)).Msg("failed to close pending popup pane") | |||
| 692 | - | } | |||
| 693 | - | } | |||
| 694 | - | pm.clearReusableNamedPopupByWebViewID(popupID) | |||
| 695 | - | if hooks.getWebView != nil && hooks.releaseWebView != nil && hooks.getWebView(pending.PaneID) != nil { | |||
| 696 | - | hooks.releaseWebView(ctx, pending.PaneID) | |||
| 697 | - | } | |||
| 698 | - | log.Debug().Str("pane_id", string(pending.PaneID)).Msg("cleaned up pending popup that was never shown") | |||
| 699 | - | return | |||
| 700 | } | 687 | } | ||
| 688 | + | paneID, ok := hooks.findPaneByWebViewID(popupID) | |||
| 689 | + | return paneID, ok && paneID != "" | |||
| 690 | + | } | |||
| 701 | 691 | ||||
| 702 | - | var ( | 692 | + | func (pm *popupManager) releasePopupWebView(ctx context.Context, hooks popupCoordinatorHooks, paneID entity.PaneID) { |
| 703 | - | paneID entity.PaneID | 693 | + | if paneID == "" || hooks.getWebView == nil || hooks.releaseWebView == nil { |
| 704 | - | ok bool | |||
| 705 | - | ) | |||
| 706 | - | if hooks.findPaneByWebViewID != nil { | |||
| 707 | - | paneID, ok = hooks.findPaneByWebViewID(popupID) | |||
| 708 | - | } | |||
| 709 | - | if !ok || paneID == "" { | |||
| 710 | - | if hooks.handlePopupOAuthClose != nil { | |||
| 711 | - | hooks.handlePopupOAuthClose(ctx, popupID) | |||
| 712 | - | } | |||
| 713 | - | pm.clearReusableNamedPopupByWebViewID(popupID) | |||
| 714 | - | log.Warn().Msg("popup close: could not find pane for webview") | |||
| 715 | return | 694 | return | ||
| 716 | } | 695 | } | ||
| 696 | + | if hooks.getWebView(paneID) != nil { | |||
| 697 | + | hooks.releaseWebView(ctx, paneID) | |||
| 698 | + | } | |||
| 699 | + | } | |||
| 717 | 700 | ||||
| 701 | + | func (pm *popupManager) cleanupPopupClose( | |||
| 702 | + | ctx context.Context, | |||
| 703 | + | hooks popupCoordinatorHooks, | |||
| 704 | + | popupID port.WebViewID, | |||
| 705 | + | paneID entity.PaneID, | |||
| 706 | + | closeErrMsg string, | |||
| 707 | + | ) { | |||
| 708 | + | log := logging.FromContext(ctx) | |||
| 718 | if hooks.handlePopupOAuthClose != nil { | 709 | if hooks.handlePopupOAuthClose != nil { | ||
| 719 | hooks.handlePopupOAuthClose(ctx, popupID) | 710 | hooks.handlePopupOAuthClose(ctx, popupID) | ||
| 720 | } | 711 | } | ||
| 721 | - | if pm.onClosePane != nil { | 712 | + | if paneID != "" && pm.onClosePane != nil { |
| 722 | if err := pm.onClosePane(ctx, paneID); err != nil { | 713 | if err := pm.onClosePane(ctx, paneID); err != nil { | ||
| 723 | - | log.Warn().Err(err).Str("pane_id", string(paneID)).Msg("failed to close popup pane") | 714 | + | log.Warn().Err(err).Str("pane_id", string(paneID)).Msg(closeErrMsg) |
| 724 | } | 715 | } | ||
| 725 | } | 716 | } | ||
| 726 | pm.clearReusableNamedPopupByWebViewID(popupID) | 717 | pm.clearReusableNamedPopupByWebViewID(popupID) | ||
| 727 | - | if hooks.getWebView != nil && hooks.releaseWebView != nil && hooks.getWebView(paneID) != nil { | 718 | + | pm.releasePopupWebView(ctx, hooks, paneID) |
| 728 | - | hooks.releaseWebView(ctx, paneID) | 719 | + | } |
| 720 | + | ||||
| 721 | + | func (pm *popupManager) handlePopupClose(ctx context.Context, hooks popupCoordinatorHooks, popupID port.WebViewID) { | |||
| 722 | + | log := logging.FromContext(ctx) | |||
| 723 | + | pm.logPopupCloseSignal(ctx, hooks, popupID) | |||
| 724 | + | ||||
| 725 | + | pending, wasPending := pm.takePendingPopup(popupID) | |||
| 726 | + | if wasPending && pending != nil { | |||
| 727 | + | pm.cleanupPopupClose(ctx, hooks, popupID, pending.PaneID, "failed to close pending popup pane") | |||
| 728 | + | log.Debug().Str("pane_id", string(pending.PaneID)).Msg("cleaned up pending popup that was never shown") | |||
| 729 | + | return | |||
| 730 | + | } | |||
| 731 | + | ||||
| 732 | + | paneID, ok := pm.findPopupPaneForClose(hooks, popupID) | |||
| 733 | + | if !ok { | |||
| 734 | + | pm.cleanupPopupClose(ctx, hooks, popupID, "", "") | |||
| 735 | + | log.Warn().Msg("popup close: could not find pane for webview") | |||
| 736 | + | return | |||
| 729 | } | 737 | } | ||
| 730 | 738 | ||||
| 739 | + | pm.cleanupPopupClose(ctx, hooks, popupID, paneID, "failed to close popup pane") | |||
| 731 | log.Info().Str("pane_id", string(paneID)).Msg("popup closed") | 740 | log.Info().Str("pane_id", string(paneID)).Msg("popup closed") | ||
| 732 | } | 741 | } | ||
| 733 | 742 | ||||