mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 04:09:13 +00:00
LibWeb: Do not run microtasks when the event loop is paused
For example, running `alert(1)` will pause the event loop, during which time no JavaScript should execute. This patch extends this disruption to microtasks. This avoids a crash inside the microtask executor, which asserts the JS execution context stack is empty. This makes us behave the same as Firefox in the following page: <script> queueMicrotask(() => { console.log("inside microtask"); }); alert("hi"); </script> Before the aforementioned assertion was added, we would execute that microtask before showing the alert. Firefox does not do this, and now we don't either.
This commit is contained in:
parent
34bf833a0a
commit
43dc0f52a6
Notes:
github-actions[bot]
2025-01-19 20:48:47 +00:00
Author: https://github.com/trflynn89
Commit: 43dc0f52a6
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3309
Reviewed-by: https://github.com/tcl3 ✅
4 changed files with 42 additions and 21 deletions
|
@ -503,6 +503,9 @@ void perform_a_microtask_checkpoint()
|
||||||
// https://html.spec.whatwg.org/#perform-a-microtask-checkpoint
|
// https://html.spec.whatwg.org/#perform-a-microtask-checkpoint
|
||||||
void EventLoop::perform_a_microtask_checkpoint()
|
void EventLoop::perform_a_microtask_checkpoint()
|
||||||
{
|
{
|
||||||
|
if (execution_paused())
|
||||||
|
return;
|
||||||
|
|
||||||
// NOTE: This assertion is per requirement 9.5 of the ECMA-262 spec, see: https://tc39.es/ecma262/#sec-jobs
|
// NOTE: This assertion is per requirement 9.5 of the ECMA-262 spec, see: https://tc39.es/ecma262/#sec-jobs
|
||||||
// > At some future point in time, when there is no running context in the agent for which the job is scheduled and that agent's execution context stack is empty...
|
// > At some future point in time, when there is no running context in the agent for which the job is scheduled and that agent's execution context stack is empty...
|
||||||
VERIFY(vm().execution_context_stack().is_empty());
|
VERIFY(vm().execution_context_stack().is_empty());
|
||||||
|
@ -653,6 +656,8 @@ EventLoop::PauseHandle::~PauseHandle()
|
||||||
// https://html.spec.whatwg.org/multipage/webappapis.html#pause
|
// https://html.spec.whatwg.org/multipage/webappapis.html#pause
|
||||||
EventLoop::PauseHandle EventLoop::pause()
|
EventLoop::PauseHandle EventLoop::pause()
|
||||||
{
|
{
|
||||||
|
m_execution_paused = true;
|
||||||
|
|
||||||
// 1. Let global be the current global object.
|
// 1. Let global be the current global object.
|
||||||
auto& global = current_principal_global_object();
|
auto& global = current_principal_global_object();
|
||||||
|
|
||||||
|
@ -667,7 +672,6 @@ EventLoop::PauseHandle EventLoop::pause()
|
||||||
// not run further tasks, and any script in the currently running task must block. User agents should remain
|
// not run further tasks, and any script in the currently running task must block. User agents should remain
|
||||||
// responsive to user input while paused, however, albeit in a reduced capacity since the event loop will not be
|
// responsive to user input while paused, however, albeit in a reduced capacity since the event loop will not be
|
||||||
// doing anything.
|
// doing anything.
|
||||||
m_execution_paused = true;
|
|
||||||
|
|
||||||
return PauseHandle { *this, global, time_before_pause };
|
return PauseHandle { *this, global, time_before_pause };
|
||||||
}
|
}
|
||||||
|
|
1
Tests/LibWeb/Text/expected/alert.txt
Normal file
1
Tests/LibWeb/Text/expected/alert.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
PASS (didn't crash)
|
7
Tests/LibWeb/Text/input/alert.html
Normal file
7
Tests/LibWeb/Text/input/alert.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<script src="include.js"></script>
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
alert("Well hello friends!");
|
||||||
|
println("PASS (didn't crash)");
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -379,6 +379,30 @@ static void run_test(HeadlessWebView& view, Test& test, Application& app)
|
||||||
|
|
||||||
view.on_text_test_finish = {};
|
view.on_text_test_finish = {};
|
||||||
|
|
||||||
|
promise->when_resolved([&view, &test, &app](auto) {
|
||||||
|
auto url = URL::create_with_file_scheme(MUST(FileSystem::real_path(test.input_path)));
|
||||||
|
|
||||||
|
switch (test.mode) {
|
||||||
|
case TestMode::Text:
|
||||||
|
case TestMode::Layout:
|
||||||
|
run_dump_test(view, test, url, app.per_test_timeout_in_seconds * 1000);
|
||||||
|
return;
|
||||||
|
case TestMode::Ref:
|
||||||
|
run_ref_test(view, test, url, app.per_test_timeout_in_seconds * 1000);
|
||||||
|
return;
|
||||||
|
case TestMode::Crash:
|
||||||
|
run_dump_test(view, test, url, app.per_test_timeout_in_seconds * 1000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
});
|
||||||
|
|
||||||
|
view.load("about:blank"sv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_ui_callbacks_for_tests(HeadlessWebView& view)
|
||||||
|
{
|
||||||
view.on_request_file_picker = [&](auto const& accepted_file_types, auto allow_multiple_files) {
|
view.on_request_file_picker = [&](auto const& accepted_file_types, auto allow_multiple_files) {
|
||||||
// Create some dummy files for tests.
|
// Create some dummy files for tests.
|
||||||
Vector<Web::HTML::SelectedFile> selected_files;
|
Vector<Web::HTML::SelectedFile> selected_files;
|
||||||
|
@ -420,26 +444,10 @@ static void run_test(HeadlessWebView& view, Test& test, Application& app)
|
||||||
view.file_picker_closed(move(selected_files));
|
view.file_picker_closed(move(selected_files));
|
||||||
};
|
};
|
||||||
|
|
||||||
promise->when_resolved([&view, &test, &app](auto) {
|
view.on_request_alert = [&](auto const&) {
|
||||||
auto url = URL::create_with_file_scheme(MUST(FileSystem::real_path(test.input_path)));
|
// For tests, just close the alert right away to unblock JS execution.
|
||||||
|
view.alert_closed();
|
||||||
switch (test.mode) {
|
};
|
||||||
case TestMode::Text:
|
|
||||||
case TestMode::Layout:
|
|
||||||
run_dump_test(view, test, url, app.per_test_timeout_in_seconds * 1000);
|
|
||||||
return;
|
|
||||||
case TestMode::Ref:
|
|
||||||
run_ref_test(view, test, url, app.per_test_timeout_in_seconds * 1000);
|
|
||||||
return;
|
|
||||||
case TestMode::Crash:
|
|
||||||
run_dump_test(view, test, url, app.per_test_timeout_in_seconds * 1000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
});
|
|
||||||
|
|
||||||
view.load("about:blank"sv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<void> run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize window_size)
|
ErrorOr<void> run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize window_size)
|
||||||
|
@ -516,6 +524,7 @@ ErrorOr<void> run_tests(Core::AnonymousBuffer const& theme, Web::DevicePixelSize
|
||||||
Vector<TestCompletion> non_passing_tests;
|
Vector<TestCompletion> non_passing_tests;
|
||||||
|
|
||||||
app.for_each_web_view([&](auto& view) {
|
app.for_each_web_view([&](auto& view) {
|
||||||
|
set_ui_callbacks_for_tests(view);
|
||||||
view.clear_content_filters();
|
view.clear_content_filters();
|
||||||
|
|
||||||
auto run_next_test = [&]() {
|
auto run_next_test = [&]() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue