1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 09:48:11 +00:00

LibWeb: Make <script src> loads partially async (by following the spec)

Instead of firing up a network request and synchronously blocking for it
to finish via a nested event loop, we now start an asynchronous request
when encountering <script src>.

Once the script load finishes (or fails), it gets executed at one of the
synchronization points in the HTML parser.

This solves some long-standing issues with random unexpected events
getting dispatched in the middle of parsing.
This commit is contained in:
Andreas Kling 2021-09-20 17:06:10 +02:00
parent e11ae33c66
commit c34da16089
3 changed files with 40 additions and 22 deletions

View file

@ -195,6 +195,7 @@ public:
void add_script_to_execute_as_soon_as_possible(Badge<HTML::HTMLScriptElement>, HTML::HTMLScriptElement&);
NonnullRefPtrVector<HTML::HTMLScriptElement> take_scripts_to_execute_as_soon_as_possible(Badge<HTML::HTMLDocumentParser>);
NonnullRefPtrVector<HTML::HTMLScriptElement>& scripts_to_execute_as_soon_as_possible() { return m_scripts_to_execute_as_soon_as_possible; }
QuirksMode mode() const { return m_quirks_mode; }
bool in_quirks_mode() const { return m_quirks_mode == QuirksMode::Yes; }

View file

@ -291,9 +291,8 @@ void HTMLScriptElement::prepare_script()
// Fetch a classic script given url, settings object, options, classic script CORS setting, and encoding.
auto request = LoadRequest::create_for_url_on_page(url, document().page());
// FIXME: This load should be made asynchronous and the parser should spin an event loop etc.
m_script_filename = url.to_string();
ResourceLoader::the().load_sync(
ResourceLoader::the().load(
request,
[this, url](auto data, auto&, auto) {
if (data.is_null()) {
@ -310,6 +309,8 @@ void HTMLScriptElement::prepare_script()
},
[this](auto&, auto) {
m_failed_to_load = true;
dbgln("HONK! Failed to load script, but ready nonetheless.");
script_became_ready();
});
} else if (m_script_type == ScriptType::Module) {
// FIXME: -> "module"
@ -374,21 +375,34 @@ void HTMLScriptElement::prepare_script()
// Add the element to the end of the list of scripts that will execute in order as soon as possible associated with the element's preparation-time document.
m_preparation_time_document->add_script_to_execute_as_soon_as_possible({}, *this);
// FIXME: When the script is ready, run the following steps:
// FIXME: 1. If the element is not now the first element in the list of scripts
// When the script is ready, run the following steps:
when_the_script_is_ready([this] {
// 1. If the element is not now the first element in the list of scripts
// that will execute in order as soon as possible to which it was added above,
// then mark the element as ready but return without executing the script yet.
if (this != &m_preparation_time_document->scripts_to_execute_as_soon_as_possible().first()) {
m_script_ready = true;
return;
}
// FIXME: 2. Execution: Execute the script block corresponding to the first script element
for (;;) {
// 2. Execution: Execute the script block corresponding to the first script element
// in this list of scripts that will execute in order as soon as possible.
//
// FIXME: 3. Remove the first element from this list of scripts that will execute in order
m_preparation_time_document->scripts_to_execute_as_soon_as_possible().first().execute_script();
// 3. Remove the first element from this list of scripts that will execute in order
// as soon as possible.
//
// FIXME: 4. If this list of scripts that will execute in order as soon as possible is still
m_preparation_time_document->scripts_to_execute_as_soon_as_possible().take_first();
// 4. If this list of scripts that will execute in order as soon as possible is still
// not empty and the first entry has already been marked as ready, then jump back
// to the step labeled execution.
if (!m_preparation_time_document->scripts_to_execute_as_soon_as_possible().is_empty() && m_preparation_time_document->scripts_to_execute_as_soon_as_possible().first().m_script_ready)
continue;
break;
}
});
}
// -> If the script's type is "classic", and the element has a src attribute
@ -399,6 +413,8 @@ void HTMLScriptElement::prepare_script()
m_preparation_time_document->add_script_to_execute_as_soon_as_possible({}, *this);
// FIXME: When the script is ready, execute the script block and then remove the element
// from the set of scripts that will execute as soon as possible.
TODO();
}
// FIXME: -> If the element does not have a src attribute, and the element is "parser-inserted",

View file

@ -184,8 +184,11 @@ void HTMLDocumentParser::run(const AK::URL& url)
m_stack_of_open_elements.pop();
auto scripts_to_execute_when_parsing_has_finished = m_document->take_scripts_to_execute_when_parsing_has_finished({});
for (auto& script : scripts_to_execute_when_parsing_has_finished) {
// FIXME: Spin the event loop until the script is ready to be parser executed and there's no style sheets blocking scripts.
main_thread_event_loop().spin_until([&] {
return script.is_ready_to_be_parser_executed() && !document().has_a_style_sheet_that_is_blocking_scripts();
});
script.execute_script();
}
@ -193,12 +196,10 @@ void HTMLDocumentParser::run(const AK::URL& url)
content_loaded_event->set_bubbles(true);
m_document->dispatch_event(content_loaded_event);
// FIXME: The document parser shouldn't execute these, it should just spin the event loop until the list becomes empty.
// FIXME: Once the set has been added, also spin the event loop until the set becomes empty.
auto scripts_to_execute_as_soon_as_possible = m_document->take_scripts_to_execute_as_soon_as_possible({});
for (auto& script : scripts_to_execute_as_soon_as_possible) {
script.execute_script();
}
// 7. Spin the event loop until the set of scripts that will execute as soon as possible and the list of scripts that will execute in order as soon as possible are empty.
main_thread_event_loop().spin_until([&] {
return m_document->scripts_to_execute_as_soon_as_possible().is_empty();
});
// FIXME: Spin the event loop until there is nothing that delays the load event in the Document.