GopeedLab/gopeed · error

webview page is closed

Error message

webview page is closed

What it means

pageWrapper.Execute (internal/webview/goprovider/provider.go:186) marshals the call, allocates a reqID channel, then checks p.closed under the page mutex. Every Execute/Evaluate call made after Close() set that flag fails immediately with this error instead of dispatching to the dead webview.

Source

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

	return nil
}

func (p *pageWrapper) Execute(expression string, args ...any) (any, error) {
	startedAt := time.Now()
	if args == nil {
		args = []any{}
	}
	argsJSON, err := json.Marshal(args)
	if err != nil {
		return nil, err
	}
	reqID := fmt.Sprintf("req-%d", providerSeq.Add(1))
	ch := make(chan evalResult, 1)

	p.mu.Lock()
	if p.closed {
		p.mu.Unlock()
		return nil, fmt.Errorf("webview page is closed")
	}
	p.pending[reqID] = ch
	p.mu.Unlock()

	script := buildExecuteScript(p.callbackName, reqID, expression, string(argsJSON))
	if err := p.dispatch(func(w webview.WebView) error {
		w.Eval(script)
		return nil
	}); err != nil {
		p.mu.Lock()
		delete(p.pending, reqID)
		p.mu.Unlock()
		return nil, err
	}

	result := <-ch
	if result.err != nil {
		p.tracef("execute failed %dms err=%v", time.Since(startedAt).Milliseconds(), result.err)

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Stop all background tasks that touch the page before calling Close()
  2. Treat Close as terminal: never reuse the wrapper, create a new page for the next navigation
  3. Route every page access through your own guard that checks a 'closed' flag you set at shutdown

Example fix

// before
page.Close()
_, err := page.Execute(`() => location.href`) // "webview page is closed"

// after
cancelPollers()
if err := page.Close(); err != nil { return err }
page = nil // make stale references obvious; open a new page when needed
Defensive patterns

Strategy: validation

Validate before calling

// Track page lifetime in the caller
type pageRef struct {
    mu     sync.Mutex
    page   enginewebview.Page
    closed bool
}

func (r *pageRef) execute(expr string, args []any) (any, error) {
    r.mu.Lock()
    if r.closed || r.page == nil {
        r.mu.Unlock()
        return nil, errors.New("page already closed")
    }
    r.mu.Unlock()
    return r.page.Execute(expr, args)
}

Try / catch

val, err := r.execute(expr, args)
if err != nil && strings.Contains(err.Error(), "webview page is closed") {
    return nil, ErrSessionClosed // map to your own terminal condition; do not retry
}

Prevention

When it happens

Trigger: Calling page.Execute or the navigation-state polling after page.Close() returned; a concurrent Close() that wins the race while an Evaluate is being set up; keeping a stale page reference in application state after closing the window.

Common situations: Cleanup paths that close the page while background pollers (progress tickers, state watchers) are still running; promise chains that resolve after the user closed the window; caches of page objects that outlive their window.

Related errors


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