Merge pull request #358 from bnema/fix/cef-canvas-background
fix(cef): keep browser canvas white, move anti-flash to presentation layer
80 files · +1985 -2004
M go.mod +1 -1
| @@ -6,7 +6,7 @@ require ( | |||
| 6 | 6 | github.com/a-h/templ v0.3.1020 | |
| 7 | 7 | github.com/andybalholm/brotli v1.2.2 | |
| 8 | 8 | github.com/bnema/purego v0.11.0-bnema.4 | |
| 9 | - | github.com/bnema/purego-cef v0.13.4 | |
| 9 | + | github.com/bnema/purego-cef v0.14.1 | |
| 10 | 10 | github.com/bnema/purego-cef2gtk v0.8.5 | |
| 11 | 11 | github.com/bnema/purego-pipewire v0.1.5 | |
| 12 | 12 | github.com/bnema/purego-sqlite v0.1.4 | |
| @@ -6,7 +6,7 @@ require ( | |||||
| 6 | github.com/a-h/templ v0.3.1020 | 6 | github.com/a-h/templ v0.3.1020 | ||
| 7 | github.com/andybalholm/brotli v1.2.2 | 7 | github.com/andybalholm/brotli v1.2.2 | ||
| 8 | github.com/bnema/purego v0.11.0-bnema.4 | 8 | github.com/bnema/purego v0.11.0-bnema.4 | ||
| 9 | - | github.com/bnema/purego-cef v0.13.4 | 9 | + | github.com/bnema/purego-cef v0.14.1 |
| 10 | github.com/bnema/purego-cef2gtk v0.8.5 | 10 | github.com/bnema/purego-cef2gtk v0.8.5 | ||
| 11 | github.com/bnema/purego-pipewire v0.1.5 | 11 | github.com/bnema/purego-pipewire v0.1.5 | ||
| 12 | github.com/bnema/purego-sqlite v0.1.4 | 12 | github.com/bnema/purego-sqlite v0.1.4 | ||
M go.sum +2 -2 generated file: collapsed
A internal/infrastructure/cef/background_guard.go +106 -0
| @@ -0,0 +1,106 @@ | |||
| 1 | + | package cef | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/puregotk/v4/gdk" | |
| 7 | + | "github.com/bnema/puregotk/v4/gtk" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | // opaqueWhiteBackground is Chromium's standard windowless canvas fallback. It is | |
| 11 | + | // baked into every BrowserSettings.BackgroundColor so pages that paint nothing | |
| 12 | + | // get an opaque white canvas (never a theme-dark one). A zero value must not be | |
| 13 | + | // used: in windowless/OSR mode a zero-alpha background enables transparent | |
| 14 | + | // painting, which this engine does not want. | |
| 15 | + | const opaqueWhiteBackground uint32 = 0xFFFFFFFF | |
| 16 | + | ||
| 17 | + | // flashGuardCSSClassPrefix scopes each WebView's transient background provider to | |
| 18 | + | // its own render widget so guards from different tabs never collide. | |
| 19 | + | const flashGuardCSSClassPrefix = "dumber-cef-flash-guard-" | |
| 20 | + | ||
| 21 | + | // colorByteMask isolates a single 8-bit channel from a packed ARGB value. | |
| 22 | + | const colorByteMask = 0xFF | |
| 23 | + | ||
| 24 | + | // applyBackgroundFlashGuard paints the theme background color behind the CEF | |
| 25 | + | // render widget until the first frame is presented. It is the presentation-layer | |
| 26 | + | // replacement for the former dark BrowserSettings.BackgroundColor: Chromium's | |
| 27 | + | // canvas is now always opaque white, so the anti-white-flash lives here instead. | |
| 28 | + | // The bridge widget (a GtkPicture/GtkGLArea) renders its CSS background until an | |
| 29 | + | // opaque frame texture covers it, at which point markFirstFramePainted tears the | |
| 30 | + | // guard down. Must run on the GTK main thread. | |
| 31 | + | func (wv *WebView) applyBackgroundFlashGuard(widget *gtk.Widget) { | |
| 32 | + | if wv == nil || widget == nil { | |
| 33 | + | return | |
| 34 | + | } | |
| 35 | + | argb := wv.backgroundColor | |
| 36 | + | if argb == 0 { | |
| 37 | + | return // No theme color configured; nothing to guard against. | |
| 38 | + | } | |
| 39 | + | display := gdk.DisplayGetDefault() | |
| 40 | + | if display == nil { | |
| 41 | + | return | |
| 42 | + | } | |
| 43 | + | ||
| 44 | + | a := float64((argb>>24)&colorByteMask) / colorScale | |
| 45 | + | r := (argb >> 16) & colorByteMask | |
| 46 | + | g := (argb >> 8) & colorByteMask | |
| 47 | + | b := argb & colorByteMask | |
| 48 | + | class := fmt.Sprintf("%s%d", flashGuardCSSClassPrefix, uint64(wv.id)) | |
| 49 | + | css := fmt.Sprintf(".%s { background-color: rgba(%d, %d, %d, %.4f); }", class, r, g, b, a) | |
| 50 | + | ||
| 51 | + | provider := gtk.NewCssProvider() | |
| 52 | + | if provider == nil { | |
| 53 | + | return | |
| 54 | + | } | |
| 55 | + | provider.LoadFromString(css) | |
| 56 | + | ||
| 57 | + | wv.bgGuardMu.Lock() | |
| 58 | + | defer wv.bgGuardMu.Unlock() | |
| 59 | + | if wv.firstFramePainted.Load() { | |
| 60 | + | return // First frame already arrived before the guard could be installed. | |
| 61 | + | } | |
| 62 | + | widget.AddCssClass(class) | |
| 63 | + | gtk.StyleContextAddProviderForDisplay(display, provider, uint(gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)) | |
| 64 | + | wv.bgGuardProvider = provider | |
| 65 | + | wv.bgGuardWidget = widget | |
| 66 | + | wv.bgGuardClass = class | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // markFirstFramePainted records that the first CEF frame has been presented and | |
| 70 | + | // schedules teardown of the background flash guard so later frames render | |
| 71 | + | // normally. Safe to call from any thread and idempotent. | |
| 72 | + | func (wv *WebView) markFirstFramePainted() { | |
| 73 | + | if wv == nil || !wv.firstFramePainted.CompareAndSwap(false, true) { | |
| 74 | + | return | |
| 75 | + | } | |
| 76 | + | wv.runOnGTK(func() { | |
| 77 | + | wv.removeBackgroundFlashGuard() | |
| 78 | + | }) | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | // removeBackgroundFlashGuard detaches the transient theme-background provider and | |
| 82 | + | // clears the render widget's guard class. Must run on the GTK main thread. It is | |
| 83 | + | // safe to call when no guard is installed. | |
| 84 | + | func (wv *WebView) removeBackgroundFlashGuard() { | |
| 85 | + | if wv == nil { | |
| 86 | + | return | |
| 87 | + | } | |
| 88 | + | wv.bgGuardMu.Lock() | |
| 89 | + | provider := wv.bgGuardProvider | |
| 90 | + | widget := wv.bgGuardWidget | |
| 91 | + | class := wv.bgGuardClass | |
| 92 | + | wv.bgGuardProvider = nil | |
| 93 | + | wv.bgGuardWidget = nil | |
| 94 | + | wv.bgGuardClass = "" | |
| 95 | + | wv.bgGuardMu.Unlock() | |
| 96 | + | ||
| 97 | + | if provider == nil { | |
| 98 | + | return | |
| 99 | + | } | |
| 100 | + | if widget != nil && class != "" { | |
| 101 | + | widget.RemoveCssClass(class) | |
| 102 | + | } | |
| 103 | + | if display := gdk.DisplayGetDefault(); display != nil { | |
| 104 | + | gtk.StyleContextRemoveProviderForDisplay(display, provider) | |
| 105 | + | } | |
| 106 | + | } | |
| @@ -0,0 +1,106 @@ | |||||
| 1 | + | package cef | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/puregotk/v4/gdk" | |||
| 7 | + | "github.com/bnema/puregotk/v4/gtk" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | // opaqueWhiteBackground is Chromium's standard windowless canvas fallback. It is | |||
| 11 | + | // baked into every BrowserSettings.BackgroundColor so pages that paint nothing | |||
| 12 | + | // get an opaque white canvas (never a theme-dark one). A zero value must not be | |||
| 13 | + | // used: in windowless/OSR mode a zero-alpha background enables transparent | |||
| 14 | + | // painting, which this engine does not want. | |||
| 15 | + | const opaqueWhiteBackground uint32 = 0xFFFFFFFF | |||
| 16 | + | ||||
| 17 | + | // flashGuardCSSClassPrefix scopes each WebView's transient background provider to | |||
| 18 | + | // its own render widget so guards from different tabs never collide. | |||
| 19 | + | const flashGuardCSSClassPrefix = "dumber-cef-flash-guard-" | |||
| 20 | + | ||||
| 21 | + | // colorByteMask isolates a single 8-bit channel from a packed ARGB value. | |||
| 22 | + | const colorByteMask = 0xFF | |||
| 23 | + | ||||
| 24 | + | // applyBackgroundFlashGuard paints the theme background color behind the CEF | |||
| 25 | + | // render widget until the first frame is presented. It is the presentation-layer | |||
| 26 | + | // replacement for the former dark BrowserSettings.BackgroundColor: Chromium's | |||
| 27 | + | // canvas is now always opaque white, so the anti-white-flash lives here instead. | |||
| 28 | + | // The bridge widget (a GtkPicture/GtkGLArea) renders its CSS background until an | |||
| 29 | + | // opaque frame texture covers it, at which point markFirstFramePainted tears the | |||
| 30 | + | // guard down. Must run on the GTK main thread. | |||
| 31 | + | func (wv *WebView) applyBackgroundFlashGuard(widget *gtk.Widget) { | |||
| 32 | + | if wv == nil || widget == nil { | |||
| 33 | + | return | |||
| 34 | + | } | |||
| 35 | + | argb := wv.backgroundColor | |||
| 36 | + | if argb == 0 { | |||
| 37 | + | return // No theme color configured; nothing to guard against. | |||
| 38 | + | } | |||
| 39 | + | display := gdk.DisplayGetDefault() | |||
| 40 | + | if display == nil { | |||
| 41 | + | return | |||
| 42 | + | } | |||
| 43 | + | ||||
| 44 | + | a := float64((argb>>24)&colorByteMask) / colorScale | |||
| 45 | + | r := (argb >> 16) & colorByteMask | |||
| 46 | + | g := (argb >> 8) & colorByteMask | |||
| 47 | + | b := argb & colorByteMask | |||
| 48 | + | class := fmt.Sprintf("%s%d", flashGuardCSSClassPrefix, uint64(wv.id)) | |||
| 49 | + | css := fmt.Sprintf(".%s { background-color: rgba(%d, %d, %d, %.4f); }", class, r, g, b, a) | |||
| 50 | + | ||||
| 51 | + | provider := gtk.NewCssProvider() | |||
| 52 | + | if provider == nil { | |||
| 53 | + | return | |||
| 54 | + | } | |||
| 55 | + | provider.LoadFromString(css) | |||
| 56 | + | ||||
| 57 | + | wv.bgGuardMu.Lock() | |||
| 58 | + | defer wv.bgGuardMu.Unlock() | |||
| 59 | + | if wv.firstFramePainted.Load() { | |||
| 60 | + | return // First frame already arrived before the guard could be installed. | |||
| 61 | + | } | |||
| 62 | + | widget.AddCssClass(class) | |||
| 63 | + | gtk.StyleContextAddProviderForDisplay(display, provider, uint(gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)) | |||
| 64 | + | wv.bgGuardProvider = provider | |||
| 65 | + | wv.bgGuardWidget = widget | |||
| 66 | + | wv.bgGuardClass = class | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // markFirstFramePainted records that the first CEF frame has been presented and | |||
| 70 | + | // schedules teardown of the background flash guard so later frames render | |||
| 71 | + | // normally. Safe to call from any thread and idempotent. | |||
| 72 | + | func (wv *WebView) markFirstFramePainted() { | |||
| 73 | + | if wv == nil || !wv.firstFramePainted.CompareAndSwap(false, true) { | |||
| 74 | + | return | |||
| 75 | + | } | |||
| 76 | + | wv.runOnGTK(func() { | |||
| 77 | + | wv.removeBackgroundFlashGuard() | |||
| 78 | + | }) | |||
| 79 | + | } | |||
| 80 | + | ||||
| 81 | + | // removeBackgroundFlashGuard detaches the transient theme-background provider and | |||
| 82 | + | // clears the render widget's guard class. Must run on the GTK main thread. It is | |||
| 83 | + | // safe to call when no guard is installed. | |||
| 84 | + | func (wv *WebView) removeBackgroundFlashGuard() { | |||
| 85 | + | if wv == nil { | |||
| 86 | + | return | |||
| 87 | + | } | |||
| 88 | + | wv.bgGuardMu.Lock() | |||
| 89 | + | provider := wv.bgGuardProvider | |||
| 90 | + | widget := wv.bgGuardWidget | |||
| 91 | + | class := wv.bgGuardClass | |||
| 92 | + | wv.bgGuardProvider = nil | |||
| 93 | + | wv.bgGuardWidget = nil | |||
| 94 | + | wv.bgGuardClass = "" | |||
| 95 | + | wv.bgGuardMu.Unlock() | |||
| 96 | + | ||||
| 97 | + | if provider == nil { | |||
| 98 | + | return | |||
| 99 | + | } | |||
| 100 | + | if widget != nil && class != "" { | |||
| 101 | + | widget.RemoveCssClass(class) | |||
| 102 | + | } | |||
| 103 | + | if display := gdk.DisplayGetDefault(); display != nil { | |||
| 104 | + | gtk.StyleContextRemoveProviderForDisplay(display, provider) | |||
| 105 | + | } | |||
| 106 | + | } | |||
M internal/infrastructure/cef/factory.go +12 -8
| @@ -27,7 +27,7 @@ type WebViewFactory struct { | |||
| 27 | 27 | windowlessFrameRate int32 | |
| 28 | 28 | windowlessFrameRateMax int32 | |
| 29 | 29 | inputConfig RuntimeInputConfig | |
| 30 | - | bgColor atomic.Uint32 // packed ARGB for BrowserSettings.BackgroundColor | |
| 30 | + | bgColor atomic.Uint32 // packed ARGB theme color for the presentation-layer flash guard | |
| 31 | 31 | audioOutputFactory port.AudioOutputFactory | |
| 32 | 32 | } | |
| 33 | 33 | ||
| @@ -69,7 +69,9 @@ func newWebViewFactory(engine *Engine, opts webViewFactoryOptions) *WebViewFacto | |||
| 69 | 69 | } | |
| 70 | 70 | } | |
| 71 | 71 | ||
| 72 | - | // setDefaultBackgroundColor stores a packed ARGB color for new browser creation. | |
| 72 | + | // setDefaultBackgroundColor stores the packed ARGB theme color used by the | |
| 73 | + | // presentation-layer flash guard on newly-created WebViews. It no longer feeds | |
| 74 | + | // BrowserSettings.BackgroundColor, which is always opaque white. | |
| 73 | 75 | func (f *WebViewFactory) setDefaultBackgroundColor(r, g, b, a float64) { | |
| 74 | 76 | const s = colorScale | |
| 75 | 77 | argb := uint32(a*s)<<24 | uint32(r*s)<<16 | uint32(g*s)<<8 | uint32(b*s) | |
| @@ -96,9 +98,9 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||
| 96 | 98 | settings := purecef.NewBrowserSettings() | |
| 97 | 99 | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | |
| 98 | 100 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | |
| 99 | - | if bg := f.bgColor.Load(); bg != 0 { | |
| 100 | - | settings.BackgroundColor = bg | |
| 101 | - | } | |
| 101 | + | // Always opaque white: Chromium's standard canvas fallback. The theme | |
| 102 | + | // background is applied at the presentation layer as a flash guard instead. | |
| 103 | + | settings.BackgroundColor = opaqueWhiteBackground | |
| 102 | 104 | ||
| 103 | 105 | // Defer browser creation until the bridge view has a non-zero size. | |
| 104 | 106 | // CEF requires GetViewRect to return a non-empty rect, but the GL area | |
| @@ -142,6 +144,8 @@ func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | |||
| 142 | 144 | wv.nativeWidget = popupRoot | |
| 143 | 145 | } | |
| 144 | 146 | } | |
| 147 | + | // Guard the render widget with the theme background until the first frame. | |
| 148 | + | wv.applyBackgroundFlashGuard(nativeWidget) | |
| 145 | 149 | }) | |
| 146 | 150 | if !result.Completed() { | |
| 147 | 151 | wv.destroyViewBridgeOnGTKAsync() | |
| @@ -383,9 +387,9 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||
| 383 | 387 | settings := purecef.NewBrowserSettings() | |
| 384 | 388 | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | |
| 385 | 389 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | |
| 386 | - | if bg := f.bgColor.Load(); bg != 0 { | |
| 387 | - | settings.BackgroundColor = bg | |
| 388 | - | } | |
| 390 | + | // Always opaque white: the theme background is applied as a presentation-layer | |
| 391 | + | // flash guard, not baked into Chromium's canvas. | |
| 392 | + | settings.BackgroundColor = opaqueWhiteBackground | |
| 389 | 393 | ||
| 390 | 394 | if err := f.configureInitialBrowserCreation(ctx, popupWV, popupWV.client, &windowInfo, &settings, func(w, h int32) { | |
| 391 | 395 | f.handlePopupShellInitialResize(ctx, popupWV, func(w, h int32) { | |
| @@ -27,7 +27,7 @@ type WebViewFactory struct { | |||||
| 27 | windowlessFrameRate int32 | 27 | windowlessFrameRate int32 | ||
| 28 | windowlessFrameRateMax int32 | 28 | windowlessFrameRateMax int32 | ||
| 29 | inputConfig RuntimeInputConfig | 29 | inputConfig RuntimeInputConfig | ||
| 30 | - | bgColor atomic.Uint32 // packed ARGB for BrowserSettings.BackgroundColor | 30 | + | bgColor atomic.Uint32 // packed ARGB theme color for the presentation-layer flash guard |
| 31 | audioOutputFactory port.AudioOutputFactory | 31 | audioOutputFactory port.AudioOutputFactory | ||
| 32 | } | 32 | } | ||
| 33 | 33 | ||||
| @@ -69,7 +69,9 @@ func newWebViewFactory(engine *Engine, opts webViewFactoryOptions) *WebViewFacto | |||||
| 69 | } | 69 | } | ||
| 70 | } | 70 | } | ||
| 71 | 71 | ||||
| 72 | - | // setDefaultBackgroundColor stores a packed ARGB color for new browser creation. | 72 | + | // setDefaultBackgroundColor stores the packed ARGB theme color used by the |
| 73 | + | // presentation-layer flash guard on newly-created WebViews. It no longer feeds | |||
| 74 | + | // BrowserSettings.BackgroundColor, which is always opaque white. | |||
| 73 | func (f *WebViewFactory) setDefaultBackgroundColor(r, g, b, a float64) { | 75 | func (f *WebViewFactory) setDefaultBackgroundColor(r, g, b, a float64) { | ||
| 74 | const s = colorScale | 76 | const s = colorScale | ||
| 75 | argb := uint32(a*s)<<24 | uint32(r*s)<<16 | uint32(g*s)<<8 | uint32(b*s) | 77 | argb := uint32(a*s)<<24 | uint32(r*s)<<16 | uint32(g*s)<<8 | uint32(b*s) | ||
| @@ -96,9 +98,9 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||||
| 96 | settings := purecef.NewBrowserSettings() | 98 | settings := purecef.NewBrowserSettings() | ||
| 97 | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | 99 | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | ||
| 98 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | 100 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | ||
| 99 | - | if bg := f.bgColor.Load(); bg != 0 { | 101 | + | // Always opaque white: Chromium's standard canvas fallback. The theme |
| 100 | - | settings.BackgroundColor = bg | 102 | + | // background is applied at the presentation layer as a flash guard instead. |
| 101 | - | } | 103 | + | settings.BackgroundColor = opaqueWhiteBackground |
| 102 | 104 | ||||
| 103 | // Defer browser creation until the bridge view has a non-zero size. | 105 | // Defer browser creation until the bridge view has a non-zero size. | ||
| 104 | // CEF requires GetViewRect to return a non-empty rect, but the GL area | 106 | // CEF requires GetViewRect to return a non-empty rect, but the GL area | ||
| @@ -142,6 +144,8 @@ func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | |||||
| 142 | wv.nativeWidget = popupRoot | 144 | wv.nativeWidget = popupRoot | ||
| 143 | } | 145 | } | ||
| 144 | } | 146 | } | ||
| 147 | + | // Guard the render widget with the theme background until the first frame. | |||
| 148 | + | wv.applyBackgroundFlashGuard(nativeWidget) | |||
| 145 | }) | 149 | }) | ||
| 146 | if !result.Completed() { | 150 | if !result.Completed() { | ||
| 147 | wv.destroyViewBridgeOnGTKAsync() | 151 | wv.destroyViewBridgeOnGTKAsync() | ||
| @@ -383,9 +387,9 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||||
| 383 | settings := purecef.NewBrowserSettings() | 387 | settings := purecef.NewBrowserSettings() | ||
| 384 | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | 388 | cef2gtk.ConfigureBrowserSettings(&settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: f.windowlessFrameRate}) | ||
| 385 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | 389 | settings.LocalStorage = 1 // CEF_STATE_ENABLED | ||
| 386 | - | if bg := f.bgColor.Load(); bg != 0 { | 390 | + | // Always opaque white: the theme background is applied as a presentation-layer |
| 387 | - | settings.BackgroundColor = bg | 391 | + | // flash guard, not baked into Chromium's canvas. |
| 388 | - | } | 392 | + | settings.BackgroundColor = opaqueWhiteBackground |
| 389 | 393 | ||||
| 390 | if err := f.configureInitialBrowserCreation(ctx, popupWV, popupWV.client, &windowInfo, &settings, func(w, h int32) { | 394 | if err := f.configureInitialBrowserCreation(ctx, popupWV, popupWV.client, &windowInfo, &settings, func(w, h int32) { | ||
| 391 | f.handlePopupShellInitialResize(ctx, popupWV, func(w, h int32) { | 395 | f.handlePopupShellInitialResize(ctx, popupWV, func(w, h int32) { | ||
M internal/infrastructure/cef/render_handler_adapter.go +9 -0
| @@ -51,6 +51,15 @@ func newDumberRenderHandler(wv *WebView) purecef.RenderHandler { | |||
| 51 | 51 | ||
| 52 | 52 | var unsupportedPaintOnce sync.Once | |
| 53 | 53 | hooks := startupPresentationHooks(activeStartupTrace()) | |
| 54 | + | // Tear down the theme background flash guard once the first frame is | |
| 55 | + | // presented, without dropping the startup trace mark the bridge already set. | |
| 56 | + | firstAcceleratedPaint := hooks.OnFirstAcceleratedPaint | |
| 57 | + | hooks.OnFirstAcceleratedPaint = func() { | |
| 58 | + | if firstAcceleratedPaint != nil { | |
| 59 | + | firstAcceleratedPaint() | |
| 60 | + | } | |
| 61 | + | wv.markFirstFramePainted() | |
| 62 | + | } | |
| 54 | 63 | hooks.OnUnsupportedPaint = func() { | |
| 55 | 64 | unsupportedPaintOnce.Do(func() { | |
| 56 | 65 | if wv.ctx != nil { | |
| @@ -51,6 +51,15 @@ func newDumberRenderHandler(wv *WebView) purecef.RenderHandler { | |||||
| 51 | 51 | ||||
| 52 | var unsupportedPaintOnce sync.Once | 52 | var unsupportedPaintOnce sync.Once | ||
| 53 | hooks := startupPresentationHooks(activeStartupTrace()) | 53 | hooks := startupPresentationHooks(activeStartupTrace()) | ||
| 54 | + | // Tear down the theme background flash guard once the first frame is | |||
| 55 | + | // presented, without dropping the startup trace mark the bridge already set. | |||
| 56 | + | firstAcceleratedPaint := hooks.OnFirstAcceleratedPaint | |||
| 57 | + | hooks.OnFirstAcceleratedPaint = func() { | |||
| 58 | + | if firstAcceleratedPaint != nil { | |||
| 59 | + | firstAcceleratedPaint() | |||
| 60 | + | } | |||
| 61 | + | wv.markFirstFramePainted() | |||
| 62 | + | } | |||
| 54 | hooks.OnUnsupportedPaint = func() { | 63 | hooks.OnUnsupportedPaint = func() { | ||
| 55 | unsupportedPaintOnce.Do(func() { | 64 | unsupportedPaintOnce.Do(func() { | ||
| 56 | if wv.ctx != nil { | 65 | if wv.ctx != nil { | ||
M internal/infrastructure/cef/webview.go +20 -11
| @@ -239,6 +239,18 @@ type WebView struct { | |||
| 239 | 239 | backgroundColor uint32 | |
| 240 | 240 | inputConfig RuntimeInputConfig | |
| 241 | 241 | ||
| 242 | + | // Background flash guard: a theme-colored CSS provider painted behind the | |
| 243 | + | // render widget until the first CEF frame is presented. It replaces the | |
| 244 | + | // former dark BrowserSettings.BackgroundColor anti-white-flash, whose canvas | |
| 245 | + | // is now always opaque white. State is guarded by bgGuardMu; firstFramePainted | |
| 246 | + | // gates the one-shot teardown and resets naturally when a fresh WebView is | |
| 247 | + | // created after a browser recreation. | |
| 248 | + | firstFramePainted atomic.Bool | |
| 249 | + | bgGuardMu sync.Mutex | |
| 250 | + | bgGuardProvider *gtk.CssProvider | |
| 251 | + | bgGuardWidget *gtk.Widget | |
| 252 | + | bgGuardClass string | |
| 253 | + | ||
| 242 | 254 | // Touchpad input diagnostics are debounced to avoid flooding logs while | |
| 243 | 255 | // debugging continuous scroll streams. | |
| 244 | 256 | touchpadNavigation *touchpadNavigationRecognizer | |
| @@ -992,7 +1004,7 @@ func (wv *WebView) prepareNativePopup( | |||
| 992 | 1004 | prep.parent.trackPendingNativePopup(popupID, wv) | |
| 993 | 1005 | } | |
| 994 | 1006 | ||
| 995 | - | configureNativePopupWindow(windowInfo, settings, prep.frameRate, prep.backgroundColor) | |
| 1007 | + | configureNativePopupWindow(windowInfo, settings, prep.frameRate) | |
| 996 | 1008 | clientSlot.Set(client) | |
| 997 | 1009 | return true | |
| 998 | 1010 | } | |
| @@ -1007,9 +1019,8 @@ func (wv *WebView) nativePopupPreparationSnapshot() (nativePopupPreparation, boo | |||
| 1007 | 1019 | return nativePopupPreparation{}, false | |
| 1008 | 1020 | } | |
| 1009 | 1021 | return nativePopupPreparation{ | |
| 1010 | - | parent: wv.nativePopupParent, | |
| 1011 | - | frameRate: wv.windowlessFrameRate, | |
| 1012 | - | backgroundColor: wv.backgroundColor, | |
| 1022 | + | parent: wv.nativePopupParent, | |
| 1023 | + | frameRate: wv.windowlessFrameRate, | |
| 1013 | 1024 | }, true | |
| 1014 | 1025 | } | |
| 1015 | 1026 | ||
| @@ -1045,7 +1056,6 @@ func configureNativePopupWindow( | |||
| 1045 | 1056 | windowInfo *purecef.WindowInfo, | |
| 1046 | 1057 | settings *purecef.BrowserSettings, | |
| 1047 | 1058 | frameRate int32, | |
| 1048 | - | backgroundColor uint32, | |
| 1049 | 1059 | ) { | |
| 1050 | 1060 | cef2gtk.ConfigureWindowInfo(windowInfo, cef2gtk.WindowInfoOptions{}) | |
| 1051 | 1061 | if externalBeginFrameEnabled() { | |
| @@ -1056,15 +1066,13 @@ func configureNativePopupWindow( | |||
| 1056 | 1066 | } | |
| 1057 | 1067 | cef2gtk.ConfigureBrowserSettings(settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: frameRate}) | |
| 1058 | 1068 | settings.LocalStorage = 1 | |
| 1059 | - | if backgroundColor != 0 { | |
| 1060 | - | settings.BackgroundColor = backgroundColor | |
| 1061 | - | } | |
| 1069 | + | // Always opaque white; the popup shell WebView carries the theme flash guard. | |
| 1070 | + | settings.BackgroundColor = opaqueWhiteBackground | |
| 1062 | 1071 | } | |
| 1063 | 1072 | ||
| 1064 | 1073 | type nativePopupPreparation struct { | |
| 1065 | - | parent *WebView | |
| 1066 | - | frameRate int32 | |
| 1067 | - | backgroundColor uint32 | |
| 1074 | + | parent *WebView | |
| 1075 | + | frameRate int32 | |
| 1068 | 1076 | } | |
| 1069 | 1077 | ||
| 1070 | 1078 | func (wv *WebView) handleNativePopupAborted() { | |
| @@ -1244,6 +1252,7 @@ func (wv *WebView) destroyViewBridgeOnGTKThread() { | |||
| 1244 | 1252 | if wv == nil || wv.viewBridge == nil { | |
| 1245 | 1253 | return | |
| 1246 | 1254 | } | |
| 1255 | + | wv.removeBackgroundFlashGuard() | |
| 1247 | 1256 | if wv.removeSizeObserver != nil { | |
| 1248 | 1257 | wv.removeSizeObserver() | |
| 1249 | 1258 | wv.removeSizeObserver = nil | |
| @@ -239,6 +239,18 @@ type WebView struct { | |||||
| 239 | backgroundColor uint32 | 239 | backgroundColor uint32 | ||
| 240 | inputConfig RuntimeInputConfig | 240 | inputConfig RuntimeInputConfig | ||
| 241 | 241 | ||||
| 242 | + | // Background flash guard: a theme-colored CSS provider painted behind the | |||
| 243 | + | // render widget until the first CEF frame is presented. It replaces the | |||
| 244 | + | // former dark BrowserSettings.BackgroundColor anti-white-flash, whose canvas | |||
| 245 | + | // is now always opaque white. State is guarded by bgGuardMu; firstFramePainted | |||
| 246 | + | // gates the one-shot teardown and resets naturally when a fresh WebView is | |||
| 247 | + | // created after a browser recreation. | |||
| 248 | + | firstFramePainted atomic.Bool | |||
| 249 | + | bgGuardMu sync.Mutex | |||
| 250 | + | bgGuardProvider *gtk.CssProvider | |||
| 251 | + | bgGuardWidget *gtk.Widget | |||
| 252 | + | bgGuardClass string | |||
| 253 | + | ||||
| 242 | // Touchpad input diagnostics are debounced to avoid flooding logs while | 254 | // Touchpad input diagnostics are debounced to avoid flooding logs while | ||
| 243 | // debugging continuous scroll streams. | 255 | // debugging continuous scroll streams. | ||
| 244 | touchpadNavigation *touchpadNavigationRecognizer | 256 | touchpadNavigation *touchpadNavigationRecognizer | ||
| @@ -992,7 +1004,7 @@ func (wv *WebView) prepareNativePopup( | |||||
| 992 | prep.parent.trackPendingNativePopup(popupID, wv) | 1004 | prep.parent.trackPendingNativePopup(popupID, wv) | ||
| 993 | } | 1005 | } | ||
| 994 | 1006 | ||||
| 995 | - | configureNativePopupWindow(windowInfo, settings, prep.frameRate, prep.backgroundColor) | 1007 | + | configureNativePopupWindow(windowInfo, settings, prep.frameRate) |
| 996 | clientSlot.Set(client) | 1008 | clientSlot.Set(client) | ||
| 997 | return true | 1009 | return true | ||
| 998 | } | 1010 | } | ||
| @@ -1007,9 +1019,8 @@ func (wv *WebView) nativePopupPreparationSnapshot() (nativePopupPreparation, boo | |||||
| 1007 | return nativePopupPreparation{}, false | 1019 | return nativePopupPreparation{}, false | ||
| 1008 | } | 1020 | } | ||
| 1009 | return nativePopupPreparation{ | 1021 | return nativePopupPreparation{ | ||
| 1010 | - | parent: wv.nativePopupParent, | 1022 | + | parent: wv.nativePopupParent, |
| 1011 | - | frameRate: wv.windowlessFrameRate, | 1023 | + | frameRate: wv.windowlessFrameRate, |
| 1012 | - | backgroundColor: wv.backgroundColor, | |||
| 1013 | }, true | 1024 | }, true | ||
| 1014 | } | 1025 | } | ||
| 1015 | 1026 | ||||
| @@ -1045,7 +1056,6 @@ func configureNativePopupWindow( | |||||
| 1045 | windowInfo *purecef.WindowInfo, | 1056 | windowInfo *purecef.WindowInfo, | ||
| 1046 | settings *purecef.BrowserSettings, | 1057 | settings *purecef.BrowserSettings, | ||
| 1047 | frameRate int32, | 1058 | frameRate int32, | ||
| 1048 | - | backgroundColor uint32, | |||
| 1049 | ) { | 1059 | ) { | ||
| 1050 | cef2gtk.ConfigureWindowInfo(windowInfo, cef2gtk.WindowInfoOptions{}) | 1060 | cef2gtk.ConfigureWindowInfo(windowInfo, cef2gtk.WindowInfoOptions{}) | ||
| 1051 | if externalBeginFrameEnabled() { | 1061 | if externalBeginFrameEnabled() { | ||
| @@ -1056,15 +1066,13 @@ func configureNativePopupWindow( | |||||
| 1056 | } | 1066 | } | ||
| 1057 | cef2gtk.ConfigureBrowserSettings(settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: frameRate}) | 1067 | cef2gtk.ConfigureBrowserSettings(settings, cef2gtk.BrowserSettingsOptions{WindowlessFrameRate: frameRate}) | ||
| 1058 | settings.LocalStorage = 1 | 1068 | settings.LocalStorage = 1 | ||
| 1059 | - | if backgroundColor != 0 { | 1069 | + | // Always opaque white; the popup shell WebView carries the theme flash guard. |
| 1060 | - | settings.BackgroundColor = backgroundColor | 1070 | + | settings.BackgroundColor = opaqueWhiteBackground |
| 1061 | - | } | |||
| 1062 | } | 1071 | } | ||
| 1063 | 1072 | ||||
| 1064 | type nativePopupPreparation struct { | 1073 | type nativePopupPreparation struct { | ||
| 1065 | - | parent *WebView | 1074 | + | parent *WebView |
| 1066 | - | frameRate int32 | 1075 | + | frameRate int32 |
| 1067 | - | backgroundColor uint32 | |||
| 1068 | } | 1076 | } | ||
| 1069 | 1077 | ||||
| 1070 | func (wv *WebView) handleNativePopupAborted() { | 1078 | func (wv *WebView) handleNativePopupAborted() { | ||
| @@ -1244,6 +1252,7 @@ func (wv *WebView) destroyViewBridgeOnGTKThread() { | |||||
| 1244 | if wv == nil || wv.viewBridge == nil { | 1252 | if wv == nil || wv.viewBridge == nil { | ||
| 1245 | return | 1253 | return | ||
| 1246 | } | 1254 | } | ||
| 1255 | + | wv.removeBackgroundFlashGuard() | |||
| 1247 | if wv.removeSizeObserver != nil { | 1256 | if wv.removeSizeObserver != nil { | ||
| 1248 | wv.removeSizeObserver() | 1257 | wv.removeSizeObserver() | ||
| 1249 | wv.removeSizeObserver = nil | 1258 | wv.removeSizeObserver = nil | ||
M internal/infrastructure/cef/webview_handlers_test.go +2 -2
| @@ -380,14 +380,14 @@ func TestConfigureNativePopupWindow_UsesSharedTextureWindowlessDefaults(t *testi | |||
| 380 | 380 | windowInfo := purecef.NewWindowInfo() | |
| 381 | 381 | settings := purecef.NewBrowserSettings() | |
| 382 | 382 | ||
| 383 | - | configureNativePopupWindow(&windowInfo, &settings, 72, 0xFF112233) | |
| 383 | + | configureNativePopupWindow(&windowInfo, &settings, 72) | |
| 384 | 384 | ||
| 385 | 385 | require.Equal(t, purecef.WindowHandle(0), windowInfo.ParentWindow) | |
| 386 | 386 | require.Equal(t, int32(1), windowInfo.WindowlessRenderingEnabled) | |
| 387 | 387 | require.Equal(t, int32(1), windowInfo.SharedTextureEnabled) | |
| 388 | 388 | require.Equal(t, int32(72), settings.WindowlessFrameRate) | |
| 389 | 389 | require.Equal(t, int32(1), settings.LocalStorage) | |
| 390 | - | require.Equal(t, uint32(0xFF112233), settings.BackgroundColor) | |
| 390 | + | require.Equal(t, opaqueWhiteBackground, settings.BackgroundColor) | |
| 391 | 391 | } | |
| 392 | 392 | ||
| 393 | 393 | func TestOnLoadStartFiresCommittedAndUpdatesURI(t *testing.T) { | |
| @@ -380,14 +380,14 @@ func TestConfigureNativePopupWindow_UsesSharedTextureWindowlessDefaults(t *testi | |||||
| 380 | windowInfo := purecef.NewWindowInfo() | 380 | windowInfo := purecef.NewWindowInfo() | ||
| 381 | settings := purecef.NewBrowserSettings() | 381 | settings := purecef.NewBrowserSettings() | ||
| 382 | 382 | ||||
| 383 | - | configureNativePopupWindow(&windowInfo, &settings, 72, 0xFF112233) | 383 | + | configureNativePopupWindow(&windowInfo, &settings, 72) |
| 384 | 384 | ||||
| 385 | require.Equal(t, purecef.WindowHandle(0), windowInfo.ParentWindow) | 385 | require.Equal(t, purecef.WindowHandle(0), windowInfo.ParentWindow) | ||
| 386 | require.Equal(t, int32(1), windowInfo.WindowlessRenderingEnabled) | 386 | require.Equal(t, int32(1), windowInfo.WindowlessRenderingEnabled) | ||
| 387 | require.Equal(t, int32(1), windowInfo.SharedTextureEnabled) | 387 | require.Equal(t, int32(1), windowInfo.SharedTextureEnabled) | ||
| 388 | require.Equal(t, int32(72), settings.WindowlessFrameRate) | 388 | require.Equal(t, int32(72), settings.WindowlessFrameRate) | ||
| 389 | require.Equal(t, int32(1), settings.LocalStorage) | 389 | require.Equal(t, int32(1), settings.LocalStorage) | ||
| 390 | - | require.Equal(t, uint32(0xFF112233), settings.BackgroundColor) | 390 | + | require.Equal(t, opaqueWhiteBackground, settings.BackgroundColor) |
| 391 | } | 391 | } | ||
| 392 | 392 | ||||
| 393 | func TestOnLoadStartFiresCommittedAndUpdatesURI(t *testing.T) { | 393 | func TestOnLoadStartFiresCommittedAndUpdatesURI(t *testing.T) { | ||
M vendor/github.com/bnema/purego-cef/cef/cef_browser_gen.go +15 -2 generated file: collapsed
M vendor/github.com/bnema/purego-cef/cef/cef_command_line_gen.go +15 -2 generated file: collapsed
A vendor/github.com/bnema/purego-cef/cef/cef_component_updater_gen.go +323 -0
| @@ -0,0 +1,323 @@ | |||
| 1 | + | // Code generated by cefgen. DO NOT EDIT. | |
| 2 | + | ||
| 3 | + | package cef | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "runtime" | |
| 7 | + | "sync" | |
| 8 | + | "unsafe" | |
| 9 | + | ||
| 10 | + | "github.com/bnema/purego-cef/internal/capi" | |
| 11 | + | ||
| 12 | + | portin "github.com/bnema/purego-cef/internal/ports/in" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | // ComponentUpdateCallback Callback structure for component update results. | |
| 16 | + | type ComponentUpdateCallback = portin.ComponentUpdateCallback | |
| 17 | + | ||
| 18 | + | // componentUpdateCallbackWrapper wraps a user-provided ComponentUpdateCallback implementation together | |
| 19 | + | // with the raw CEF struct pointer allocated by NewComponentUpdateCallback. It satisfies the | |
| 20 | + | // ComponentUpdateCallback interface (by embedding the user impl) and core.RawPointerHolder | |
| 21 | + | // (so extractRawPointer can recover the raw pointer). | |
| 22 | + | type componentUpdateCallbackWrapper struct { | |
| 23 | + | ComponentUpdateCallback // embed user impl for interface delegation | |
| 24 | + | rawPtr *capi.CEFComponentUpdateCallbackT | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | func (w *componentUpdateCallbackWrapper) RawPointer() unsafe.Pointer { | |
| 28 | + | return unsafe.Pointer(w.rawPtr) | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | var componentUpdateCallbackOnCompleteSharedOnce sync.Once | |
| 32 | + | var componentUpdateCallbackOnCompleteSharedCallback uintptr | |
| 33 | + | ||
| 34 | + | func componentUpdateCallbackOnCompleteCEFCallback() uintptr { | |
| 35 | + | return sharedCEFCallback(&componentUpdateCallbackOnCompleteSharedOnce, &componentUpdateCallbackOnCompleteSharedCallback, func(self uintptr, arg0 uintptr, arg1 uintptr) { | |
| 36 | + | impl, ownerOK := cefCallbackOwnerAs[ComponentUpdateCallback](self) | |
| 37 | + | if !ownerOK { | |
| 38 | + | return | |
| 39 | + | } | |
| 40 | + | componentID := goString(unsafe.Pointer(arg0)) | |
| 41 | + | error := ComponentUpdateError(arg1) | |
| 42 | + | impl.OnComplete(componentID, error) | |
| 43 | + | }) | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | // NewComponentUpdateCallback creates a CEF handler backed by the given implementation. | |
| 47 | + | func NewComponentUpdateCallback(impl ComponentUpdateCallback) ComponentUpdateCallback { | |
| 48 | + | if isNilImpl(impl) { | |
| 49 | + | return nil | |
| 50 | + | } | |
| 51 | + | r := new(capi.CEFComponentUpdateCallbackT) | |
| 52 | + | w := &componentUpdateCallbackWrapper{rawPtr: r} | |
| 53 | + | w.ComponentUpdateCallback = impl | |
| 54 | + | initRefCount(unsafe.Pointer(r), unsafe.Sizeof(*r), w) | |
| 55 | + | ||
| 56 | + | r.OverrideOnComplete(componentUpdateCallbackOnCompleteCEFCallback()) | |
| 57 | + | ||
| 58 | + | return w | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | // componentUpdateCallbackImpl is a reverse wrapper for a CEF-owned ComponentUpdateCallback pointer. | |
| 62 | + | // Release is idempotent, but callers must externally synchronize Release with | |
| 63 | + | // concurrent method calls and must not use the wrapper after Release returns. | |
| 64 | + | type componentUpdateCallbackImpl struct { | |
| 65 | + | rawPtr *capi.CEFComponentUpdateCallbackT | |
| 66 | + | releaseOnce sync.Once | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | func (obj *componentUpdateCallbackImpl) OnComplete(componentID string, error ComponentUpdateError) { | |
| 70 | + | if obj == nil || obj.rawPtr == nil { | |
| 71 | + | return | |
| 72 | + | } | |
| 73 | + | rawPtr := obj.rawPtr | |
| 74 | + | componentIDStr := cefString(componentID) | |
| 75 | + | defer freeCefString(&componentIDStr) | |
| 76 | + | rawPtr.CallOnComplete(uintptr(unsafe.Pointer(&componentIDStr)), uintptr(error)) | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | func (obj *componentUpdateCallbackImpl) RawPointer() unsafe.Pointer { | |
| 80 | + | if obj == nil || obj.rawPtr == nil { | |
| 81 | + | return nil | |
| 82 | + | } | |
| 83 | + | return unsafe.Pointer(obj.rawPtr) | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | // Release releases the underlying CEF object. | |
| 87 | + | func (obj *componentUpdateCallbackImpl) Release() { | |
| 88 | + | if obj == nil { | |
| 89 | + | return | |
| 90 | + | } | |
| 91 | + | obj.releaseOnce.Do(func() { | |
| 92 | + | if obj.rawPtr == nil { | |
| 93 | + | return | |
| 94 | + | } | |
| 95 | + | rawPtr := obj.rawPtr | |
| 96 | + | obj.rawPtr = nil | |
| 97 | + | runtime.SetFinalizer(obj, nil) | |
| 98 | + | base := (*capi.CEFBaseRefCountedT)(unsafe.Pointer(rawPtr)) | |
| 99 | + | base.CallRelease() | |
| 100 | + | }) | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | // wrapComponentUpdateCallback wraps a CEF handler pointer received from CEF into a thin Go façade. | |
| 104 | + | func wrapComponentUpdateCallback(ptr unsafe.Pointer) ComponentUpdateCallback { | |
| 105 | + | if ptr == nil { | |
| 106 | + | return nil | |
| 107 | + | } | |
| 108 | + | r := (*capi.CEFComponentUpdateCallbackT)(ptr) | |
| 109 | + | base := (*capi.CEFBaseRefCountedT)(ptr) | |
| 110 | + | base.CallAddRef() | |
| 111 | + | impl := &componentUpdateCallbackImpl{rawPtr: r} | |
| 112 | + | runtime.SetFinalizer(impl, (*componentUpdateCallbackImpl).Release) | |
| 113 | + | return impl | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | // Component Structure representing a snapshot of a component's state at the time of retrieval. To get updated information, retrieve a new cef_component_t object via cef_component_updater_t::GetComponentByID or GetComponents. The functions of this structure may be called on any thread. | |
| 117 | + | type Component = portin.Component | |
| 118 | + | ||
| 119 | + | // componentImpl is a reverse wrapper for a CEF-owned Component pointer. | |
| 120 | + | // Release is idempotent, but callers must externally synchronize Release with | |
| 121 | + | // concurrent method calls and must not use the wrapper after Release returns. | |
| 122 | + | type componentImpl struct { | |
| 123 | + | rawPtr *capi.CEFComponentT | |
| 124 | + | releaseOnce sync.Once | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | func (obj *componentImpl) GetID() string { | |
| 128 | + | if obj == nil || obj.rawPtr == nil { | |
| 129 | + | return "" | |
| 130 | + | } | |
| 131 | + | rawPtr := obj.rawPtr | |
| 132 | + | ret := rawPtr.CallGetID() | |
| 133 | + | return goStringUserfree(unsafe.Pointer(ret)) | |
| 134 | + | } | |
| 135 | + | ||
| 136 | + | func (obj *componentImpl) GetName() string { | |
| 137 | + | if obj == nil || obj.rawPtr == nil { | |
| 138 | + | return "" | |
| 139 | + | } | |
| 140 | + | rawPtr := obj.rawPtr | |
| 141 | + | ret := rawPtr.CallGetName() | |
| 142 | + | return goStringUserfree(unsafe.Pointer(ret)) | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | func (obj *componentImpl) GetVersion() string { | |
| 146 | + | if obj == nil || obj.rawPtr == nil { | |
| 147 | + | return "" | |
| 148 | + | } | |
| 149 | + | rawPtr := obj.rawPtr | |
| 150 | + | ret := rawPtr.CallGetVersion() | |
| 151 | + | return goStringUserfree(unsafe.Pointer(ret)) | |
| 152 | + | } | |
| 153 | + | ||
| 154 | + | func (obj *componentImpl) GetState() ComponentState { | |
| 155 | + | if obj == nil || obj.rawPtr == nil { | |
| 156 | + | return 0 | |
| 157 | + | } | |
| 158 | + | rawPtr := obj.rawPtr | |
| 159 | + | ret := rawPtr.CallGetState() | |
| 160 | + | return ComponentState(ret) | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | func (obj *componentImpl) RawPointer() unsafe.Pointer { | |
| 164 | + | if obj == nil || obj.rawPtr == nil { | |
| 165 | + | return nil | |
| 166 | + | } | |
| 167 | + | return unsafe.Pointer(obj.rawPtr) | |
| 168 | + | } | |
| 169 | + | ||
| 170 | + | // Release releases the underlying CEF object. | |
| 171 | + | func (obj *componentImpl) Release() { | |
| 172 | + | if obj == nil { | |
| 173 | + | return | |
| 174 | + | } | |
| 175 | + | obj.releaseOnce.Do(func() { | |
| 176 | + | if obj.rawPtr == nil { | |
| 177 | + | return | |
| 178 | + | } | |
| 179 | + | rawPtr := obj.rawPtr | |
| 180 | + | obj.rawPtr = nil | |
| 181 | + | runtime.SetFinalizer(obj, nil) | |
| 182 | + | base := (*capi.CEFBaseRefCountedT)(unsafe.Pointer(rawPtr)) | |
| 183 | + | base.CallRelease() | |
| 184 | + | }) | |
| 185 | + | } | |
| 186 | + | ||
| 187 | + | func wrapComponent(ptr unsafe.Pointer) Component { | |
| 188 | + | if ptr == nil { | |
| 189 | + | return nil | |
| 190 | + | } | |
| 191 | + | r := (*capi.CEFComponentT)(ptr) | |
| 192 | + | base := (*capi.CEFBaseRefCountedT)(ptr) | |
| 193 | + | base.CallAddRef() | |
| 194 | + | impl := &componentImpl{rawPtr: r} | |
| 195 | + | runtime.SetFinalizer(impl, (*componentImpl).Release) | |
| 196 | + | return impl | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | // ComponentUpdater This structure provides access to Chromium's component updater service, allowing clients to discover registered components and trigger on-demand updates. The functions of this structure may only be called on the browser process UI thread. If the CEF context is not initialized or the component updater service is not available, functions will return safe defaults (0, nullptr, or NULL). | |
| 200 | + | type ComponentUpdater = portin.ComponentUpdater | |
| 201 | + | ||
| 202 | + | // componentUpdaterImpl is a reverse wrapper for a CEF-owned ComponentUpdater pointer. | |
| 203 | + | // Release is idempotent, but callers must externally synchronize Release with | |
| 204 | + | // concurrent method calls and must not use the wrapper after Release returns. | |
| 205 | + | type componentUpdaterImpl struct { | |
| 206 | + | rawPtr *capi.CEFComponentUpdaterT | |
| 207 | + | releaseOnce sync.Once | |
| 208 | + | } | |
| 209 | + | ||
| 210 | + | func (obj *componentUpdaterImpl) GetComponentCount() int { | |
| 211 | + | if obj == nil || obj.rawPtr == nil { | |
| 212 | + | return 0 | |
| 213 | + | } | |
| 214 | + | rawPtr := obj.rawPtr | |
| 215 | + | ret := rawPtr.CallGetComponentCount() | |
| 216 | + | return int(ret) | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | func (obj *componentUpdaterImpl) GetComponents(componentscount *int, components []Component) { | |
| 220 | + | if obj == nil || obj.rawPtr == nil { | |
| 221 | + | return | |
| 222 | + | } | |
| 223 | + | rawPtr := obj.rawPtr | |
| 224 | + | var componentsRaw []uintptr | |
| 225 | + | var componentsPtr unsafe.Pointer | |
| 226 | + | componentsCountPtr := componentscount | |
| 227 | + | if componentsCountPtr == nil { | |
| 228 | + | componentsCountScratch := len(components) | |
| 229 | + | componentsCountPtr = &componentsCountScratch | |
| 230 | + | } else { | |
| 231 | + | *componentsCountPtr = len(components) | |
| 232 | + | } | |
| 233 | + | if len(components) > 0 { | |
| 234 | + | componentsRaw = make([]uintptr, len(components)) | |
| 235 | + | componentsPtr = unsafe.Pointer(&componentsRaw[0]) | |
| 236 | + | } | |
| 237 | + | rawPtr.CallGetComponents(uintptr(unsafe.Pointer(componentsCountPtr)), uintptr(componentsPtr)) | |
| 238 | + | if len(components) > 0 { | |
| 239 | + | n := len(components) | |
| 240 | + | if *componentsCountPtr < n { | |
| 241 | + | n = *componentsCountPtr | |
| 242 | + | } | |
| 243 | + | for i := 0; i < n; i++ { | |
| 244 | + | components[i] = wrapComponent(unsafe.Pointer(componentsRaw[i])) | |
| 245 | + | } | |
| 246 | + | } | |
| 247 | + | } | |
| 248 | + | ||
| 249 | + | func (obj *componentUpdaterImpl) GetComponentByID(componentID string) Component { | |
| 250 | + | if obj == nil || obj.rawPtr == nil { | |
| 251 | + | return nil | |
| 252 | + | } | |
| 253 | + | rawPtr := obj.rawPtr | |
| 254 | + | componentIDStr := cefString(componentID) | |
| 255 | + | defer freeCefString(&componentIDStr) | |
| 256 | + | ret := rawPtr.CallGetComponentByID(uintptr(unsafe.Pointer(&componentIDStr))) | |
| 257 | + | return wrapComponent(unsafe.Pointer(ret)) | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | func (obj *componentUpdaterImpl) Update(componentID string, priority ComponentUpdatePriority, callback ComponentUpdateCallback) { | |
| 261 | + | if obj == nil || obj.rawPtr == nil { | |
| 262 | + | return | |
| 263 | + | } | |
| 264 | + | rawPtr := obj.rawPtr | |
| 265 | + | componentIDStr := cefString(componentID) | |
| 266 | + | defer freeCefString(&componentIDStr) | |
| 267 | + | rawPtr.CallUpdate(uintptr(unsafe.Pointer(&componentIDStr)), uintptr(priority), uintptr(extractOrWrapRawPointer(callback, func() any { return NewComponentUpdateCallback(callback) }))) | |
| 268 | + | } | |
| 269 | + | ||
| 270 | + | func (obj *componentUpdaterImpl) RawPointer() unsafe.Pointer { | |
| 271 | + | if obj == nil || obj.rawPtr == nil { | |
| 272 | + | return nil | |
| 273 | + | } | |
| 274 | + | return unsafe.Pointer(obj.rawPtr) | |
| 275 | + | } | |
| 276 | + | ||
| 277 | + | // Release releases the underlying CEF object. | |
| 278 | + | func (obj *componentUpdaterImpl) Release() { | |
| 279 | + | if obj == nil { | |
| 280 | + | return | |
| 281 | + | } | |
| 282 | + | obj.releaseOnce.Do(func() { | |
| 283 | + | if obj.rawPtr == nil { | |
| 284 | + | return | |
| 285 | + | } | |
| 286 | + | rawPtr := obj.rawPtr | |
| 287 | + | obj.rawPtr = nil | |
| 288 | + | runtime.SetFinalizer(obj, nil) | |
| 289 | + | base := (*capi.CEFBaseRefCountedT)(unsafe.Pointer(rawPtr)) | |
| 290 | + | base.CallRelease() | |
| 291 | + | }) | |
| 292 | + | } | |
| 293 | + | ||
| 294 | + | func wrapComponentUpdater(ptr unsafe.Pointer) ComponentUpdater { | |
| 295 | + | if ptr == nil { | |
| 296 | + | return nil | |
| 297 | + | } | |
| 298 | + | r := (*capi.CEFComponentUpdaterT)(ptr) | |
| 299 | + | base := (*capi.CEFBaseRefCountedT)(ptr) | |
| 300 | + | base.CallAddRef() | |
| 301 | + | impl := &componentUpdaterImpl{rawPtr: r} | |
| 302 | + | runtime.SetFinalizer(impl, (*componentUpdaterImpl).Release) | |
| 303 | + | return impl | |
| 304 | + | } | |
| 305 | + | ||
| 306 | + | // takeComponentUpdater adopts a CEF ComponentUpdater pointer whose reference is already owned by | |
| 307 | + | // the caller (as returned by a global factory function). Unlike wrapComponentUpdater it | |
| 308 | + | // does NOT call AddRef, because the C API already transferred one reference to us. | |
| 309 | + | func takeComponentUpdater(ptr unsafe.Pointer) ComponentUpdater { | |
| 310 | + | if ptr == nil { | |
| 311 | + | return nil | |
| 312 | + | } | |
| 313 | + | r := (*capi.CEFComponentUpdaterT)(ptr) | |
| 314 | + | impl := &componentUpdaterImpl{rawPtr: r} | |
| 315 | + | runtime.SetFinalizer(impl, (*componentUpdaterImpl).Release) | |
| 316 | + | return impl | |
| 317 | + | } | |
| 318 | + | ||
| 319 | + | // ComponentUpdaterGet Returns the global cef_component_updater_t singleton. Returns nullptr if called from the incorrect thread. | |
| 320 | + | func ComponentUpdaterGet() ComponentUpdater { | |
| 321 | + | ret := capi.CEFComponentUpdaterGet() | |
| 322 | + | return takeComponentUpdater(ret) | |
| 323 | + | } | |
| @@ -0,0 +1,323 @@ | |||||
| 1 | + | // Code generated by cefgen. DO NOT EDIT. | |||
| 2 | + | ||||
| 3 | + | package cef | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "runtime" | |||
| 7 | + | "sync" | |||
| 8 | + | "unsafe" | |||
| 9 | + | ||||
| 10 | + | "github.com/bnema/purego-cef/internal/capi" | |||
| 11 | + | ||||
| 12 | + | portin "github.com/bnema/purego-cef/internal/ports/in" | |||
| 13 | + | ) | |||
| 14 | + | ||||
| 15 | + | // ComponentUpdateCallback Callback structure for component update results. | |||
| 16 | + | type ComponentUpdateCallback = portin.ComponentUpdateCallback | |||
| 17 | + | ||||
| 18 | + | // componentUpdateCallbackWrapper wraps a user-provided ComponentUpdateCallback implementation together | |||
| 19 | + | // with the raw CEF struct pointer allocated by NewComponentUpdateCallback. It satisfies the | |||
| 20 | + | // ComponentUpdateCallback interface (by embedding the user impl) and core.RawPointerHolder | |||
| 21 | + | // (so extractRawPointer can recover the raw pointer). | |||
| 22 | + | type componentUpdateCallbackWrapper struct { | |||
| 23 | + | ComponentUpdateCallback // embed user impl for interface delegation | |||
| 24 | + | rawPtr *capi.CEFComponentUpdateCallbackT | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | func (w *componentUpdateCallbackWrapper) RawPointer() unsafe.Pointer { | |||
| 28 | + | return unsafe.Pointer(w.rawPtr) | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | var componentUpdateCallbackOnCompleteSharedOnce sync.Once | |||
| 32 | + | var componentUpdateCallbackOnCompleteSharedCallback uintptr | |||
| 33 | + | ||||
| 34 | + | func componentUpdateCallbackOnCompleteCEFCallback() uintptr { | |||
| 35 | + | return sharedCEFCallback(&componentUpdateCallbackOnCompleteSharedOnce, &componentUpdateCallbackOnCompleteSharedCallback, func(self uintptr, arg0 uintptr, arg1 uintptr) { | |||
| 36 | + | impl, ownerOK := cefCallbackOwnerAs[ComponentUpdateCallback](self) | |||
| 37 | + | if !ownerOK { | |||
| 38 | + | return | |||
| 39 | + | } | |||
| 40 | + | componentID := goString(unsafe.Pointer(arg0)) | |||
| 41 | + | error := ComponentUpdateError(arg1) | |||
| 42 | + | impl.OnComplete(componentID, error) | |||
| 43 | + | }) | |||
| 44 | + | } | |||
| 45 | + | ||||
| 46 | + | // NewComponentUpdateCallback creates a CEF handler backed by the given implementation. | |||
| 47 | + | func NewComponentUpdateCallback(impl ComponentUpdateCallback) ComponentUpdateCallback { | |||
| 48 | + | if isNilImpl(impl) { | |||
| 49 | + | return nil | |||
| 50 | + | } | |||
| 51 | + | r := new(capi.CEFComponentUpdateCallbackT) | |||
| 52 | + | w := &componentUpdateCallbackWrapper{rawPtr: r} | |||
| 53 | + | w.ComponentUpdateCallback = impl | |||
| 54 | + | initRefCount(unsafe.Pointer(r), unsafe.Sizeof(*r), w) | |||
| 55 | + | ||||
| 56 | + | r.OverrideOnComplete(componentUpdateCallbackOnCompleteCEFCallback()) | |||
| 57 | + | ||||
| 58 | + | return w | |||
| 59 | + | } | |||
| 60 | + | ||||
| 61 | + | // componentUpdateCallbackImpl is a reverse wrapper for a CEF-owned ComponentUpdateCallback pointer. | |||
| 62 | + | // Release is idempotent, but callers must externally synchronize Release with | |||
| 63 | + | // concurrent method calls and must not use the wrapper after Release returns. | |||
| 64 | + | type componentUpdateCallbackImpl struct { | |||
| 65 | + | rawPtr *capi.CEFComponentUpdateCallbackT | |||
| 66 | + | releaseOnce sync.Once | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | func (obj *componentUpdateCallbackImpl) OnComplete(componentID string, error ComponentUpdateError) { | |||
| 70 | + | if obj == nil || obj.rawPtr == nil { | |||
| 71 | + | return | |||
| 72 | + | } | |||
| 73 | + | rawPtr := obj.rawPtr | |||
| 74 | + | componentIDStr := cefString(componentID) | |||
| 75 | + | defer freeCefString(&componentIDStr) | |||
| 76 | + | rawPtr.CallOnComplete(uintptr(unsafe.Pointer(&componentIDStr)), uintptr(error)) | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | func (obj *componentUpdateCallbackImpl) RawPointer() unsafe.Pointer { | |||
| 80 | + | if obj == nil || obj.rawPtr == nil { | |||
| 81 | + | return nil | |||
| 82 | + | } | |||
| 83 | + | return unsafe.Pointer(obj.rawPtr) | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | // Release releases the underlying CEF object. | |||
| 87 | + | func (obj *componentUpdateCallbackImpl) Release() { | |||
| 88 | + | if obj == nil { | |||
| 89 | + | return | |||
| 90 | + | } | |||
| 91 | + | obj.releaseOnce.Do(func() { | |||
| 92 | + | if obj.rawPtr == nil { | |||
| 93 | + | return | |||
| 94 | + | } | |||
| 95 | + | rawPtr := obj.rawPtr | |||
| 96 | + | obj.rawPtr = nil | |||
| 97 | + | runtime.SetFinalizer(obj, nil) | |||
| 98 | + | base := (*capi.CEFBaseRefCountedT)(unsafe.Pointer(rawPtr)) | |||
| 99 | + | base.CallRelease() | |||
| 100 | + | }) | |||
| 101 | + | } | |||
| 102 | + | ||||
| 103 | + | // wrapComponentUpdateCallback wraps a CEF handler pointer received from CEF into a thin Go façade. | |||
| 104 | + | func wrapComponentUpdateCallback(ptr unsafe.Pointer) ComponentUpdateCallback { | |||
| 105 | + | if ptr == nil { | |||
| 106 | + | return nil | |||
| 107 | + | } | |||
| 108 | + | r := (*capi.CEFComponentUpdateCallbackT)(ptr) | |||
| 109 | + | base := (*capi.CEFBaseRefCountedT)(ptr) | |||
| 110 | + | base.CallAddRef() | |||
| 111 | + | impl := &componentUpdateCallbackImpl{rawPtr: r} | |||
| 112 | + | runtime.SetFinalizer(impl, (*componentUpdateCallbackImpl).Release) | |||
| 113 | + | return impl | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | // Component Structure representing a snapshot of a component's state at the time of retrieval. To get updated information, retrieve a new cef_component_t object via cef_component_updater_t::GetComponentByID or GetComponents. The functions of this structure may be called on any thread. | |||
| 117 | + | type Component = portin.Component | |||
| 118 | + | ||||
| 119 | + | // componentImpl is a reverse wrapper for a CEF-owned Component pointer. | |||
| 120 | + | // Release is idempotent, but callers must externally synchronize Release with | |||
| 121 | + | // concurrent method calls and must not use the wrapper after Release returns. | |||
| 122 | + | type componentImpl struct { | |||
| 123 | + | rawPtr *capi.CEFComponentT | |||
| 124 | + | releaseOnce sync.Once | |||
| 125 | + | } | |||
| 126 | + | ||||
| 127 | + | func (obj *componentImpl) GetID() string { | |||
| 128 | + | if obj == nil || obj.rawPtr == nil { | |||
| 129 | + | return "" | |||
| 130 | + | } | |||
| 131 | + | rawPtr := obj.rawPtr | |||
| 132 | + | ret := rawPtr.CallGetID() | |||
| 133 | + | return goStringUserfree(unsafe.Pointer(ret)) | |||
| 134 | + | } | |||
| 135 | + | ||||
| 136 | + | func (obj *componentImpl) GetName() string { | |||
| 137 | + | if obj == nil || obj.rawPtr == nil { | |||
| 138 | + | return "" | |||
| 139 | + | } | |||
| 140 | + | rawPtr := obj.rawPtr | |||
| 141 | + | ret := rawPtr.CallGetName() | |||
| 142 | + | return goStringUserfree(unsafe.Pointer(ret)) | |||
| 143 | + | } | |||
| 144 | + | ||||
| 145 | + | func (obj *componentImpl) GetVersion() string { | |||
| 146 | + | if obj == nil || obj.rawPtr == nil { | |||
| 147 | + | return "" | |||
| 148 | + | } | |||
| 149 | + | rawPtr := obj.rawPtr | |||
| 150 | + | ret := rawPtr.CallGetVersion() | |||
| 151 | + | return goStringUserfree(unsafe.Pointer(ret)) | |||
| 152 | + | } | |||
| 153 | + | ||||
| 154 | + | func (obj *componentImpl) GetState() ComponentState { | |||
| 155 | + | if obj == nil || obj.rawPtr == nil { | |||
| 156 | + | return 0 | |||
| 157 | + | } | |||
| 158 | + | rawPtr := obj.rawPtr | |||
| 159 | + | ret := rawPtr.CallGetState() | |||
| 160 | + | return ComponentState(ret) | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | func (obj *componentImpl) RawPointer() unsafe.Pointer { | |||
| 164 | + | if obj == nil || obj.rawPtr == nil { | |||
| 165 | + | return nil | |||
| 166 | + | } | |||
| 167 | + | return unsafe.Pointer(obj.rawPtr) | |||
| 168 | + | } | |||
| 169 | + | ||||
| 170 | + | // Release releases the underlying CEF object. | |||
| 171 | + | func (obj *componentImpl) Release() { | |||
| 172 | + | if obj == nil { | |||
| 173 | + | return | |||
| 174 | + | } | |||
| 175 | + | obj.releaseOnce.Do(func() { | |||
| 176 | + | if obj.rawPtr == nil { | |||
| 177 | + | return | |||
| 178 | + | } | |||
| 179 | + | rawPtr := obj.rawPtr | |||
| 180 | + | obj.rawPtr = nil | |||
| 181 | + | runtime.SetFinalizer(obj, nil) | |||
| 182 | + | base := (*capi.CEFBaseRefCountedT)(unsafe.Pointer(rawPtr)) | |||
| 183 | + | base.CallRelease() | |||
| 184 | + | }) | |||
| 185 | + | } | |||
| 186 | + | ||||
| 187 | + | func wrapComponent(ptr unsafe.Pointer) Component { | |||
| 188 | + | if ptr == nil { | |||
| 189 | + | return nil | |||
| 190 | + | } | |||
| 191 | + | r := (*capi.CEFComponentT)(ptr) | |||
| 192 | + | base := (*capi.CEFBaseRefCountedT)(ptr) | |||
| 193 | + | base.CallAddRef() | |||
| 194 | + | impl := &componentImpl{rawPtr: r} | |||
| 195 | + | runtime.SetFinalizer(impl, (*componentImpl).Release) | |||
| 196 | + | return impl | |||
| 197 | + | } | |||
| 198 | + | ||||
| 199 | + | // ComponentUpdater This structure provides access to Chromium's component updater service, allowing clients to discover registered components and trigger on-demand updates. The functions of this structure may only be called on the browser process UI thread. If the CEF context is not initialized or the component updater service is not available, functions will return safe defaults (0, nullptr, or NULL). | |||
| 200 | + | type ComponentUpdater = portin.ComponentUpdater | |||
| 201 | + | ||||
| 202 | + | // componentUpdaterImpl is a reverse wrapper for a CEF-owned ComponentUpdater pointer. | |||
| 203 | + | // Release is idempotent, but callers must externally synchronize Release with | |||
| 204 | + | // concurrent method calls and must not use the wrapper after Release returns. | |||
| 205 | + | type componentUpdaterImpl struct { | |||
| 206 | + | rawPtr *capi.CEFComponentUpdaterT | |||
| 207 | + | releaseOnce sync.Once | |||
| 208 | + | } | |||
| 209 | + | ||||
| 210 | + | func (obj *componentUpdaterImpl) GetComponentCount() int { | |||
| 211 | + | if obj == nil || obj.rawPtr == nil { | |||
| 212 | + | return 0 | |||
| 213 | + | } | |||
| 214 | + | rawPtr := obj.rawPtr | |||
| 215 | + | ret := rawPtr.CallGetComponentCount() | |||
| 216 | + | return int(ret) | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | func (obj *componentUpdaterImpl) GetComponents(componentscount *int, components []Component) { | |||
| 220 | + | if obj == nil || obj.rawPtr == nil { | |||
| 221 | + | return | |||
| 222 | + | } | |||
| 223 | + | rawPtr := obj.rawPtr | |||
| 224 | + | var componentsRaw []uintptr | |||
| 225 | + | var componentsPtr unsafe.Pointer | |||
| 226 | + | componentsCountPtr := componentscount | |||
| 227 | + | if componentsCountPtr == nil { | |||
| 228 | + | componentsCountScratch := len(components) | |||
| 229 | + | componentsCountPtr = &componentsCountScratch | |||
| 230 | + | } else { | |||
| 231 | + | *componentsCountPtr = len(components) | |||
| 232 | + | } | |||
| 233 | + | if len(components) > 0 { | |||
| 234 | + | componentsRaw = make([]uintptr, len(components)) | |||
| 235 | + | componentsPtr = unsafe.Pointer(&componentsRaw[0]) | |||
| 236 | + | } | |||
| 237 | + | rawPtr.CallGetComponents(uintptr(unsafe.Pointer(componentsCountPtr)), uintptr(componentsPtr)) | |||
| 238 | + | if len(components) > 0 { | |||
| 239 | + | n := len(components) | |||
| 240 | + | if *componentsCountPtr < n { | |||
| 241 | + | n = *componentsCountPtr | |||
| 242 | + | } | |||
| 243 | + | for i := 0; i < n; i++ { | |||
| 244 | + | components[i] = wrapComponent(unsafe.Pointer(componentsRaw[i])) | |||
| 245 | + | } | |||
| 246 | + | } | |||
| 247 | + | } | |||
| 248 | + | ||||
| 249 | + | func (obj *componentUpdaterImpl) GetComponentByID(componentID string) Component { | |||
| 250 | + | if obj == nil || obj.rawPtr == nil { | |||
| 251 | + | return nil | |||
| 252 | + | } | |||
| 253 | + | rawPtr := obj.rawPtr | |||
| 254 | + | componentIDStr := cefString(componentID) | |||
| 255 | + | defer freeCefString(&componentIDStr) | |||
| 256 | + | ret := rawPtr.CallGetComponentByID(uintptr(unsafe.Pointer(&componentIDStr))) | |||
| 257 | + | return wrapComponent(unsafe.Pointer(ret)) | |||
| 258 | + | } | |||
| 259 | + | ||||
| 260 | + | func (obj *componentUpdaterImpl) Update(componentID string, priority ComponentUpdatePriority, callback ComponentUpdateCallback) { | |||
| 261 | + | if obj == nil || obj.rawPtr == nil { | |||
| 262 | + | return | |||
| 263 | + | } | |||
| 264 | + | rawPtr := obj.rawPtr | |||
| 265 | + | componentIDStr := cefString(componentID) | |||
| 266 | + | defer freeCefString(&componentIDStr) | |||
| 267 | + | rawPtr.CallUpdate(uintptr(unsafe.Pointer(&componentIDStr)), uintptr(priority), uintptr(extractOrWrapRawPointer(callback, func() any { return NewComponentUpdateCallback(callback) }))) | |||
| 268 | + | } | |||
| 269 | + | ||||
| 270 | + | func (obj *componentUpdaterImpl) RawPointer() unsafe.Pointer { | |||
| 271 | + | if obj == nil || obj.rawPtr == nil { | |||
| 272 | + | return nil | |||
| 273 | + | } | |||
| 274 | + | return unsafe.Pointer(obj.rawPtr) | |||
| 275 | + | } | |||
| 276 | + | ||||
| 277 | + | // Release releases the underlying CEF object. | |||
| 278 | + | func (obj *componentUpdaterImpl) Release() { | |||
| 279 | + | if obj == nil { | |||
| 280 | + | return | |||
| 281 | + | } | |||
| 282 | + | obj.releaseOnce.Do(func() { | |||
| 283 | + | if obj.rawPtr == nil { | |||
| 284 | + | return | |||
| 285 | + | } | |||
| 286 | + | rawPtr := obj.rawPtr | |||
| 287 | + | obj.rawPtr = nil | |||
| 288 | + | runtime.SetFinalizer(obj, nil) | |||
| 289 | + | base := (*capi.CEFBaseRefCountedT)(unsafe.Pointer(rawPtr)) | |||
| 290 | + | base.CallRelease() | |||
| 291 | + | }) | |||
| 292 | + | } | |||
| 293 | + | ||||
| 294 | + | func wrapComponentUpdater(ptr unsafe.Pointer) ComponentUpdater { | |||
| 295 | + | if ptr == nil { | |||
| 296 | + | return nil | |||
| 297 | + | } | |||
| 298 | + | r := (*capi.CEFComponentUpdaterT)(ptr) | |||
| 299 | + | base := (*capi.CEFBaseRefCountedT)(ptr) | |||
| 300 | + | base.CallAddRef() | |||
| 301 | + | impl := &componentUpdaterImpl{rawPtr: r} | |||
| 302 | + | runtime.SetFinalizer(impl, (*componentUpdaterImpl).Release) | |||
| 303 | + | return impl | |||
| 304 | + | } | |||
| 305 | + | ||||
| 306 | + | // takeComponentUpdater adopts a CEF ComponentUpdater pointer whose reference is already owned by | |||
| 307 | + | // the caller (as returned by a global factory function). Unlike wrapComponentUpdater it | |||
| 308 | + | // does NOT call AddRef, because the C API already transferred one reference to us. | |||
| 309 | + | func takeComponentUpdater(ptr unsafe.Pointer) ComponentUpdater { | |||
| 310 | + | if ptr == nil { | |||
| 311 | + | return nil | |||
| 312 | + | } | |||
| 313 | + | r := (*capi.CEFComponentUpdaterT)(ptr) | |||
| 314 | + | impl := &componentUpdaterImpl{rawPtr: r} | |||
| 315 | + | runtime.SetFinalizer(impl, (*componentUpdaterImpl).Release) | |||
| 316 | + | return impl | |||
| 317 | + | } | |||
| 318 | + | ||||
| 319 | + | // ComponentUpdaterGet Returns the global cef_component_updater_t singleton. Returns nullptr if called from the incorrect thread. | |||
| 320 | + | func ComponentUpdaterGet() ComponentUpdater { | |||
| 321 | + | ret := capi.CEFComponentUpdaterGet() | |||
| 322 | + | return takeComponentUpdater(ret) | |||
| 323 | + | } | |||