mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-28 02:27:19 +02:00
LibWeb: Use reject_the_finished_promise() in abort_a_navigate_event()
This aligns our implementation with the specification. Doing this fixes a number of WPT tests because this sets `m_ongoing_api_method_tracker` to null, avoiding an assertion that previously caused a crash.
This commit is contained in:
committed by
Shannon Booth
parent
206a18acd4
commit
dc649a7e46
Notes:
github-actions[bot]
2026-02-14 19:23:21 +00:00
Author: https://github.com/tcl3 Commit: https://github.com/LadybirdBrowser/ladybird/commit/dc649a7e464 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/7875 Reviewed-by: https://github.com/shannonbooth ✅
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
<div id="d"></div>
|
||||
<script>
|
||||
promise_test(async () => {
|
||||
let onnavigate_called = false;
|
||||
navigation.onnavigate = () => onnavigate_called = true;
|
||||
await navigation.navigate("#d").committed;
|
||||
assert_equals(location.hash, "#d");
|
||||
assert_true(onnavigate_called);
|
||||
assert_equals(document.querySelector(":target"), d);
|
||||
}, "navigate() navigates same-document and fires onnavigate (async)");
|
||||
|
||||
test(() => {
|
||||
let onnavigate_called = false;
|
||||
navigation.onnavigate = () => onnavigate_called = true;
|
||||
navigation.navigate("#d");
|
||||
assert_equals(location.hash, "#d");
|
||||
assert_true(onnavigate_called);
|
||||
assert_equals(document.querySelector(":target"), d);
|
||||
}, "navigate() navigates same-document and fires onnavigate (sync)");
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<script src="../../../resources/testharness.js"></script>
|
||||
<script src="../../../resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
|
||||
<script>
|
||||
promise_test(async t => {
|
||||
let start_length = navigation.entries().length;
|
||||
let start_index = navigation.currentEntry.index;
|
||||
// Wait for after the load event so that the navigation doesn't get converted
|
||||
// into a replace navigation.
|
||||
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
|
||||
|
||||
navigation.addEventListener("navigate", e => e.intercept());
|
||||
|
||||
const result1 = navigation.navigate("#1");
|
||||
const result2 = navigation.navigate("#2");
|
||||
|
||||
assert_equals(navigation.entries().length, start_length + 2);
|
||||
assert_array_equals(navigation.entries().slice(start_index).map(e => (new URL(e.url)).hash), ["", "#1", "#2"]);
|
||||
|
||||
await assertCommittedFulfillsFinishedRejectsDOM(t, result1, navigation.entries()[start_index + 1], "AbortError");
|
||||
await assertBothFulfill(t, result2, navigation.currentEntry);
|
||||
}, "interrupted navigate() promises with intercept()");
|
||||
</script>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<script src="../../../resources/testharness.js"></script>
|
||||
<script src="../../../resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
|
||||
<script>
|
||||
promise_test(async t => {
|
||||
let start_length = navigation.entries().length;
|
||||
let start_index = navigation.currentEntry.index;
|
||||
// Wait for after the load event so that the navigation doesn't get converted
|
||||
// into a replace navigation.
|
||||
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
|
||||
|
||||
let result2;
|
||||
navigation.onnavigate = t.step_func(e => {
|
||||
if (e.info == 1) {
|
||||
result2 = navigation.navigate("#2", { info: 2 });
|
||||
assert_true(e.defaultPrevented);
|
||||
}
|
||||
});
|
||||
|
||||
const result1 = navigation.navigate("#1", { info: 1 });
|
||||
|
||||
assert_equals(navigation.entries().length, start_length + 1);
|
||||
assert_array_equals(navigation.entries().slice(start_index).map(e => (new URL(e.url)).hash), ["", "#2"]);
|
||||
|
||||
await assertBothRejectDOM(t, result1, "AbortError");
|
||||
await assertBothFulfill(t, result2, navigation.currentEntry);
|
||||
}, "if navigate() is called inside onnavigate, the previous navigation and navigate event are cancelled");
|
||||
</script>
|
||||
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<script src="../../../resources/testharness.js"></script>
|
||||
<script src="../../../resources/testharnessreport.js"></script>
|
||||
<script src="resources/helpers.js"></script>
|
||||
|
||||
<script>
|
||||
promise_test(async t => {
|
||||
let start_length = navigation.entries().length;
|
||||
let start_index = navigation.currentEntry.index;
|
||||
// Wait for after the load event so that the navigation doesn't get converted
|
||||
// into a replace navigation.
|
||||
await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0));
|
||||
|
||||
const result1 = navigation.navigate("#1");
|
||||
const result2 = navigation.navigate("#2");
|
||||
|
||||
assert_equals(navigation.entries().length, start_length + 2);
|
||||
assert_array_equals(navigation.entries().slice(start_index).map(e => (new URL(e.url)).hash), ["", "#1", "#2"]);
|
||||
|
||||
await assertCommittedFulfillsFinishedRejectsDOM(t, result1, navigation.entries()[start_index + 1], "AbortError");
|
||||
await assertBothFulfill(t, result2, navigation.currentEntry);
|
||||
}, "interrupted navigate() promises");
|
||||
</script>
|
||||
@@ -0,0 +1,154 @@
|
||||
window.assertReturnValue = (result, w = window) => {
|
||||
assert_equals(Object.getPrototypeOf(result), w.Object.prototype, "result object must be from the right realm");
|
||||
assert_array_equals(Reflect.ownKeys(result), ["committed", "finished"]);
|
||||
assert_true(result.committed instanceof w.Promise);
|
||||
assert_true(result.finished instanceof w.Promise);
|
||||
assert_not_equals(result.committed, result.finished);
|
||||
};
|
||||
|
||||
window.assertNeverSettles = (t, result, w = window) => {
|
||||
assertReturnValue(result, w);
|
||||
result.committed.then(
|
||||
t.unreached_func("committed must not fulfill"),
|
||||
t.unreached_func("committed must not reject")
|
||||
);
|
||||
|
||||
result.finished.then(
|
||||
t.unreached_func("finished must not fulfill"),
|
||||
t.unreached_func("finished must not reject")
|
||||
);
|
||||
};
|
||||
|
||||
window.assertBothFulfillEntryNotAvailable = async (t, result, w = window) => {
|
||||
assertReturnValue(result, w);
|
||||
|
||||
// Don't use await here so that we can catch out-of-order settlements.
|
||||
let committedValue;
|
||||
result.committed.then(
|
||||
t.step_func(v => { committedValue = v;}),
|
||||
t.unreached_func("committed must not reject")
|
||||
);
|
||||
|
||||
const finishedValue = await result.finished;
|
||||
|
||||
assert_not_equals(committedValue, undefined, "committed must fulfill before finished");
|
||||
assert_equals(finishedValue, committedValue, "committed and finished must fulfill with the same value");
|
||||
assert_true(finishedValue instanceof w.NavigationHistoryEntry, "fulfillment value must be a NavigationHistoryEntry");
|
||||
};
|
||||
|
||||
window.assertBothFulfill = async (t, result, expected, w = window) => {
|
||||
assertReturnValue(result, w);
|
||||
|
||||
// Don't use await here so that we can catch out-of-order settlements.
|
||||
let committedValue;
|
||||
result.committed.then(
|
||||
t.step_func(v => { committedValue = v; }),
|
||||
t.unreached_func("committed must not reject")
|
||||
);
|
||||
|
||||
const finishedValue = await result.finished;
|
||||
|
||||
assert_not_equals(committedValue, undefined, "committed must fulfill before finished");
|
||||
assert_equals(finishedValue, committedValue, "committed and finished must fulfill with the same value");
|
||||
assert_true(finishedValue instanceof w.NavigationHistoryEntry, "fulfillment value must be a NavigationHistoryEntry");
|
||||
assert_equals(finishedValue, expected);
|
||||
};
|
||||
|
||||
window.assertCommittedFulfillsFinishedRejectsExactly = async (t, result, expectedEntry, expectedRejection, w = window) => {
|
||||
assertReturnValue(result, w);
|
||||
|
||||
// Don't use await here so that we can catch out-of-order settlements.
|
||||
let committedValue;
|
||||
result.committed.then(
|
||||
t.step_func(v => { committedValue = v; }),
|
||||
t.unreached_func("committed must not reject")
|
||||
);
|
||||
|
||||
await promise_rejects_exactly(t, expectedRejection, result.finished);
|
||||
|
||||
assert_not_equals(committedValue, undefined, "committed must fulfill before finished rejects");
|
||||
assert_true(committedValue instanceof w.NavigationHistoryEntry, "fulfillment value must be a NavigationHistoryEntry");
|
||||
assert_equals(committedValue, expectedEntry);
|
||||
};
|
||||
|
||||
window.assertCommittedFulfillsFinishedRejectsDOM = async (t, result, expectedEntry, expectedDOMExceptionCode, w = window, domExceptionConstructor = w.DOMException, navigationHistoryEntryConstuctor = w.NavigationHistoryEntry) => {
|
||||
assertReturnValue(result, w);
|
||||
|
||||
let committedValue;
|
||||
result.committed.then(
|
||||
t.step_func(v => { committedValue = v; }),
|
||||
t.unreached_func("committed must not reject")
|
||||
);
|
||||
|
||||
await promise_rejects_dom(t, expectedDOMExceptionCode, domExceptionConstructor, result.finished);
|
||||
|
||||
assert_not_equals(committedValue, undefined, "committed must fulfill before finished rejects");
|
||||
assert_true(committedValue instanceof navigationHistoryEntryConstuctor, "fulfillment value must be an NavigationHistoryEntry");
|
||||
assert_equals(committedValue, expectedEntry);
|
||||
};
|
||||
|
||||
// We cannot use Promise.all() because the automatic coercion behavior when
|
||||
// promises from multiple realms are involved causes it to hang if one of the
|
||||
// promises is from a detached iframe's realm. See discussion at
|
||||
// https://github.com/whatwg/html/issues/11252#issuecomment-2984143855.
|
||||
window.waitForAllLenient = (iterable) => {
|
||||
const { promise: all, resolve, reject } = Promise.withResolvers();
|
||||
let remaining = 0;
|
||||
let results = [];
|
||||
for (const promise of iterable) {
|
||||
let index = remaining++;
|
||||
promise.then(v => {
|
||||
results[index] = v;
|
||||
--remaining;
|
||||
if (!remaining) {
|
||||
resolve(results);
|
||||
}
|
||||
return v;
|
||||
}, v => reject(v));
|
||||
}
|
||||
|
||||
if (!remaining) {
|
||||
resolve(results);
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
window.assertBothRejectExactly = async (t, result, expectedRejection, w = window) => {
|
||||
assertReturnValue(result, w);
|
||||
|
||||
let committedReason, finishedReason;
|
||||
await waitForAllLenient([
|
||||
result.committed.then(
|
||||
t.unreached_func("committed must not fulfill"),
|
||||
t.step_func(r => { committedReason = r; })
|
||||
),
|
||||
result.finished.then(
|
||||
t.unreached_func("finished must not fulfill"),
|
||||
t.step_func(r => { finishedReason = r; })
|
||||
)
|
||||
]);
|
||||
|
||||
assert_equals(committedReason, finishedReason, "committed and finished must reject with the same value");
|
||||
assert_equals(expectedRejection, committedReason);
|
||||
};
|
||||
|
||||
window.assertBothRejectDOM = async (t, result, expectedDOMExceptionCode, w = window, domExceptionConstructor = w.DOMException) => {
|
||||
assertReturnValue(result, w);
|
||||
|
||||
// Don't use await here so that we can catch out-of-order settlements.
|
||||
let committedReason, finishedReason;
|
||||
await waitForAllLenient([
|
||||
result.committed.then(
|
||||
t.unreached_func("committed must not fulfill"),
|
||||
t.step_func(r => { committedReason = r; })
|
||||
),
|
||||
result.finished.then(
|
||||
t.unreached_func("finished must not fulfill"),
|
||||
t.step_func(r => { finishedReason = r; })
|
||||
)
|
||||
]);
|
||||
|
||||
assert_equals(committedReason, finishedReason, "committed and finished must reject with the same value");
|
||||
assert_throws_dom(expectedDOMExceptionCode, domExceptionConstructor, () => { throw committedReason; });
|
||||
};
|
||||
Reference in New Issue
Block a user