Skip to content

Tab.waitUntilLoaded() throws TimeoutWaitingForReadyStateException instead of returning false #87

Description

@morisil

Tab.waitUntilLoaded() (markanywhere-browse 0.4.0, WaitUntilLoaded.kt) is documented as best-effort:

On a never-quiet page the [timeout] cap inside each waiter trips and capture proceeds anyway.
@return true if both the network and the DOM went quiet within [timeout], false if either hit its cap (capture should still proceed — false means "best effort", not "failed").

Its first step does not keep that promise:

waitForReadyState(ReadyState.COMPLETE, timeout = timeout.inWholeMilliseconds)

kdriver's waitForReadyState throws TimeoutWaitingForReadyStateException when the cap is reached, where the other two waiters (waitForNetworkIdle, waitForDomIdle) return false.
So on any page whose document.readyState never reaches complete, the call throws after 15 s instead of returning false.

Where it bites

A page whose document has fully arrived but one subresource never finishes, e.g. an <img> the server never answers.
In the wild: Wayback Machine captures (web.archive.org/web/<ts>/…), whose toolbar assets can hang.

In umwelt, the exception escaped after a navigation or click had already happened.
The caller got an untyped 502 for a page that had in fact loaded, and an agent would retry a click that had already gone through.

Minimal repro

<!doctype html><html><body><h1>Stalled</h1><img src="/stalled.png"></body></html>

With /stalled.png never answered, tab.waitUntilLoaded() throws TimeoutWaitingForReadyStateException: Timeout waiting for readyState == COMPLETE after 15 s.

Suggested fix

Catch the timeout and fold it into the boolean result, so waitUntilLoaded() returns false like the other two waiters:

val ready = try {
    waitForReadyState(ReadyState.COMPLETE, timeout = timeout.inWholeMilliseconds)
} catch (_: TimeoutWaitingForReadyStateException) {
    false
}

Workaround in umwelt

LocalUmweltClient.settle() catches the exception around every waitUntilLoaded() call, and umwelt-e2e pins it with a /stalled.html fixture.
Once this is fixed upstream, that wrapper can go.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions