GopeedLab/gopeed · error

invalid navigation state

Error message

invalid navigation state

What it means

navigationState() (internal/webview/goprovider/provider.go:402) evaluates an inline JS expression that returns an object literal {url, readyState} and requires the decoded result to be map[string]any. If Execute yields anything else — typically nil after the JS context was destroyed mid-navigation — this error returns. It is the shape-check between the JS bridge and the Go struct.

Source

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

	}
	return false
}

func parseURL(raw string) (*url.URL, error) {
	return url.Parse(raw)
}

func (p *pageWrapper) navigationState() (navigationState, error) {
	value, err := p.Execute(`() => ({
		url: String(location.href || ""),
		readyState: document.readyState || "",
	})`)
	if err != nil {
		return navigationState{}, err
	}
	stateMap, ok := value.(map[string]any)
	if !ok {
		return navigationState{}, fmt.Errorf("invalid navigation state")
	}
	state := navigationState{}
	if urlValue, ok := stateMap["url"].(string); ok {
		state.URL = urlValue
	}
	if readyValue, ok := stateMap["readyState"].(string); ok {
		state.ReadyState = readyValue
	}
	return state, nil
}

func (p *pageWrapper) dispatch(fn func(w webview.WebView) error) error {
	p.mu.Lock()
	if p.closed {
		p.mu.Unlock()
		return fmt.Errorf("webview page is closed")
	}
	w := p.view

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Retry the state read after a short delay — transient nulls during navigation are the common cause
  2. Ensure the page is still open and the navigation has settled before querying state
  3. If persistent, verify the Execute path end-to-end with a trivial expression like '() => 1' to isolate bridge problems

Example fix

// before
state, err := page.NavigationState() // "invalid navigation state" mid-navigation

// after
var state map[string]any
for attempt := 0; attempt < 3; attempt++ {
    v, err := page.Execute(`() => ({ url: location.href, readyState: document.readyState })`)
    if err == nil {
        if m, ok := v.(map[string]any); ok {
            state = m
            break
        }
    }
    time.Sleep(200 * time.Millisecond) // let the navigation settle
}
Defensive patterns

Strategy: retry

Validate before calling

// Tolerant state read: retry while the JS context settles
func readNavState(page enginewebview.Page) (map[string]any, error) {
    for attempt := 0; attempt < 3; attempt++ {
        v, err := page.Execute(`() => ({ url: String(location.href || ""), readyState: document.readyState || "" })`, nil)
        if err == nil {
            if m, ok := v.(map[string]any); ok {
                return m, nil
            }
        }
        time.Sleep(200 * time.Millisecond)
    }
    return nil, errors.New("navigation state unavailable")
}

Type guard

func isNavState(v any) bool {
    m, ok := v.(map[string]any)
    return ok && m["url"] != nil
}

Try / catch

v, err := page.Execute(expr, nil)
if err == nil {
    if m, ok := v.(map[string]any); !ok {
        // transient during navigation: back off once, then fail
        time.Sleep(250 * time.Millisecond)
        v, err = page.Execute(expr, nil)
    }
}

Prevention

When it happens

Trigger: Polling navigation state exactly while the page navigates or is destroyed (eval returns null/undefined); an Execute plumbing fault where the callback resolves with a non-object JSON value; a closed or crashed render process making the eval return nothing.

Common situations: Happens transiently during waitForNavigation polling windows on fast navigations; persistent when the page crashes or the bridge callback name is misconfigured so results never arrive in object form.

Related errors


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