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 falseView on GitHub (pinned to 7b7327ffb3)
Solutions
- Increase the GotoOptions timeout for slow pages
- Use waitUntil "load" instead of stricter levels unless you truly need them
- If redirects rename the URL, wait on the final URL or relax the URL expectation
- 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
- Default to waitUntil "load"; require stricter levels only when a test proves the need
- Size timeouts to the slowest real page in your target set
- Account for redirects: the readiness check compares against the final URL
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16).
Data as JSON: /api/errors/10eec3278f702b39.
Report an issue: GitHub.