diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index ba381d7ac0..dec30f6e80 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -396,4 +397,9 @@ NonnullRefPtr Document::create_text_node(const String& data) return adopt(*new Text(*this, data)); } +void Document::set_pending_parsing_blocking_script(Badge, HTMLScriptElement* script) +{ + m_pending_parsing_blocking_script = script; +} + } diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index 601dbf7bd9..dc9c88bb56 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -128,6 +128,8 @@ public: NonnullRefPtr create_element(const String& tag_name); NonnullRefPtr create_text_node(const String& data); + void set_pending_parsing_blocking_script(Badge, HTMLScriptElement*); + private: virtual RefPtr create_layout_node(const StyleProperties* parent_style) const override; @@ -151,6 +153,8 @@ private: String m_source; OwnPtr m_interpreter; + + RefPtr m_pending_parsing_blocking_script; }; template<> diff --git a/Libraries/LibWeb/DOM/HTMLScriptElement.cpp b/Libraries/LibWeb/DOM/HTMLScriptElement.cpp index bdd8504b5b..6a69f9724b 100644 --- a/Libraries/LibWeb/DOM/HTMLScriptElement.cpp +++ b/Libraries/LibWeb/DOM/HTMLScriptElement.cpp @@ -43,6 +43,16 @@ HTMLScriptElement::~HTMLScriptElement() { } +void HTMLScriptElement::set_parser_document(Badge, Document& document) +{ + m_parser_document = document.make_weak_ptr(); +} + +void HTMLScriptElement::set_non_blocking(Badge, bool non_blocking) +{ + m_non_blocking = non_blocking; +} + void HTMLScriptElement::children_changed() { HTMLElement::children_changed(); @@ -105,4 +115,125 @@ void HTMLScriptElement::inserted_into(Node& new_parent) document().interpreter().run(*program); } +void HTMLScriptElement::prepare_script(Badge) +{ + if (m_already_started) + return; + RefPtr parser_document = m_parser_document.ptr(); + m_parser_document = nullptr; + + if (parser_document && !has_attribute("async")) { + m_non_blocking = true; + } + + auto source_text = child_text_content(); + if (!has_attribute("src") && source_text.is_empty()) + return; + + if (!is_connected()) + return; + + // FIXME: Check the "type" and "language" attributes + + if (parser_document) { + m_parser_document = parser_document->make_weak_ptr(); + m_non_blocking = false; + } + + m_already_started = true; + m_preparation_time_document = document().make_weak_ptr(); + + if (parser_document && parser_document.ptr() != m_preparation_time_document.ptr()) { + return; + } + + // FIXME: Check if scripting is disabled, if so return + // FIXME: Check the "nomodule" content attribute + // FIXME: Check CSP + // FIXME: Check "event" and "for" attributes + // FIXME: Check "charset" attribute + // FIXME: Check CORS + // FIXME: Module script credentials mode + // FIXME: Cryptographic nonce + // FIXME: Check "integrity" attribute + // FIXME: Check "referrerpolicy" attribute + + m_parser_inserted = !!m_parser_document; + + // FIXME: Check fetch options + + if (has_attribute("src")) { + auto src = attribute("src"); + if (src.is_empty()) { + // FIXME: Fire an "error" event at the element and return + ASSERT_NOT_REACHED(); + } + 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(); + } + + // FIXME: Check classic vs. module script type + + ResourceLoader::the().load(url, [this, url](auto& data, auto&) { + if (data.is_null()) { + dbg() << "HTMLScriptElement: Failed to load " << url; + return; + } + m_script_source = String::copy(data); + script_became_ready(); + }); + } else { + // FIXME: Check classic vs. module script type + m_script_source = source_text; + script_became_ready(); + } + + // FIXME: Check classic vs. module + if (has_attribute("src") && has_attribute("defer") && m_parser_inserted && !has_attribute("async")) { + // FIXME: Add the element to the end of the list of scripts that will execute + // when the document has finished parsing associated with the Document + // of the parser that created the element. + ASSERT_NOT_REACHED(); + } + + else if (has_attribute("src") && m_parser_inserted && !has_attribute("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("src") && !has_attribute("async") && !m_non_blocking) { + ASSERT_NOT_REACHED(); + } + + else if (has_attribute("src")) { + ASSERT_NOT_REACHED(); + } + + else { + ASSERT_NOT_REACHED(); + } +} + +void HTMLScriptElement::script_became_ready() +{ + ASSERT(m_script_ready_callback); + m_script_ready_callback(); + m_script_ready_callback = nullptr; +} + +void HTMLScriptElement::when_the_script_is_ready(Function callback) +{ + if (m_script_ready) { + callback(); + return; + } + m_script_ready_callback = move(callback); +} + } diff --git a/Libraries/LibWeb/DOM/HTMLScriptElement.h b/Libraries/LibWeb/DOM/HTMLScriptElement.h index 92f856d0a7..8374372a9c 100644 --- a/Libraries/LibWeb/DOM/HTMLScriptElement.h +++ b/Libraries/LibWeb/DOM/HTMLScriptElement.h @@ -26,6 +26,7 @@ #pragma once +#include #include namespace Web { @@ -37,6 +38,35 @@ public: virtual void inserted_into(Node&) override; virtual void children_changed() override; + + bool is_non_blocking() const { return m_non_blocking; } + + void set_parser_document(Badge, Document&); + void set_non_blocking(Badge, bool); + void prepare_script(Badge); + +private: + void script_became_ready(); + void when_the_script_is_ready(Function); + + WeakPtr m_parser_document; + WeakPtr m_preparation_time_document; + bool m_non_blocking { false }; + bool m_already_started { false }; + bool m_parser_inserted { false }; + bool m_from_an_external_file { false }; + bool m_script_ready { false }; + bool m_ready_to_be_parser_executed { false }; + + Function m_script_ready_callback; + + String m_script_source; }; +template<> +inline bool is(const Node& node) +{ + return is(node) && to(node).tag_name().equals_ignoring_case("script"); +} + } diff --git a/Libraries/LibWeb/Parser/HTMLDocumentParser.cpp b/Libraries/LibWeb/Parser/HTMLDocumentParser.cpp index 84be5dd5e0..3b7aad8ad3 100644 --- a/Libraries/LibWeb/Parser/HTMLDocumentParser.cpp +++ b/Libraries/LibWeb/Parser/HTMLDocumentParser.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -51,9 +52,10 @@ HTMLDocumentParser::~HTMLDocumentParser() { } -void HTMLDocumentParser::run() +void HTMLDocumentParser::run(const URL& url) { m_document = adopt(*new Document); + m_document->set_url(url); for (;;) { auto optional_token = m_tokenizer.next_token(); @@ -212,6 +214,29 @@ void HTMLDocumentParser::handle_in_head(HTMLToken& token) return; } + if (token.is_start_tag() && token.tag_name() == "script") { + auto adjusted_insertion_location = find_appropriate_place_for_inserting_node(); + auto element = create_element_for(token); + auto& script_element = to(*element); + script_element.set_parser_document({}, document()); + script_element.set_non_blocking({}, false); + + if (m_parsing_fragment) { + TODO(); + } + + if (m_invoked_via_document_write) { + TODO(); + } + + adjusted_insertion_location->append_child(element, false); + m_stack_of_open_elements.push(element); + m_tokenizer.switch_to({}, HTMLTokenizer::State::ScriptData); + m_original_insertion_mode = m_insertion_mode; + m_insertion_mode = InsertionMode::Text; + return; + } + if (token.is_start_tag() && token.tag_name() == "meta") { auto element = insert_html_element(token); m_stack_of_open_elements.pop(); @@ -425,6 +450,17 @@ void HTMLDocumentParser::handle_in_body(HTMLToken& token) ASSERT_NOT_REACHED(); } +void HTMLDocumentParser::increment_script_nesting_level() +{ + ++m_script_nesting_level; +} + +void HTMLDocumentParser::decrement_script_nesting_level() +{ + ASSERT(m_script_nesting_level); + --m_script_nesting_level; +} + void HTMLDocumentParser::handle_text(HTMLToken& token) { if (token.is_character()) { @@ -432,7 +468,17 @@ void HTMLDocumentParser::handle_text(HTMLToken& token) return; } if (token.is_end_tag() && token.tag_name() == "script") { - ASSERT_NOT_REACHED(); + NonnullRefPtr script = to(current_node()); + m_stack_of_open_elements.pop(); + m_insertion_mode = m_original_insertion_mode; + // FIXME: Handle tokenizer insertion point stuff here. + increment_script_nesting_level(); + script->prepare_script({}); + decrement_script_nesting_level(); + if (script_nesting_level() == 0) + m_parser_pause_flag = false; + // FIXME: Handle tokenizer insertion point stuff here too. + return; } if (token.is_end_tag()) { m_stack_of_open_elements.pop(); diff --git a/Libraries/LibWeb/Parser/HTMLDocumentParser.h b/Libraries/LibWeb/Parser/HTMLDocumentParser.h index 437e6c1cc8..09b92fbe20 100644 --- a/Libraries/LibWeb/Parser/HTMLDocumentParser.h +++ b/Libraries/LibWeb/Parser/HTMLDocumentParser.h @@ -63,7 +63,7 @@ public: explicit HTMLDocumentParser(const StringView& input); ~HTMLDocumentParser(); - void run(); + void run(const URL&); Document& document(); @@ -100,6 +100,9 @@ private: void reconstruct_the_active_formatting_elements(); void process_using_the_rules_for(InsertionMode, HTMLToken&); void parse_generic_raw_text_element(HTMLToken&); + void increment_script_nesting_level(); + void decrement_script_nesting_level(); + size_t script_nesting_level() const { return m_script_nesting_level; } InsertionMode m_insertion_mode { InsertionMode::Initial }; InsertionMode m_original_insertion_mode { InsertionMode::Initial }; @@ -114,6 +117,10 @@ private: bool m_frameset_ok { true }; bool m_parsing_fragment { false }; bool m_scripting_enabled { true }; + bool m_invoked_via_document_write { false }; + + bool m_parser_pause_flag { false }; + size_t m_script_nesting_level { 0 }; RefPtr m_document; RefPtr m_head_element; diff --git a/Libraries/LibWeb/Parser/HTMLTokenizer.cpp b/Libraries/LibWeb/Parser/HTMLTokenizer.cpp index 98d6948cb5..eb277e27d3 100644 --- a/Libraries/LibWeb/Parser/HTMLTokenizer.cpp +++ b/Libraries/LibWeb/Parser/HTMLTokenizer.cpp @@ -38,22 +38,28 @@ ASSERT_NOT_REACHED(); \ } while (0) -#define SWITCH_TO(new_state) \ - will_switch_to(State::new_state); \ - m_state = State::new_state; \ - current_input_character = next_codepoint(); \ - goto new_state; +#define SWITCH_TO(new_state) \ + do { \ + will_switch_to(State::new_state); \ + m_state = State::new_state; \ + current_input_character = next_codepoint(); \ + goto new_state; \ + } while (0) -#define RECONSUME_IN(new_state) \ - will_reconsume_in(State::new_state); \ - m_state = State::new_state; \ - goto new_state; +#define RECONSUME_IN(new_state) \ + do { \ + will_reconsume_in(State::new_state); \ + m_state = State::new_state; \ + goto new_state; \ + } while (0) #define SWITCH_TO_AND_EMIT_CURRENT_TOKEN(new_state) \ - will_switch_to(State::new_state); \ - m_state = State::new_state; \ - will_emit(m_current_token); \ - return m_current_token; + do { \ + will_switch_to(State::new_state); \ + m_state = State::new_state; \ + will_emit(m_current_token); \ + return m_current_token; \ + } while (0) #define DONT_CONSUME_NEXT_INPUT_CHARACTER --m_cursor; @@ -77,23 +83,29 @@ #define ANYTHING_ELSE if (1) -#define EMIT_EOF \ - if (m_has_emitted_eof) \ - return {}; \ - m_has_emitted_eof = true; \ - create_new_token(HTMLToken::Type::EndOfFile); \ - will_emit(m_current_token); \ - return m_current_token; +#define EMIT_EOF \ + do { \ + if (m_has_emitted_eof) \ + return {}; \ + m_has_emitted_eof = true; \ + create_new_token(HTMLToken::Type::EndOfFile); \ + will_emit(m_current_token); \ + return m_current_token; \ + } while (0) -#define EMIT_CURRENT_TOKEN \ - will_emit(m_current_token); \ - return m_current_token; +#define EMIT_CURRENT_TOKEN \ + do { \ + will_emit(m_current_token); \ + return m_current_token; \ + } while (0) -#define EMIT_CHARACTER(codepoint) \ - create_new_token(HTMLToken::Type::Character); \ - m_current_token.m_comment_or_character.data.append(codepoint); \ - will_emit(m_current_token); \ - return m_current_token; +#define EMIT_CHARACTER(codepoint) \ + do { \ + create_new_token(HTMLToken::Type::Character); \ + m_current_token.m_comment_or_character.data.append(codepoint); \ + will_emit(m_current_token); \ + return m_current_token; \ + } while (0) #define EMIT_CURRENT_CHARACTER \ EMIT_CHARACTER(current_input_character.value()); @@ -915,8 +927,104 @@ Optional HTMLTokenizer::next_token() } END_STATE + BEGIN_STATE(ScriptData) + { + ON('<') + { + SWITCH_TO(ScriptDataLessThanSign); + } + ON(0) + { + TODO(); + } + ON_EOF + { + EMIT_EOF; + } + ANYTHING_ELSE + { + EMIT_CURRENT_CHARACTER; + } + } + END_STATE + + BEGIN_STATE(ScriptDataLessThanSign) + { + ON('/') + { + m_temporary_buffer.clear(); + SWITCH_TO(ScriptDataEndTagOpen); + } + ON('!') + { + TODO(); + } + ANYTHING_ELSE + { + EMIT_CHARACTER('<'); + RECONSUME_IN(ScriptData); + } + } + END_STATE + + BEGIN_STATE(ScriptDataEndTagOpen) + { + ON_ASCII_ALPHA + { + create_new_token(HTMLToken::Type::EndTag); + RECONSUME_IN(ScriptDataEndTagName); + } + ANYTHING_ELSE + { + TODO(); + } + } + END_STATE + + BEGIN_STATE(ScriptDataEndTagName) + { + ON_WHITESPACE + { + if (current_end_tag_token_is_appropriate()) + SWITCH_TO(BeforeAttributeName); + // FIXME: Otherwise, treat it as per the "anything else" entry below. + TODO(); + } + ON('/') + { + if (current_end_tag_token_is_appropriate()) + SWITCH_TO(SelfClosingStartTag); + // FIXME: Otherwise, treat it as per the "anything else" entry below. + TODO(); + } + ON('>') + { + if (current_end_tag_token_is_appropriate()) + SWITCH_TO_AND_EMIT_CURRENT_TOKEN(Data); + // FIXME: Otherwise, treat it as per the "anything else" entry below. + TODO(); + } + ON_ASCII_UPPER_ALPHA + { + m_current_token.m_tag.tag_name.append(tolower(current_input_character.value())); + m_temporary_buffer.append(current_input_character.value()); + continue; + } + ON_ASCII_LOWER_ALPHA + { + m_current_token.m_tag.tag_name.append(current_input_character.value()); + m_temporary_buffer.append(current_input_character.value()); + continue; + } + ANYTHING_ELSE + { + TODO(); + } + } + END_STATE + default: - ASSERT_NOT_REACHED(); + TODO(); } } } @@ -986,5 +1094,4 @@ bool HTMLTokenizer::current_end_tag_token_is_appropriate() const return false; return m_current_token.tag_name() == m_last_emitted_start_tag.tag_name(); } - } diff --git a/Userland/ht.cpp b/Userland/ht.cpp index 27667530cb..b3354c2a7f 100644 --- a/Userland/ht.cpp +++ b/Userland/ht.cpp @@ -48,7 +48,7 @@ int main(int argc, char** argv) auto contents = file_or_error.value()->read_all(); Web::HTMLDocumentParser parser(contents); - parser.run(); + parser.run(URL::create_with_file_protocol(input_path)); auto& document = parser.document(); Web::dump_tree(document);