{"record":{"id":"10eec3278f702b39","repo":"GopeedLab/gopeed","slug":"webview-navigation-timeout","errorCode":null,"errorMessage":"webview navigation timeout","messagePattern":"webview navigation timeout","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/webview/goprovider/provider.go","lineNumber":350,"sourceCode":"\nfunc (p *pageWrapper) waitForNavigation(targetURL string, timeout time.Duration, waitUntil string) error {\n\tdeadline := time.Now().Add(timeout)\n\tticker := time.NewTicker(100 * time.Millisecond)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-p.loads:\n\t\t\tif waitUntil == \"load\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\tcase <-ticker.C:\n\t\t\tstate, err := p.navigationState()\n\t\t\tif err == nil && state.ready(targetURL, waitUntil) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif time.Now().After(deadline) {\n\t\t\t\treturn fmt.Errorf(\"webview navigation timeout\")\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype navigationState struct {\n\tURL        string `json:\"url\"`\n\tReadyState string `json:\"readyState\"`\n}\n\nfunc (s navigationState) ready(targetURL string, waitUntil string) bool {\n\tif s.URL == \"\" || s.URL == \"about:blank\" {\n\t\treturn false\n\t}\n\tswitch waitUntil {\n\tcase \"domcontentloaded\":\n\t\tif s.ReadyState == \"loading\" || s.ReadyState == \"\" {\n\t\t\treturn false","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/webview/goprovider/provider.go#L332-L368","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nerr := page.Goto(url, enginewebview.GotoOptions{Timeout: 5 * time.Second, WaitUntil: \"networkidle\"})\n\n// after\nerr := page.Goto(url, enginewebview.GotoOptions{Timeout: 60 * time.Second, WaitUntil: \"load\"})","handlingStrategy":"retry","validationCode":"// Choose waitUntil and timeout from measured page weight\nfunc gotoOpts(pageHeavy bool) enginewebview.GotoOptions {\n    if pageHeavy {\n        return enginewebview.GotoOptions{Timeout: 60 * time.Second, WaitUntil: \"load\"}\n    }\n    return enginewebview.GotoOptions{Timeout: 15 * time.Second, WaitUntil: \"load\"}","typeGuard":null,"tryCatchPattern":"if err := page.Goto(url, opts); err != nil {\n    if strings.Contains(err.Error(), \"webview navigation timeout\") {\n        // page may still be usable; check state before deciding to retry\n        if st, err2 := probeState(page); err2 == nil && st != \"\" {\n            return nil // content arrived, only the readiness condition missed\n        }\n        return page.Goto(url, relaxedOpts) // one retry with a larger budget\n    }\n    return err\n}","preventionTips":["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"],"tags":["webview","navigation","timeout","goto"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}