Problem: A document could drop a load if it ran a stream of synchronous
same-document history navigations (say, a pushState flood) while it was
concurrently loaded again. The load never finished — so on sanitizer/
slow builds, this had been intermittently taking down unrelated tests in
CI — since test-web reuses one WebContent process, and the next test’s
load can arrive while the previous document’s history work is still
draining. A synchronous commit also claimed a session history step it
never retired — so claimed steps piled up without bound.
Cause: A sync same-document navigation committed immediately and could
jump the session-history-traversal queue while a queued apply-history-
step — such as a cross-document load — was still waiting behind it. The
queued run read the active session-history entry after the sync
navigation had installed it, but before its step number was assigned —
then judged itself stale against that still-pending step, and was
discarded. The shared step numbering was fragile under the same nesting:
A number computed from the current step alone could collide with an in-
flight one — and a stale run that completed later could write its own
step back over a newer one. 394312ab5a stopped the crash this used to
cause, but the races remained.
Fix: Treat a queued push whose displayed entry’s step is still pending
as live rather than stale — so the concurrent load isn’t dropped. Number
apply-history-step runs, and let a run commit its target step only if no
newer run has committed one — so a stale run can’t move the current step
backwards. Claim each new step past every claimed-but-uncommitted step —
rather than from the current step alone, and keep clearing the forward
session history from removing those entries. And retire the step a sync
commit claims, since it applies in the same task, and nothing else will.
See https://github.com/LadybirdBrowser/ladybird/issues/10028
31 lines
1.3 KiB
HTML
31 lines
1.3 KiB
HTML
<!DOCTYPE html>
|
|
<script src="../include.js"></script>
|
|
<!-- A stream of same-document pushStates keeps claimed-but-uncommitted steps in flight while the page is loaded again
|
|
in the same way a request from the UI process loads it. The load used to race the in-flight push machinery in three
|
|
ways: (1) it could read the pushed entry's still-pending step, (2) it could claim a step number an in-flight push
|
|
already held, (3) and clearing the forward session history could remove the in-flight push's entry out from under
|
|
its apply-history-step run. The reloaded page signals completion. The races need the load to land inside another
|
|
run's window — so this reproduces most readily on slow builds (e.g., Sanitizer builds). -->
|
|
<body>
|
|
<script>
|
|
asyncTest(done => {
|
|
if (location.search.includes("loaded")) {
|
|
println("PASS (didn't crash)");
|
|
done();
|
|
return;
|
|
}
|
|
let round = 0;
|
|
(function pump() {
|
|
if (round++ >= 60)
|
|
return;
|
|
for (let i = 0; i < 5; i++)
|
|
history.pushState({ i }, "", "#s" + round + "-" + i);
|
|
setTimeout(pump);
|
|
})();
|
|
const next = new URL(location.href);
|
|
next.search = "?loaded";
|
|
next.hash = "";
|
|
setTimeout(() => internals.loadURL(next.href), 8);
|
|
});
|
|
</script>
|
|
</body>
|