GopeedLab/gopeed · error

webview startup timeout

Error message

webview startup timeout

What it means

pageWrapper.start() spawns the native webview (webview.New or NewHeadless), binds the callback, sets bootstrap HTML, then waits on the ready channel with a hard 10-second budget. If the runtime never signals ready within that window, this error returns and the page is unusable. The tracef call just before it records how many ms actually elapsed.

Source

Thrown at internal/webview/goprovider/provider.go:147

		applyWindowOptions(w, p.opts)
		w.SetHtml(buildBootstrapHTML(p.callbackName, p.readyID))
		w.Run()
		w.Destroy()
		close(p.done)
	}

	if !postMainThreadTask(run) {
		go run()
	}

	select {
	case err := <-p.ready:
		p.tracef("open %dms", time.Since(startedAt).Milliseconds())
		return err
	case <-time.After(10 * time.Second):
		p.tracef("open timeout %dms", time.Since(startedAt).Milliseconds())
		return fmt.Errorf("webview startup timeout")
	}
}

func (p *pageWrapper) AddInitScript(script string) error {
	return p.dispatch(func(w webview.WebView) error {
		w.Init(script)
		return nil
	})
}

func (p *pageWrapper) Goto(url string, opts enginewebview.GotoOptions) error {
	startedAt := time.Now()
	p.drainLoads()
	if err := p.dispatch(func(w webview.WebView) error {
		p.mu.Lock()
		p.url = url
		p.mu.Unlock()
		w.Navigate(url)

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Set the Headless option when there is no display (start() then uses webview.NewHeadless)
  2. On Linux install WebKitGTK dev/runtime packages; on Windows confirm the WebView2 runtime is installed
  3. Ensure DISPLAY/WAYLAND_DISPLAY are set in remote sessions, or forward X
  4. Check the provider trace logs (title containing the debug marker) to see whether open hung at 0ms or crept to 10000ms — a 0ms hang points at a blocked task queue, a near-10000ms value at slow init

Example fix

// before
page, err := provider.NewPage(enginewebview.BrowserOptions{}) // in CI: no display -> startup timeout

// after
page, err := provider.NewPage(enginewebview.BrowserOptions{Headless: true}) // headless init path
Defensive patterns

Strategy: retry

Validate before calling

// Check the environment before opening a webview
func canOpenGUI() bool {
    if os.Getenv("Headless") != "" { /* app-level headless flag */ }
    return os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != "" || runtime.GOOS == "windows" || runtime.GOOS == "darwin"
}

Try / catch

page, err := provider.NewPage(opts)
if err != nil {
    if strings.Contains(err.Error(), "webview startup timeout") {
        // one cold-start retry, then abort the session; do not loop on a broken environment
        page, err = provider.NewPage(opts)
        if err != nil {
            return fmt.Errorf("webview unavailable in this environment: %w", err)
        }
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: Running a visible webview where no GUI session exists (no DISPLAY / Wayland session, e.g. plain SSH or a container) without Headless set; missing WebView2 runtime on Windows or WebKitGTK shared libraries on Linux; the main-thread task queue blocked by another long-running window; extremely slow cold start under heavy load.

Common situations: CI pipelines or servers invoking webview-dependent features without headless mode; minimal Docker images lacking libwebkit2gtk; first launch after an OS update while the webview runtime rebuilds its cache; multiple providers competing for the one UI thread.

Understand the failure class

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/8bfe16c10c52059e. Report an issue: GitHub.