mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 04:25:13 +00:00
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.
This commit is contained in:
parent
3a30180e1e
commit
45da08a1e6
Notes:
sideshowbarker
2024-07-19 06:10:09 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/45da08a1e69
8 changed files with 365 additions and 34 deletions
|
@ -45,6 +45,7 @@
|
|||
#include <LibWeb/DOM/HTMLBodyElement.h>
|
||||
#include <LibWeb/DOM/HTMLHeadElement.h>
|
||||
#include <LibWeb/DOM/HTMLHtmlElement.h>
|
||||
#include <LibWeb/DOM/HTMLScriptElement.h>
|
||||
#include <LibWeb/DOM/HTMLTitleElement.h>
|
||||
#include <LibWeb/DOM/Text.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));
|
||||
}
|
||||
|
||||
void Document::set_pending_parsing_blocking_script(Badge<HTMLScriptElement>, HTMLScriptElement* script)
|
||||
{
|
||||
m_pending_parsing_blocking_script = script;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -128,6 +128,8 @@ public:
|
|||
NonnullRefPtr<Element> create_element(const String& tag_name);
|
||||
NonnullRefPtr<Text> create_text_node(const String& data);
|
||||
|
||||
void set_pending_parsing_blocking_script(Badge<HTMLScriptElement>, HTMLScriptElement*);
|
||||
|
||||
private:
|
||||
virtual RefPtr<LayoutNode> create_layout_node(const StyleProperties* parent_style) const override;
|
||||
|
||||
|
@ -151,6 +153,8 @@ private:
|
|||
String m_source;
|
||||
|
||||
OwnPtr<JS::Interpreter> m_interpreter;
|
||||
|
||||
RefPtr<HTMLScriptElement> m_pending_parsing_blocking_script;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
HTMLElement::children_changed();
|
||||
|
@ -105,4 +115,125 @@ void HTMLScriptElement::inserted_into(Node& new_parent)
|
|||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <LibWeb/DOM/HTMLElement.h>
|
||||
|
||||
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<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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <LibWeb/DOM/ElementFactory.h>
|
||||
#include <LibWeb/DOM/HTMLFormElement.h>
|
||||
#include <LibWeb/DOM/HTMLHeadElement.h>
|
||||
#include <LibWeb/DOM/HTMLScriptElement.h>
|
||||
#include <LibWeb/DOM/Text.h>
|
||||
#include <LibWeb/Parser/HTMLDocumentParser.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->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<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") {
|
||||
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<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()) {
|
||||
m_stack_of_open_elements.pop();
|
||||
|
|
|
@ -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<Document> m_document;
|
||||
RefPtr<HTMLHeadElement> m_head_element;
|
||||
|
|
|
@ -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<HTMLToken> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue