Add experimental multi-threaded CEF loop mode
- commit
- 3f8c6a56cb7511ab77418666353826027a1393ae
- author
- brice · · 2026-03-20 13:48 UTC
- committer
- brice · · 2026-03-28 04:36 UTC
- parent
- 95bfd15
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +159 -44
M internal/infrastructure/cef/engine.go +11 -2
| @@ -29,8 +29,9 @@ type Engine struct { | |||
| 29 | 29 | factory *WebViewFactory | |
| 30 | 30 | pool *WebViewPool | |
| 31 | 31 | ||
| 32 | - | externalMessagePump bool | |
| 33 | - | manualPumpInterval int64 | |
| 32 | + | multiThreadedMessageLoop bool | |
| 33 | + | externalMessagePump bool | |
| 34 | + | manualPumpInterval int64 | |
| 34 | 35 | ||
| 35 | 36 | // nextWorkAtMs is the next deadline, in Unix milliseconds, when | |
| 36 | 37 | // CefDoMessageLoopWork should run. It is written from any Chromium thread | |
| @@ -152,6 +153,7 @@ func (e *Engine) ConfigureDownloads(_ context.Context, _ string, _ port.Download | |||
| 152 | 153 | func (e *Engine) OnToolkitReady(_ context.Context) error { | |
| 153 | 154 | log := logging.FromContext(e.ctx) | |
| 154 | 155 | log.Debug(). | |
| 156 | + | Bool("multi_threaded_message_loop", e.multiThreadedMessageLoop). | |
| 155 | 157 | Bool("external_message_pump", e.externalMessagePump). | |
| 156 | 158 | Int64("manual_pump_interval_ms", e.manualPumpInterval). | |
| 157 | 159 | Msg("cef: OnToolkitReady called, starting message pump") | |
| @@ -344,6 +346,10 @@ func (e *Engine) scheduleMessagePumpWork(delayMs int64) { | |||
| 344 | 346 | } | |
| 345 | 347 | ||
| 346 | 348 | func (e *Engine) startMessagePump() { | |
| 349 | + | if e.multiThreadedMessageLoop { | |
| 350 | + | logging.FromContext(e.ctx).Info().Msg("cef: multi-threaded message loop enabled, skipping external/manual pump") | |
| 351 | + | return | |
| 352 | + | } | |
| 347 | 353 | if e.externalMessagePump { | |
| 348 | 354 | e.startExternalMessagePump() | |
| 349 | 355 | return | |
| @@ -458,6 +464,9 @@ func (e *Engine) startManualMessagePump() { | |||
| 458 | 464 | } | |
| 459 | 465 | ||
| 460 | 466 | func (e *Engine) stopMessagePump() { | |
| 467 | + | if e.multiThreadedMessageLoop { | |
| 468 | + | return | |
| 469 | + | } | |
| 461 | 470 | e.pumpClosing.Store(true) | |
| 462 | 471 | e.pumpEnabled.Store(false) | |
| 463 | 472 | e.nextWorkAtMs.Store(0) | |
| @@ -29,8 +29,9 @@ type Engine struct { | |||||
| 29 | factory *WebViewFactory | 29 | factory *WebViewFactory | ||
| 30 | pool *WebViewPool | 30 | pool *WebViewPool | ||
| 31 | 31 | ||||
| 32 | - | externalMessagePump bool | 32 | + | multiThreadedMessageLoop bool |
| 33 | - | manualPumpInterval int64 | 33 | + | externalMessagePump bool |
| 34 | + | manualPumpInterval int64 | |||
| 34 | 35 | ||||
| 35 | // nextWorkAtMs is the next deadline, in Unix milliseconds, when | 36 | // nextWorkAtMs is the next deadline, in Unix milliseconds, when | ||
| 36 | // CefDoMessageLoopWork should run. It is written from any Chromium thread | 37 | // CefDoMessageLoopWork should run. It is written from any Chromium thread | ||
| @@ -152,6 +153,7 @@ func (e *Engine) ConfigureDownloads(_ context.Context, _ string, _ port.Download | |||||
| 152 | func (e *Engine) OnToolkitReady(_ context.Context) error { | 153 | func (e *Engine) OnToolkitReady(_ context.Context) error { | ||
| 153 | log := logging.FromContext(e.ctx) | 154 | log := logging.FromContext(e.ctx) | ||
| 154 | log.Debug(). | 155 | log.Debug(). | ||
| 156 | + | Bool("multi_threaded_message_loop", e.multiThreadedMessageLoop). | |||
| 155 | Bool("external_message_pump", e.externalMessagePump). | 157 | Bool("external_message_pump", e.externalMessagePump). | ||
| 156 | Int64("manual_pump_interval_ms", e.manualPumpInterval). | 158 | Int64("manual_pump_interval_ms", e.manualPumpInterval). | ||
| 157 | Msg("cef: OnToolkitReady called, starting message pump") | 159 | Msg("cef: OnToolkitReady called, starting message pump") | ||
| @@ -344,6 +346,10 @@ func (e *Engine) scheduleMessagePumpWork(delayMs int64) { | |||||
| 344 | } | 346 | } | ||
| 345 | 347 | ||||
| 346 | func (e *Engine) startMessagePump() { | 348 | func (e *Engine) startMessagePump() { | ||
| 349 | + | if e.multiThreadedMessageLoop { | |||
| 350 | + | logging.FromContext(e.ctx).Info().Msg("cef: multi-threaded message loop enabled, skipping external/manual pump") | |||
| 351 | + | return | |||
| 352 | + | } | |||
| 347 | if e.externalMessagePump { | 353 | if e.externalMessagePump { | ||
| 348 | e.startExternalMessagePump() | 354 | e.startExternalMessagePump() | ||
| 349 | return | 355 | return | ||
| @@ -458,6 +464,9 @@ func (e *Engine) startManualMessagePump() { | |||||
| 458 | } | 464 | } | ||
| 459 | 465 | ||||
| 460 | func (e *Engine) stopMessagePump() { | 466 | func (e *Engine) stopMessagePump() { | ||
| 467 | + | if e.multiThreadedMessageLoop { | |||
| 468 | + | return | |||
| 469 | + | } | |||
| 461 | e.pumpClosing.Store(true) | 470 | e.pumpClosing.Store(true) | ||
| 462 | e.pumpEnabled.Store(false) | 471 | e.pumpEnabled.Store(false) | ||
| 463 | e.nextWorkAtMs.Store(0) | 472 | e.nextWorkAtMs.Store(0) | ||
M internal/infrastructure/cef/engine_init.go +9 -3
| @@ -18,7 +18,11 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig) (*Engine, error) | |||
| 18 | 18 | logger := logging.FromContext(ctx) | |
| 19 | 19 | // 1. Initialize CEF. | |
| 20 | 20 | settings := purecef.DefaultSettings() | |
| 21 | + | settings.MultiThreadedMessageLoop = parseBoolEnv("DUMBER_CEF_MULTI_THREADED_MESSAGE_LOOP", false) | |
| 21 | 22 | settings.ExternalMessagePump = parseBoolEnv("DUMBER_CEF_EXTERNAL_PUMP", settings.ExternalMessagePump) | |
| 23 | + | if settings.MultiThreadedMessageLoop { | |
| 24 | + | settings.ExternalMessagePump = false | |
| 25 | + | } | |
| 22 | 26 | if cfg.CEFDir != "" { | |
| 23 | 27 | settings.CEFDir = cfg.CEFDir | |
| 24 | 28 | } | |
| @@ -52,12 +56,14 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig) (*Engine, error) | |||
| 52 | 56 | ||
| 53 | 57 | // 2. Build the engine early so the App can reference it for the pump callback. | |
| 54 | 58 | eng := &Engine{ | |
| 55 | - | ctx: ctx, | |
| 56 | - | externalMessagePump: settings.ExternalMessagePump, | |
| 57 | - | manualPumpInterval: parseInt64Env("DUMBER_CEF_MANUAL_PUMP_MS", 10), | |
| 59 | + | ctx: ctx, | |
| 60 | + | multiThreadedMessageLoop: settings.MultiThreadedMessageLoop, | |
| 61 | + | externalMessagePump: settings.ExternalMessagePump, | |
| 62 | + | manualPumpInterval: parseInt64Env("DUMBER_CEF_MANUAL_PUMP_MS", 10), | |
| 58 | 63 | } | |
| 59 | 64 | ||
| 60 | 65 | logger.Info(). | |
| 66 | + | Bool("multi_threaded_message_loop", settings.MultiThreadedMessageLoop). | |
| 61 | 67 | Bool("external_message_pump", settings.ExternalMessagePump). | |
| 62 | 68 | Int64("manual_pump_interval_ms", eng.manualPumpInterval). | |
| 63 | 69 | Msg("cef: configured message pump mode") | |
| @@ -18,7 +18,11 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig) (*Engine, error) | |||||
| 18 | logger := logging.FromContext(ctx) | 18 | logger := logging.FromContext(ctx) | ||
| 19 | // 1. Initialize CEF. | 19 | // 1. Initialize CEF. | ||
| 20 | settings := purecef.DefaultSettings() | 20 | settings := purecef.DefaultSettings() | ||
| 21 | + | settings.MultiThreadedMessageLoop = parseBoolEnv("DUMBER_CEF_MULTI_THREADED_MESSAGE_LOOP", false) | |||
| 21 | settings.ExternalMessagePump = parseBoolEnv("DUMBER_CEF_EXTERNAL_PUMP", settings.ExternalMessagePump) | 22 | settings.ExternalMessagePump = parseBoolEnv("DUMBER_CEF_EXTERNAL_PUMP", settings.ExternalMessagePump) | ||
| 23 | + | if settings.MultiThreadedMessageLoop { | |||
| 24 | + | settings.ExternalMessagePump = false | |||
| 25 | + | } | |||
| 22 | if cfg.CEFDir != "" { | 26 | if cfg.CEFDir != "" { | ||
| 23 | settings.CEFDir = cfg.CEFDir | 27 | settings.CEFDir = cfg.CEFDir | ||
| 24 | } | 28 | } | ||
| @@ -52,12 +56,14 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig) (*Engine, error) | |||||
| 52 | 56 | ||||
| 53 | // 2. Build the engine early so the App can reference it for the pump callback. | 57 | // 2. Build the engine early so the App can reference it for the pump callback. | ||
| 54 | eng := &Engine{ | 58 | eng := &Engine{ | ||
| 55 | - | ctx: ctx, | 59 | + | ctx: ctx, |
| 56 | - | externalMessagePump: settings.ExternalMessagePump, | 60 | + | multiThreadedMessageLoop: settings.MultiThreadedMessageLoop, |
| 57 | - | manualPumpInterval: parseInt64Env("DUMBER_CEF_MANUAL_PUMP_MS", 10), | 61 | + | externalMessagePump: settings.ExternalMessagePump, |
| 62 | + | manualPumpInterval: parseInt64Env("DUMBER_CEF_MANUAL_PUMP_MS", 10), | |||
| 58 | } | 63 | } | ||
| 59 | 64 | ||||
| 60 | logger.Info(). | 65 | logger.Info(). | ||
| 66 | + | Bool("multi_threaded_message_loop", settings.MultiThreadedMessageLoop). | |||
| 61 | Bool("external_message_pump", settings.ExternalMessagePump). | 67 | Bool("external_message_pump", settings.ExternalMessagePump). | ||
| 62 | Int64("manual_pump_interval_ms", eng.manualPumpInterval). | 68 | Int64("manual_pump_interval_ms", eng.manualPumpInterval). | ||
| 63 | Msg("cef: configured message pump mode") | 69 | Msg("cef: configured message pump mode") | ||
M internal/infrastructure/cef/factory.go +5 -2
| @@ -109,8 +109,11 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||
| 109 | 109 | ||
| 110 | 110 | // On subsequent resizes, notify CEF so it re-queries GetViewRect. | |
| 111 | 111 | pipeline.onResizeCB = func(_, _ int32) { | |
| 112 | - | if wv.host != nil { | |
| 113 | - | wv.host.WasResized() | |
| 112 | + | wv.mu.RLock() | |
| 113 | + | host := wv.host | |
| 114 | + | wv.mu.RUnlock() | |
| 115 | + | if host != nil { | |
| 116 | + | host.WasResized() | |
| 114 | 117 | } | |
| 115 | 118 | } | |
| 116 | 119 | ||
| @@ -109,8 +109,11 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||||
| 109 | 109 | ||||
| 110 | // On subsequent resizes, notify CEF so it re-queries GetViewRect. | 110 | // On subsequent resizes, notify CEF so it re-queries GetViewRect. | ||
| 111 | pipeline.onResizeCB = func(_, _ int32) { | 111 | pipeline.onResizeCB = func(_, _ int32) { | ||
| 112 | - | if wv.host != nil { | 112 | + | wv.mu.RLock() |
| 113 | - | wv.host.WasResized() | 113 | + | host := wv.host |
| 114 | + | wv.mu.RUnlock() | |||
| 115 | + | if host != nil { | |||
| 116 | + | host.WasResized() | |||
| 114 | } | 117 | } | ||
| 115 | } | 118 | } | ||
| 116 | 119 | ||||
M internal/infrastructure/cef/webview.go +74 -21
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | "unsafe" | |
| 10 | 10 | ||
| 11 | 11 | purecef "github.com/bnema/purego-cef/cef" | |
| 12 | + | "github.com/jwijenbergh/puregotk/v4/glib" | |
| 12 | 13 | ||
| 13 | 14 | "github.com/bnema/dumber/internal/application/port" | |
| 14 | 15 | ) | |
| @@ -92,12 +93,16 @@ func (wv *WebView) LoadURI(_ context.Context, uri string) error { | |||
| 92 | 93 | if wv.destroyed.Load() { | |
| 93 | 94 | return errDestroyed | |
| 94 | 95 | } | |
| 95 | - | if wv.browser == nil { | |
| 96 | + | wv.mu.Lock() | |
| 97 | + | browser := wv.browser | |
| 98 | + | if browser == nil { | |
| 96 | 99 | // Browser not yet created — queue the URI for OnAfterCreated. | |
| 97 | 100 | wv.pendingURI = uri | |
| 101 | + | wv.mu.Unlock() | |
| 98 | 102 | return nil | |
| 99 | 103 | } | |
| 100 | - | wv.browser.GetMainFrame().LoadURL(uri) | |
| 104 | + | wv.mu.Unlock() | |
| 105 | + | browser.GetMainFrame().LoadURL(uri) | |
| 101 | 106 | return nil | |
| 102 | 107 | } | |
| 103 | 108 | ||
| @@ -106,11 +111,14 @@ func (wv *WebView) LoadHTML(_ context.Context, content, _ string) error { | |||
| 106 | 111 | if wv.destroyed.Load() { | |
| 107 | 112 | return errDestroyed | |
| 108 | 113 | } | |
| 109 | - | if wv.browser == nil { | |
| 114 | + | wv.mu.RLock() | |
| 115 | + | browser := wv.browser | |
| 116 | + | wv.mu.RUnlock() | |
| 117 | + | if browser == nil { | |
| 110 | 118 | return errNoBrowser | |
| 111 | 119 | } | |
| 112 | 120 | dataURL := "data:text/html;base64," + base64.StdEncoding.EncodeToString([]byte(content)) | |
| 113 | - | wv.browser.GetMainFrame().LoadURL(dataURL) | |
| 121 | + | browser.GetMainFrame().LoadURL(dataURL) | |
| 114 | 122 | return nil | |
| 115 | 123 | } | |
| 116 | 124 | ||
| @@ -119,10 +127,13 @@ func (wv *WebView) Reload(_ context.Context) error { | |||
| 119 | 127 | if wv.destroyed.Load() { | |
| 120 | 128 | return errDestroyed | |
| 121 | 129 | } | |
| 122 | - | if wv.browser == nil { | |
| 130 | + | wv.mu.RLock() | |
| 131 | + | browser := wv.browser | |
| 132 | + | wv.mu.RUnlock() | |
| 133 | + | if browser == nil { | |
| 123 | 134 | return errNoBrowser | |
| 124 | 135 | } | |
| 125 | - | wv.browser.Reload() | |
| 136 | + | browser.Reload() | |
| 126 | 137 | return nil | |
| 127 | 138 | } | |
| 128 | 139 | ||
| @@ -131,10 +142,13 @@ func (wv *WebView) ReloadBypassCache(_ context.Context) error { | |||
| 131 | 142 | if wv.destroyed.Load() { | |
| 132 | 143 | return errDestroyed | |
| 133 | 144 | } | |
| 134 | - | if wv.browser == nil { | |
| 145 | + | wv.mu.RLock() | |
| 146 | + | browser := wv.browser | |
| 147 | + | wv.mu.RUnlock() | |
| 148 | + | if browser == nil { | |
| 135 | 149 | return errNoBrowser | |
| 136 | 150 | } | |
| 137 | - | wv.browser.ReloadIgnoreCache() | |
| 151 | + | browser.ReloadIgnoreCache() | |
| 138 | 152 | return nil | |
| 139 | 153 | } | |
| 140 | 154 | ||
| @@ -143,10 +157,13 @@ func (wv *WebView) Stop(_ context.Context) error { | |||
| 143 | 157 | if wv.destroyed.Load() { | |
| 144 | 158 | return errDestroyed | |
| 145 | 159 | } | |
| 146 | - | if wv.browser == nil { | |
| 160 | + | wv.mu.RLock() | |
| 161 | + | browser := wv.browser | |
| 162 | + | wv.mu.RUnlock() | |
| 163 | + | if browser == nil { | |
| 147 | 164 | return errNoBrowser | |
| 148 | 165 | } | |
| 149 | - | wv.browser.StopLoad() | |
| 166 | + | browser.StopLoad() | |
| 150 | 167 | return nil | |
| 151 | 168 | } | |
| 152 | 169 | ||
| @@ -155,10 +172,13 @@ func (wv *WebView) GoBack(_ context.Context) error { | |||
| 155 | 172 | if wv.destroyed.Load() { | |
| 156 | 173 | return errDestroyed | |
| 157 | 174 | } | |
| 158 | - | if wv.browser == nil { | |
| 175 | + | wv.mu.RLock() | |
| 176 | + | browser := wv.browser | |
| 177 | + | wv.mu.RUnlock() | |
| 178 | + | if browser == nil { | |
| 159 | 179 | return errNoBrowser | |
| 160 | 180 | } | |
| 161 | - | wv.browser.GoBack() | |
| 181 | + | browser.GoBack() | |
| 162 | 182 | return nil | |
| 163 | 183 | } | |
| 164 | 184 | ||
| @@ -167,10 +187,13 @@ func (wv *WebView) GoForward(_ context.Context) error { | |||
| 167 | 187 | if wv.destroyed.Load() { | |
| 168 | 188 | return errDestroyed | |
| 169 | 189 | } | |
| 170 | - | if wv.browser == nil { | |
| 190 | + | wv.mu.RLock() | |
| 191 | + | browser := wv.browser | |
| 192 | + | wv.mu.RUnlock() | |
| 193 | + | if browser == nil { | |
| 171 | 194 | return errNoBrowser | |
| 172 | 195 | } | |
| 173 | - | wv.browser.GoForward() | |
| 196 | + | browser.GoForward() | |
| 174 | 197 | return nil | |
| 175 | 198 | } | |
| 176 | 199 | ||
| @@ -295,10 +318,16 @@ func (wv *WebView) SetCallbacks(cb *port.WebViewCallbacks) { | |||
| 295 | 318 | ||
| 296 | 319 | // RunJavaScript executes a script in the main world. Fire-and-forget. | |
| 297 | 320 | func (wv *WebView) RunJavaScript(_ context.Context, script string) { | |
| 298 | - | if wv.destroyed.Load() || wv.browser == nil { | |
| 321 | + | if wv.destroyed.Load() { | |
| 299 | 322 | return | |
| 300 | 323 | } | |
| 301 | - | wv.browser.GetMainFrame().ExecuteJavaScript(script, "", 0) | |
| 324 | + | wv.mu.RLock() | |
| 325 | + | browser := wv.browser | |
| 326 | + | wv.mu.RUnlock() | |
| 327 | + | if browser == nil { | |
| 328 | + | return | |
| 329 | + | } | |
| 330 | + | browser.GetMainFrame().ExecuteJavaScript(script, "", 0) | |
| 302 | 331 | } | |
| 303 | 332 | ||
| 304 | 333 | // SetBackgroundColor is a no-op in Phase 1. | |
| @@ -321,8 +350,11 @@ func (wv *WebView) Destroy() { | |||
| 321 | 350 | if !wv.destroyed.CompareAndSwap(false, true) { | |
| 322 | 351 | return | |
| 323 | 352 | } | |
| 324 | - | if wv.host != nil { | |
| 325 | - | wv.host.CloseBrowser(1) | |
| 353 | + | wv.mu.RLock() | |
| 354 | + | host := wv.host | |
| 355 | + | wv.mu.RUnlock() | |
| 356 | + | if host != nil { | |
| 357 | + | host.CloseBrowser(1) | |
| 326 | 358 | } | |
| 327 | 359 | if wv.pipeline != nil { | |
| 328 | 360 | wv.pipeline.destroy() | |
| @@ -354,7 +386,9 @@ func (wv *WebView) updateURI(uri string) { | |||
| 354 | 386 | cb := wv.callbacks | |
| 355 | 387 | wv.mu.RUnlock() | |
| 356 | 388 | if cb != nil && cb.OnURIChanged != nil { | |
| 357 | - | cb.OnURIChanged(uri) | |
| 389 | + | wv.runOnGTK(func() { | |
| 390 | + | cb.OnURIChanged(uri) | |
| 391 | + | }) | |
| 358 | 392 | } | |
| 359 | 393 | } | |
| 360 | 394 | ||
| @@ -367,7 +401,9 @@ func (wv *WebView) updateTitle(title string) { | |||
| 367 | 401 | cb := wv.callbacks | |
| 368 | 402 | wv.mu.RUnlock() | |
| 369 | 403 | if cb != nil && cb.OnTitleChanged != nil { | |
| 370 | - | cb.OnTitleChanged(title) | |
| 404 | + | wv.runOnGTK(func() { | |
| 405 | + | cb.OnTitleChanged(title) | |
| 406 | + | }) | |
| 371 | 407 | } | |
| 372 | 408 | } | |
| 373 | 409 | ||
| @@ -380,7 +416,9 @@ func (wv *WebView) updateProgress(progress float64) { | |||
| 380 | 416 | cb := wv.callbacks | |
| 381 | 417 | wv.mu.RUnlock() | |
| 382 | 418 | if cb != nil && cb.OnProgressChanged != nil { | |
| 383 | - | cb.OnProgressChanged(progress) | |
| 419 | + | wv.runOnGTK(func() { | |
| 420 | + | cb.OnProgressChanged(progress) | |
| 421 | + | }) | |
| 384 | 422 | } | |
| 385 | 423 | } | |
| 386 | 424 | ||
| @@ -392,5 +430,20 @@ func (wv *WebView) updateLoadState(loading, back, fwd bool) { | |||
| 392 | 430 | wv.mu.Unlock() | |
| 393 | 431 | } | |
| 394 | 432 | ||
| 433 | + | func (wv *WebView) runOnGTK(fn func()) { | |
| 434 | + | if fn == nil { | |
| 435 | + | return | |
| 436 | + | } | |
| 437 | + | if wv.engine == nil || !wv.engine.multiThreadedMessageLoop { | |
| 438 | + | fn() | |
| 439 | + | return | |
| 440 | + | } | |
| 441 | + | ||
| 442 | + | cb := glib.SourceOnceFunc(func(_ uintptr) { | |
| 443 | + | fn() | |
| 444 | + | }) | |
| 445 | + | glib.IdleAddOnce(&cb, 0) | |
| 446 | + | } | |
| 447 | + | ||
| 395 | 448 | // suppress unused import for unsafe (used by NativeWidget via GoPointer). | |
| 396 | 449 | var _ = unsafe.Pointer(nil) | |
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | "unsafe" | 9 | "unsafe" | ||
| 10 | 10 | ||||
| 11 | purecef "github.com/bnema/purego-cef/cef" | 11 | purecef "github.com/bnema/purego-cef/cef" | ||
| 12 | + | "github.com/jwijenbergh/puregotk/v4/glib" | |||
| 12 | 13 | ||||
| 13 | "github.com/bnema/dumber/internal/application/port" | 14 | "github.com/bnema/dumber/internal/application/port" | ||
| 14 | ) | 15 | ) | ||
| @@ -92,12 +93,16 @@ func (wv *WebView) LoadURI(_ context.Context, uri string) error { | |||||
| 92 | if wv.destroyed.Load() { | 93 | if wv.destroyed.Load() { | ||
| 93 | return errDestroyed | 94 | return errDestroyed | ||
| 94 | } | 95 | } | ||
| 95 | - | if wv.browser == nil { | 96 | + | wv.mu.Lock() |
| 97 | + | browser := wv.browser | |||
| 98 | + | if browser == nil { | |||
| 96 | // Browser not yet created — queue the URI for OnAfterCreated. | 99 | // Browser not yet created — queue the URI for OnAfterCreated. | ||
| 97 | wv.pendingURI = uri | 100 | wv.pendingURI = uri | ||
| 101 | + | wv.mu.Unlock() | |||
| 98 | return nil | 102 | return nil | ||
| 99 | } | 103 | } | ||
| 100 | - | wv.browser.GetMainFrame().LoadURL(uri) | 104 | + | wv.mu.Unlock() |
| 105 | + | browser.GetMainFrame().LoadURL(uri) | |||
| 101 | return nil | 106 | return nil | ||
| 102 | } | 107 | } | ||
| 103 | 108 | ||||
| @@ -106,11 +111,14 @@ func (wv *WebView) LoadHTML(_ context.Context, content, _ string) error { | |||||
| 106 | if wv.destroyed.Load() { | 111 | if wv.destroyed.Load() { | ||
| 107 | return errDestroyed | 112 | return errDestroyed | ||
| 108 | } | 113 | } | ||
| 109 | - | if wv.browser == nil { | 114 | + | wv.mu.RLock() |
| 115 | + | browser := wv.browser | |||
| 116 | + | wv.mu.RUnlock() | |||
| 117 | + | if browser == nil { | |||
| 110 | return errNoBrowser | 118 | return errNoBrowser | ||
| 111 | } | 119 | } | ||
| 112 | dataURL := "data:text/html;base64," + base64.StdEncoding.EncodeToString([]byte(content)) | 120 | dataURL := "data:text/html;base64," + base64.StdEncoding.EncodeToString([]byte(content)) | ||
| 113 | - | wv.browser.GetMainFrame().LoadURL(dataURL) | 121 | + | browser.GetMainFrame().LoadURL(dataURL) |
| 114 | return nil | 122 | return nil | ||
| 115 | } | 123 | } | ||
| 116 | 124 | ||||
| @@ -119,10 +127,13 @@ func (wv *WebView) Reload(_ context.Context) error { | |||||
| 119 | if wv.destroyed.Load() { | 127 | if wv.destroyed.Load() { | ||
| 120 | return errDestroyed | 128 | return errDestroyed | ||
| 121 | } | 129 | } | ||
| 122 | - | if wv.browser == nil { | 130 | + | wv.mu.RLock() |
| 131 | + | browser := wv.browser | |||
| 132 | + | wv.mu.RUnlock() | |||
| 133 | + | if browser == nil { | |||
| 123 | return errNoBrowser | 134 | return errNoBrowser | ||
| 124 | } | 135 | } | ||
| 125 | - | wv.browser.Reload() | 136 | + | browser.Reload() |
| 126 | return nil | 137 | return nil | ||
| 127 | } | 138 | } | ||
| 128 | 139 | ||||
| @@ -131,10 +142,13 @@ func (wv *WebView) ReloadBypassCache(_ context.Context) error { | |||||
| 131 | if wv.destroyed.Load() { | 142 | if wv.destroyed.Load() { | ||
| 132 | return errDestroyed | 143 | return errDestroyed | ||
| 133 | } | 144 | } | ||
| 134 | - | if wv.browser == nil { | 145 | + | wv.mu.RLock() |
| 146 | + | browser := wv.browser | |||
| 147 | + | wv.mu.RUnlock() | |||
| 148 | + | if browser == nil { | |||
| 135 | return errNoBrowser | 149 | return errNoBrowser | ||
| 136 | } | 150 | } | ||
| 137 | - | wv.browser.ReloadIgnoreCache() | 151 | + | browser.ReloadIgnoreCache() |
| 138 | return nil | 152 | return nil | ||
| 139 | } | 153 | } | ||
| 140 | 154 | ||||
| @@ -143,10 +157,13 @@ func (wv *WebView) Stop(_ context.Context) error { | |||||
| 143 | if wv.destroyed.Load() { | 157 | if wv.destroyed.Load() { | ||
| 144 | return errDestroyed | 158 | return errDestroyed | ||
| 145 | } | 159 | } | ||
| 146 | - | if wv.browser == nil { | 160 | + | wv.mu.RLock() |
| 161 | + | browser := wv.browser | |||
| 162 | + | wv.mu.RUnlock() | |||
| 163 | + | if browser == nil { | |||
| 147 | return errNoBrowser | 164 | return errNoBrowser | ||
| 148 | } | 165 | } | ||
| 149 | - | wv.browser.StopLoad() | 166 | + | browser.StopLoad() |
| 150 | return nil | 167 | return nil | ||
| 151 | } | 168 | } | ||
| 152 | 169 | ||||
| @@ -155,10 +172,13 @@ func (wv *WebView) GoBack(_ context.Context) error { | |||||
| 155 | if wv.destroyed.Load() { | 172 | if wv.destroyed.Load() { | ||
| 156 | return errDestroyed | 173 | return errDestroyed | ||
| 157 | } | 174 | } | ||
| 158 | - | if wv.browser == nil { | 175 | + | wv.mu.RLock() |
| 176 | + | browser := wv.browser | |||
| 177 | + | wv.mu.RUnlock() | |||
| 178 | + | if browser == nil { | |||
| 159 | return errNoBrowser | 179 | return errNoBrowser | ||
| 160 | } | 180 | } | ||
| 161 | - | wv.browser.GoBack() | 181 | + | browser.GoBack() |
| 162 | return nil | 182 | return nil | ||
| 163 | } | 183 | } | ||
| 164 | 184 | ||||
| @@ -167,10 +187,13 @@ func (wv *WebView) GoForward(_ context.Context) error { | |||||
| 167 | if wv.destroyed.Load() { | 187 | if wv.destroyed.Load() { | ||
| 168 | return errDestroyed | 188 | return errDestroyed | ||
| 169 | } | 189 | } | ||
| 170 | - | if wv.browser == nil { | 190 | + | wv.mu.RLock() |
| 191 | + | browser := wv.browser | |||
| 192 | + | wv.mu.RUnlock() | |||
| 193 | + | if browser == nil { | |||
| 171 | return errNoBrowser | 194 | return errNoBrowser | ||
| 172 | } | 195 | } | ||
| 173 | - | wv.browser.GoForward() | 196 | + | browser.GoForward() |
| 174 | return nil | 197 | return nil | ||
| 175 | } | 198 | } | ||
| 176 | 199 | ||||
| @@ -295,10 +318,16 @@ func (wv *WebView) SetCallbacks(cb *port.WebViewCallbacks) { | |||||
| 295 | 318 | ||||
| 296 | // RunJavaScript executes a script in the main world. Fire-and-forget. | 319 | // RunJavaScript executes a script in the main world. Fire-and-forget. | ||
| 297 | func (wv *WebView) RunJavaScript(_ context.Context, script string) { | 320 | func (wv *WebView) RunJavaScript(_ context.Context, script string) { | ||
| 298 | - | if wv.destroyed.Load() || wv.browser == nil { | 321 | + | if wv.destroyed.Load() { |
| 299 | return | 322 | return | ||
| 300 | } | 323 | } | ||
| 301 | - | wv.browser.GetMainFrame().ExecuteJavaScript(script, "", 0) | 324 | + | wv.mu.RLock() |
| 325 | + | browser := wv.browser | |||
| 326 | + | wv.mu.RUnlock() | |||
| 327 | + | if browser == nil { | |||
| 328 | + | return | |||
| 329 | + | } | |||
| 330 | + | browser.GetMainFrame().ExecuteJavaScript(script, "", 0) | |||
| 302 | } | 331 | } | ||
| 303 | 332 | ||||
| 304 | // SetBackgroundColor is a no-op in Phase 1. | 333 | // SetBackgroundColor is a no-op in Phase 1. | ||
| @@ -321,8 +350,11 @@ func (wv *WebView) Destroy() { | |||||
| 321 | if !wv.destroyed.CompareAndSwap(false, true) { | 350 | if !wv.destroyed.CompareAndSwap(false, true) { | ||
| 322 | return | 351 | return | ||
| 323 | } | 352 | } | ||
| 324 | - | if wv.host != nil { | 353 | + | wv.mu.RLock() |
| 325 | - | wv.host.CloseBrowser(1) | 354 | + | host := wv.host |
| 355 | + | wv.mu.RUnlock() | |||
| 356 | + | if host != nil { | |||
| 357 | + | host.CloseBrowser(1) | |||
| 326 | } | 358 | } | ||
| 327 | if wv.pipeline != nil { | 359 | if wv.pipeline != nil { | ||
| 328 | wv.pipeline.destroy() | 360 | wv.pipeline.destroy() | ||
| @@ -354,7 +386,9 @@ func (wv *WebView) updateURI(uri string) { | |||||
| 354 | cb := wv.callbacks | 386 | cb := wv.callbacks | ||
| 355 | wv.mu.RUnlock() | 387 | wv.mu.RUnlock() | ||
| 356 | if cb != nil && cb.OnURIChanged != nil { | 388 | if cb != nil && cb.OnURIChanged != nil { | ||
| 357 | - | cb.OnURIChanged(uri) | 389 | + | wv.runOnGTK(func() { |
| 390 | + | cb.OnURIChanged(uri) | |||
| 391 | + | }) | |||
| 358 | } | 392 | } | ||
| 359 | } | 393 | } | ||
| 360 | 394 | ||||
| @@ -367,7 +401,9 @@ func (wv *WebView) updateTitle(title string) { | |||||
| 367 | cb := wv.callbacks | 401 | cb := wv.callbacks | ||
| 368 | wv.mu.RUnlock() | 402 | wv.mu.RUnlock() | ||
| 369 | if cb != nil && cb.OnTitleChanged != nil { | 403 | if cb != nil && cb.OnTitleChanged != nil { | ||
| 370 | - | cb.OnTitleChanged(title) | 404 | + | wv.runOnGTK(func() { |
| 405 | + | cb.OnTitleChanged(title) | |||
| 406 | + | }) | |||
| 371 | } | 407 | } | ||
| 372 | } | 408 | } | ||
| 373 | 409 | ||||
| @@ -380,7 +416,9 @@ func (wv *WebView) updateProgress(progress float64) { | |||||
| 380 | cb := wv.callbacks | 416 | cb := wv.callbacks | ||
| 381 | wv.mu.RUnlock() | 417 | wv.mu.RUnlock() | ||
| 382 | if cb != nil && cb.OnProgressChanged != nil { | 418 | if cb != nil && cb.OnProgressChanged != nil { | ||
| 383 | - | cb.OnProgressChanged(progress) | 419 | + | wv.runOnGTK(func() { |
| 420 | + | cb.OnProgressChanged(progress) | |||
| 421 | + | }) | |||
| 384 | } | 422 | } | ||
| 385 | } | 423 | } | ||
| 386 | 424 | ||||
| @@ -392,5 +430,20 @@ func (wv *WebView) updateLoadState(loading, back, fwd bool) { | |||||
| 392 | wv.mu.Unlock() | 430 | wv.mu.Unlock() | ||
| 393 | } | 431 | } | ||
| 394 | 432 | ||||
| 433 | + | func (wv *WebView) runOnGTK(fn func()) { | |||
| 434 | + | if fn == nil { | |||
| 435 | + | return | |||
| 436 | + | } | |||
| 437 | + | if wv.engine == nil || !wv.engine.multiThreadedMessageLoop { | |||
| 438 | + | fn() | |||
| 439 | + | return | |||
| 440 | + | } | |||
| 441 | + | ||||
| 442 | + | cb := glib.SourceOnceFunc(func(_ uintptr) { | |||
| 443 | + | fn() | |||
| 444 | + | }) | |||
| 445 | + | glib.IdleAddOnce(&cb, 0) | |||
| 446 | + | } | |||
| 447 | + | ||||
| 395 | // suppress unused import for unsafe (used by NativeWidget via GoPointer). | 448 | // suppress unused import for unsafe (used by NativeWidget via GoPointer). | ||
| 396 | var _ = unsafe.Pointer(nil) | 449 | var _ = unsafe.Pointer(nil) | ||
M internal/infrastructure/cef/webview_handlers.go +60 -16
| @@ -99,15 +99,31 @@ func (h *handlerSet) OnPopupShow(_ purecef.Browser, _ int32) {} | |||
| 99 | 99 | func (h *handlerSet) OnPopupSize(_ purecef.Browser, _ *purecef.Rect) {} | |
| 100 | 100 | ||
| 101 | 101 | // OnPaint receives the BGRA pixel buffer from CEF and forwards dirty rects | |
| 102 | - | // to the render pipeline for GPU upload. | |
| 102 | + | // to the render pipeline for GPU upload. With a multi-threaded CEF UI loop we | |
| 103 | + | // must first copy the transient CEF buffer before hopping back to GTK. | |
| 103 | 104 | func (h *handlerSet) OnPaint( | |
| 104 | 105 | _ purecef.Browser, _ purecef.PaintElementType, | |
| 105 | 106 | dirtyRects []purecef.Rect, buffer unsafe.Pointer, width, height int32, | |
| 106 | 107 | ) { | |
| 108 | + | if buffer == nil || width <= 0 || height <= 0 { | |
| 109 | + | return | |
| 110 | + | } | |
| 107 | 111 | rects := make([]rect, len(dirtyRects)) | |
| 108 | 112 | for i, dr := range dirtyRects { | |
| 109 | 113 | rects[i] = rect{X: dr.X, Y: dr.Y, Width: dr.Width, Height: dr.Height} | |
| 110 | 114 | } | |
| 115 | + | if h.wv.engine != nil && h.wv.engine.multiThreadedMessageLoop { | |
| 116 | + | bufSize := int(width) * int(height) * 4 | |
| 117 | + | if bufSize <= 0 { | |
| 118 | + | return | |
| 119 | + | } | |
| 120 | + | pixels := make([]byte, bufSize) | |
| 121 | + | copy(pixels, unsafe.Slice((*byte)(buffer), bufSize)) | |
| 122 | + | h.wv.runOnGTK(func() { | |
| 123 | + | h.wv.pipeline.handlePaint(unsafe.Pointer(&pixels[0]), width, height, rects) | |
| 124 | + | }) | |
| 125 | + | return | |
| 126 | + | } | |
| 111 | 127 | h.wv.pipeline.handlePaint(buffer, width, height, rects) | |
| 112 | 128 | } | |
| 113 | 129 | ||
| @@ -170,10 +186,14 @@ func (h *handlerSet) OnFullscreenModeChange(_ purecef.Browser, fullscreen int32) | |||
| 170 | 186 | return | |
| 171 | 187 | } | |
| 172 | 188 | if entering && cb.OnEnterFullscreen != nil { | |
| 173 | - | cb.OnEnterFullscreen() | |
| 189 | + | h.wv.runOnGTK(func() { | |
| 190 | + | cb.OnEnterFullscreen() | |
| 191 | + | }) | |
| 174 | 192 | } | |
| 175 | 193 | if !entering && cb.OnLeaveFullscreen != nil { | |
| 176 | - | cb.OnLeaveFullscreen() | |
| 194 | + | h.wv.runOnGTK(func() { | |
| 195 | + | cb.OnLeaveFullscreen() | |
| 196 | + | }) | |
| 177 | 197 | } | |
| 178 | 198 | } | |
| 179 | 199 | ||
| @@ -185,7 +205,9 @@ func (h *handlerSet) OnStatusMessage(_ purecef.Browser, value string) { | |||
| 185 | 205 | cb := h.wv.callbacks | |
| 186 | 206 | h.wv.mu.RUnlock() | |
| 187 | 207 | if cb != nil && cb.OnLinkHover != nil { | |
| 188 | - | cb.OnLinkHover(value) | |
| 208 | + | h.wv.runOnGTK(func() { | |
| 209 | + | cb.OnLinkHover(value) | |
| 210 | + | }) | |
| 189 | 211 | } | |
| 190 | 212 | } | |
| 191 | 213 | ||
| @@ -202,7 +224,9 @@ func (h *handlerSet) OnLoadingProgressChange(_ purecef.Browser, progress float64 | |||
| 202 | 224 | ||
| 203 | 225 | func (h *handlerSet) OnCursorChange(_ purecef.Browser, _ uintptr, cursorType purecef.CursorType, _ *purecef.CursorInfo) int32 { | |
| 204 | 226 | name := cefCursorToGDKName(cursorType) | |
| 205 | - | h.wv.pipeline.glArea.SetCursorFromName(&name) | |
| 227 | + | h.wv.runOnGTK(func() { | |
| 228 | + | h.wv.pipeline.glArea.SetCursorFromName(&name) | |
| 229 | + | }) | |
| 206 | 230 | return 1 // handled | |
| 207 | 231 | } | |
| 208 | 232 | ||
| @@ -226,9 +250,13 @@ func (h *handlerSet) OnLoadingStateChange(_ purecef.Browser, isloading, cangobac | |||
| 226 | 250 | h.wv.mu.RUnlock() | |
| 227 | 251 | if cb != nil && cb.OnLoadChanged != nil { | |
| 228 | 252 | if loading { | |
| 229 | - | cb.OnLoadChanged(port.LoadStarted) | |
| 253 | + | h.wv.runOnGTK(func() { | |
| 254 | + | cb.OnLoadChanged(port.LoadStarted) | |
| 255 | + | }) | |
| 230 | 256 | } else { | |
| 231 | - | cb.OnLoadChanged(port.LoadFinished) | |
| 257 | + | h.wv.runOnGTK(func() { | |
| 258 | + | cb.OnLoadChanged(port.LoadFinished) | |
| 259 | + | }) | |
| 232 | 260 | } | |
| 233 | 261 | } | |
| 234 | 262 | } | |
| @@ -245,7 +273,9 @@ func (h *handlerSet) OnLoadStart(_ purecef.Browser, frame purecef.Frame, _ purec | |||
| 245 | 273 | cb := h.wv.callbacks | |
| 246 | 274 | h.wv.mu.RUnlock() | |
| 247 | 275 | if cb != nil && cb.OnLoadChanged != nil { | |
| 248 | - | cb.OnLoadChanged(port.LoadCommitted) | |
| 276 | + | h.wv.runOnGTK(func() { | |
| 277 | + | cb.OnLoadChanged(port.LoadCommitted) | |
| 278 | + | }) | |
| 249 | 279 | } | |
| 250 | 280 | } | |
| 251 | 281 | ||
| @@ -268,10 +298,14 @@ func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatu | |||
| 268 | 298 | h.wv.mu.RUnlock() | |
| 269 | 299 | if cb != nil { | |
| 270 | 300 | if cb.OnLoadChanged != nil { | |
| 271 | - | cb.OnLoadChanged(port.LoadFinished) | |
| 301 | + | h.wv.runOnGTK(func() { | |
| 302 | + | cb.OnLoadChanged(port.LoadFinished) | |
| 303 | + | }) | |
| 272 | 304 | } | |
| 273 | 305 | if cb.OnProgressChanged != nil { | |
| 274 | - | cb.OnProgressChanged(1.0) | |
| 306 | + | h.wv.runOnGTK(func() { | |
| 307 | + | cb.OnProgressChanged(1.0) | |
| 308 | + | }) | |
| 275 | 309 | } | |
| 276 | 310 | } | |
| 277 | 311 | } | |
| @@ -316,13 +350,19 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||
| 316 | 350 | if h.wv.engine != nil { | |
| 317 | 351 | h.wv.engine.recordBrowserAfterCreated(browser) | |
| 318 | 352 | } | |
| 353 | + | host := browser.GetHost() | |
| 354 | + | ||
| 355 | + | h.wv.mu.Lock() | |
| 319 | 356 | h.wv.browser = browser | |
| 320 | - | h.wv.host = browser.GetHost() | |
| 321 | - | h.wv.input.setHost(h.wv.host) | |
| 357 | + | h.wv.host = host | |
| 358 | + | uri := h.wv.pendingURI | |
| 359 | + | h.wv.pendingURI = "" | |
| 360 | + | h.wv.mu.Unlock() | |
| 361 | + | ||
| 362 | + | h.wv.input.setHost(host) | |
| 322 | 363 | ||
| 323 | 364 | // Replay any navigation that was requested before the browser existed. | |
| 324 | - | if uri := h.wv.pendingURI; uri != "" { | |
| 325 | - | h.wv.pendingURI = "" | |
| 365 | + | if uri != "" { | |
| 326 | 366 | browser.GetMainFrame().LoadURL(uri) | |
| 327 | 367 | } | |
| 328 | 368 | } | |
| @@ -338,7 +378,9 @@ func (h *handlerSet) OnBeforeClose(_ purecef.Browser) { | |||
| 338 | 378 | cb := h.wv.callbacks | |
| 339 | 379 | h.wv.mu.RUnlock() | |
| 340 | 380 | if cb != nil && cb.OnClose != nil { | |
| 341 | - | cb.OnClose() | |
| 381 | + | h.wv.runOnGTK(func() { | |
| 382 | + | cb.OnClose() | |
| 383 | + | }) | |
| 342 | 384 | } | |
| 343 | 385 | } | |
| 344 | 386 | ||
| @@ -424,7 +466,9 @@ func (h *handlerSet) OnRenderProcessTerminated(_ purecef.Browser, status purecef | |||
| 424 | 466 | uri := h.wv.uri | |
| 425 | 467 | h.wv.mu.RUnlock() | |
| 426 | 468 | if cb != nil && cb.OnWebProcessTerminated != nil { | |
| 427 | - | cb.OnWebProcessTerminated(reason, label, uri) | |
| 469 | + | h.wv.runOnGTK(func() { | |
| 470 | + | cb.OnWebProcessTerminated(reason, label, uri) | |
| 471 | + | }) | |
| 428 | 472 | } | |
| 429 | 473 | } | |
| 430 | 474 | ||
| @@ -99,15 +99,31 @@ func (h *handlerSet) OnPopupShow(_ purecef.Browser, _ int32) {} | |||||
| 99 | func (h *handlerSet) OnPopupSize(_ purecef.Browser, _ *purecef.Rect) {} | 99 | func (h *handlerSet) OnPopupSize(_ purecef.Browser, _ *purecef.Rect) {} | ||
| 100 | 100 | ||||
| 101 | // OnPaint receives the BGRA pixel buffer from CEF and forwards dirty rects | 101 | // OnPaint receives the BGRA pixel buffer from CEF and forwards dirty rects | ||
| 102 | - | // to the render pipeline for GPU upload. | 102 | + | // to the render pipeline for GPU upload. With a multi-threaded CEF UI loop we |
| 103 | + | // must first copy the transient CEF buffer before hopping back to GTK. | |||
| 103 | func (h *handlerSet) OnPaint( | 104 | func (h *handlerSet) OnPaint( | ||
| 104 | _ purecef.Browser, _ purecef.PaintElementType, | 105 | _ purecef.Browser, _ purecef.PaintElementType, | ||
| 105 | dirtyRects []purecef.Rect, buffer unsafe.Pointer, width, height int32, | 106 | dirtyRects []purecef.Rect, buffer unsafe.Pointer, width, height int32, | ||
| 106 | ) { | 107 | ) { | ||
| 108 | + | if buffer == nil || width <= 0 || height <= 0 { | |||
| 109 | + | return | |||
| 110 | + | } | |||
| 107 | rects := make([]rect, len(dirtyRects)) | 111 | rects := make([]rect, len(dirtyRects)) | ||
| 108 | for i, dr := range dirtyRects { | 112 | for i, dr := range dirtyRects { | ||
| 109 | rects[i] = rect{X: dr.X, Y: dr.Y, Width: dr.Width, Height: dr.Height} | 113 | rects[i] = rect{X: dr.X, Y: dr.Y, Width: dr.Width, Height: dr.Height} | ||
| 110 | } | 114 | } | ||
| 115 | + | if h.wv.engine != nil && h.wv.engine.multiThreadedMessageLoop { | |||
| 116 | + | bufSize := int(width) * int(height) * 4 | |||
| 117 | + | if bufSize <= 0 { | |||
| 118 | + | return | |||
| 119 | + | } | |||
| 120 | + | pixels := make([]byte, bufSize) | |||
| 121 | + | copy(pixels, unsafe.Slice((*byte)(buffer), bufSize)) | |||
| 122 | + | h.wv.runOnGTK(func() { | |||
| 123 | + | h.wv.pipeline.handlePaint(unsafe.Pointer(&pixels[0]), width, height, rects) | |||
| 124 | + | }) | |||
| 125 | + | return | |||
| 126 | + | } | |||
| 111 | h.wv.pipeline.handlePaint(buffer, width, height, rects) | 127 | h.wv.pipeline.handlePaint(buffer, width, height, rects) | ||
| 112 | } | 128 | } | ||
| 113 | 129 | ||||
| @@ -170,10 +186,14 @@ func (h *handlerSet) OnFullscreenModeChange(_ purecef.Browser, fullscreen int32) | |||||
| 170 | return | 186 | return | ||
| 171 | } | 187 | } | ||
| 172 | if entering && cb.OnEnterFullscreen != nil { | 188 | if entering && cb.OnEnterFullscreen != nil { | ||
| 173 | - | cb.OnEnterFullscreen() | 189 | + | h.wv.runOnGTK(func() { |
| 190 | + | cb.OnEnterFullscreen() | |||
| 191 | + | }) | |||
| 174 | } | 192 | } | ||
| 175 | if !entering && cb.OnLeaveFullscreen != nil { | 193 | if !entering && cb.OnLeaveFullscreen != nil { | ||
| 176 | - | cb.OnLeaveFullscreen() | 194 | + | h.wv.runOnGTK(func() { |
| 195 | + | cb.OnLeaveFullscreen() | |||
| 196 | + | }) | |||
| 177 | } | 197 | } | ||
| 178 | } | 198 | } | ||
| 179 | 199 | ||||
| @@ -185,7 +205,9 @@ func (h *handlerSet) OnStatusMessage(_ purecef.Browser, value string) { | |||||
| 185 | cb := h.wv.callbacks | 205 | cb := h.wv.callbacks | ||
| 186 | h.wv.mu.RUnlock() | 206 | h.wv.mu.RUnlock() | ||
| 187 | if cb != nil && cb.OnLinkHover != nil { | 207 | if cb != nil && cb.OnLinkHover != nil { | ||
| 188 | - | cb.OnLinkHover(value) | 208 | + | h.wv.runOnGTK(func() { |
| 209 | + | cb.OnLinkHover(value) | |||
| 210 | + | }) | |||
| 189 | } | 211 | } | ||
| 190 | } | 212 | } | ||
| 191 | 213 | ||||
| @@ -202,7 +224,9 @@ func (h *handlerSet) OnLoadingProgressChange(_ purecef.Browser, progress float64 | |||||
| 202 | 224 | ||||
| 203 | func (h *handlerSet) OnCursorChange(_ purecef.Browser, _ uintptr, cursorType purecef.CursorType, _ *purecef.CursorInfo) int32 { | 225 | func (h *handlerSet) OnCursorChange(_ purecef.Browser, _ uintptr, cursorType purecef.CursorType, _ *purecef.CursorInfo) int32 { | ||
| 204 | name := cefCursorToGDKName(cursorType) | 226 | name := cefCursorToGDKName(cursorType) | ||
| 205 | - | h.wv.pipeline.glArea.SetCursorFromName(&name) | 227 | + | h.wv.runOnGTK(func() { |
| 228 | + | h.wv.pipeline.glArea.SetCursorFromName(&name) | |||
| 229 | + | }) | |||
| 206 | return 1 // handled | 230 | return 1 // handled | ||
| 207 | } | 231 | } | ||
| 208 | 232 | ||||
| @@ -226,9 +250,13 @@ func (h *handlerSet) OnLoadingStateChange(_ purecef.Browser, isloading, cangobac | |||||
| 226 | h.wv.mu.RUnlock() | 250 | h.wv.mu.RUnlock() | ||
| 227 | if cb != nil && cb.OnLoadChanged != nil { | 251 | if cb != nil && cb.OnLoadChanged != nil { | ||
| 228 | if loading { | 252 | if loading { | ||
| 229 | - | cb.OnLoadChanged(port.LoadStarted) | 253 | + | h.wv.runOnGTK(func() { |
| 254 | + | cb.OnLoadChanged(port.LoadStarted) | |||
| 255 | + | }) | |||
| 230 | } else { | 256 | } else { | ||
| 231 | - | cb.OnLoadChanged(port.LoadFinished) | 257 | + | h.wv.runOnGTK(func() { |
| 258 | + | cb.OnLoadChanged(port.LoadFinished) | |||
| 259 | + | }) | |||
| 232 | } | 260 | } | ||
| 233 | } | 261 | } | ||
| 234 | } | 262 | } | ||
| @@ -245,7 +273,9 @@ func (h *handlerSet) OnLoadStart(_ purecef.Browser, frame purecef.Frame, _ purec | |||||
| 245 | cb := h.wv.callbacks | 273 | cb := h.wv.callbacks | ||
| 246 | h.wv.mu.RUnlock() | 274 | h.wv.mu.RUnlock() | ||
| 247 | if cb != nil && cb.OnLoadChanged != nil { | 275 | if cb != nil && cb.OnLoadChanged != nil { | ||
| 248 | - | cb.OnLoadChanged(port.LoadCommitted) | 276 | + | h.wv.runOnGTK(func() { |
| 277 | + | cb.OnLoadChanged(port.LoadCommitted) | |||
| 278 | + | }) | |||
| 249 | } | 279 | } | ||
| 250 | } | 280 | } | ||
| 251 | 281 | ||||
| @@ -268,10 +298,14 @@ func (h *handlerSet) OnLoadEnd(_ purecef.Browser, frame purecef.Frame, httpStatu | |||||
| 268 | h.wv.mu.RUnlock() | 298 | h.wv.mu.RUnlock() | ||
| 269 | if cb != nil { | 299 | if cb != nil { | ||
| 270 | if cb.OnLoadChanged != nil { | 300 | if cb.OnLoadChanged != nil { | ||
| 271 | - | cb.OnLoadChanged(port.LoadFinished) | 301 | + | h.wv.runOnGTK(func() { |
| 302 | + | cb.OnLoadChanged(port.LoadFinished) | |||
| 303 | + | }) | |||
| 272 | } | 304 | } | ||
| 273 | if cb.OnProgressChanged != nil { | 305 | if cb.OnProgressChanged != nil { | ||
| 274 | - | cb.OnProgressChanged(1.0) | 306 | + | h.wv.runOnGTK(func() { |
| 307 | + | cb.OnProgressChanged(1.0) | |||
| 308 | + | }) | |||
| 275 | } | 309 | } | ||
| 276 | } | 310 | } | ||
| 277 | } | 311 | } | ||
| @@ -316,13 +350,19 @@ func (h *handlerSet) OnAfterCreated(browser purecef.Browser) { | |||||
| 316 | if h.wv.engine != nil { | 350 | if h.wv.engine != nil { | ||
| 317 | h.wv.engine.recordBrowserAfterCreated(browser) | 351 | h.wv.engine.recordBrowserAfterCreated(browser) | ||
| 318 | } | 352 | } | ||
| 353 | + | host := browser.GetHost() | |||
| 354 | + | ||||
| 355 | + | h.wv.mu.Lock() | |||
| 319 | h.wv.browser = browser | 356 | h.wv.browser = browser | ||
| 320 | - | h.wv.host = browser.GetHost() | 357 | + | h.wv.host = host |
| 321 | - | h.wv.input.setHost(h.wv.host) | 358 | + | uri := h.wv.pendingURI |
| 359 | + | h.wv.pendingURI = "" | |||
| 360 | + | h.wv.mu.Unlock() | |||
| 361 | + | ||||
| 362 | + | h.wv.input.setHost(host) | |||
| 322 | 363 | ||||
| 323 | // Replay any navigation that was requested before the browser existed. | 364 | // Replay any navigation that was requested before the browser existed. | ||
| 324 | - | if uri := h.wv.pendingURI; uri != "" { | 365 | + | if uri != "" { |
| 325 | - | h.wv.pendingURI = "" | |||
| 326 | browser.GetMainFrame().LoadURL(uri) | 366 | browser.GetMainFrame().LoadURL(uri) | ||
| 327 | } | 367 | } | ||
| 328 | } | 368 | } | ||
| @@ -338,7 +378,9 @@ func (h *handlerSet) OnBeforeClose(_ purecef.Browser) { | |||||
| 338 | cb := h.wv.callbacks | 378 | cb := h.wv.callbacks | ||
| 339 | h.wv.mu.RUnlock() | 379 | h.wv.mu.RUnlock() | ||
| 340 | if cb != nil && cb.OnClose != nil { | 380 | if cb != nil && cb.OnClose != nil { | ||
| 341 | - | cb.OnClose() | 381 | + | h.wv.runOnGTK(func() { |
| 382 | + | cb.OnClose() | |||
| 383 | + | }) | |||
| 342 | } | 384 | } | ||
| 343 | } | 385 | } | ||
| 344 | 386 | ||||
| @@ -424,7 +466,9 @@ func (h *handlerSet) OnRenderProcessTerminated(_ purecef.Browser, status purecef | |||||
| 424 | uri := h.wv.uri | 466 | uri := h.wv.uri | ||
| 425 | h.wv.mu.RUnlock() | 467 | h.wv.mu.RUnlock() | ||
| 426 | if cb != nil && cb.OnWebProcessTerminated != nil { | 468 | if cb != nil && cb.OnWebProcessTerminated != nil { | ||
| 427 | - | cb.OnWebProcessTerminated(reason, label, uri) | 469 | + | h.wv.runOnGTK(func() { |
| 470 | + | cb.OnWebProcessTerminated(reason, label, uri) | |||
| 471 | + | }) | |||
| 428 | } | 472 | } | ||
| 429 | } | 473 | } | ||
| 430 | 474 | ||||