From e01dfaac9ad06016aa935b70e998987b65700eec Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 15 Oct 2021 09:57:07 -0400 Subject: [PATCH] LibWeb: Implement Attribute closer to the spec and with an IDL file Note our Attribute class is what the spec refers to as just "Attr". The main differences between the existing implementation and the spec are just that the spec defines more fields. Attributes can contain namespace URIs and prefixes. However, note that these are not parsed in HTML documents unless the document content-type is XML. So for now, these are initialized to null. Web pages are able to set the namespace via JavaScript (setAttributeNS), so these fields may be filled in when the corresponding APIs are implemented. The main change to be aware of is that an attribute is a node. This has implications on how attributes are stored in the Element class. Nodes are non-copyable and non-movable because these constructors are deleted by the EventTarget base class. This means attributes cannot be stored in a Vector or HashMap as these containers assume copyability / movability. So for now, the Vector holding attributes is changed to hold RefPtrs to attributes instead. This might change when attribute storage is implemented according to the spec (by way of NamedNodeMap). --- .../LibWeb/WrapperGenerator.cpp | 4 ++ Userland/Libraries/LibWeb/CMakeLists.txt | 3 ++ Userland/Libraries/LibWeb/DOM/Attribute.cpp | 24 ++++++++++++ Userland/Libraries/LibWeb/DOM/Attribute.h | 37 +++++++++++++----- Userland/Libraries/LibWeb/DOM/Attribute.idl | 12 ++++++ Userland/Libraries/LibWeb/DOM/Element.cpp | 12 +++--- Userland/Libraries/LibWeb/DOM/Element.h | 4 +- Userland/Libraries/LibWeb/Forward.h | 2 + .../HTML/Parser/HTMLEncodingDetection.cpp | 38 ++++++++++--------- .../HTML/Parser/HTMLEncodingDetection.h | 8 ++-- .../LibWeb/HTML/Parser/HTMLParser.cpp | 4 +- 11 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 Userland/Libraries/LibWeb/DOM/Attribute.cpp create mode 100644 Userland/Libraries/LibWeb/DOM/Attribute.idl diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp index a707ce13c28..d9dcec0c281 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/WrapperGenerator.cpp @@ -926,6 +926,8 @@ static bool is_wrappable_type(IDL::Type const& type) return true; if (type.name == "Selection") return true; + if (type.name == "Attribute") + return true; return false; } @@ -1628,6 +1630,7 @@ void generate_implementation(IDL::Interface const& interface) #include #include #include +#include #include #include #include @@ -2836,6 +2839,7 @@ void generate_prototype_implementation(IDL::Interface const& interface) #include #include #include +#include #include #include #include diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 2f407f77034..f16da893c34 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -54,6 +54,8 @@ set(SOURCES Cookie/ParsedCookie.cpp DOM/AbortController.cpp DOM/AbortSignal.cpp + DOM/Attribute.cpp + DOM/Attribute.idl DOM/CharacterData.cpp DOM/CharacterData.idl DOM/Comment.cpp @@ -366,6 +368,7 @@ libweb_js_wrapper(CSS/MediaQueryListEvent) libweb_js_wrapper(CSS/Screen) libweb_js_wrapper(CSS/StyleSheet) libweb_js_wrapper(CSS/StyleSheetList) +libweb_js_wrapper(DOM/Attribute) libweb_js_wrapper(DOM/AbortController) libweb_js_wrapper(DOM/AbortSignal) libweb_js_wrapper(DOM/CharacterData) diff --git a/Userland/Libraries/LibWeb/DOM/Attribute.cpp b/Userland/Libraries/LibWeb/DOM/Attribute.cpp new file mode 100644 index 00000000000..996a80a4045 --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/Attribute.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace Web::DOM { + +NonnullRefPtr Attribute::create(Document& document, FlyString local_name, String value) +{ + return adopt_ref(*new Attribute(document, move(local_name), move(value))); +} + +Attribute::Attribute(Document& document, FlyString local_name, String value) + : Node(document, NodeType::ATTRIBUTE_NODE) + , m_qualified_name(move(local_name), {}, {}) + , m_value(move(value)) +{ +} + +} diff --git a/Userland/Libraries/LibWeb/DOM/Attribute.h b/Userland/Libraries/LibWeb/DOM/Attribute.h index 5261a5bf7d2..0a9dfd97b95 100644 --- a/Userland/Libraries/LibWeb/DOM/Attribute.h +++ b/Userland/Libraries/LibWeb/DOM/Attribute.h @@ -7,25 +7,42 @@ #pragma once #include +#include +#include namespace Web::DOM { -class Attribute { +// https://dom.spec.whatwg.org/#attr +class Attribute final : public Node { public: - Attribute(const FlyString& name, const String& value) - : m_name(name) - , m_value(value) - { - } + using WrapperType = Bindings::AttributeWrapper; - const FlyString& name() const { return m_name; } - const String& value() const { return m_value; } + static NonnullRefPtr create(Document&, FlyString local_name, String value); - void set_value(const String& value) { m_value = value; } + virtual ~Attribute() override = default; + + virtual FlyString node_name() const override { return name(); } + + FlyString const& namespace_uri() const { return m_qualified_name.namespace_(); } + FlyString const& prefix() const { return m_qualified_name.prefix(); } + FlyString const& local_name() const { return m_qualified_name.local_name(); } + String const& name() const { return m_qualified_name.as_string(); } + + String const& value() const { return m_value; } + void set_value(String value) { m_value = move(value); } + + Element const* owner_element() const { return m_owner_element; } + void set_owner_element(Element const* owner_element) { m_owner_element = owner_element; } + + // Always returns true: https://dom.spec.whatwg.org/#dom-attr-specified + constexpr bool specified() const { return true; } private: - FlyString m_name; + Attribute(Document&, FlyString local_name, String value); + + QualifiedName m_qualified_name; String m_value; + Element const* m_owner_element { nullptr }; }; } diff --git a/Userland/Libraries/LibWeb/DOM/Attribute.idl b/Userland/Libraries/LibWeb/DOM/Attribute.idl new file mode 100644 index 00000000000..8b15866a3fc --- /dev/null +++ b/Userland/Libraries/LibWeb/DOM/Attribute.idl @@ -0,0 +1,12 @@ +[Exposed=Window] +interface Attribute : Node { + readonly attribute DOMString? namespaceURI; + readonly attribute DOMString? prefix; + readonly attribute DOMString localName; + readonly attribute DOMString name; + [CEReactions] attribute DOMString value; + + readonly attribute Element? ownerElement; + + readonly attribute boolean specified; +}; diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp index bed9e07cfe0..e6ea910150a 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.cpp +++ b/Userland/Libraries/LibWeb/DOM/Element.cpp @@ -49,8 +49,8 @@ Element::~Element() Attribute* Element::find_attribute(const FlyString& name) { for (auto& attribute : m_attributes) { - if (attribute.name() == name) - return &attribute; + if (attribute->name() == name) + return attribute; } return nullptr; } @@ -58,8 +58,8 @@ Attribute* Element::find_attribute(const FlyString& name) const Attribute* Element::find_attribute(const FlyString& name) const { for (auto& attribute : m_attributes) { - if (attribute.name() == name) - return &attribute; + if (attribute->name() == name) + return attribute; } return nullptr; } @@ -82,7 +82,7 @@ ExceptionOr Element::set_attribute(const FlyString& name, const String& va if (auto* attribute = find_attribute(name)) attribute->set_value(value); else - m_attributes.empend(name, value); + m_attributes.append(Attribute::create(document(), name, value)); parse_attribute(name, value); return {}; @@ -92,7 +92,7 @@ void Element::remove_attribute(const FlyString& name) { CSS::StyleInvalidator style_invalidator(document()); - m_attributes.remove_first_matching([&](auto& attribute) { return attribute.name() == name; }); + m_attributes.remove_first_matching([&](auto& attribute) { return attribute->name() == name; }); } bool Element::has_class(const FlyString& class_name, CaseSensitivity case_sensitivity) const diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h index d23c6402805..cc829ff53dc 100644 --- a/Userland/Libraries/LibWeb/DOM/Element.h +++ b/Userland/Libraries/LibWeb/DOM/Element.h @@ -67,7 +67,7 @@ public: void for_each_attribute(Callback callback) const { for (auto& attribute : m_attributes) - callback(attribute.name(), attribute.value()); + callback(attribute->name(), attribute->value()); } bool has_class(const FlyString&, CaseSensitivity = CaseSensitivity::CaseSensitive) const; @@ -132,7 +132,7 @@ private: QualifiedName m_qualified_name; String m_html_uppercased_qualified_name; - Vector m_attributes; + Vector> m_attributes; RefPtr m_inline_style; diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index ab862c7c11a..2f3dd779dde 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -71,6 +71,7 @@ class UnsetStyleValue; namespace Web::DOM { class AbortController; class AbortSignal; +class Attribute; class CharacterData; class Comment; class CustomEvent; @@ -290,6 +291,7 @@ class URLSearchParamsIterator; namespace Web::Bindings { class AbortControllerWrapper; class AbortSignalWrapper; +class AttributeWrapper; class CanvasRenderingContext2DWrapper; class CharacterDataWrapper; class CloseEventWrapper; diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.cpp index 460e0a3872c..f0c5c524b1b 100644 --- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.cpp +++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include @@ -94,7 +96,7 @@ Optional extract_character_encoding_from_meta_element(String const& stri return TextCodec::get_standardized_encoding(encoding); } -Optional prescan_get_attribute(const ByteBuffer& input, size_t& position) +RefPtr prescan_get_attribute(DOM::Document& document, const ByteBuffer& input, size_t& position) { if (!prescan_skip_whitespace_and_slashes(input, position)) return {}; @@ -109,7 +111,7 @@ Optional prescan_get_attribute(const ByteBuffer& input, size_t& } else if (input[position] == '\t' || input[position] == '\n' || input[position] == '\f' || input[position] == '\r' || input[position] == ' ') goto spaces; else if (input[position] == '/' || input[position] == '>') - return DOM::Attribute(attribute_name.to_string(), ""); + return DOM::Attribute::create(document, attribute_name.to_string(), ""); else attribute_name.append_as_lowercase(input[position]); ++position; @@ -121,7 +123,7 @@ spaces: if (!prescan_skip_whitespace_and_slashes(input, position)) return {}; if (input[position] != '=') - return DOM::Attribute(attribute_name.to_string(), ""); + return DOM::Attribute::create(document, attribute_name.to_string(), ""); ++position; value: @@ -134,13 +136,13 @@ value: ++position; for (; !prescan_should_abort(input, position); ++position) { if (input[position] == quote_character) - return DOM::Attribute(attribute_name.to_string(), attribute_value.to_string()); + return DOM::Attribute::create(document, attribute_name.to_string(), attribute_value.to_string()); else attribute_value.append_as_lowercase(input[position]); } return {}; } else if (input[position] == '>') - return DOM::Attribute(attribute_name.to_string(), ""); + return DOM::Attribute::create(document, attribute_name.to_string(), ""); else attribute_value.append_as_lowercase(input[position]); @@ -150,7 +152,7 @@ value: for (; !prescan_should_abort(input, position); ++position) { if (input[position] == '\t' || input[position] == '\n' || input[position] == '\f' || input[position] == '\r' || input[position] == ' ' || input[position] == '>') - return DOM::Attribute(attribute_name.to_string(), attribute_value.to_string()); + return DOM::Attribute::create(document, attribute_name.to_string(), attribute_value.to_string()); else attribute_value.append_as_lowercase(input[position]); } @@ -158,7 +160,7 @@ value: } // https://html.spec.whatwg.org/multipage/parsing.html#prescan-a-byte-stream-to-determine-its-encoding -Optional run_prescan_byte_stream_algorithm(const ByteBuffer& input) +Optional run_prescan_byte_stream_algorithm(DOM::Document& document, const ByteBuffer& input) { // https://html.spec.whatwg.org/multipage/parsing.html#prescan-a-byte-stream-to-determine-its-encoding @@ -194,24 +196,24 @@ Optional run_prescan_byte_stream_algorithm(const ByteBuffer& input) Optional charset {}; while (true) { - auto attribute = prescan_get_attribute(input, position); - if (!attribute.has_value()) + auto attribute = prescan_get_attribute(document, input, position); + if (!attribute) break; - if (attribute_list.contains_slow(attribute.value().name())) + if (attribute_list.contains_slow(attribute->name())) continue; - auto& attribute_name = attribute.value().name(); - attribute_list.append(attribute.value().name()); + auto& attribute_name = attribute->name(); + attribute_list.append(attribute->name()); if (attribute_name == "http-equiv") { - got_pragma = attribute.value().value() == "content-type"; + got_pragma = attribute->value() == "content-type"; } else if (attribute_name == "content") { - auto encoding = extract_character_encoding_from_meta_element(attribute.value().value()); + auto encoding = extract_character_encoding_from_meta_element(attribute->value()); if (encoding.has_value() && !charset.has_value()) { charset = encoding.value(); need_pragma = true; } } else if (attribute_name == "charset") { - auto maybe_charset = TextCodec::get_standardized_encoding(attribute.value().value()); + auto maybe_charset = TextCodec::get_standardized_encoding(attribute->value()); if (maybe_charset.has_value()) { charset = Optional { maybe_charset }; need_pragma = { false }; @@ -231,7 +233,7 @@ Optional run_prescan_byte_stream_algorithm(const ByteBuffer& input) && ((input[position + 1] == '/' && isalpha(input[position + 2])) || isalpha(input[position + 1]))) { position += 2; prescan_skip_whitespace_and_slashes(input, position); - while (prescan_get_attribute(input, position).has_value()) { }; + while (prescan_get_attribute(document, input, position)) { }; } else if (!prescan_should_abort(input, position + 1) && input[position] == '<' && (input[position + 1] == '!' || input[position + 1] == '/' || input[position + 1] == '?')) { position += 2; while (input[position] != '>') { @@ -247,7 +249,7 @@ Optional run_prescan_byte_stream_algorithm(const ByteBuffer& input) } // https://html.spec.whatwg.org/multipage/parsing.html#determining-the-character-encoding -String run_encoding_sniffing_algorithm(const ByteBuffer& input) +String run_encoding_sniffing_algorithm(DOM::Document& document, const ByteBuffer& input) { if (input.size() >= 2) { if (input[0] == 0xFE && input[1] == 0xFF) { @@ -265,7 +267,7 @@ String run_encoding_sniffing_algorithm(const ByteBuffer& input) // at any later step in this algorithm. // FIXME: If the transport layer specifies a character encoding, and it is supported. - auto optional_encoding = run_prescan_byte_stream_algorithm(input); + auto optional_encoding = run_prescan_byte_stream_algorithm(document, input); if (optional_encoding.has_value()) { return optional_encoding.value(); } diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.h b/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.h index 4d9a1e9ab2a..52784e8d7ca 100644 --- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.h +++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLEncodingDetection.h @@ -8,7 +8,7 @@ #include #include -#include +#include namespace Web::HTML { @@ -16,8 +16,8 @@ bool prescan_should_abort(const ByteBuffer& input, const size_t& position); bool prescan_is_whitespace_or_slash(const u8& byte); bool prescan_skip_whitespace_and_slashes(const ByteBuffer& input, size_t& position); Optional extract_character_encoding_from_meta_element(String const&); -Optional prescan_get_attribute(const ByteBuffer& input, size_t& position); -Optional run_prescan_byte_stream_algorithm(const ByteBuffer& input); -String run_encoding_sniffing_algorithm(const ByteBuffer& input); +RefPtr prescan_get_attribute(DOM::Document&, const ByteBuffer& input, size_t& position); +Optional run_prescan_byte_stream_algorithm(DOM::Document&, const ByteBuffer& input); +String run_encoding_sniffing_algorithm(DOM::Document&, const ByteBuffer& input); } diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp index d9fc29f64bf..1a7ada5a103 100644 --- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp +++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp @@ -2810,7 +2810,7 @@ void HTMLParser::handle_in_frameset(HTMLToken& token) } if (token.is_end_of_file()) { - //FIXME: If the current node is not the root html element, then this is a parse error. + // FIXME: If the current node is not the root html element, then this is a parse error. stop_parsing(); return; @@ -3162,7 +3162,7 @@ NonnullOwnPtr HTMLParser::create_with_uncertain_encoding(DOM::Docume { if (document.has_encoding()) return make(document, input, document.encoding().value()); - auto encoding = run_encoding_sniffing_algorithm(input); + auto encoding = run_encoding_sniffing_algorithm(document, input); dbgln("The encoding sniffing algorithm returned encoding '{}'", encoding); return make(document, input, encoding); }