diff options
author | Luke <luke.wilde@live.co.uk> | 2021-01-28 20:31:20 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-29 08:49:50 +0100 |
commit | 3f5532d43e49e9919a8728c8eb17c625be7e7883 (patch) | |
tree | 151d1b166f7c65c13b0ff84d47a7e3d71559acd7 /Userland/Libraries/LibWeb | |
parent | dbbc378fb2be2fb0f5fe8f767d25b6caf65df3c3 (diff) | |
download | serenity-3f5532d43e49e9919a8728c8eb17c625be7e7883.zip |
LibWeb: Flesh out prepare_script and execute_script
This fills in a bunch of the FIXMEs that was in prepare_script.
execute_script is almost finished, it's just missing the module side.
As an aside, let's not assert when inserting a script element with
innerHTML.
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/Document.h | 11 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/DOM/Document.idl | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/AttributeNames.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp | 201 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h | 7 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp | 5 |
6 files changed, 186 insertions, 40 deletions
diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index c7367f6838..be7050b2cc 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -42,6 +42,7 @@ #include <LibWeb/DOM/DOMImplementation.h> #include <LibWeb/DOM/NonElementParentNode.h> #include <LibWeb/DOM/ParentNode.h> +#include <LibWeb/HTML/HTMLScriptElement.h> namespace Web::DOM { @@ -220,6 +221,13 @@ public: const NonnullRefPtr<DOMImplementation> implementation() const { return m_implementation; } + RefPtr<HTML::HTMLScriptElement> current_script() const { return m_current_script; } + void set_current_script(Badge<HTML::HTMLScriptElement>, RefPtr<HTML::HTMLScriptElement> script) { m_current_script = script; } + + u32 ignore_destructive_writes_counter() const { return m_ignore_destructive_writes_counter; } + void increment_ignore_destructive_writes_counter() { m_ignore_destructive_writes_counter++; } + void decrement_ignore_destructive_writes_counter() { m_ignore_destructive_writes_counter--; } + virtual EventTarget* get_parent(const Event&) override; private: @@ -289,8 +297,11 @@ private: bool m_ready_for_post_load_tasks { false }; NonnullRefPtr<DOMImplementation> m_implementation; + RefPtr<HTML::HTMLScriptElement> m_current_script; bool m_should_invalidate_styles_on_attribute_changes { true }; + + u32 m_ignore_destructive_writes_counter { 0 }; }; } diff --git a/Userland/Libraries/LibWeb/DOM/Document.idl b/Userland/Libraries/LibWeb/DOM/Document.idl index a691c583a2..9aa2878d62 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.idl +++ b/Userland/Libraries/LibWeb/DOM/Document.idl @@ -30,6 +30,7 @@ interface Document : Node { readonly attribute Element? documentElement; attribute HTMLElement? body; readonly attribute HTMLHeadElement? head; + readonly attribute HTMLScriptElement? currentScript; readonly attribute DOMString readyState; diff --git a/Userland/Libraries/LibWeb/HTML/AttributeNames.h b/Userland/Libraries/LibWeb/HTML/AttributeNames.h index 670ec9bdb4..8c2eb9c403 100644 --- a/Userland/Libraries/LibWeb/HTML/AttributeNames.h +++ b/Userland/Libraries/LibWeb/HTML/AttributeNames.h @@ -98,6 +98,7 @@ namespace AttributeNames { __ENUMERATE_HTML_ATTRIBUTE(ismap) \ __ENUMERATE_HTML_ATTRIBUTE(label) \ __ENUMERATE_HTML_ATTRIBUTE(lang) \ + __ENUMERATE_HTML_ATTRIBUTE(language) \ __ENUMERATE_HTML_ATTRIBUTE(link) \ __ENUMERATE_HTML_ATTRIBUTE(longdesc) \ __ENUMERATE_HTML_ATTRIBUTE(loop) \ diff --git a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp index cc3031984c..ec436062af 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.cpp @@ -24,10 +24,13 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include <AK/Debug.h> #include <AK/StringBuilder.h> #include <LibJS/Parser.h> +#include <LibTextCodec/Decoder.h> #include <LibWeb/DOM/Document.h> #include <LibWeb/DOM/Event.h> +#include <LibWeb/DOM/ShadowRoot.h> #include <LibWeb/DOM/Text.h> #include <LibWeb/HTML/EventNames.h> #include <LibWeb/HTML/HTMLScriptElement.h> @@ -56,16 +59,68 @@ void HTMLScriptElement::set_non_blocking(Badge<HTMLDocumentParser>, bool non_blo void HTMLScriptElement::execute_script() { - document().run_javascript(m_script_source); + if (m_preparation_time_document.ptr() != &document()) { + dbgln("HTMLScriptElement: Refusing to run script because the preparation time document is not the same as the node document."); + return; + } + + if (m_script_source.is_null()) { + dbgln("HTMLScriptElement: Refusing to run script because the script source is null."); + dispatch_event(DOM::Event::create(HTML::EventNames::error)); + return; + } + + bool incremented_destructive_writes_counter = false; - if (has_attribute(HTML::AttributeNames::src)) - dispatch_event(DOM::Event::create(EventNames::load)); + if (m_from_an_external_file || m_script_type == ScriptType::Module) { + document().increment_ignore_destructive_writes_counter(); + incremented_destructive_writes_counter = true; + } + + if (m_script_type == ScriptType::Classic) { + auto old_current_script = document().current_script(); + if (!is<DOM::ShadowRoot>(root())) + document().set_current_script({}, this); + else + document().set_current_script({}, nullptr); + + if (m_from_an_external_file) + dbgln<HTML_SCRIPT_DEBUG>("HTMLScriptElement: Running script {}", attribute(HTML::AttributeNames::src)); + else + dbgln<HTML_SCRIPT_DEBUG>("HTMLScriptElement: Running inline script"); + + document().run_javascript(m_script_source); + + document().set_current_script({}, old_current_script); + } else { + ASSERT(!document().current_script()); + TODO(); + } + + if (incremented_destructive_writes_counter) + document().decrement_ignore_destructive_writes_counter(); + + if (m_from_an_external_file) + dispatch_event(DOM::Event::create(HTML::EventNames::load)); +} + +// https://mimesniff.spec.whatwg.org/#javascript-mime-type-essence-match +static bool is_javascript_mime_type_essence_match(const String& mime_type) +{ + // FIXME: This operates on the whole mime type, instead of just the essence. https://mimesniff.spec.whatwg.org/#mime-type-essence + // It'd probably be best to make a helper class for mime types, since there is a whole spec about mime types. + auto lowercase_mime_type = mime_type.to_lowercase(); + return lowercase_mime_type.is_one_of("application/ecmascript", "application/javascript", "application/x-ecmascript", "application/x-javascript", "text/ecmascript", "text/javascript", "text/javascript1.0", "text/javascript1.1", "text/javascript1.2", "text/javascript1.3", "text/javascript1.4", "text/javascript1.5", "text/jscript", "text/livescript", "text/x-ecmascript", "text/x-javascript"); } +// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script void HTMLScriptElement::prepare_script(Badge<HTMLDocumentParser>) { - if (m_already_started) + if (m_already_started) { + dbgln("HTMLScriptElement: Refusing to run script because it has already started."); return; + } + RefPtr<DOM::Document> parser_document = m_parser_document.ptr(); m_parser_document = nullptr; @@ -74,13 +129,37 @@ void HTMLScriptElement::prepare_script(Badge<HTMLDocumentParser>) } auto source_text = child_text_content(); - if (!has_attribute(HTML::AttributeNames::src) && source_text.is_empty()) + if (!has_attribute(HTML::AttributeNames::src) && source_text.is_empty()) { + dbgln("HTMLScriptElement: Refusing to run empty script."); return; + } - if (!is_connected()) + if (!is_connected()) { + dbgln("HTMLScriptElement: Refusing to run script because the element is not connected."); return; + } - // FIXME: Check the "type" and "language" attributes + String script_block_type; + bool has_type = has_attribute(HTML::AttributeNames::type); + bool has_language = has_attribute(HTML::AttributeNames::language); + if ((has_type && attribute(HTML::AttributeNames::type).is_empty()) + || (!has_type && has_language && attribute(HTML::AttributeNames::language).is_empty()) + || (!has_type && !has_language)) { + script_block_type = "text/javascript"; + } else if (has_type) { + script_block_type = attribute(HTML::AttributeNames::type).trim_whitespace(); + } else if (!attribute(HTML::AttributeNames::language).is_empty()) { + script_block_type = String::formatted("text/{}", attribute(HTML::AttributeNames::language)); + } + + if (is_javascript_mime_type_essence_match(script_block_type)) { + m_script_type = ScriptType::Classic; + } else if (script_block_type.equals_ignoring_case("module")) { + m_script_type = ScriptType::Module; + } else { + dbgln("HTMLScriptElement: Refusing to run script because the type '{}' is not recognized.", script_block_type); + return; + } if (parser_document) { m_parser_document = *parser_document; @@ -91,13 +170,34 @@ void HTMLScriptElement::prepare_script(Badge<HTMLDocumentParser>) m_preparation_time_document = document(); if (parser_document && parser_document.ptr() != m_preparation_time_document.ptr()) { + dbgln("HTMLScriptElement: Refusing to run script because the parser document is not the same as the preparation time document."); return; } // FIXME: Check if scripting is disabled, if so return - // FIXME: Check the "nomodule" content attribute + + if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::nomodule)) { + dbgln("HTMLScriptElement: Refusing to run classic script because it has the nomodule attribute."); + return; + } + // FIXME: Check CSP - // FIXME: Check "event" and "for" attributes + + if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::event) && has_attribute(HTML::AttributeNames::for_)) { + auto for_ = attribute(HTML::AttributeNames::for_).trim_whitespace(); + auto event = attribute(HTML::AttributeNames::event).trim_whitespace(); + + if (!for_.equals_ignoring_case("window")) { + dbgln("HTMLScriptElement: Refusing to run classic script because the provided 'for' attribute is not equal to 'window'"); + return; + } + + if (!event.equals_ignoring_case("onload") && !event.equals_ignoring_case("onload()")) { + dbgln("HTMLScriptElement: Refusing to run classic script because the provided 'event' attribute is not equal to 'onload' or 'onload()'"); + return; + } + } + // FIXME: Check "charset" attribute // FIXME: Check CORS // FIXME: Module script credentials mode @@ -112,60 +212,83 @@ void HTMLScriptElement::prepare_script(Badge<HTMLDocumentParser>) if (has_attribute(HTML::AttributeNames::src)) { auto src = attribute(HTML::AttributeNames::src); if (src.is_empty()) { - // FIXME: Fire an "error" event at the element and return - ASSERT_NOT_REACHED(); + dbgln("HTMLScriptElement: Refusing to run script because the src attribute is empty."); + // FIXME: Queue a task to do this. + dispatch_event(DOM::Event::create(HTML::EventNames::error)); + return; } m_from_an_external_file = true; auto url = document().complete_url(src); if (!url.is_valid()) { - // FIXME: Fire an "error" event at the element and return - ASSERT_NOT_REACHED(); + dbgln("HTMLScriptElement: Refusing to run script because the src URL '{}' is invalid.", url); + // FIXME: Queue a task to do this. + dispatch_event(DOM::Event::create(HTML::EventNames::error)); + return; } - // FIXME: Check classic vs. module script type - - // FIXME: This load should be made asynchronous and the parser should spin an event loop etc. - ResourceLoader::the().load_sync( - url, - [this, url](auto data, auto&) { - if (data.is_null()) { - dbgln("HTMLScriptElement: Failed to load {}", url); - return; - } - m_script_source = String::copy(data); - script_became_ready(); - }, - [this](auto&) { - m_failed_to_load = true; - }); + if (m_script_type == ScriptType::Classic) { + // FIXME: This load should be made asynchronous and the parser should spin an event loop etc. + ResourceLoader::the().load_sync( + url, + [this, url](auto data, auto&) { + if (data.is_null()) { + dbgln("HTMLScriptElement: Failed to load {}", url); + return; + } + m_script_source = String::copy(data); + script_became_ready(); + }, + [this](auto&) { + m_failed_to_load = true; + }); + } else { + TODO(); + } } else { - // FIXME: Check classic vs. module script type - m_script_source = source_text; - script_became_ready(); + if (m_script_type == ScriptType::Classic) { + m_script_source = source_text; + script_became_ready(); + } else { + TODO(); + } } - // FIXME: Check classic vs. module - if (has_attribute(HTML::AttributeNames::src) && has_attribute(HTML::AttributeNames::defer) && m_parser_inserted && !has_attribute(HTML::AttributeNames::async)) { + if ((m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::src) && has_attribute(HTML::AttributeNames::defer) && m_parser_inserted && !has_attribute(HTML::AttributeNames::async)) + || (m_script_type == ScriptType::Module && m_parser_inserted && !has_attribute(HTML::AttributeNames::async))) { document().add_script_to_execute_when_parsing_has_finished({}, *this); + when_the_script_is_ready([this] { + m_ready_to_be_parser_executed = true; + }); } - else if (has_attribute(HTML::AttributeNames::src) && m_parser_inserted && !has_attribute(HTML::AttributeNames::async)) { - + else if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::src) && m_parser_inserted && !has_attribute(HTML::AttributeNames::async)) { document().set_pending_parsing_blocking_script({}, this); when_the_script_is_ready([this] { m_ready_to_be_parser_executed = true; }); } - else if (has_attribute(HTML::AttributeNames::src) && !has_attribute(HTML::AttributeNames::async) && !m_non_blocking) { - ASSERT_NOT_REACHED(); + else if ((m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::src) && !has_attribute(HTML::AttributeNames::async) && !m_non_blocking) + || (m_script_type == ScriptType::Module && !has_attribute(HTML::AttributeNames::async) && !m_non_blocking)) { + TODO(); } - else if (has_attribute(HTML::AttributeNames::src)) { + else if ((m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::src)) || m_script_type == ScriptType::Module) { + // FIXME: This should add to a set, not a list. 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. } + // FIXME: If the element does not have a src attribute, and the element is "parser-inserted", + // and either the parser that created the script is an XML parser or it's an HTML parser + // whose script nesting level is not greater than one, and the element's parser document + // has a style sheet that is blocking scripts: + // The element is the pending parsing-blocking script of its parser document. + // (There can only be one such script per Document at a time.) + // Set the element's "ready to be parser-executed" flag. The parser will handle executing the script. + else { // Immediately execute the script block, even if other scripts are already executing. execute_script(); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h index b15afcdd7a..b8f2ea3ec8 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLScriptElement.h @@ -62,6 +62,13 @@ private: bool m_ready_to_be_parser_executed { false }; bool m_failed_to_load { false }; + enum class ScriptType { + Classic, + Module + }; + + ScriptType m_script_type { ScriptType::Classic }; + Function<void()> m_script_ready_callback; String m_script_source; diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp index fff01fba46..775cc21dba 100644 --- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp +++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLDocumentParser.cpp @@ -170,6 +170,7 @@ void HTMLDocumentParser::run(const URL& url) 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. script.execute_script(); } @@ -177,6 +178,8 @@ void HTMLDocumentParser::run(const 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(); @@ -583,7 +586,7 @@ void HTMLDocumentParser::handle_in_head(HTMLToken& token) script_element.set_non_blocking({}, false); if (m_parsing_fragment) { - TODO(); + script_element.set_already_started({}, true); } if (m_invoked_via_document_write) { |