feat(webkit): implement web process memory pressure settings
- commit
- e6cc2c80e6cc20d2258ea081f2044a848f3acea8
- author
- brice · · 2025-12-29 17:07 UTC
- committer
- brice · · 2025-12-29 17:28 UTC
- parent
- 6b96161
- signature
- unsigned
3 files · +70 -56
M internal/infrastructure/webkit/context.go +19 -30
| @@ -74,21 +74,29 @@ func NewWebKitContextWithOptions(ctx context.Context, opts port.WebKitContextOpt | |||
| 74 | 74 | return nil, fmt.Errorf("failed to init network session: %w", err) | |
| 75 | 75 | } | |
| 76 | 76 | ||
| 77 | - | // WebViews in this codebase are created via WebKit's default WebContext. | |
| 78 | - | // If we create a separate WebContext here and only register our custom | |
| 79 | - | // dumb:// scheme on it, internal pages will break with "The URL can't be shown". | |
| 80 | - | // | |
| 81 | - | // Additionally, WebKit's "memory-pressure-settings" is a construct-only property, | |
| 82 | - | // so setting it after `webkit.NewWebContext()` triggers a GLib critical and has no effect. | |
| 83 | - | // Until we can reliably construct a WebContext with memory-pressure-settings AND ensure | |
| 84 | - | // all WebViews use that context, we intentionally stick to the default WebContext. | |
| 77 | + | // Create WebContext - use custom constructor if memory pressure settings are configured | |
| 85 | 78 | if opts.IsWebProcessMemoryConfigured() { | |
| 86 | - | log.Warn().Msg("web process memory pressure settings requested but not applied (construct-only WebContext property; would break dumb:// scheme registration)") | |
| 79 | + | memSettings := buildMemoryPressureSettings(opts.WebProcessMemory) | |
| 80 | + | if memSettings != nil { | |
| 81 | + | wkCtx.webContext = webkit.NewWebContextWithMemoryPressureSettings(memSettings) | |
| 82 | + | if wkCtx.webContext != nil { | |
| 83 | + | log.Info(). | |
| 84 | + | Int("limit_mb", opts.WebProcessMemory.MemoryLimitMB). | |
| 85 | + | Float64("poll_sec", opts.WebProcessMemory.PollIntervalSec). | |
| 86 | + | Float64("conservative", opts.WebProcessMemory.ConservativeThreshold). | |
| 87 | + | Float64("strict", opts.WebProcessMemory.StrictThreshold). | |
| 88 | + | Float64("kill", opts.WebProcessMemory.KillThreshold). | |
| 89 | + | Msg("created WebContext with memory pressure settings") | |
| 90 | + | } | |
| 91 | + | } | |
| 87 | 92 | } | |
| 88 | 93 | ||
| 89 | - | wkCtx.webContext = webkit.WebContextGetDefault() | |
| 94 | + | // Fall back to default WebContext if custom creation failed or wasn't needed | |
| 95 | + | if wkCtx.webContext == nil { | |
| 96 | + | wkCtx.webContext = webkit.WebContextGetDefault() | |
| 97 | + | } | |
| 90 | 98 | if wkCtx.webContext == nil { | |
| 91 | - | return nil, fmt.Errorf("failed to get default WebContext") | |
| 99 | + | return nil, fmt.Errorf("failed to create or get WebContext") | |
| 92 | 100 | } | |
| 93 | 101 | ||
| 94 | 102 | // Set cache model for browser-style caching | |
| @@ -103,25 +111,6 @@ func NewWebKitContextWithOptions(ctx context.Context, opts port.WebKitContextOpt | |||
| 103 | 111 | return wkCtx, nil | |
| 104 | 112 | } | |
| 105 | 113 | ||
| 106 | - | // createWebContextWithMemorySettings creates a WebContext with memory-pressure-settings. | |
| 107 | - | // The memory-pressure-settings property is construct-only, so we need to set it at creation time. | |
| 108 | - | func createWebContextWithMemorySettings(settings *webkit.MemoryPressureSettings) *webkit.WebContext { | |
| 109 | - | if settings == nil { | |
| 110 | - | return nil | |
| 111 | - | } | |
| 112 | - | ||
| 113 | - | // Create a new WebContext and set memory pressure settings | |
| 114 | - | // Note: memory-pressure-settings is construct-only, but we try setting it via property | |
| 115 | - | // as an alternative to g_object_new_with_properties which may not work reliably | |
| 116 | - | ctx := webkit.NewWebContext() | |
| 117 | - | if ctx != nil { | |
| 118 | - | // Try to set the property - this may not work for construct-only properties | |
| 119 | - | // but it's the best we can do without lower-level GObject access | |
| 120 | - | ctx.SetPropertyMemoryPressureSettings(settings.GoPointer()) | |
| 121 | - | } | |
| 122 | - | return ctx | |
| 123 | - | } | |
| 124 | - | ||
| 125 | 114 | // initNetworkSession creates and configures the persistent network session. | |
| 126 | 115 | func (c *WebKitContext) initNetworkSession() error { | |
| 127 | 116 | // Create persistent network session | |
| @@ -74,21 +74,29 @@ func NewWebKitContextWithOptions(ctx context.Context, opts port.WebKitContextOpt | |||||
| 74 | return nil, fmt.Errorf("failed to init network session: %w", err) | 74 | return nil, fmt.Errorf("failed to init network session: %w", err) | ||
| 75 | } | 75 | } | ||
| 76 | 76 | ||||
| 77 | - | // WebViews in this codebase are created via WebKit's default WebContext. | 77 | + | // Create WebContext - use custom constructor if memory pressure settings are configured |
| 78 | - | // If we create a separate WebContext here and only register our custom | |||
| 79 | - | // dumb:// scheme on it, internal pages will break with "The URL can't be shown". | |||
| 80 | - | // | |||
| 81 | - | // Additionally, WebKit's "memory-pressure-settings" is a construct-only property, | |||
| 82 | - | // so setting it after `webkit.NewWebContext()` triggers a GLib critical and has no effect. | |||
| 83 | - | // Until we can reliably construct a WebContext with memory-pressure-settings AND ensure | |||
| 84 | - | // all WebViews use that context, we intentionally stick to the default WebContext. | |||
| 85 | if opts.IsWebProcessMemoryConfigured() { | 78 | if opts.IsWebProcessMemoryConfigured() { | ||
| 86 | - | log.Warn().Msg("web process memory pressure settings requested but not applied (construct-only WebContext property; would break dumb:// scheme registration)") | 79 | + | memSettings := buildMemoryPressureSettings(opts.WebProcessMemory) |
| 80 | + | if memSettings != nil { | |||
| 81 | + | wkCtx.webContext = webkit.NewWebContextWithMemoryPressureSettings(memSettings) | |||
| 82 | + | if wkCtx.webContext != nil { | |||
| 83 | + | log.Info(). | |||
| 84 | + | Int("limit_mb", opts.WebProcessMemory.MemoryLimitMB). | |||
| 85 | + | Float64("poll_sec", opts.WebProcessMemory.PollIntervalSec). | |||
| 86 | + | Float64("conservative", opts.WebProcessMemory.ConservativeThreshold). | |||
| 87 | + | Float64("strict", opts.WebProcessMemory.StrictThreshold). | |||
| 88 | + | Float64("kill", opts.WebProcessMemory.KillThreshold). | |||
| 89 | + | Msg("created WebContext with memory pressure settings") | |||
| 90 | + | } | |||
| 91 | + | } | |||
| 87 | } | 92 | } | ||
| 88 | 93 | ||||
| 89 | - | wkCtx.webContext = webkit.WebContextGetDefault() | 94 | + | // Fall back to default WebContext if custom creation failed or wasn't needed |
| 95 | + | if wkCtx.webContext == nil { | |||
| 96 | + | wkCtx.webContext = webkit.WebContextGetDefault() | |||
| 97 | + | } | |||
| 90 | if wkCtx.webContext == nil { | 98 | if wkCtx.webContext == nil { | ||
| 91 | - | return nil, fmt.Errorf("failed to get default WebContext") | 99 | + | return nil, fmt.Errorf("failed to create or get WebContext") |
| 92 | } | 100 | } | ||
| 93 | 101 | ||||
| 94 | // Set cache model for browser-style caching | 102 | // Set cache model for browser-style caching | ||
| @@ -103,25 +111,6 @@ func NewWebKitContextWithOptions(ctx context.Context, opts port.WebKitContextOpt | |||||
| 103 | return wkCtx, nil | 111 | return wkCtx, nil | ||
| 104 | } | 112 | } | ||
| 105 | 113 | ||||
| 106 | - | // createWebContextWithMemorySettings creates a WebContext with memory-pressure-settings. | |||
| 107 | - | // The memory-pressure-settings property is construct-only, so we need to set it at creation time. | |||
| 108 | - | func createWebContextWithMemorySettings(settings *webkit.MemoryPressureSettings) *webkit.WebContext { | |||
| 109 | - | if settings == nil { | |||
| 110 | - | return nil | |||
| 111 | - | } | |||
| 112 | - | ||||
| 113 | - | // Create a new WebContext and set memory pressure settings | |||
| 114 | - | // Note: memory-pressure-settings is construct-only, but we try setting it via property | |||
| 115 | - | // as an alternative to g_object_new_with_properties which may not work reliably | |||
| 116 | - | ctx := webkit.NewWebContext() | |||
| 117 | - | if ctx != nil { | |||
| 118 | - | // Try to set the property - this may not work for construct-only properties | |||
| 119 | - | // but it's the best we can do without lower-level GObject access | |||
| 120 | - | ctx.SetPropertyMemoryPressureSettings(settings.GoPointer()) | |||
| 121 | - | } | |||
| 122 | - | return ctx | |||
| 123 | - | } | |||
| 124 | - | ||||
| 125 | // initNetworkSession creates and configures the persistent network session. | 114 | // initNetworkSession creates and configures the persistent network session. | ||
| 126 | func (c *WebKitContext) initNetworkSession() error { | 115 | func (c *WebKitContext) initNetworkSession() error { | ||
| 127 | // Create persistent network session | 116 | // Create persistent network session | ||
M internal/infrastructure/webkit/scheme_handler.go +43 -8
| @@ -91,10 +91,20 @@ type configHardwarePayload struct { | |||
| 91 | 91 | ||
| 92 | 92 | type configPerformancePayload struct { | |
| 93 | 93 | Profile string `json:"profile"` | |
| 94 | + | Custom configCustomPerformance `json:"custom"` | |
| 94 | 95 | Resolved configResolvedPerformance `json:"resolved"` | |
| 95 | 96 | Hardware configHardwarePayload `json:"hardware"` | |
| 96 | 97 | } | |
| 97 | 98 | ||
| 99 | + | // configCustomPerformance holds user-editable fields for custom profile. | |
| 100 | + | type configCustomPerformance struct { | |
| 101 | + | SkiaCPUThreads int `json:"skia_cpu_threads"` | |
| 102 | + | SkiaGPUThreads int `json:"skia_gpu_threads"` | |
| 103 | + | WebProcessMemoryMB int `json:"web_process_memory_mb"` | |
| 104 | + | NetworkProcessMemoryMB int `json:"network_process_memory_mb"` | |
| 105 | + | WebViewPoolPrewarm int `json:"webview_pool_prewarm"` | |
| 106 | + | } | |
| 107 | + | ||
| 98 | 108 | // configResolvedPerformance shows the actual values that will be applied at startup. | |
| 99 | 109 | type configResolvedPerformance struct { | |
| 100 | 110 | SkiaCPUThreads int `json:"skia_cpu_threads"` | |
| @@ -199,6 +209,13 @@ func (h *DumbSchemeHandler) buildConfigResponse(cfg *config.Config) *SchemeRespo | |||
| 199 | 209 | }, | |
| 200 | 210 | Performance: configPerformancePayload{ | |
| 201 | 211 | Profile: string(cfg.Performance.Profile), | |
| 212 | + | Custom: configCustomPerformance{ | |
| 213 | + | SkiaCPUThreads: cfg.Performance.SkiaCPUPaintingThreads, | |
| 214 | + | SkiaGPUThreads: cfg.Performance.SkiaGPUPaintingThreads, | |
| 215 | + | WebProcessMemoryMB: cfg.Performance.WebProcessMemoryLimitMB, | |
| 216 | + | NetworkProcessMemoryMB: cfg.Performance.NetworkProcessMemoryLimitMB, | |
| 217 | + | WebViewPoolPrewarm: cfg.Performance.WebViewPoolPrewarmCount, | |
| 218 | + | }, | |
| 202 | 219 | Resolved: configResolvedPerformance{ | |
| 203 | 220 | SkiaCPUThreads: resolved.SkiaCPUPaintingThreads, | |
| 204 | 221 | SkiaGPUThreads: resolved.SkiaGPUPaintingThreads, | |
| @@ -494,6 +511,8 @@ func (h *DumbSchemeHandler) sendResponse(req *webkit.URISchemeRequest, response | |||
| 494 | 511 | } | |
| 495 | 512 | ||
| 496 | 513 | // RegisterWithContext registers the dumb:// scheme with a WebKitContext. | |
| 514 | + | // The scheme is always registered on the default WebContext to ensure WebViews | |
| 515 | + | // (which use the default WebContext) can load dumb:// URLs. | |
| 497 | 516 | func (h *DumbSchemeHandler) RegisterWithContext(wkCtx *WebKitContext) { | |
| 498 | 517 | if wkCtx == nil || wkCtx.Context() == nil { | |
| 499 | 518 | h.logger.Error().Msg("cannot register scheme: context is nil") | |
| @@ -504,15 +523,31 @@ func (h *DumbSchemeHandler) RegisterWithContext(wkCtx *WebKitContext) { | |||
| 504 | 523 | h.HandleRequest(reqPtr) | |
| 505 | 524 | }) | |
| 506 | 525 | ||
| 507 | - | wkCtx.Context().RegisterUriScheme("dumb", &callback, 0, nil) | |
| 526 | + | // Always register on the default WebContext since WebViews use it | |
| 527 | + | defaultCtx := webkit.WebContextGetDefault() | |
| 528 | + | if defaultCtx != nil { | |
| 529 | + | defaultCtx.RegisterUriScheme("dumb", &callback, 0, nil) | |
| 530 | + | ||
| 531 | + | // Mark scheme as local, secure, and CORS-enabled for proper security policies | |
| 532 | + | if secMgr := defaultCtx.GetSecurityManager(); secMgr != nil { | |
| 533 | + | secMgr.RegisterUriSchemeAsLocal("dumb") | |
| 534 | + | secMgr.RegisterUriSchemeAsSecure("dumb") | |
| 535 | + | secMgr.RegisterUriSchemeAsCorsEnabled("dumb") | |
| 536 | + | } | |
| 537 | + | h.logger.Debug().Msg("dumb:// scheme registered on default WebContext") | |
| 538 | + | } | |
| 508 | 539 | ||
| 509 | - | // Mark scheme as local, secure, and CORS-enabled for proper security policies | |
| 510 | - | secMgr := wkCtx.Context().GetSecurityManager() | |
| 511 | - | if secMgr != nil { | |
| 512 | - | secMgr.RegisterUriSchemeAsLocal("dumb") | |
| 513 | - | secMgr.RegisterUriSchemeAsSecure("dumb") | |
| 514 | - | secMgr.RegisterUriSchemeAsCorsEnabled("dumb") | |
| 515 | - | h.logger.Debug().Msg("dumb:// scheme registered as local, secure, and CORS-enabled") | |
| 540 | + | // Also register on custom WebContext if different from default | |
| 541 | + | customCtx := wkCtx.Context() | |
| 542 | + | if customCtx.GoPointer() != 0 && (defaultCtx == nil || customCtx.GoPointer() != defaultCtx.GoPointer()) { | |
| 543 | + | wkCtx.Context().RegisterUriScheme("dumb", &callback, 0, nil) | |
| 544 | + | ||
| 545 | + | if secMgr := customCtx.GetSecurityManager(); secMgr != nil { | |
| 546 | + | secMgr.RegisterUriSchemeAsLocal("dumb") | |
| 547 | + | secMgr.RegisterUriSchemeAsSecure("dumb") | |
| 548 | + | secMgr.RegisterUriSchemeAsCorsEnabled("dumb") | |
| 549 | + | } | |
| 550 | + | h.logger.Debug().Msg("dumb:// scheme also registered on custom WebContext") | |
| 516 | 551 | } | |
| 517 | 552 | ||
| 518 | 553 | h.logger.Info().Msg("dumb:// scheme registered") | |
| @@ -91,10 +91,20 @@ type configHardwarePayload struct { | |||||
| 91 | 91 | ||||
| 92 | type configPerformancePayload struct { | 92 | type configPerformancePayload struct { | ||
| 93 | Profile string `json:"profile"` | 93 | Profile string `json:"profile"` | ||
| 94 | + | Custom configCustomPerformance `json:"custom"` | |||
| 94 | Resolved configResolvedPerformance `json:"resolved"` | 95 | Resolved configResolvedPerformance `json:"resolved"` | ||
| 95 | Hardware configHardwarePayload `json:"hardware"` | 96 | Hardware configHardwarePayload `json:"hardware"` | ||
| 96 | } | 97 | } | ||
| 97 | 98 | ||||
| 99 | + | // configCustomPerformance holds user-editable fields for custom profile. | |||
| 100 | + | type configCustomPerformance struct { | |||
| 101 | + | SkiaCPUThreads int `json:"skia_cpu_threads"` | |||
| 102 | + | SkiaGPUThreads int `json:"skia_gpu_threads"` | |||
| 103 | + | WebProcessMemoryMB int `json:"web_process_memory_mb"` | |||
| 104 | + | NetworkProcessMemoryMB int `json:"network_process_memory_mb"` | |||
| 105 | + | WebViewPoolPrewarm int `json:"webview_pool_prewarm"` | |||
| 106 | + | } | |||
| 107 | + | ||||
| 98 | // configResolvedPerformance shows the actual values that will be applied at startup. | 108 | // configResolvedPerformance shows the actual values that will be applied at startup. | ||
| 99 | type configResolvedPerformance struct { | 109 | type configResolvedPerformance struct { | ||
| 100 | SkiaCPUThreads int `json:"skia_cpu_threads"` | 110 | SkiaCPUThreads int `json:"skia_cpu_threads"` | ||
| @@ -199,6 +209,13 @@ func (h *DumbSchemeHandler) buildConfigResponse(cfg *config.Config) *SchemeRespo | |||||
| 199 | }, | 209 | }, | ||
| 200 | Performance: configPerformancePayload{ | 210 | Performance: configPerformancePayload{ | ||
| 201 | Profile: string(cfg.Performance.Profile), | 211 | Profile: string(cfg.Performance.Profile), | ||
| 212 | + | Custom: configCustomPerformance{ | |||
| 213 | + | SkiaCPUThreads: cfg.Performance.SkiaCPUPaintingThreads, | |||
| 214 | + | SkiaGPUThreads: cfg.Performance.SkiaGPUPaintingThreads, | |||
| 215 | + | WebProcessMemoryMB: cfg.Performance.WebProcessMemoryLimitMB, | |||
| 216 | + | NetworkProcessMemoryMB: cfg.Performance.NetworkProcessMemoryLimitMB, | |||
| 217 | + | WebViewPoolPrewarm: cfg.Performance.WebViewPoolPrewarmCount, | |||
| 218 | + | }, | |||
| 202 | Resolved: configResolvedPerformance{ | 219 | Resolved: configResolvedPerformance{ | ||
| 203 | SkiaCPUThreads: resolved.SkiaCPUPaintingThreads, | 220 | SkiaCPUThreads: resolved.SkiaCPUPaintingThreads, | ||
| 204 | SkiaGPUThreads: resolved.SkiaGPUPaintingThreads, | 221 | SkiaGPUThreads: resolved.SkiaGPUPaintingThreads, | ||
| @@ -494,6 +511,8 @@ func (h *DumbSchemeHandler) sendResponse(req *webkit.URISchemeRequest, response | |||||
| 494 | } | 511 | } | ||
| 495 | 512 | ||||
| 496 | // RegisterWithContext registers the dumb:// scheme with a WebKitContext. | 513 | // RegisterWithContext registers the dumb:// scheme with a WebKitContext. | ||
| 514 | + | // The scheme is always registered on the default WebContext to ensure WebViews | |||
| 515 | + | // (which use the default WebContext) can load dumb:// URLs. | |||
| 497 | func (h *DumbSchemeHandler) RegisterWithContext(wkCtx *WebKitContext) { | 516 | func (h *DumbSchemeHandler) RegisterWithContext(wkCtx *WebKitContext) { | ||
| 498 | if wkCtx == nil || wkCtx.Context() == nil { | 517 | if wkCtx == nil || wkCtx.Context() == nil { | ||
| 499 | h.logger.Error().Msg("cannot register scheme: context is nil") | 518 | h.logger.Error().Msg("cannot register scheme: context is nil") | ||
| @@ -504,15 +523,31 @@ func (h *DumbSchemeHandler) RegisterWithContext(wkCtx *WebKitContext) { | |||||
| 504 | h.HandleRequest(reqPtr) | 523 | h.HandleRequest(reqPtr) | ||
| 505 | }) | 524 | }) | ||
| 506 | 525 | ||||
| 507 | - | wkCtx.Context().RegisterUriScheme("dumb", &callback, 0, nil) | 526 | + | // Always register on the default WebContext since WebViews use it |
| 527 | + | defaultCtx := webkit.WebContextGetDefault() | |||
| 528 | + | if defaultCtx != nil { | |||
| 529 | + | defaultCtx.RegisterUriScheme("dumb", &callback, 0, nil) | |||
| 530 | + | ||||
| 531 | + | // Mark scheme as local, secure, and CORS-enabled for proper security policies | |||
| 532 | + | if secMgr := defaultCtx.GetSecurityManager(); secMgr != nil { | |||
| 533 | + | secMgr.RegisterUriSchemeAsLocal("dumb") | |||
| 534 | + | secMgr.RegisterUriSchemeAsSecure("dumb") | |||
| 535 | + | secMgr.RegisterUriSchemeAsCorsEnabled("dumb") | |||
| 536 | + | } | |||
| 537 | + | h.logger.Debug().Msg("dumb:// scheme registered on default WebContext") | |||
| 538 | + | } | |||
| 508 | 539 | ||||
| 509 | - | // Mark scheme as local, secure, and CORS-enabled for proper security policies | 540 | + | // Also register on custom WebContext if different from default |
| 510 | - | secMgr := wkCtx.Context().GetSecurityManager() | 541 | + | customCtx := wkCtx.Context() |
| 511 | - | if secMgr != nil { | 542 | + | if customCtx.GoPointer() != 0 && (defaultCtx == nil || customCtx.GoPointer() != defaultCtx.GoPointer()) { |
| 512 | - | secMgr.RegisterUriSchemeAsLocal("dumb") | 543 | + | wkCtx.Context().RegisterUriScheme("dumb", &callback, 0, nil) |
| 513 | - | secMgr.RegisterUriSchemeAsSecure("dumb") | 544 | + | |
| 514 | - | secMgr.RegisterUriSchemeAsCorsEnabled("dumb") | 545 | + | if secMgr := customCtx.GetSecurityManager(); secMgr != nil { |
| 515 | - | h.logger.Debug().Msg("dumb:// scheme registered as local, secure, and CORS-enabled") | 546 | + | secMgr.RegisterUriSchemeAsLocal("dumb") |
| 547 | + | secMgr.RegisterUriSchemeAsSecure("dumb") | |||
| 548 | + | secMgr.RegisterUriSchemeAsCorsEnabled("dumb") | |||
| 549 | + | } | |||
| 550 | + | h.logger.Debug().Msg("dumb:// scheme also registered on custom WebContext") | |||
| 516 | } | 551 | } | ||
| 517 | 552 | ||||
| 518 | h.logger.Info().Msg("dumb:// scheme registered") | 553 | h.logger.Info().Msg("dumb:// scheme registered") | ||
M internal/infrastructure/webkit/webview.go +8 -18
| @@ -248,25 +248,15 @@ func NewWebView(ctx context.Context, wkCtx *WebKitContext, settings *SettingsMan | |||
| 248 | 248 | return nil, fmt.Errorf("webkit context not initialized") | |
| 249 | 249 | } | |
| 250 | 250 | ||
| 251 | - | // Use NetworkSession-aware constructor for persistent cookie/storage. | |
| 252 | - | inner := webkit.NewWebViewWithNetworkSession(wkCtx.NetworkSession()) | |
| 251 | + | // Use options constructor to pass both WebContext and NetworkSession. | |
| 252 | + | // This ensures WebViews use our custom WebContext (with memory pressure settings) | |
| 253 | + | // and the persistent NetworkSession for cookie/storage. | |
| 254 | + | inner := webkit.NewWebViewWithOptions(&webkit.WebViewOptions{ | |
| 255 | + | WebContext: wkCtx.Context(), | |
| 256 | + | NetworkSession: wkCtx.NetworkSession(), | |
| 257 | + | }) | |
| 253 | 258 | if inner == nil { | |
| 254 | - | return nil, fmt.Errorf("failed to create webkit webview with network session") | |
| 255 | - | } | |
| 256 | - | ||
| 257 | - | // Sanity check: our internal dumb:// scheme must be registered on the same WebContext | |
| 258 | - | // that the WebView is actually using. | |
| 259 | - | if expectedCtx := wkCtx.Context(); expectedCtx != nil { | |
| 260 | - | if actualCtx := inner.GetContext(); actualCtx != nil { | |
| 261 | - | expectedPtr := expectedCtx.GoPointer() | |
| 262 | - | actualPtr := actualCtx.GoPointer() | |
| 263 | - | if expectedPtr != 0 && actualPtr != 0 && expectedPtr != actualPtr { | |
| 264 | - | log.Warn(). | |
| 265 | - | Uint64("expected_web_context", uint64(expectedPtr)). | |
| 266 | - | Uint64("actual_web_context", uint64(actualPtr)). | |
| 267 | - | Msg("webview WebContext mismatch; internal dumb:// pages may fail") | |
| 268 | - | } | |
| 269 | - | } | |
| 259 | + | return nil, fmt.Errorf("failed to create webkit webview with options") | |
| 270 | 260 | } | |
| 271 | 261 | ||
| 272 | 262 | // Set background color IMMEDIATELY after creation, before any other operations. | |
| @@ -248,25 +248,15 @@ func NewWebView(ctx context.Context, wkCtx *WebKitContext, settings *SettingsMan | |||||
| 248 | return nil, fmt.Errorf("webkit context not initialized") | 248 | return nil, fmt.Errorf("webkit context not initialized") | ||
| 249 | } | 249 | } | ||
| 250 | 250 | ||||
| 251 | - | // Use NetworkSession-aware constructor for persistent cookie/storage. | 251 | + | // Use options constructor to pass both WebContext and NetworkSession. |
| 252 | - | inner := webkit.NewWebViewWithNetworkSession(wkCtx.NetworkSession()) | 252 | + | // This ensures WebViews use our custom WebContext (with memory pressure settings) |
| 253 | + | // and the persistent NetworkSession for cookie/storage. | |||
| 254 | + | inner := webkit.NewWebViewWithOptions(&webkit.WebViewOptions{ | |||
| 255 | + | WebContext: wkCtx.Context(), | |||
| 256 | + | NetworkSession: wkCtx.NetworkSession(), | |||
| 257 | + | }) | |||
| 253 | if inner == nil { | 258 | if inner == nil { | ||
| 254 | - | return nil, fmt.Errorf("failed to create webkit webview with network session") | 259 | + | return nil, fmt.Errorf("failed to create webkit webview with options") |
| 255 | - | } | |||
| 256 | - | ||||
| 257 | - | // Sanity check: our internal dumb:// scheme must be registered on the same WebContext | |||
| 258 | - | // that the WebView is actually using. | |||
| 259 | - | if expectedCtx := wkCtx.Context(); expectedCtx != nil { | |||
| 260 | - | if actualCtx := inner.GetContext(); actualCtx != nil { | |||
| 261 | - | expectedPtr := expectedCtx.GoPointer() | |||
| 262 | - | actualPtr := actualCtx.GoPointer() | |||
| 263 | - | if expectedPtr != 0 && actualPtr != 0 && expectedPtr != actualPtr { | |||
| 264 | - | log.Warn(). | |||
| 265 | - | Uint64("expected_web_context", uint64(expectedPtr)). | |||
| 266 | - | Uint64("actual_web_context", uint64(actualPtr)). | |||
| 267 | - | Msg("webview WebContext mismatch; internal dumb:// pages may fail") | |||
| 268 | - | } | |||
| 269 | - | } | |||
| 270 | } | 260 | } | ||
| 271 | 261 | ||||
| 272 | // Set background color IMMEDIATELY after creation, before any other operations. | 262 | // Set background color IMMEDIATELY after creation, before any other operations. | ||