feat(webkit): implement persistent storage for cookies and session data
- Add NetworkSession management with idempotent initialization - Add DataDir/CacheDir to webkit.Config - Configure CookieManager on WebView's actual session - Store window reference in WebView for proper lifecycle - Verify session is non-ephemeral before use
- commit
- 8d33e03aa2a255225e9822b83868e8020db734a4
- author
- bamen · · 2025-10-08 18:54 UTC
- committer
- bamen · · 2025-10-08 17:47 UTC
- parent
- 7a8b759
- signature
- unsigned
3 files · +161 -17
A pkg/webkit/network_session.go +99 -0
| @@ -0,0 +1,99 @@ | |||
| 1 | + | package webkit | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "fmt" | |
| 5 | + | "log" | |
| 6 | + | ||
| 7 | + | webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | var ( | |
| 11 | + | // globalNetworkSession holds the persistent network session to prevent garbage collection | |
| 12 | + | // This must be kept alive for the entire application lifetime | |
| 13 | + | globalNetworkSession *webkit.NetworkSession | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // InitPersistentSession creates and initializes a persistent NetworkSession | |
| 17 | + | // with the provided data and cache directories. | |
| 18 | + | // This should be called before creating any WebViews to ensure they use persistent storage. | |
| 19 | + | // Returns an error if initialization fails. | |
| 20 | + | // | |
| 21 | + | // IMPORTANT: Once created, this session is stored globally and will be used by all | |
| 22 | + | // WebViews created afterward, as per WebKitGTK 6.0's design where the first created | |
| 23 | + | // session becomes the default for the network process. | |
| 24 | + | func InitPersistentSession(dataDir, cacheDir string) error { | |
| 25 | + | // If session already exists, return success (idempotent) | |
| 26 | + | // This ensures we only create ONE session for the entire application | |
| 27 | + | if globalNetworkSession != nil { | |
| 28 | + | log.Printf("[webkit] Using existing persistent network session") | |
| 29 | + | return nil | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | // Create persistent network session using gotk4 API | |
| 33 | + | // Per WebKitGTK 6.0 docs: "The first WebKitNetworkSession created becomes the default" | |
| 34 | + | session := webkit.NewNetworkSession(dataDir, cacheDir) | |
| 35 | + | if session == nil { | |
| 36 | + | return fmt.Errorf("failed to create persistent network session") | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | // Store the session globally to prevent garbage collection | |
| 40 | + | // This is CRITICAL - if the session is GC'd, WebKit falls back to ephemeral storage | |
| 41 | + | globalNetworkSession = session | |
| 42 | + | ||
| 43 | + | log.Printf("[webkit] Persistent network session created: data=%s, cache=%s", dataDir, cacheDir) | |
| 44 | + | ||
| 45 | + | // Verify the session is not ephemeral | |
| 46 | + | if session.IsEphemeral() { | |
| 47 | + | return fmt.Errorf("created session is ephemeral despite providing data directories") | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | // Verify the WebsiteDataManager is not ephemeral | |
| 51 | + | websiteDataManager := session.WebsiteDataManager() | |
| 52 | + | if websiteDataManager == nil { | |
| 53 | + | return fmt.Errorf("failed to get website data manager from network session") | |
| 54 | + | } | |
| 55 | + | if websiteDataManager.IsEphemeral() { | |
| 56 | + | return fmt.Errorf("website data manager is ephemeral despite providing data directories") | |
| 57 | + | } | |
| 58 | + | log.Printf("[webkit] WebsiteDataManager verified as non-ephemeral") | |
| 59 | + | ||
| 60 | + | // Configure CookieManager for persistent cookie storage | |
| 61 | + | // This is REQUIRED - without this, cookies are not persisted to disk | |
| 62 | + | cookieManager := session.CookieManager() | |
| 63 | + | if cookieManager == nil { | |
| 64 | + | return fmt.Errorf("failed to get cookie manager from network session") | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | // Set cookie persistent storage to SQLite database | |
| 68 | + | // Format: filepath.Join(dataDir, "cookies.db") | |
| 69 | + | cookiePath := dataDir + "/cookies.db" | |
| 70 | + | cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite) | |
| 71 | + | ||
| 72 | + | // Set cookie accept policy to no third-party (default) | |
| 73 | + | cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty) | |
| 74 | + | ||
| 75 | + | log.Printf("[webkit] Cookie manager configured: storage=%s, policy=no-third-party", cookiePath) | |
| 76 | + | ||
| 77 | + | // Enable persistent credential storage (HTTP auth, etc.) | |
| 78 | + | session.SetPersistentCredentialStorageEnabled(true) | |
| 79 | + | ||
| 80 | + | // Verify this is indeed the default session now | |
| 81 | + | defaultSession := webkit.NetworkSessionGetDefault() | |
| 82 | + | if defaultSession == nil { | |
| 83 | + | return fmt.Errorf("default network session is nil after creating persistent session") | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | // Check if they're the same session by comparing ephemeral status | |
| 87 | + | if defaultSession.IsEphemeral() { | |
| 88 | + | return fmt.Errorf("default network session is ephemeral after creating persistent session") | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | log.Printf("[webkit] Network session verified as persistent and set as default") | |
| 92 | + | return nil | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | // GetGlobalNetworkSession returns the global network session. | |
| 96 | + | // Returns nil if InitPersistentSession hasn't been called successfully. | |
| 97 | + | func GetGlobalNetworkSession() *webkit.NetworkSession { | |
| 98 | + | return globalNetworkSession | |
| 99 | + | } | |
| @@ -0,0 +1,99 @@ | |||||
| 1 | + | package webkit | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "fmt" | |||
| 5 | + | "log" | |||
| 6 | + | ||||
| 7 | + | webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | var ( | |||
| 11 | + | // globalNetworkSession holds the persistent network session to prevent garbage collection | |||
| 12 | + | // This must be kept alive for the entire application lifetime | |||
| 13 | + | globalNetworkSession *webkit.NetworkSession | |||
| 14 | + | ) | |||
| 15 | + | ||||
| 16 | + | // InitPersistentSession creates and initializes a persistent NetworkSession | |||
| 17 | + | // with the provided data and cache directories. | |||
| 18 | + | // This should be called before creating any WebViews to ensure they use persistent storage. | |||
| 19 | + | // Returns an error if initialization fails. | |||
| 20 | + | // | |||
| 21 | + | // IMPORTANT: Once created, this session is stored globally and will be used by all | |||
| 22 | + | // WebViews created afterward, as per WebKitGTK 6.0's design where the first created | |||
| 23 | + | // session becomes the default for the network process. | |||
| 24 | + | func InitPersistentSession(dataDir, cacheDir string) error { | |||
| 25 | + | // If session already exists, return success (idempotent) | |||
| 26 | + | // This ensures we only create ONE session for the entire application | |||
| 27 | + | if globalNetworkSession != nil { | |||
| 28 | + | log.Printf("[webkit] Using existing persistent network session") | |||
| 29 | + | return nil | |||
| 30 | + | } | |||
| 31 | + | ||||
| 32 | + | // Create persistent network session using gotk4 API | |||
| 33 | + | // Per WebKitGTK 6.0 docs: "The first WebKitNetworkSession created becomes the default" | |||
| 34 | + | session := webkit.NewNetworkSession(dataDir, cacheDir) | |||
| 35 | + | if session == nil { | |||
| 36 | + | return fmt.Errorf("failed to create persistent network session") | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | // Store the session globally to prevent garbage collection | |||
| 40 | + | // This is CRITICAL - if the session is GC'd, WebKit falls back to ephemeral storage | |||
| 41 | + | globalNetworkSession = session | |||
| 42 | + | ||||
| 43 | + | log.Printf("[webkit] Persistent network session created: data=%s, cache=%s", dataDir, cacheDir) | |||
| 44 | + | ||||
| 45 | + | // Verify the session is not ephemeral | |||
| 46 | + | if session.IsEphemeral() { | |||
| 47 | + | return fmt.Errorf("created session is ephemeral despite providing data directories") | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | // Verify the WebsiteDataManager is not ephemeral | |||
| 51 | + | websiteDataManager := session.WebsiteDataManager() | |||
| 52 | + | if websiteDataManager == nil { | |||
| 53 | + | return fmt.Errorf("failed to get website data manager from network session") | |||
| 54 | + | } | |||
| 55 | + | if websiteDataManager.IsEphemeral() { | |||
| 56 | + | return fmt.Errorf("website data manager is ephemeral despite providing data directories") | |||
| 57 | + | } | |||
| 58 | + | log.Printf("[webkit] WebsiteDataManager verified as non-ephemeral") | |||
| 59 | + | ||||
| 60 | + | // Configure CookieManager for persistent cookie storage | |||
| 61 | + | // This is REQUIRED - without this, cookies are not persisted to disk | |||
| 62 | + | cookieManager := session.CookieManager() | |||
| 63 | + | if cookieManager == nil { | |||
| 64 | + | return fmt.Errorf("failed to get cookie manager from network session") | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | // Set cookie persistent storage to SQLite database | |||
| 68 | + | // Format: filepath.Join(dataDir, "cookies.db") | |||
| 69 | + | cookiePath := dataDir + "/cookies.db" | |||
| 70 | + | cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite) | |||
| 71 | + | ||||
| 72 | + | // Set cookie accept policy to no third-party (default) | |||
| 73 | + | cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty) | |||
| 74 | + | ||||
| 75 | + | log.Printf("[webkit] Cookie manager configured: storage=%s, policy=no-third-party", cookiePath) | |||
| 76 | + | ||||
| 77 | + | // Enable persistent credential storage (HTTP auth, etc.) | |||
| 78 | + | session.SetPersistentCredentialStorageEnabled(true) | |||
| 79 | + | ||||
| 80 | + | // Verify this is indeed the default session now | |||
| 81 | + | defaultSession := webkit.NetworkSessionGetDefault() | |||
| 82 | + | if defaultSession == nil { | |||
| 83 | + | return fmt.Errorf("default network session is nil after creating persistent session") | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | // Check if they're the same session by comparing ephemeral status | |||
| 87 | + | if defaultSession.IsEphemeral() { | |||
| 88 | + | return fmt.Errorf("default network session is ephemeral after creating persistent session") | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | log.Printf("[webkit] Network session verified as persistent and set as default") | |||
| 92 | + | return nil | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | // GetGlobalNetworkSession returns the global network session. | |||
| 96 | + | // Returns nil if InitPersistentSession hasn't been called successfully. | |||
| 97 | + | func GetGlobalNetworkSession() *webkit.NetworkSession { | |||
| 98 | + | return globalNetworkSession | |||
| 99 | + | } | |||
M pkg/webkit/types.go +6 -0
| @@ -44,6 +44,12 @@ type Config struct { | |||
| 44 | 44 | ||
| 45 | 45 | // MinimumFontSize in pixels | |
| 46 | 46 | MinimumFontSize int | |
| 47 | + | ||
| 48 | + | // DataDir is the directory for persistent data (cookies, localStorage, etc.) | |
| 49 | + | DataDir string | |
| 50 | + | ||
| 51 | + | // CacheDir is the directory for HTTP cache | |
| 52 | + | CacheDir string | |
| 47 | 53 | } | |
| 48 | 54 | ||
| 49 | 55 | // GetDefaultConfig returns a Config with sensible defaults | |
| @@ -44,6 +44,12 @@ type Config struct { | |||||
| 44 | 44 | ||||
| 45 | // MinimumFontSize in pixels | 45 | // MinimumFontSize in pixels | ||
| 46 | MinimumFontSize int | 46 | MinimumFontSize int | ||
| 47 | + | ||||
| 48 | + | // DataDir is the directory for persistent data (cookies, localStorage, etc.) | |||
| 49 | + | DataDir string | |||
| 50 | + | ||||
| 51 | + | // CacheDir is the directory for HTTP cache | |||
| 52 | + | CacheDir string | |||
| 47 | } | 53 | } | ||
| 48 | 54 | ||||
| 49 | // GetDefaultConfig returns a Config with sensible defaults | 55 | // GetDefaultConfig returns a Config with sensible defaults | ||
M pkg/webkit/webview.go +56 -17
| @@ -18,8 +18,9 @@ var ( | |||
| 18 | 18 | ||
| 19 | 19 | // WebView wraps a WebKitGTK WebView | |
| 20 | 20 | type WebView struct { | |
| 21 | - | view *webkit.WebView | |
| 22 | - | id uint64 | |
| 21 | + | view *webkit.WebView | |
| 22 | + | window *Window | |
| 23 | + | id uint64 | |
| 23 | 24 | ||
| 24 | 25 | // State | |
| 25 | 26 | config *Config | |
| @@ -47,17 +48,55 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 47 | 48 | ||
| 48 | 49 | InitMainThread() | |
| 49 | 50 | ||
| 50 | - | // Create WebKitGTK WebView | |
| 51 | + | // Create GTK Window | |
| 52 | + | window, err := NewWindow("Dumb Browser") | |
| 53 | + | if err != nil { | |
| 54 | + | return nil, err | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | // Initialize persistent session - REQUIRED, no ephemeral fallback | |
| 58 | + | // This must be done BEFORE creating the WebView | |
| 59 | + | if cfg.DataDir == "" || cfg.CacheDir == "" { | |
| 60 | + | return nil, fmt.Errorf("data and cache directories are required for persistent storage") | |
| 61 | + | } | |
| 62 | + | ||
| 63 | + | if err := InitPersistentSession(cfg.DataDir, cfg.CacheDir); err != nil { | |
| 64 | + | return nil, fmt.Errorf("failed to initialize persistent storage: %w", err) | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | // Create WebView - it will automatically use the persistent session | |
| 68 | + | // Per WebKitGTK 6.0: newly created session becomes the default for all WebViews | |
| 51 | 69 | wkView := webkit.NewWebView() | |
| 52 | 70 | if wkView == nil { | |
| 53 | 71 | return nil, ErrWebViewNotInitialized | |
| 54 | 72 | } | |
| 55 | 73 | ||
| 74 | + | // Verify the WebView is using the persistent session | |
| 75 | + | viewSession := wkView.NetworkSession() | |
| 76 | + | if viewSession == nil { | |
| 77 | + | return nil, fmt.Errorf("WebView has no network session") | |
| 78 | + | } | |
| 79 | + | if viewSession.IsEphemeral() { | |
| 80 | + | return nil, fmt.Errorf("WebView is using ephemeral session despite persistent session initialization") | |
| 81 | + | } | |
| 82 | + | ||
| 83 | + | // CRITICAL: Configure CookieManager on the WebView's actual session | |
| 84 | + | // This ensures cookies are persisted regardless of whether the WebView | |
| 85 | + | // uses our global session or creates its own | |
| 86 | + | cookieManager := viewSession.CookieManager() | |
| 87 | + | if cookieManager == nil { | |
| 88 | + | return nil, fmt.Errorf("failed to get cookie manager from WebView's network session") | |
| 89 | + | } | |
| 90 | + | cookiePath := cfg.DataDir + "/cookies.db" | |
| 91 | + | cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite) | |
| 92 | + | cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty) | |
| 93 | + | ||
| 56 | 94 | // Generate unique ID | |
| 57 | 95 | id := atomic.AddUint64(&viewIDCounter, 1) | |
| 58 | 96 | ||
| 59 | 97 | wv := &WebView{ | |
| 60 | 98 | view: wkView, | |
| 99 | + | window: window, | |
| 61 | 100 | id: id, | |
| 62 | 101 | config: cfg, | |
| 63 | 102 | } | |
| @@ -70,6 +109,9 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||
| 70 | 109 | // Setup event handlers | |
| 71 | 110 | wv.setupEventHandlers() | |
| 72 | 111 | ||
| 112 | + | // Add WebView as child of window | |
| 113 | + | window.SetChild(wkView) | |
| 114 | + | ||
| 73 | 115 | // Register in global registry | |
| 74 | 116 | viewMu.Lock() | |
| 75 | 117 | viewRegistry[id] = wv | |
| @@ -107,6 +149,9 @@ func (w *WebView) applyConfig() error { | |||
| 107 | 149 | settings.SetUserAgent(w.config.UserAgent) | |
| 108 | 150 | } | |
| 109 | 151 | ||
| 152 | + | // Enable developer tools (F12, inspector) | |
| 153 | + | settings.SetEnableDeveloperExtras(true) | |
| 154 | + | ||
| 110 | 155 | // Enable hardware acceleration if configured | |
| 111 | 156 | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlways) | |
| 112 | 157 | ||
| @@ -246,7 +291,14 @@ func (w *WebView) Show() error { | |||
| 246 | 291 | return ErrWebViewDestroyed | |
| 247 | 292 | } | |
| 248 | 293 | ||
| 294 | + | // Make the WebView widget visible | |
| 249 | 295 | w.view.SetVisible(true) | |
| 296 | + | ||
| 297 | + | // Show the window containing the WebView | |
| 298 | + | if w.window != nil { | |
| 299 | + | w.window.Show() | |
| 300 | + | } | |
| 301 | + | ||
| 250 | 302 | return nil | |
| 251 | 303 | } | |
| 252 | 304 | ||
| @@ -543,20 +595,7 @@ func (w *WebView) Window() *Window { | |||
| 543 | 595 | return nil | |
| 544 | 596 | } | |
| 545 | 597 | ||
| 546 | - | // Get the root window | |
| 547 | - | widget := getWidget(w.view) | |
| 548 | - | if widget != nil { | |
| 549 | - | root := widget.Root() | |
| 550 | - | if root != nil { | |
| 551 | - | // Try to cast the native widget to a gtk.Window | |
| 552 | - | if obj := root.Cast(); obj != nil { | |
| 553 | - | if gtkWin, ok := obj.(*gtk.Window); ok { | |
| 554 | - | return &Window{win: gtkWin} | |
| 555 | - | } | |
| 556 | - | } | |
| 557 | - | } | |
| 558 | - | } | |
| 559 | - | return nil | |
| 598 | + | return w.window | |
| 560 | 599 | } | |
| 561 | 600 | ||
| 562 | 601 | // UpdateContentFilters updates the content filtering rules | |
| @@ -18,8 +18,9 @@ var ( | |||||
| 18 | 18 | ||||
| 19 | // WebView wraps a WebKitGTK WebView | 19 | // WebView wraps a WebKitGTK WebView | ||
| 20 | type WebView struct { | 20 | type WebView struct { | ||
| 21 | - | view *webkit.WebView | 21 | + | view *webkit.WebView |
| 22 | - | id uint64 | 22 | + | window *Window |
| 23 | + | id uint64 | |||
| 23 | 24 | ||||
| 24 | // State | 25 | // State | ||
| 25 | config *Config | 26 | config *Config | ||
| @@ -47,17 +48,55 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 47 | 48 | ||||
| 48 | InitMainThread() | 49 | InitMainThread() | ||
| 49 | 50 | ||||
| 50 | - | // Create WebKitGTK WebView | 51 | + | // Create GTK Window |
| 52 | + | window, err := NewWindow("Dumb Browser") | |||
| 53 | + | if err != nil { | |||
| 54 | + | return nil, err | |||
| 55 | + | } | |||
| 56 | + | ||||
| 57 | + | // Initialize persistent session - REQUIRED, no ephemeral fallback | |||
| 58 | + | // This must be done BEFORE creating the WebView | |||
| 59 | + | if cfg.DataDir == "" || cfg.CacheDir == "" { | |||
| 60 | + | return nil, fmt.Errorf("data and cache directories are required for persistent storage") | |||
| 61 | + | } | |||
| 62 | + | ||||
| 63 | + | if err := InitPersistentSession(cfg.DataDir, cfg.CacheDir); err != nil { | |||
| 64 | + | return nil, fmt.Errorf("failed to initialize persistent storage: %w", err) | |||
| 65 | + | } | |||
| 66 | + | ||||
| 67 | + | // Create WebView - it will automatically use the persistent session | |||
| 68 | + | // Per WebKitGTK 6.0: newly created session becomes the default for all WebViews | |||
| 51 | wkView := webkit.NewWebView() | 69 | wkView := webkit.NewWebView() | ||
| 52 | if wkView == nil { | 70 | if wkView == nil { | ||
| 53 | return nil, ErrWebViewNotInitialized | 71 | return nil, ErrWebViewNotInitialized | ||
| 54 | } | 72 | } | ||
| 55 | 73 | ||||
| 74 | + | // Verify the WebView is using the persistent session | |||
| 75 | + | viewSession := wkView.NetworkSession() | |||
| 76 | + | if viewSession == nil { | |||
| 77 | + | return nil, fmt.Errorf("WebView has no network session") | |||
| 78 | + | } | |||
| 79 | + | if viewSession.IsEphemeral() { | |||
| 80 | + | return nil, fmt.Errorf("WebView is using ephemeral session despite persistent session initialization") | |||
| 81 | + | } | |||
| 82 | + | ||||
| 83 | + | // CRITICAL: Configure CookieManager on the WebView's actual session | |||
| 84 | + | // This ensures cookies are persisted regardless of whether the WebView | |||
| 85 | + | // uses our global session or creates its own | |||
| 86 | + | cookieManager := viewSession.CookieManager() | |||
| 87 | + | if cookieManager == nil { | |||
| 88 | + | return nil, fmt.Errorf("failed to get cookie manager from WebView's network session") | |||
| 89 | + | } | |||
| 90 | + | cookiePath := cfg.DataDir + "/cookies.db" | |||
| 91 | + | cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqlite) | |||
| 92 | + | cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdParty) | |||
| 93 | + | ||||
| 56 | // Generate unique ID | 94 | // Generate unique ID | ||
| 57 | id := atomic.AddUint64(&viewIDCounter, 1) | 95 | id := atomic.AddUint64(&viewIDCounter, 1) | ||
| 58 | 96 | ||||
| 59 | wv := &WebView{ | 97 | wv := &WebView{ | ||
| 60 | view: wkView, | 98 | view: wkView, | ||
| 99 | + | window: window, | |||
| 61 | id: id, | 100 | id: id, | ||
| 62 | config: cfg, | 101 | config: cfg, | ||
| 63 | } | 102 | } | ||
| @@ -70,6 +109,9 @@ func NewWebView(cfg *Config) (*WebView, error) { | |||||
| 70 | // Setup event handlers | 109 | // Setup event handlers | ||
| 71 | wv.setupEventHandlers() | 110 | wv.setupEventHandlers() | ||
| 72 | 111 | ||||
| 112 | + | // Add WebView as child of window | |||
| 113 | + | window.SetChild(wkView) | |||
| 114 | + | ||||
| 73 | // Register in global registry | 115 | // Register in global registry | ||
| 74 | viewMu.Lock() | 116 | viewMu.Lock() | ||
| 75 | viewRegistry[id] = wv | 117 | viewRegistry[id] = wv | ||
| @@ -107,6 +149,9 @@ func (w *WebView) applyConfig() error { | |||||
| 107 | settings.SetUserAgent(w.config.UserAgent) | 149 | settings.SetUserAgent(w.config.UserAgent) | ||
| 108 | } | 150 | } | ||
| 109 | 151 | ||||
| 152 | + | // Enable developer tools (F12, inspector) | |||
| 153 | + | settings.SetEnableDeveloperExtras(true) | |||
| 154 | + | ||||
| 110 | // Enable hardware acceleration if configured | 155 | // Enable hardware acceleration if configured | ||
| 111 | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlways) | 156 | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlways) | ||
| 112 | 157 | ||||
| @@ -246,7 +291,14 @@ func (w *WebView) Show() error { | |||||
| 246 | return ErrWebViewDestroyed | 291 | return ErrWebViewDestroyed | ||
| 247 | } | 292 | } | ||
| 248 | 293 | ||||
| 294 | + | // Make the WebView widget visible | |||
| 249 | w.view.SetVisible(true) | 295 | w.view.SetVisible(true) | ||
| 296 | + | ||||
| 297 | + | // Show the window containing the WebView | |||
| 298 | + | if w.window != nil { | |||
| 299 | + | w.window.Show() | |||
| 300 | + | } | |||
| 301 | + | ||||
| 250 | return nil | 302 | return nil | ||
| 251 | } | 303 | } | ||
| 252 | 304 | ||||
| @@ -543,20 +595,7 @@ func (w *WebView) Window() *Window { | |||||
| 543 | return nil | 595 | return nil | ||
| 544 | } | 596 | } | ||
| 545 | 597 | ||||
| 546 | - | // Get the root window | 598 | + | return w.window |
| 547 | - | widget := getWidget(w.view) | |||
| 548 | - | if widget != nil { | |||
| 549 | - | root := widget.Root() | |||
| 550 | - | if root != nil { | |||
| 551 | - | // Try to cast the native widget to a gtk.Window | |||
| 552 | - | if obj := root.Cast(); obj != nil { | |||
| 553 | - | if gtkWin, ok := obj.(*gtk.Window); ok { | |||
| 554 | - | return &Window{win: gtkWin} | |||
| 555 | - | } | |||
| 556 | - | } | |||
| 557 | - | } | |||
| 558 | - | } | |||
| 559 | - | return nil | |||
| 560 | } | 599 | } | ||
| 561 | 600 | ||||
| 562 | // UpdateContentFilters updates the content filtering rules | 601 | // UpdateContentFilters updates the content filtering rules | ||