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:
parent
e11ae33c66
commit
c34da16089
3 changed files with 40 additions and 22 deletions
|
@ -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; }
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue