Преглед на файлове

LibWeb: A whole bunch of work towards spec-compliant <script> elements

This is still very unfinished, but there's at least a skeleton of code.
Andreas Kling преди 5 години
родител
ревизия
45da08a1e6

+ 6 - 0
Libraries/LibWeb/DOM/Document.cpp

@@ -45,6 +45,7 @@
 #include <LibWeb/DOM/HTMLBodyElement.h>
 #include <LibWeb/DOM/HTMLBodyElement.h>
 #include <LibWeb/DOM/HTMLHeadElement.h>
 #include <LibWeb/DOM/HTMLHeadElement.h>
 #include <LibWeb/DOM/HTMLHtmlElement.h>
 #include <LibWeb/DOM/HTMLHtmlElement.h>
+#include <LibWeb/DOM/HTMLScriptElement.h>
 #include <LibWeb/DOM/HTMLTitleElement.h>
 #include <LibWeb/DOM/HTMLTitleElement.h>
 #include <LibWeb/DOM/Text.h>
 #include <LibWeb/DOM/Text.h>
 #include <LibWeb/DOM/Window.h>
 #include <LibWeb/DOM/Window.h>
@@ -396,4 +397,9 @@ NonnullRefPtr<Text> Document::create_text_node(const String& data)
     return adopt(*new Text(*this, data));
     return adopt(*new Text(*this, data));
 }
 }
 
 
+void Document::set_pending_parsing_blocking_script(Badge<HTMLScriptElement>, HTMLScriptElement* script)
+{
+    m_pending_parsing_blocking_script = script;
+}
+
 }
 }

+ 4 - 0
Libraries/LibWeb/DOM/Document.h

@@ -128,6 +128,8 @@ public:
     NonnullRefPtr<Element> create_element(const String& tag_name);
     NonnullRefPtr<Element> create_element(const String& tag_name);
     NonnullRefPtr<Text> create_text_node(const String& data);
     NonnullRefPtr<Text> create_text_node(const String& data);
 
 
+    void set_pending_parsing_blocking_script(Badge<HTMLScriptElement>, HTMLScriptElement*);
+
 private:
 private:
     virtual RefPtr<LayoutNode> create_layout_node(const StyleProperties* parent_style) const override;
     virtual RefPtr<LayoutNode> create_layout_node(const StyleProperties* parent_style) const override;
 
 
@@ -151,6 +153,8 @@ private:
     String m_source;
     String m_source;
 
 
     OwnPtr<JS::Interpreter> m_interpreter;
     OwnPtr<JS::Interpreter> m_interpreter;
+
+    RefPtr<HTMLScriptElement> m_pending_parsing_blocking_script;
 };
 };
 
 
 template<>
 template<>

+ 131 - 0
Libraries/LibWeb/DOM/HTMLScriptElement.cpp

@@ -43,6 +43,16 @@ HTMLScriptElement::~HTMLScriptElement()
 {
 {
 }
 }
 
 
+void HTMLScriptElement::set_parser_document(Badge<HTMLDocumentParser>, Document& document)
+{
+    m_parser_document = document.make_weak_ptr();
+}
+
+void HTMLScriptElement::set_non_blocking(Badge<HTMLDocumentParser>, bool non_blocking)
+{
+    m_non_blocking = non_blocking;
+}
+
 void HTMLScriptElement::children_changed()
 void HTMLScriptElement::children_changed()
 {
 {
     HTMLElement::children_changed();
     HTMLElement::children_changed();
@@ -105,4 +115,125 @@ void HTMLScriptElement::inserted_into(Node& new_parent)
     document().interpreter().run(*program);
     document().interpreter().run(*program);
 }
 }
 
 
+void HTMLScriptElement::prepare_script(Badge<HTMLDocumentParser>)
+{
+    if (m_already_started)
+        return;
+    RefPtr<Document> 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<void()> callback)
+{
+    if (m_script_ready) {
+        callback();
+        return;
+    }
+    m_script_ready_callback = move(callback);
+}
+
 }
 }

+ 30 - 0
Libraries/LibWeb/DOM/HTMLScriptElement.h

@@ -26,6 +26,7 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <AK/Function.h>
 #include <LibWeb/DOM/HTMLElement.h>
 #include <LibWeb/DOM/HTMLElement.h>
 
 
 namespace Web {
 namespace Web {
@@ -37,6 +38,35 @@ public:
 
 
     virtual void inserted_into(Node&) override;
     virtual void inserted_into(Node&) override;
     virtual void children_changed() override;
     virtual void children_changed() override;
+
+    bool is_non_blocking() const { return m_non_blocking; }
+
+    void set_parser_document(Badge<HTMLDocumentParser>, Document&);
+    void set_non_blocking(Badge<HTMLDocumentParser>, bool);
+    void prepare_script(Badge<HTMLDocumentParser>);
+
+private:
+    void script_became_ready();
+    void when_the_script_is_ready(Function<void()>);
+
+    WeakPtr<Document> m_parser_document;
+    WeakPtr<Document> 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<void()> m_script_ready_callback;
+
+    String m_script_source;
 };
 };
 
 
+template<>
+inline bool is<HTMLScriptElement>(const Node& node)
+{
+    return is<Element>(node) && to<Element>(node).tag_name().equals_ignoring_case("script");
+}
+
 }
 }

+ 48 - 2
Libraries/LibWeb/Parser/HTMLDocumentParser.cpp

@@ -31,6 +31,7 @@
 #include <LibWeb/DOM/ElementFactory.h>
 #include <LibWeb/DOM/ElementFactory.h>
 #include <LibWeb/DOM/HTMLFormElement.h>
 #include <LibWeb/DOM/HTMLFormElement.h>
 #include <LibWeb/DOM/HTMLHeadElement.h>
 #include <LibWeb/DOM/HTMLHeadElement.h>
+#include <LibWeb/DOM/HTMLScriptElement.h>
 #include <LibWeb/DOM/Text.h>
 #include <LibWeb/DOM/Text.h>
 #include <LibWeb/Parser/HTMLDocumentParser.h>
 #include <LibWeb/Parser/HTMLDocumentParser.h>
 #include <LibWeb/Parser/HTMLToken.h>
 #include <LibWeb/Parser/HTMLToken.h>
@@ -51,9 +52,10 @@ HTMLDocumentParser::~HTMLDocumentParser()
 {
 {
 }
 }
 
 
-void HTMLDocumentParser::run()
+void HTMLDocumentParser::run(const URL& url)
 {
 {
     m_document = adopt(*new Document);
     m_document = adopt(*new Document);
+    m_document->set_url(url);
 
 
     for (;;) {
     for (;;) {
         auto optional_token = m_tokenizer.next_token();
         auto optional_token = m_tokenizer.next_token();
@@ -212,6 +214,29 @@ void HTMLDocumentParser::handle_in_head(HTMLToken& token)
         return;
         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<HTMLScriptElement>(*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") {
     if (token.is_start_tag() && token.tag_name() == "meta") {
         auto element = insert_html_element(token);
         auto element = insert_html_element(token);
         m_stack_of_open_elements.pop();
         m_stack_of_open_elements.pop();
@@ -425,6 +450,17 @@ void HTMLDocumentParser::handle_in_body(HTMLToken& token)
     ASSERT_NOT_REACHED();
     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)
 void HTMLDocumentParser::handle_text(HTMLToken& token)
 {
 {
     if (token.is_character()) {
     if (token.is_character()) {
@@ -432,7 +468,17 @@ void HTMLDocumentParser::handle_text(HTMLToken& token)
         return;
         return;
     }
     }
     if (token.is_end_tag() && token.tag_name() == "script") {
     if (token.is_end_tag() && token.tag_name() == "script") {
-        ASSERT_NOT_REACHED();
+        NonnullRefPtr<HTMLScriptElement> script = to<HTMLScriptElement>(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()) {
     if (token.is_end_tag()) {
         m_stack_of_open_elements.pop();
         m_stack_of_open_elements.pop();

+ 8 - 1
Libraries/LibWeb/Parser/HTMLDocumentParser.h

@@ -63,7 +63,7 @@ public:
     explicit HTMLDocumentParser(const StringView& input);
     explicit HTMLDocumentParser(const StringView& input);
     ~HTMLDocumentParser();
     ~HTMLDocumentParser();
 
 
-    void run();
+    void run(const URL&);
 
 
     Document& document();
     Document& document();
 
 
@@ -100,6 +100,9 @@ private:
     void reconstruct_the_active_formatting_elements();
     void reconstruct_the_active_formatting_elements();
     void process_using_the_rules_for(InsertionMode, HTMLToken&);
     void process_using_the_rules_for(InsertionMode, HTMLToken&);
     void parse_generic_raw_text_element(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_insertion_mode { InsertionMode::Initial };
     InsertionMode m_original_insertion_mode { InsertionMode::Initial };
     InsertionMode m_original_insertion_mode { InsertionMode::Initial };
@@ -114,6 +117,10 @@ private:
     bool m_frameset_ok { true };
     bool m_frameset_ok { true };
     bool m_parsing_fragment { false };
     bool m_parsing_fragment { false };
     bool m_scripting_enabled { true };
     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<Document> m_document;
     RefPtr<Document> m_document;
     RefPtr<HTMLHeadElement> m_head_element;
     RefPtr<HTMLHeadElement> m_head_element;

+ 137 - 30
Libraries/LibWeb/Parser/HTMLTokenizer.cpp

@@ -38,22 +38,28 @@
         ASSERT_NOT_REACHED();                                                                               \
         ASSERT_NOT_REACHED();                                                                               \
     } while (0)
     } 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) \
 #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;
 #define DONT_CONSUME_NEXT_INPUT_CHARACTER --m_cursor;
 
 
@@ -77,23 +83,29 @@
 
 
 #define ANYTHING_ELSE if (1)
 #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 \
 #define EMIT_CURRENT_CHARACTER \
     EMIT_CHARACTER(current_input_character.value());
     EMIT_CHARACTER(current_input_character.value());
@@ -915,8 +927,104 @@ Optional<HTMLToken> HTMLTokenizer::next_token()
             }
             }
             END_STATE
             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:
         default:
-            ASSERT_NOT_REACHED();
+            TODO();
         }
         }
     }
     }
 }
 }
@@ -986,5 +1094,4 @@ bool HTMLTokenizer::current_end_tag_token_is_appropriate() const
         return false;
         return false;
     return m_current_token.tag_name() == m_last_emitted_start_tag.tag_name();
     return m_current_token.tag_name() == m_last_emitted_start_tag.tag_name();
 }
 }
-
 }
 }

+ 1 - 1
Userland/ht.cpp

@@ -48,7 +48,7 @@ int main(int argc, char** argv)
     auto contents = file_or_error.value()->read_all();
     auto contents = file_or_error.value()->read_all();
 
 
     Web::HTMLDocumentParser parser(contents);
     Web::HTMLDocumentParser parser(contents);
-    parser.run();
+    parser.run(URL::create_with_file_protocol(input_path));
 
 
     auto& document = parser.document();
     auto& document = parser.document();
     Web::dump_tree(document);
     Web::dump_tree(document);