GopeedLab/gopeed · error

webview navigation timeout

Error message

webview navigation timeout

What it means

Goto calls waitForNavigation (internal/webview/goprovider/provider.go:333), which loops on a 100ms ticker and the page's load-event channel until navigationState() reports the page ready for the chosen waitUntil level. When the deadline passes first, this error returns. Note that 'load' short-circuits on the load event, while stricter levels must be satisfied by the polled JS state (URL match plus readyState).

Source

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

func (p *pageWrapper) waitForNavigation(targetURL string, timeout time.Duration, waitUntil string) error {
	deadline := time.Now().Add(timeout)
	ticker := time.NewTicker(100 * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-p.loads:
			if waitUntil == "load" {
				return nil
			}
		case <-ticker.C:
			state, err := p.navigationState()
			if err == nil && state.ready(targetURL, waitUntil) {
				return nil
			}
			if time.Now().After(deadline) {
				return fmt.Errorf("webview navigation timeout")
			}
		}
	}
}

type navigationState struct {
	URL        string `json:"url"`
	ReadyState string `json:"readyState"`
}

func (s navigationState) ready(targetURL string, waitUntil string) bool {
	if s.URL == "" || s.URL == "about:blank" {
		return false
	}
	switch waitUntil {
	case "domcontentloaded":
		if s.ReadyState == "loading" || s.ReadyState == "" {
			return false

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Increase the GotoOptions timeout for slow pages
  2. Use waitUntil "load" instead of stricter levels unless you truly need them
  3. If redirects rename the URL, wait on the final URL or relax the URL expectation
  4. Check basic connectivity to the target before blaming the timeout

Example fix

// before
err := page.Goto(url, enginewebview.GotoOptions{Timeout: 5 * time.Second, WaitUntil: "networkidle"})

// after
err := page.Goto(url, enginewebview.GotoOptions{Timeout: 60 * time.Second, WaitUntil: "load"})
Defensive patterns

Strategy: retry

Validate before calling

// Choose waitUntil and timeout from measured page weight
func gotoOpts(pageHeavy bool) enginewebview.GotoOptions {
    if pageHeavy {
        return enginewebview.GotoOptions{Timeout: 60 * time.Second, WaitUntil: "load"}
    }
    return enginewebview.GotoOptions{Timeout: 15 * time.Second, WaitUntil: "load"}

Try / catch

if err := page.Goto(url, opts); err != nil {
    if strings.Contains(err.Error(), "webview navigation timeout") {
        // page may still be usable; check state before deciding to retry
        if st, err2 := probeState(page); err2 == nil && st != "" {
            return nil // content arrived, only the readiness condition missed
        }
        return page.Goto(url, relaxedOpts) // one retry with a larger budget
    }
    return err
}

Prevention

When it happens

Trigger: Page genuinely slower than the configured timeout (large assets, slow origin); waitUntil stricter than 'load' whose condition never becomes true (readyState stuck below the required level, or the final URL after redirects does not match the target URL check in state.ready); navigation interrupted so the load event never fires.

Common situations: Default timeouts used against heavy pages; SPA redirects that change the URL so the URL-match in ready() never passes; captive portals or blocked networks where loading stalls; pages that never reach the stricter readyState because of long-polling scripts.

Understand the failure class

Related errors


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