summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/DOM
diff options
context:
space:
mode:
authorLuke Wilde <lukew@serenityos.org>2023-03-29 23:46:18 +0100
committerAndreas Kling <kling@serenityos.org>2023-04-06 11:36:56 +0200
commit034aaf3f51e4779f03eaa5becb016e04a4ff9f4d (patch)
tree987344660dc313c74a1d7fbd57cf31567357ccf9 /Userland/Libraries/LibWeb/DOM
parent083b547e97fbba26c9db43d1b5f5e6d3ac15d617 (diff)
downloadserenity-034aaf3f51e4779f03eaa5becb016e04a4ff9f4d.zip
LibWeb: Introduce CustomElementRegistry and creating custom elements
The main missing feature here is form associated custom elements.
Diffstat (limited to 'Userland/Libraries/LibWeb/DOM')
-rw-r--r--Userland/Libraries/LibWeb/DOM/Attr.cpp30
-rw-r--r--Userland/Libraries/LibWeb/DOM/Attr.h13
-rw-r--r--Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp2
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.cpp122
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.h13
-rw-r--r--Userland/Libraries/LibWeb/DOM/Document.idl7
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.cpp243
-rw-r--r--Userland/Libraries/LibWeb/DOM/Element.h59
-rw-r--r--Userland/Libraries/LibWeb/DOM/ElementFactory.cpp393
-rw-r--r--Userland/Libraries/LibWeb/DOM/ElementFactory.h7
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.cpp59
-rw-r--r--Userland/Libraries/LibWeb/DOM/Node.h4
-rw-r--r--Userland/Libraries/LibWeb/DOM/QualifiedName.cpp5
-rw-r--r--Userland/Libraries/LibWeb/DOM/QualifiedName.h2
-rw-r--r--Userland/Libraries/LibWeb/DOM/ShadowRoot.h22
15 files changed, 888 insertions, 93 deletions
diff --git a/Userland/Libraries/LibWeb/DOM/Attr.cpp b/Userland/Libraries/LibWeb/DOM/Attr.cpp
index 527a07d7cd..a8735498a8 100644
--- a/Userland/Libraries/LibWeb/DOM/Attr.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Attr.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2021, Tim Flynn <trflynn89@serenityos.org>
+ * Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -10,15 +11,16 @@
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/MutationType.h>
#include <LibWeb/DOM/StaticNodeList.h>
+#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
namespace Web::DOM {
-WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> Attr::create(Document& document, DeprecatedFlyString local_name, DeprecatedString value, Element const* owner_element)
+WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> Attr::create(Document& document, DeprecatedFlyString local_name, DeprecatedString value, Element* owner_element)
{
return MUST_OR_THROW_OOM(document.heap().allocate<Attr>(document.realm(), document, QualifiedName(move(local_name), {}, {}), move(value), owner_element));
}
-WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> Attr::create(Document& document, QualifiedName qualified_name, DeprecatedString value, Element const* owner_element)
+WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> Attr::create(Document& document, QualifiedName qualified_name, DeprecatedString value, Element* owner_element)
{
return MUST_OR_THROW_OOM(document.heap().allocate<Attr>(document.realm(), document, move(qualified_name), move(value), owner_element));
}
@@ -28,7 +30,7 @@ JS::NonnullGCPtr<Attr> Attr::clone(Document& document)
return *heap().allocate<Attr>(realm(), document, m_qualified_name, m_value, nullptr).release_allocated_value_but_fixme_should_propagate_errors();
}
-Attr::Attr(Document& document, QualifiedName qualified_name, DeprecatedString value, Element const* owner_element)
+Attr::Attr(Document& document, QualifiedName qualified_name, DeprecatedString value, Element* owner_element)
: Node(document, NodeType::ATTRIBUTE_NODE)
, m_qualified_name(move(qualified_name))
, m_value(move(value))
@@ -50,12 +52,17 @@ void Attr::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_owner_element.ptr());
}
+Element* Attr::owner_element()
+{
+ return m_owner_element.ptr();
+}
+
Element const* Attr::owner_element() const
{
return m_owner_element.ptr();
}
-void Attr::set_owner_element(Element const* owner_element)
+void Attr::set_owner_element(Element* owner_element)
{
m_owner_element = owner_element;
}
@@ -79,14 +86,25 @@ void Attr::set_value(DeprecatedString value)
}
// https://dom.spec.whatwg.org/#handle-attribute-changes
-void Attr::handle_attribute_changes(Element const& element, DeprecatedString const& old_value, [[maybe_unused]] DeprecatedString const& new_value)
+void Attr::handle_attribute_changes(Element& element, DeprecatedString const& old_value, DeprecatedString const& new_value)
{
// 1. Queue a mutation record of "attributes" for element with attribute’s local name, attribute’s namespace, oldValue, « », « », null, and null.
auto added_node_list = StaticNodeList::create(realm(), {}).release_value_but_fixme_should_propagate_errors();
auto removed_node_list = StaticNodeList::create(realm(), {}).release_value_but_fixme_should_propagate_errors();
element.queue_mutation_record(MutationType::attributes, local_name(), namespace_uri(), old_value, added_node_list, removed_node_list, nullptr, nullptr);
- // FIXME: 2. If element is custom, then enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attribute’s local name, oldValue, newValue, and attribute’s namespace.
+ // 2. If element is custom, then enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attribute’s local name, oldValue, newValue, and attribute’s namespace.
+ if (element.is_custom()) {
+ auto& vm = this->vm();
+
+ JS::MarkedVector<JS::Value> arguments { vm.heap() };
+ arguments.append(JS::PrimitiveString::create(vm, local_name()));
+ arguments.append(JS::PrimitiveString::create(vm, old_value));
+ arguments.append(JS::PrimitiveString::create(vm, new_value));
+ arguments.append(JS::PrimitiveString::create(vm, namespace_uri()));
+
+ element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::attributeChangedCallback, move(arguments));
+ }
// FIXME: 3. Run the attribute change steps with element, attribute’s local name, oldValue, newValue, and attribute’s namespace.
}
diff --git a/Userland/Libraries/LibWeb/DOM/Attr.h b/Userland/Libraries/LibWeb/DOM/Attr.h
index ee94fd802c..cacf2b755f 100644
--- a/Userland/Libraries/LibWeb/DOM/Attr.h
+++ b/Userland/Libraries/LibWeb/DOM/Attr.h
@@ -18,8 +18,8 @@ class Attr final : public Node {
WEB_PLATFORM_OBJECT(Attr, Node);
public:
- static WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> create(Document&, QualifiedName, DeprecatedString value = "", Element const* = nullptr);
- static WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> create(Document&, DeprecatedFlyString local_name, DeprecatedString value = "", Element const* = nullptr);
+ static WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> create(Document&, QualifiedName, DeprecatedString value = "", Element* = nullptr);
+ static WebIDL::ExceptionOr<JS::NonnullGCPtr<Attr>> create(Document&, DeprecatedFlyString local_name, DeprecatedString value = "", Element* = nullptr);
JS::NonnullGCPtr<Attr> clone(Document&);
virtual ~Attr() override = default;
@@ -34,23 +34,24 @@ public:
DeprecatedString const& value() const { return m_value; }
void set_value(DeprecatedString value);
+ Element* owner_element();
Element const* owner_element() const;
- void set_owner_element(Element const* owner_element);
+ void set_owner_element(Element* owner_element);
// Always returns true: https://dom.spec.whatwg.org/#dom-attr-specified
constexpr bool specified() const { return true; }
- void handle_attribute_changes(Element const&, DeprecatedString const& old_value, DeprecatedString const& new_value);
+ void handle_attribute_changes(Element&, DeprecatedString const& old_value, DeprecatedString const& new_value);
private:
- Attr(Document&, QualifiedName, DeprecatedString value, Element const*);
+ Attr(Document&, QualifiedName, DeprecatedString value, Element*);
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
QualifiedName m_qualified_name;
DeprecatedString m_value;
- JS::GCPtr<Element const> m_owner_element;
+ JS::GCPtr<Element> m_owner_element;
};
template<>
diff --git a/Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp b/Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp
index 9c911174d2..445549ded9 100644
--- a/Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp
+++ b/Userland/Libraries/LibWeb/DOM/DOMImplementation.cpp
@@ -56,7 +56,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Document>> DOMImplementation::create_docume
JS::GCPtr<Element> element;
if (!qualified_name.is_empty())
- element = TRY(xml_document->create_element_ns(namespace_, qualified_name /* FIXME: and an empty dictionary */));
+ element = TRY(xml_document->create_element_ns(namespace_, qualified_name, ElementCreationOptions {}));
if (doctype)
TRY(xml_document->append_child(*doctype));
diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp
index 3c89549ecb..c89e93f199 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Document.cpp
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
- * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ * Copyright (c) 2021-2023, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2021, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
@@ -37,6 +37,9 @@
#include <LibWeb/Dump.h>
#include <LibWeb/HTML/AttributeNames.h>
#include <LibWeb/HTML/BrowsingContext.h>
+#include <LibWeb/HTML/CustomElements/CustomElementDefinition.h>
+#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
+#include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/EventNames.h>
#include <LibWeb/HTML/HTMLAnchorElement.h>
@@ -680,7 +683,7 @@ void Document::set_title(DeprecatedString const& title)
JS::GCPtr<HTML::HTMLTitleElement> title_element = head_element->first_child_of_type<HTML::HTMLTitleElement>();
if (!title_element) {
- title_element = &static_cast<HTML::HTMLTitleElement&>(*create_element(HTML::TagNames::title).release_value());
+ title_element = &static_cast<HTML::HTMLTitleElement&>(*DOM::create_element(*this, HTML::TagNames::title, Namespace::HTML).release_value_but_fixme_should_propagate_errors());
MUST(head_element->append_child(*title_element));
}
@@ -1190,8 +1193,10 @@ JS::Value Document::run_javascript(StringView source, StringView filename)
}
// https://dom.spec.whatwg.org/#dom-document-createelement
-WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element(DeprecatedFlyString const& a_local_name)
+WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element(DeprecatedString const& a_local_name, Variant<DeprecatedString, ElementCreationOptions> const& options)
{
+ auto& vm = this->vm();
+
auto local_name = a_local_name;
// 1. If localName does not match the Name production, then throw an "InvalidCharacterError" DOMException.
@@ -1202,8 +1207,15 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element(Deprecat
if (document_type() == Type::HTML)
local_name = local_name.to_lowercase();
- // FIXME: 3. Let is be null.
- // FIXME: 4. If options is a dictionary and options["is"] exists, then set is to it.
+ // 3. Let is be null.
+ Optional<String> is_value;
+
+ // 4. If options is a dictionary and options["is"] exists, then set is to it.
+ if (options.has<ElementCreationOptions>()) {
+ auto const& element_creation_options = options.get<ElementCreationOptions>();
+ if (!element_creation_options.is.is_null())
+ is_value = TRY_OR_THROW_OOM(vm, String::from_utf8(element_creation_options.is));
+ }
// 5. Let namespace be the HTML namespace, if this is an HTML document or this’s content type is "application/xhtml+xml"; otherwise null.
DeprecatedFlyString namespace_;
@@ -1211,22 +1223,30 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element(Deprecat
namespace_ = Namespace::HTML;
// 6. Return the result of creating an element given this, localName, namespace, null, is, and with the synchronous custom elements flag set.
- return TRY(DOM::create_element(*this, local_name, namespace_));
+ return TRY(DOM::create_element(*this, local_name, namespace_, {}, move(is_value), true));
}
// https://dom.spec.whatwg.org/#dom-document-createelementns
// https://dom.spec.whatwg.org/#internal-createelementns-steps
-// FIXME: This only implements step 4 of the algorithm and does not take in options.
-WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element_ns(DeprecatedString const& namespace_, DeprecatedString const& qualified_name)
+WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> Document::create_element_ns(DeprecatedString const& namespace_, DeprecatedString const& qualified_name, Variant<DeprecatedString, ElementCreationOptions> const& options)
{
+ auto& vm = this->vm();
+
// 1. Let namespace, prefix, and localName be the result of passing namespace and qualifiedName to validate and extract.
auto extracted_qualified_name = TRY(validate_and_extract(realm(), namespace_, qualified_name));
- // FIXME: 2. Let is be null.
- // FIXME: 3. If options is a dictionary and options["is"] exists, then set is to it.
+ // 2. Let is be null.
+ Optional<String> is_value;
+
+ // 3. If options is a dictionary and options["is"] exists, then set is to it.
+ if (options.has<ElementCreationOptions>()) {
+ auto const& element_creation_options = options.get<ElementCreationOptions>();
+ if (!element_creation_options.is.is_null())
+ is_value = TRY_OR_THROW_OOM(vm, String::from_utf8(element_creation_options.is));
+ }
// 4. Return the result of creating an element given document, localName, namespace, prefix, is, and with the synchronous custom elements flag set.
- return TRY(DOM::create_element(*this, extracted_qualified_name.local_name(), extracted_qualified_name.namespace_(), extracted_qualified_name.prefix()));
+ return TRY(DOM::create_element(*this, extracted_qualified_name.local_name(), extracted_qualified_name.namespace_(), extracted_qualified_name.prefix(), move(is_value), true));
}
JS::NonnullGCPtr<DocumentFragment> Document::create_document_fragment()
@@ -1393,22 +1413,49 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Node>> Document::import_node(JS::NonnullGCP
// https://dom.spec.whatwg.org/#concept-node-adopt
void Document::adopt_node(Node& node)
{
+ // 1. Let oldDocument be node’s node document.
auto& old_document = node.document();
+
+ // 2. If node’s parent is non-null, then remove node.
if (node.parent())
node.remove();
+ // 3. If document is not oldDocument, then:
if (&old_document != this) {
- node.for_each_shadow_including_descendant([&](auto& inclusive_descendant) {
+ // 1. For each inclusiveDescendant in node’s shadow-including inclusive descendants:
+ node.for_each_shadow_including_inclusive_descendant([&](DOM::Node& inclusive_descendant) {
+ // 1. Set inclusiveDescendant’s node document to document.
inclusive_descendant.set_document({}, *this);
- // FIXME: If inclusiveDescendant is an element, then set the node document of each attribute in inclusiveDescendant’s attribute list to document.
+
+ // FIXME: 2. If inclusiveDescendant is an element, then set the node document of each attribute in inclusiveDescendant’s
+ // attribute list to document.
return IterationDecision::Continue;
});
- // FIXME: For each inclusiveDescendant in node’s shadow-including inclusive descendants that is custom,
- // enqueue a custom element callback reaction with inclusiveDescendant, callback name "adoptedCallback",
- // and an argument list containing oldDocument and document.
+ // 2. For each inclusiveDescendant in node’s shadow-including inclusive descendants that is custom,
+ // enqueue a custom element callback reaction with inclusiveDescendant, callback name "adoptedCallback",
+ // and an argument list containing oldDocument and document.
+ node.for_each_shadow_including_inclusive_descendant([&](DOM::Node& inclusive_descendant) {
+ if (!is<DOM::Element>(inclusive_descendant))
+ return IterationDecision::Continue;
+
+ auto& element = static_cast<DOM::Element&>(inclusive_descendant);
+ if (element.is_custom()) {
+ auto& vm = this->vm();
- node.for_each_shadow_including_descendant([&](auto& inclusive_descendant) {
+ JS::MarkedVector<JS::Value> arguments { vm.heap() };
+ arguments.append(&old_document);
+ arguments.append(this);
+
+ element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::adoptedCallback, move(arguments));
+ }
+
+ return IterationDecision::Continue;
+ });
+
+ // 3. For each inclusiveDescendant in node’s shadow-including inclusive descendants, in shadow-including tree order,
+ // run the adopting steps with inclusiveDescendant and oldDocument.
+ node.for_each_shadow_including_inclusive_descendant([&](auto& inclusive_descendant) {
inclusive_descendant.adopted_from(old_document);
return IterationDecision::Continue;
});
@@ -2049,6 +2096,36 @@ void Document::set_window(Badge<HTML::BrowsingContext>, HTML::Window& window)
m_window = &window;
}
+// https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-definition
+JS::GCPtr<HTML::CustomElementDefinition> Document::lookup_custom_element_definition(DeprecatedFlyString const& namespace_, DeprecatedFlyString const& local_name, Optional<String> const& is) const
+{
+ // 1. If namespace is not the HTML namespace, return null.
+ if (namespace_ != Namespace::HTML)
+ return nullptr;
+
+ // 2. If document's browsing context is null, return null.
+ if (!browsing_context())
+ return nullptr;
+
+ // 3. Let registry be document's relevant global object's CustomElementRegistry object.
+ auto registry = window().custom_elements().release_value_but_fixme_should_propagate_errors();
+
+ // 4. If there is custom element definition in registry with name and local name both equal to localName, return that custom element definition.
+ auto converted_local_name = String::from_utf8(local_name).release_value_but_fixme_should_propagate_errors();
+ auto maybe_definition = registry->get_definition_with_name_and_local_name(converted_local_name, converted_local_name);
+ if (maybe_definition)
+ return maybe_definition;
+
+ // 5. If there is a custom element definition in registry with name equal to is and local name equal to localName, return that custom element definition.
+ // 6. Return null.
+
+ // NOTE: If `is` has no value, it can never match as custom element definitions always have a name and localName (i.e. not stored as Optional<String>)
+ if (!is.has_value())
+ return nullptr;
+
+ return registry->get_definition_with_name_and_local_name(is.value(), converted_local_name);
+}
+
CSS::StyleSheetList& Document::style_sheets()
{
if (!m_style_sheets)
@@ -2333,6 +2410,17 @@ bool Document::query_command_supported(DeprecatedString const& command) const
return false;
}
+void Document::increment_throw_on_dynamic_markup_insertion_counter(Badge<HTML::HTMLParser>)
+{
+ ++m_throw_on_dynamic_markup_insertion_counter;
+}
+
+void Document::decrement_throw_on_dynamic_markup_insertion_counter(Badge<HTML::HTMLParser>)
+{
+ VERIFY(m_throw_on_dynamic_markup_insertion_counter);
+ --m_throw_on_dynamic_markup_insertion_counter;
+}
+
// https://html.spec.whatwg.org/multipage/scripting.html#appropriate-template-contents-owner-document
JS::NonnullGCPtr<DOM::Document> Document::appropriate_template_contents_owner_document()
{
diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h
index 1346c8549c..7014e93a40 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.h
+++ b/Userland/Libraries/LibWeb/DOM/Document.h
@@ -70,6 +70,10 @@ struct DocumentUnloadTimingInfo {
double unload_event_end_time { 0 };
};
+struct ElementCreationOptions {
+ DeprecatedString is;
+};
+
class Document
: public ParentNode
, public NonElementParentNode<Document>
@@ -221,8 +225,8 @@ public:
JS::Value run_javascript(StringView source, StringView filename = "(unknown)"sv);
- WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element(DeprecatedFlyString const& local_name);
- WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element_ns(DeprecatedString const& namespace_, DeprecatedString const& qualified_name);
+ WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element(DeprecatedString const& local_name, Variant<DeprecatedString, ElementCreationOptions> const& options);
+ WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element_ns(DeprecatedString const& namespace_, DeprecatedString const& qualified_name, Variant<DeprecatedString, ElementCreationOptions> const& options);
JS::NonnullGCPtr<DocumentFragment> create_document_fragment();
JS::NonnullGCPtr<Text> create_text_node(DeprecatedString const& data);
JS::NonnullGCPtr<Comment> create_comment(DeprecatedString const& data);
@@ -399,6 +403,11 @@ public:
bool has_active_favicon() const { return m_active_favicon; }
void check_favicon_after_loading_link_resource();
+ JS::GCPtr<HTML::CustomElementDefinition> lookup_custom_element_definition(DeprecatedFlyString const& namespace_, DeprecatedFlyString const& local_name, Optional<String> const& is) const;
+
+ void increment_throw_on_dynamic_markup_insertion_counter(Badge<HTML::HTMLParser>);
+ void decrement_throw_on_dynamic_markup_insertion_counter(Badge<HTML::HTMLParser>);
+
// https://html.spec.whatwg.org/multipage/dom.html#is-initial-about:blank
bool is_initial_about_blank() const { return m_is_initial_about_blank; }
void set_is_initial_about_blank(bool b) { m_is_initial_about_blank = b; }
diff --git a/Userland/Libraries/LibWeb/DOM/Document.idl b/Userland/Libraries/LibWeb/DOM/Document.idl
index 144340746a..0615b780af 100644
--- a/Userland/Libraries/LibWeb/DOM/Document.idl
+++ b/Userland/Libraries/LibWeb/DOM/Document.idl
@@ -74,8 +74,8 @@ interface Document : Node {
// FIXME: Should return an HTMLAllCollection
readonly attribute HTMLCollection all;
- Element createElement(DOMString tagName);
- Element createElementNS(DOMString? namespace, DOMString qualifiedName);
+ Element createElement(DOMString tagName, optional (DOMString or ElementCreationOptions) options = {});
+ Element createElementNS(DOMString? namespace, DOMString qualifiedName, optional (DOMString or ElementCreationOptions) options = {});
DocumentFragment createDocumentFragment();
Text createTextNode(DOMString data);
Comment createComment(DOMString data);
@@ -114,5 +114,8 @@ interface Document : Node {
Selection? getSelection();
};
+dictionary ElementCreationOptions {
+ DOMString is;
+};
Document includes ParentNode;
Document includes GlobalEventHandlers;
diff --git a/Userland/Libraries/LibWeb/DOM/Element.cpp b/Userland/Libraries/LibWeb/DOM/Element.cpp
index c09f9d2f17..b7ddbbe169 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Element.cpp
@@ -9,6 +9,7 @@
#include <AK/StringBuilder.h>
#include <LibWeb/Bindings/ElementPrototype.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
+#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/CSS/Parser/Parser.h>
#include <LibWeb/CSS/PropertyID.h>
#include <LibWeb/CSS/ResolvedCSSStyleDeclaration.h>
@@ -23,7 +24,10 @@
#include <LibWeb/Geometry/DOMRect.h>
#include <LibWeb/Geometry/DOMRectList.h>
#include <LibWeb/HTML/BrowsingContext.h>
+#include <LibWeb/HTML/CustomElements/CustomElementDefinition.h>
#include <LibWeb/HTML/CustomElements/CustomElementName.h>
+#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
+#include <LibWeb/HTML/CustomElements/CustomElementRegistry.h>
#include <LibWeb/HTML/EventLoop/EventLoop.h>
#include <LibWeb/HTML/HTMLBodyElement.h>
#include <LibWeb/HTML/HTMLButtonElement.h>
@@ -36,6 +40,7 @@
#include <LibWeb/HTML/HTMLSelectElement.h>
#include <LibWeb/HTML/HTMLTextAreaElement.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
+#include <LibWeb/HTML/Window.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/Layout/BlockContainer.h>
@@ -50,6 +55,7 @@
#include <LibWeb/Namespace.h>
#include <LibWeb/Page/Page.h>
#include <LibWeb/Painting/PaintableBox.h>
+#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
@@ -83,6 +89,7 @@ void Element::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_inline_style.ptr());
visitor.visit(m_class_list.ptr());
visitor.visit(m_shadow_root.ptr());
+ visitor.visit(m_custom_element_definition.ptr());
for (auto& pseudo_element_layout_node : m_pseudo_element_nodes)
visitor.visit(pseudo_element_layout_node);
}
@@ -494,7 +501,15 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<ShadowRoot>> Element::attach_shadow(ShadowR
return WebIDL::NotSupportedError::create(realm(), DeprecatedString::formatted("Element '{}' cannot be a shadow host", local_name()));
}
- // FIXME: 3. If this’s local name is a valid custom element name, or this’s is value is not null, then: ...
+ // 3. If this’s local name is a valid custom element name, or this’s is value is not null, then:
+ if (HTML::is_valid_custom_element_name(local_name()) || m_is_value.has_value()) {
+ // 1. Let definition be the result of looking up a custom element definition given this’s node document, its namespace, its local name, and its is value.
+ auto definition = document().lookup_custom_element_definition(namespace_(), local_name(), m_is_value);
+
+ // 2. If definition is not null and definition’s disable shadow is true, then throw a "NotSupportedError" DOMException.
+ if (definition && definition->disable_shadow())
+ return WebIDL::NotSupportedError::create(realm(), "Cannot attach a shadow root to a custom element that has disabled shadow roots"sv);
+ }
// 4. If this is a shadow host, then throw an "NotSupportedError" DOMException.
if (is_shadow_host())
@@ -506,7 +521,9 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<ShadowRoot>> Element::attach_shadow(ShadowR
// 6. Set shadow’s delegates focus to init["delegatesFocus"].
shadow->set_delegates_focus(init.delegates_focus);
- // FIXME: 7. If this’s custom element state is "precustomized" or "custom", then set shadow’s available to element internals to true.
+ // 7. If this’s custom element state is "precustomized" or "custom", then set shadow’s available to element internals to true.
+ if (m_custom_element_state == CustomElementState::Precustomized || m_custom_element_state == CustomElementState::Custom)
+ shadow->set_available_to_element_internals(true);
// FIXME: 8. Set shadow’s slot assignment to init["slotAssignment"].
@@ -1424,4 +1441,226 @@ bool Element::include_in_accessibility_tree() const
return false;
}
+// https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-an-element-on-the-appropriate-element-queue
+void Element::enqueue_an_element_on_the_appropriate_element_queue()
+{
+ // 1. Let reactionsStack be element's relevant agent's custom element reactions stack.
+ auto& relevant_agent = HTML::relevant_agent(*this);
+ auto* custom_data = verify_cast<Bindings::WebEngineCustomData>(relevant_agent.custom_data());
+ auto& reactions_stack = custom_data->custom_element_reactions_stack;
+
+ // 2. If reactionsStack is empty, then:
+ if (reactions_stack.element_queue_stack.is_empty()) {
+ // 1. Add element to reactionsStack's backup element queue.
+ reactions_stack.backup_element_queue.append(*this);
+
+ // 2. If reactionsStack's processing the backup element queue flag is set, then return.
+ if (reactions_stack.processing_the_backup_element_queue)
+ return;
+
+ // 3. Set reactionsStack's processing the backup element queue flag.
+ reactions_stack.processing_the_backup_element_queue = true;
+
+ // 4. Queue a microtask to perform the following steps:
+ // NOTE: `this` is protected by JS::SafeFunction
+ HTML::queue_a_microtask(&document(), [this]() {
+ auto& relevant_agent = HTML::relevant_agent(*this);
+ auto* custom_data = verify_cast<Bindings::WebEngineCustomData>(relevant_agent.custom_data());
+ auto& reactions_stack = custom_data->custom_element_reactions_stack;
+
+ // 1. Invoke custom element reactions in reactionsStack's backup element queue.
+ Bindings::invoke_custom_element_reactions(reactions_stack.backup_element_queue);
+
+ // 2. Unset reactionsStack's processing the backup element queue flag.
+ reactions_stack.processing_the_backup_element_queue = false;
+ });
+
+ return;
+ }
+
+ // 3. Otherwise, add element to element's relevant agent's current element queue.
+ custom_data->current_element_queue().append(*this);
+}
+
+// https://html.spec.whatwg.org/multipage/custom-elements.html#enqueue-a-custom-element-upgrade-reaction
+void Element::enqueue_a_custom_element_upgrade_reaction(HTML::CustomElementDefinition& custom_element_definition)
+{
+ // 1. Add a new upgrade reaction to element's custom element reaction queue, with custom element definition definition.
+ m_custom_element_reaction_queue.append(CustomElementUpgradeReaction { .custom_element_definition = custom_element_definition });
+
+ // 2. Enqueue an element on the appropriate element queue given element.
+ enqueue_an_element_on_the_appropriate_element_queue();
+}
+
+void Element::enqueue_a_custom_element_callback_reaction(FlyString const& callback_name, JS::MarkedVector<JS::Value> arguments)
+{
+ // 1. Let definition be element's custom element definition.
+ auto& definition = m_custom_element_definition;
+
+ // 2. Let callback be the value of the entry in definition's lifecycle callbacks with key callbackName.
+ auto callback_iterator = definition->lifecycle_callbacks().find(callback_name);
+
+ // 3. If callback is null, then return.
+ if (callback_iterator == definition->lifecycle_callbacks().end())
+ return;
+
+ if (callback_iterator->value.is_null())
+ return;
+
+ // 4. If callbackName is "attributeChangedCallback", then:
+ if (callback_name == HTML::CustomElementReactionNames::attributeChangedCallback) {
+ // 1. Let attributeName be the first element of args.
+ VERIFY(!arguments.is_empty());
+ auto& attribute_name_value = arguments.first();
+ VERIFY(attribute_name_value.is_string());
+ auto attribute_name = attribute_name_value.as_string().utf8_string().release_allocated_value_but_fixme_should_propagate_errors();
+
+ // 2. If definition's observed attributes does not contain attributeName, then return.
+ if (!definition->observed_attributes().contains_slow(attribute_name))
+ return;
+ }
+
+ // 5. Add a new callback reaction to element's custom element reaction queue, with callback function callback and arguments args.
+ m_custom_element_reaction_queue.append(CustomElementCallbackReaction { .callback = callback_iterator->value, .arguments = move(arguments) });
+
+ // 6. Enqueue an element on the appropriate element queue given element.
+ enqueue_an_element_on_the_appropriate_element_queue();
+}
+
+// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-upgrade-an-element
+JS::ThrowCompletionOr<void> Element::upgrade_element(JS::NonnullGCPtr<HTML::CustomElementDefinition> custom_element_definition)
+{
+ auto& realm = this->realm();
+ auto& vm = this->vm();
+
+ // 1. If element's custom element state is not "undefined" or "uncustomized", then return.
+ if (m_custom_element_state != CustomElementState::Undefined && m_custom_element_state != CustomElementState::Uncustomized)
+ return {};
+
+ // 2. Set element's custom element definition to definition.
+ m_custom_element_definition = custom_element_definition;
+
+ // 3. Set element's custom element state to "failed".
+ m_custom_element_state = CustomElementState::Failed;
+
+ // 4. For each attribute in element's attribute list, in order, enqueue a custom element callback reaction with element, callback name "attributeChangedCallback",
+ // and an argument list containing attribute's local name, null, attribute's value, and attribute's namespace.
+ for (size_t attribute_index = 0; attribute_index < m_attributes->length(); ++attribute_index) {
+ auto const* attribute = m_attributes->item(attribute_index);
+ VERIFY(attribute);
+
+ JS::MarkedVector<JS::Value> arguments { vm.heap() };
+
+ arguments.append(JS::PrimitiveString::create(vm, attribute->local_name()));
+ arguments.append(JS::js_null());
+ arguments.append(JS::PrimitiveString::create(vm, attribute->value()));
+ arguments.append(JS::PrimitiveString::create(vm, attribute->namespace_uri()));
+
+ enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::attributeChangedCallback, move(arguments));
+ }
+
+ // 5. If element is connected, then enqueue a custom element callback reaction with element, callback name "connectedCallback", and an empty argument list.
+ if (is_connected()) {
+ JS::MarkedVector<JS::Value> empty_arguments { vm.heap() };
+ enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::connectedCallback, move(empty_arguments));
+ }
+
+ // 6. Add element to the end of definition's construction stack.
+ custom_element_definition->construction_stack().append(JS::make_handle(this));
+
+ // 7. Let C be definition's constructor.
+ auto& constructor = custom_element_definition->constructor();
+
+ // 8. Run the following substeps while catching any exceptions:
+ auto attempt_to_construct_custom_element = [&]() -> JS::ThrowCompletionOr<void> {
+ // 1. If definition's disable shadow is true and element's shadow root is non-null, then throw a "NotSupportedError" DOMException.
+ if (custom_element_definition->disable_shadow() && shadow_root())
+ return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Custom element definition disables shadow DOM and the custom element has a shadow root"sv));
+
+ // 2. Set element's custom element state to "precustomized".
+ m_custom_element_state = CustomElementState::Precustomized;
+
+ // 3. Let constructResult be the result of constructing C, with no arguments.
+ auto construct_result_optional = TRY(WebIDL::construct(constructor));
+ VERIFY(construct_result_optional.has_value());
+ auto construct_result = construct_result_optional.release_value();
+
+ // 4. If SameValue(constructResult, element) is false, then throw a TypeError.
+ if (!JS::same_value(construct_result, this))
+ return vm.throw_completion<JS::TypeError>("Constructing the custom element returned a different element from the custom element"sv);
+
+ return {};
+ };
+
+ auto maybe_exception = attempt_to_construct_custom_element();
+
+ // Then, perform the following substep, regardless of whether the above steps threw an exception or not:
+ // 1. Remove the last entry from the end of definition's construction stack.
+ (void)custom_element_definition->construction_stack().take_last();
+
+ // Finally, if the above steps threw an exception, then:
+ if (maybe_exception.is_throw_completion()) {
+ // 1. Set element's custom element definition to null.
+ m_custom_element_definition = nullptr;
+
+ // 2. Empty element's custom element reaction queue.
+ m_custom_element_reaction_queue.clear();
+
+ // 3. Rethrow the exception (thus terminating this algorithm).
+ return maybe_exception.release_error();
+ }
+
+ // FIXME: 9. If element is a form-associated custom element, then:
+ // 1. Reset the form owner of element. If element is associated with a form element, then enqueue a custom element callback reaction with element, callback name "formAssociatedCallback", and « the associated form ».
+ // 2. If element is disabled, then enqueue a custom element callback reaction with element, callback name "formDisabledCallback" and « true ».
+
+ // 10. Set element's custom element state to "custom".
+ m_custom_element_state = CustomElementState::Custom;
+
+ return {};
+}
+
+// https://html.spec.whatwg.org/multipage/custom-elements.html#concept-try-upgrade
+void Element::try_to_upgrade()
+{
+ // 1. Let definition be the result of looking up a custom element definition given element's node document, element's namespace, element's local name, and element's is value.
+ auto definition = document().lookup_custom_element_definition(namespace_(), local_name(), m_is_value);
+
+ // 2. If definition is not null, then enqueue a custom element upgrade reaction given element and definition.
+ if (definition)
+ enqueue_a_custom_element_upgrade_reaction(*definition);
+}
+
+// https://dom.spec.whatwg.org/#concept-element-defined
+bool Element::is_defined() const
+{
+ // An element whose custom element state is "uncustomized" or "custom" is said to be defined.
+ return m_custom_element_state == CustomElementState::Uncustomized || m_custom_element_state == CustomElementState::Custom;
+}
+
+// https://dom.spec.whatwg.org/#concept-element-custom
+bool Element::is_custom() const
+{
+ // An element whose custom element state is "custom" is said to be custom.
+ return m_custom_element_state == CustomElementState::Custom;
+}
+
+// https://html.spec.whatwg.org/multipage/dom.html#html-element-constructors
+void Element::setup_custom_element_from_constructor(HTML::CustomElementDefinition& custom_element_definition, Optional<String> const& is_value)
+{
+ // 7.6. Set element's custom element state to "custom".
+ m_custom_element_state = CustomElementState::Custom;
+
+ // 7.7. Set element's custom element definition to definition.
+ m_custom_element_definition = custom_element_definition;
+
+ // 7.8. Set element's is value to is value.
+ m_is_value = is_value;
+}
+
+void Element::set_prefix(DeprecatedFlyString const& value)
+{
+ m_qualified_name.set_prefix(value);
+}
+
}
diff --git a/Userland/Libraries/LibWeb/DOM/Element.h b/Userland/Libraries/LibWeb/DOM/Element.h
index db40cf0c21..3a7f4ece2e 100644
--- a/Userland/Libraries/LibWeb/DOM/Element.h
+++ b/Userland/Libraries/LibWeb/DOM/Element.h
@@ -41,6 +41,29 @@ struct ScrollIntoViewOptions : public HTML::ScrollOptions {
Bindings::ScrollLogicalPosition inline_ { Bindings::ScrollLogicalPosition::Nearest };
};
+// https://html.spec.whatwg.org/multipage/custom-elements.html#upgrade-reaction
+// An upgrade reaction, which will upgrade the custom element and contains a custom element definition; or
+struct CustomElementUpgradeReaction {
+ JS::Handle<HTML::CustomElementDefinition> custom_element_definition;
+};
+
+// https://html.spec.whatwg.org/multipage/custom-elements.html#callback-reaction
+// A callback reaction, which will call a lifecycle callback, and contains a callback function as well as a list of arguments.
+struct CustomElementCallbackReaction {
+ JS::Handle<WebIDL::CallbackType> callback;
+ JS::MarkedVector<JS::Value> arguments;
+};
+
+// https://dom.spec.whatwg.org/#concept-element-custom-element-state
+// An element’s custom element state is one of "undefined", "failed", "uncustomized", "precustomized", or "custom".
+enum class CustomElementState {
+ Undefined,
+ Failed,
+ Uncustomized,
+ Precustomized,
+ Custom,
+};
+
class Element
: public ParentNode
, public ChildNode<Element>
@@ -60,6 +83,8 @@ public:
DeprecatedString const& tag_name() const { return html_uppercased_qualified_name(); }
DeprecatedFlyString const& prefix() const { return m_qualified_name.prefix(); }
+ void set_prefix(DeprecatedFlyString const& value);
+
DeprecatedFlyString const& namespace_() const { return m_qualified_name.namespace_(); }
// NOTE: This is for the JS bindings
@@ -259,6 +284,24 @@ public:
virtual bool include_in_accessibility_tree() const override;
+ void enqueue_a_custom_element_upgrade_reaction(HTML::CustomElementDefinition& custom_element_definition);
+ void enqueue_a_custom_element_callback_reaction(FlyString const& callback_name, JS::MarkedVector<JS::Value> arguments);
+
+ Vector<Variant<CustomElementUpgradeReaction, CustomElementCallbackReaction>>& custom_element_reaction_queue() { return m_custom_element_reaction_queue; }
+ Vector<Variant<CustomElementUpgradeReaction, CustomElementCallbackReaction>> const& custom_element_reaction_queue() const { return m_custom_element_reaction_queue; }
+
+ JS::ThrowCompletionOr<void> upgrade_element(JS::NonnullGCPtr<HTML::CustomElementDefinition> custom_element_definition);
+ void try_to_upgrade();
+
+ bool is_defined() const;
+ bool is_custom() const;
+
+ Optional<String> const& is_value() const { return m_is_value; }
+ void set_is_value(Optional<String> const& is) { m_is_value = is; }
+
+ void set_custom_element_state(CustomElementState value) { m_custom_element_state = value; }
+ void setup_custom_element_from_constructor(HTML::CustomElementDefinition& custom_element_definition, Optional<String> const& is_value);
+
protected:
Element(Document&, DOM::QualifiedName);
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
@@ -275,6 +318,8 @@ private:
WebIDL::ExceptionOr<JS::GCPtr<Node>> insert_adjacent(DeprecatedString const& where, JS::NonnullGCPtr<Node> node);
+ void enqueue_an_element_on_the_appropriate_element_queue();
+
QualifiedName m_qualified_name;
DeprecatedString m_html_uppercased_qualified_name;
@@ -289,6 +334,20 @@ private:
Vector<FlyString> m_classes;
Array<JS::GCPtr<Layout::Node>, to_underlying(CSS::Selector::PseudoElement::PseudoElementCount)> m_pseudo_element_nodes;
+
+ // https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-reaction-queue
+ // All elements have an associated custom element reaction queue, initially empty. Each item in the custom element reaction queue is of one of two types:
+ // NOTE: See the structs at the top of this header.
+ Vector<Variant<CustomElementUpgradeReaction, CustomElementCallbackReaction>> m_custom_element_reaction_queue;
+
+ // https://dom.spec.whatwg.org/#concept-element-custom-element-state
+ CustomElementState m_custom_element_state { CustomElementState::Undefined };
+
+ // https://dom.spec.whatwg.org/#concept-element-custom-element-definition
+ JS::GCPtr<HTML::CustomElementDefinition> m_custom_element_definition;
+
+ // https://dom.spec.whatwg.org/#concept-element-is-value
+ Optional<String> m_is_value;
};
template<>
diff --git a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp
index ac55706a5b..757d0c9067 100644
--- a/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp
+++ b/Userland/Libraries/LibWeb/DOM/ElementFactory.cpp
@@ -1,12 +1,13 @@
/*
* Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
- * Copyright (c) 2020, Luke Wilde <lukew@serenityos.org>
+ * Copyright (c) 2020-2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/ElementFactory.h>
+#include <LibWeb/HTML/CustomElements/CustomElementDefinition.h>
#include <LibWeb/HTML/CustomElements/CustomElementName.h>
#include <LibWeb/HTML/HTMLAnchorElement.h>
#include <LibWeb/HTML/HTMLAreaElement.h>
@@ -78,6 +79,8 @@
#include <LibWeb/HTML/HTMLUListElement.h>
#include <LibWeb/HTML/HTMLUnknownElement.h>
#include <LibWeb/HTML/HTMLVideoElement.h>
+#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
+#include <LibWeb/Namespace.h>
#include <LibWeb/SVG/SVGCircleElement.h>
#include <LibWeb/SVG/SVGClipPathElement.h>
#include <LibWeb/SVG/SVGDefsElement.h>
@@ -92,34 +95,173 @@
#include <LibWeb/SVG/SVGSVGElement.h>
#include <LibWeb/SVG/SVGTextContentElement.h>
#include <LibWeb/SVG/TagNames.h>
+#include <LibWeb/WebIDL/AbstractOperations.h>
namespace Web::DOM {
-// https://dom.spec.whatwg.org/#concept-create-element
-WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element(Document& document, DeprecatedFlyString local_name, DeprecatedFlyString namespace_, DeprecatedFlyString prefix)
+ErrorOr<FixedArray<DeprecatedFlyString>> valid_local_names_for_given_html_element_interface(StringView html_element_interface_name)
{
- // 1. If prefix was not given, let prefix be null.
- // NOTE: This is already taken care of by `prefix` having a default value.
+ if (html_element_interface_name == "HTMLAnchorElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::a });
+ if (html_element_interface_name == "HTMLAreaElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::area });
+ if (html_element_interface_name == "HTMLAudioElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::audio });
+ if (html_element_interface_name == "HTMLBaseElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::base });
+ if (html_element_interface_name == "HTMLBodyElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::body });
+ if (html_element_interface_name == "HTMLBRElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::br });
+ if (html_element_interface_name == "HTMLButtonElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::button });
+ if (html_element_interface_name == "HTMLCanvasElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::canvas });
+ if (html_element_interface_name == "HTMLDataElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::data });
+ if (html_element_interface_name == "HTMLDataListElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::datalist });
+ if (html_element_interface_name == "HTMLDetailsElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::details });
+ if (html_element_interface_name == "HTMLDialogElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::dialog });
+ if (html_element_interface_name == "HTMLDirectoryElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::dir });
+ if (html_element_interface_name == "HTMLDivElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::div });
+ if (html_element_interface_name == "HTMLDListElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::dl });
+ if (html_element_interface_name == "HTMLEmbedElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::embed });
+ if (html_element_interface_name == "HTMLFieldsetElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::fieldset });
+ if (html_element_interface_name == "HTMLFontElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::font });
+ if (html_element_interface_name == "HTMLFormElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::form });
+ if (html_element_interface_name == "HTMLFrameElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::frame });
+ if (html_element_interface_name == "HTMLFrameSetElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::frameset });
+ if (html_element_interface_name == "HTMLHeadElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::head });
+ if (html_element_interface_name == "HTMLHeadingElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::h1, HTML::TagNames::h2, HTML::TagNames::h3, HTML::TagNames::h4, HTML::TagNames::h5, HTML::TagNames::h6 });
+ if (html_element_interface_name == "HTMLHRElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::hr });
+ if (html_element_interface_name == "HTMLHtmlElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::html });
+ if (html_element_interface_name == "HTMLIFrameElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::iframe });
+ if (html_element_interface_name == "HTMLImageElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::img });
+ if (html_element_interface_name == "HTMLInputElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::input });
+ if (html_element_interface_name == "HTMLLabelElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::label });
+ if (html_element_interface_name == "HTMLLIElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::li });
+ if (html_element_interface_name == "HTMLLinkElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::link });
+ if (html_element_interface_name == "HTMLMapElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::map });
+ if (html_element_interface_name == "HTMLMarqueeElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::marquee });
+ if (html_element_interface_name == "HTMLMenuElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::menu });
+ if (html_element_interface_name == "HTMLMeterElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::meter });
+ if (html_element_interface_name == "HTMLModElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::ins, HTML::TagNames::del });
+ if (html_element_interface_name == "HTMLObjectElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::object });
+ if (html_element_interface_name == "HTMLOutputElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::output });
+ if (html_element_interface_name == "HTMLParagraphElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::p });
+ if (html_element_interface_name == "HTMLParamElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::param });
+ if (html_element_interface_name == "HTMLPictureElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::picture });
+ if (html_element_interface_name == "HTMLPreElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::pre, HTML::TagNames::listing, HTML::TagNames::xmp });
+ if (html_element_interface_name == "HTMLProgressElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::progress });
+ if (html_element_interface_name == "HTMLQuoteElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::blockquote, HTML::TagNames::q });
+ if (html_element_interface_name == "HTMLScriptElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::script });
+ if (html_element_interface_name == "HTMLSelectElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::select });
+ if (html_element_interface_name == "HTMLSlotElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::slot });
+ if (html_element_interface_name == "HTMLSourceElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::source });
+ if (html_element_interface_name == "HTMLSpanElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::span });
+ if (html_element_interface_name == "HTMLStyleElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::style });
+ if (html_element_interface_name == "HTMLTableCaptionElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::caption });
+ if (html_element_interface_name == "HTMLTableCellElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::td, HTML::TagNames::th });
+ if (html_element_interface_name == "HTMLTableColElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::colgroup, HTML::TagNames::col });
+ if (html_element_interface_name == "HTMLTableElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::table });
+ if (html_element_interface_name == "HTMLTableSectionElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::tbody, HTML::TagNames::thead, HTML::TagNames::tfoot });
+ if (html_element_interface_name == "HTMLTemplateElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::template_ });
+ if (html_element_interface_name == "HTMLTextAreaElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::textarea });
+ if (html_element_interface_name == "HTMLTimeElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::time });
+ if (html_element_interface_name == "HTMLTitleElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::title });
+ if (html_element_interface_name == "HTMLTrackElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::track });
+ if (html_element_interface_name == "HTMLUListElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::ul });
+ if (html_element_interface_name == "HTMLVideoElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::video });
+ if (html_element_interface_name == "HTMLElement"sv)
+ return FixedArray<DeprecatedFlyString>::create({ HTML::TagNames::article, HTML::TagNames::section, HTML::TagNames::nav, HTML::TagNames::aside, HTML::TagNames::hgroup, HTML::TagNames::header, HTML::TagNames::footer, HTML::TagNames::address, HTML::TagNames::dt, HTML::TagNames::dd, HTML::TagNames::figure, HTML::TagNames::figcaption, HTML::TagNames::main, HTML::TagNames::em, HTML::TagNames::strong, HTML::TagNames::small, HTML::TagNames::s, HTML::TagNames::cite, HTML::TagNames::dfn, HTML::TagNames::abbr, HTML::TagNames::ruby, HTML::TagNames::rt, HTML::TagNames::rp, HTML::TagNames::code, HTML::TagNames::var, HTML::TagNames::samp, HTML::TagNames::kbd, HTML::TagNames::sub, HTML::TagNames::sup, HTML::TagNames::i, HTML::TagNames::b, HTML::TagNames::u, HTML::TagNames::mark, HTML::TagNames::bdi, HTML::TagNames::bdo, HTML::TagNames::wbr, HTML::TagNames::summary, HTML::TagNames::noscript, HTML::TagNames::acronym, HTML::TagNames::basefont, HTML::TagNames::big, HTML::TagNames::center, HTML::TagNames::nobr, HTML::TagNames::noembed, HTML::TagNames::noframes, HTML::TagNames::plaintext, HTML::TagNames::rb, HTML::TagNames::rtc, HTML::TagNames::strike, HTML::TagNames::tt });
+ return FixedArray<DeprecatedFlyString>::create({});
+}
- // FIXME: 2. If is was not given, let is be null.
- // FIXME: 3. Let result be null.
- // FIXME: 4. Let definition be the result of looking up a custom element definition given document, namespace, localName, and is.
- // FIXME: 5. If definition is non-null, and definition’s name is not equal to its local name (i.e., definition represents a customized built-in element), then: ...
- // FIXME: 6. Otherwise, if definition is non-null, then: ...
+// https://html.spec.whatwg.org/multipage/dom.html#elements-in-the-dom%3Aelement-interface
+bool is_unknown_html_element(DeprecatedFlyString const& tag_name)
+{
+ // NOTE: This is intentionally case-sensitive.
- // 7. Otherwise:
- // 1. Let interface be the element interface for localName and namespace.
- // 2. Set result to a new element that implements interface, with no attributes, namespace set to namespace, namespace prefix set to prefix,
- // local name set to localName, custom element state set to "uncustomized", custom element definition set to null, is value set to is,
- // and node document set to document.
- // FIXME: 3. If namespace is the HTML namespace, and either localName is a valid custom element name or is is non-null,
- // then set result’s custom element state to "undefined".
- // 8. Return result.
+ // 1. If name is applet, bgsound, blink, isindex, keygen, multicol, nextid, or spacer, then return HTMLUnknownElement.
+ if (tag_name.is_one_of(HTML::TagNames::applet, HTML::TagNames::bgsound, HTML::TagNames::blink, HTML::TagNames::isindex, HTML::TagNames::keygen, HTML::TagNames::multicol, HTML::TagNames::nextid, HTML::TagNames::spacer))
+ return true;
- auto& realm = document.realm();
- auto lowercase_tag_name = local_name.to_lowercase();
+ // 2. If name is acronym, basefont, big, center, nobr, noembed, noframes, plaintext, rb, rtc, strike, or tt, then return HTMLElement.
+ // 3. If name is listing or xmp, then return HTMLPreElement.
+ // 4. Otherwise, if this specification defines an interface appropriate for the element type corresponding to the local name name, then return that interface.
+ // 5. If other applicable specifications define an appropriate interface for name, then return the interface they define.
+#define __ENUMERATE_HTML_TAG(name) \
+ if (tag_name == HTML::TagNames::name) \
+ return false;
+ ENUMERATE_HTML_TAGS
+#undef __ENUMERATE_HTML_TAG
+
+ // 6. If name is a valid custom element name, then return HTMLElement.
+ if (HTML::is_valid_custom_element_name(tag_name))
+ return false;
+
+ // 7. Return HTMLUnknownElement.
+ return true;
+}
+
+// https://html.spec.whatwg.org/#elements-in-the-dom:element-interface
+static WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_html_element(JS::Realm& realm, Document& document, QualifiedName qualified_name)
+{
+ auto lowercase_tag_name = qualified_name.local_name().to_lowercase();
- auto qualified_name = QualifiedName { local_name, prefix, namespace_ };
if (lowercase_tag_name == HTML::TagNames::a)
return MUST_OR_THROW_OOM(realm.heap().allocate<HTML::HTMLAnchorElement>(realm, document, move(qualified_name)));
if (lowercase_tag_name == HTML::TagNames::area)
@@ -264,39 +406,214 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element(Document& document
// Obsolete
HTML::TagNames::acronym, HTML::TagNames::basefont, HTML::TagNames::big, HTML::TagNames::center, HTML::TagNames::nobr, HTML::TagNames::noembed, HTML::TagNames::noframes, HTML::TagNames::plaintext, HTML::TagNames::rb, HTML::TagNames::rtc, HTML::TagNames::strike, HTML::TagNames::tt))
return MUST_OR_THROW_OOM(realm.heap().allocate<HTML::HTMLElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::svg)
+ if (HTML::is_valid_custom_element_name(qualified_name.local_name()))
+ return MUST_OR_THROW_OOM(realm.heap().allocate<HTML::HTMLElement>(realm, document, move(qualified_name)));
+
+ return MUST_OR_THROW_OOM(realm.heap().allocate<HTML::HTMLUnknownElement>(realm, document, move(qualified_name)));
+}
+
+static WebIDL::ExceptionOr<JS::GCPtr<SVG::SVGElement>> create_svg_element(JS::Realm& realm, Document& document, QualifiedName qualified_name)
+{
+ auto const& local_name = qualified_name.local_name();
+
+ if (local_name == SVG::TagNames::svg)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGSVGElement>(realm, document, move(qualified_name)));
// FIXME: Support SVG's mixedCase tag names properly.
- if (lowercase_tag_name.equals_ignoring_ascii_case(SVG::TagNames::clipPath))
+ if (local_name.equals_ignoring_ascii_case(SVG::TagNames::clipPath))
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGClipPathElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::circle)
+ if (local_name == SVG::TagNames::circle)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGCircleElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name.equals_ignoring_ascii_case(SVG::TagNames::defs))
+ if (local_name.equals_ignoring_ascii_case(SVG::TagNames::defs))
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGDefsElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::ellipse)
+ if (local_name == SVG::TagNames::ellipse)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGEllipseElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name.equals_ignoring_ascii_case(SVG::TagNames::foreignObject))
+ if (local_name.equals_ignoring_ascii_case(SVG::TagNames::foreignObject))
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGForeignObjectElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::line)
+ if (local_name == SVG::TagNames::line)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGLineElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::path)
+ if (local_name == SVG::TagNames::path)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPathElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::polygon)
+ if (local_name == SVG::TagNames::polygon)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPolygonElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::polyline)
+ if (local_name == SVG::TagNames::polyline)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPolylineElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::rect)
+ if (local_name == SVG::TagNames::rect)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGRectElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::g)
+ if (local_name == SVG::TagNames::g)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGGElement>(realm, document, move(qualified_name)));
- if (lowercase_tag_name == SVG::TagNames::text)
+ if (local_name == SVG::TagNames::text)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGTextContentElement>(realm, document, move(qualified_name)));
- // If name is a valid custom element name, then return HTMLElement.
- if (HTML::is_valid_custom_element_name(local_name)) {
- return MUST_OR_THROW_OOM(realm.heap().allocate<HTML::HTMLElement>(realm, document, move(qualified_name)));
+ return nullptr;
+}
+
+// https://dom.spec.whatwg.org/#concept-create-element
+WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element(Document& document, DeprecatedFlyString local_name, DeprecatedFlyString namespace_, DeprecatedFlyString prefix, Optional<String> is_value, bool synchronous_custom_elements_flag)
+{
+ auto& realm = document.realm();
+
+ // 1. If prefix was not given, let prefix be null.
+ // NOTE: This is already taken care of by `prefix` having a default value.
+
+ // 2. If is was not given, let is be null.
+ // NOTE: This is already taken care of by `is` having a default value.
+
+ // 3. Let result be null.
+ // NOTE: We collapse this into just returning an element where necessary.
+
+ // 4. Let definition be the result of looking up a custom element definition given document, namespace, localName, and is.
+ auto definition = document.lookup_custom_element_definition(namespace_, local_name, is_value);
+
+ // 5. If definition is non-null, and definition’s name is not equal to its local name (i.e., definition represents a customized built-in element), then:
+ if (definition && definition->name() != definition->local_name()) {
+ // 1. Let interface be the element interface for localName and the HTML namespace.
+ // 2. Set result to a new element that implements interface, with no attributes, namespace set to the HTML namespace,
+ // namespace prefix set to prefix, local name set to localName, custom element state set to "undefined", custom element definition set to null,
+ // is value set to is, and node document set to document.
+ auto element = TRY(create_html_element(realm, document, QualifiedName { local_name, prefix, Namespace::HTML }));
+
+ // 3. If the synchronous custom elements flag is set, then run this step while catching any exceptions:
+ if (synchronous_custom_elements_flag) {
+ // 1. Upgrade element using definition.
+ auto upgrade_result = element->upgrade_element(*definition);
+
+ // If this step threw an exception, then:
+ if (upgrade_result.is_throw_completion()) {
+ // 1. Report the exception.
+ HTML::report_exception(upgrade_result, realm);
+
+ // 2. Set result’s custom element state to "failed".
+ element->set_custom_element_state(CustomElementState::Failed);
+ }
+ }
+
+ // 4. Otherwise, enqueue a custom element upgrade reaction given result and definition.
+ else {
+ element->enqueue_a_custom_element_upgrade_reaction(*definition);
+ }
+
+ return element;
}
- return MUST_OR_THROW_OOM(realm.heap().allocate<HTML::HTMLUnknownElement>(realm, document, move(qualified_name)));
+
+ // 6. Otherwise, if definition is non-null, then:
+ if (definition) {
+ // 1. If the synchronous custom elements flag is set, then run these steps while catching any exceptions:
+ if (synchronous_custom_elements_flag) {
+ auto synchronously_upgrade_custom_element = [&]() -> JS::ThrowCompletionOr<JS::NonnullGCPtr<HTML::HTMLElement>> {
+ auto& vm = document.vm();
+
+ // 1. Let C be definition’s constructor.
+ auto& constructor = definition->constructor();
+
+ // 2. Set result to the result of constructing C, with no arguments.
+ auto result = TRY(WebIDL::construct(constructor));
+
+ // FIXME: 3. Assert: result’s custom element state and custom element definition are initialized.
+ // FIXME: 4. Assert: result’s namespace is the HTML namespace.
+ // Spec Note: IDL enforces that result is an HTMLElement object, which all use the HTML namespace.
+ // IDL does not currently convert the object for us, so we will have to do it here.
+
+ if (!result.has_value() || !result->is_object() || !is<HTML::HTMLElement>(result->as_object()))
+ return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "HTMLElement"sv);
+
+ JS::NonnullGCPtr<HTML::HTMLElement> element = verify_cast<HTML::HTMLElement>(result->as_object());
+
+ // 5. If result’s attribute list is not empty, then throw a "NotSupportedError" DOMException.
+ if (element->has_attributes())
+ return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Synchronously created custom element cannot have attributes"sv));
+
+ // 6. If result has children, then throw a "NotSupportedError" DOMException.
+ if (element->has_children())
+ return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Synchronously created custom element cannot have children"sv));
+
+ // 7. If result’s parent is not null, then throw a "NotSupportedError" DOMException.
+ if (element->parent())
+ return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Synchronously created custom element cannot have a parent"sv));
+
+ // 8. If result’s node document is not document, then throw a "NotSupportedError" DOMException.
+ if (&element->document() != &document)
+ return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Synchronously created custom element must be in the same document that element creation was invoked in"sv));
+
+ // 9. If result’s local name is not equal to localName, then throw a "NotSupportedError" DOMException.
+ if (element->local_name() != local_name)
+ return JS::throw_completion(WebIDL::NotSupportedError::create(realm, "Synchronously created custom element must have the same local name that element creation was invoked with"sv));
+
+ // 10. Set result’s namespace prefix to prefix.
+ element->set_prefix(prefix);
+
+ // 11. Set result’s is value to null.
+ element->set_is_value(Optional<String> {});
+ return element;
+ };
+
+ auto result = synchronously_upgrade_custom_element();
+
+ // If any of these steps threw an exception, then:
+ if (result.is_throw_completion()) {
+ // 1. Report the exception.
+ HTML::report_exception(result, realm);
+
+ // 2. Set result to a new element that implements the HTMLUnknownElement interface, with no attributes, namespace set to the HTML namespace, namespace prefix set to prefix,
+ // local name set to localName, custom element state set to "failed", custom element definition set to null, is value set to null, and node document set to document.
+ JS::NonnullGCPtr<Element> element = realm.heap().allocate<HTML::HTMLUnknownElement>(realm, document, QualifiedName { local_name, prefix, Namespace::HTML }).release_allocated_value_but_fixme_should_propagate_errors();
+ element->set_custom_element_state(CustomElementState::Failed);
+ return element;
+ }
+
+ return result.release_value();
+ }
+
+ // 2. Otherwise:
+ // 1. Set result to a new element that implements the HTMLElement interface, with no attributes, namespace set to the HTML namespace, namespace prefix set to prefix,
+ // local name set to localName, custom element state set to "undefined", custom element definition set to null, is value set to null, and node document set to document.
+ JS::NonnullGCPtr<Element> element = realm.heap().allocate<HTML::HTMLElement>(realm, document, QualifiedName { local_name, prefix, Namespace::HTML }).release_allocated_value_but_fixme_should_propagate_errors();
+ element->set_custom_element_state(CustomElementState::Undefined);
+
+ // 2. Enqueue a custom element upgrade reaction given result and definition.
+ element->enqueue_a_custom_element_upgrade_reaction(*definition);
+ return element;
+ }
+
+ // 7. Otherwise:
+ // 1. Let interface be the element interface for localName and namespace.
+ // 2. Set result to a new element that implements interface, with no attributes, namespace set to namespace, namespace prefix set to prefix,
+ // local name set to localName, custom element state set to "uncustomized", custom element definition set to null, is value set to is,
+ // and node document set to document.
+
+ auto qualified_name = QualifiedName { local_name, prefix, namespace_ };
+
+ if (namespace_ == Namespace::HTML) {
+ auto element = TRY(create_html_element(realm, document, move(qualified_name)));
+ element->set_is_value(move(is_value));
+ element->set_custom_element_state(CustomElementState::Uncustomized);
+
+ // 3. If namespace is the HTML namespace, and either localName is a valid custom element name or is is non-null,
+ // then set result’s custom element state to "undefined".
+ if (HTML::is_valid_custom_element_name(local_name) || is_value.has_value())
+ element->set_custom_element_state(CustomElementState::Undefined);
+
+ return element;
+ }
+
+ if (namespace_ == Namespace::SVG) {
+ auto element = TRY(create_svg_element(realm, document, qualified_name));
+ if (element) {
+ element->set_is_value(move(is_value));
+ element->set_custom_element_state(CustomElementState::Uncustomized);
+ return JS::NonnullGCPtr<Element> { *element };
+ }
+ }
+
+ // 8. Return result.
+ // NOTE: See step 3.
+
+ // https://dom.spec.whatwg.org/#concept-element-interface
+ // The element interface for any name and namespace is Element, unless stated otherwise.
+ dbgln("Potential FIXME: Creating unknown generic element '{}' in namespace '{}'", local_name, namespace_);
+ JS::NonnullGCPtr<Element> element = realm.heap().allocate<DOM::Element>(realm, document, move(qualified_name)).release_allocated_value_but_fixme_should_propagate_errors();
+ element->set_is_value(move(is_value));
+ element->set_custom_element_state(CustomElementState::Uncustomized);
+ return element;
}
}
diff --git a/Userland/Libraries/LibWeb/DOM/ElementFactory.h b/Userland/Libraries/LibWeb/DOM/ElementFactory.h
index cccdc9c886..e436e7746d 100644
--- a/Userland/Libraries/LibWeb/DOM/ElementFactory.h
+++ b/Userland/Libraries/LibWeb/DOM/ElementFactory.h
@@ -6,10 +6,15 @@
#pragma once
+#include <AK/FixedArray.h>
#include <LibWeb/DOM/Element.h>
namespace Web::DOM {
-WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element(Document&, DeprecatedFlyString local_name, DeprecatedFlyString namespace_, DeprecatedFlyString prefix = {});
+ErrorOr<FixedArray<DeprecatedFlyString>> valid_local_names_for_given_html_element_interface(StringView html_element_interface_name);
+bool is_unknown_html_element(DeprecatedFlyString const& tag_name);
+
+// FIXME: The spec doesn't say what the default value of synchronous_custom_elements_flag should be.
+WebIDL::ExceptionOr<JS::NonnullGCPtr<Element>> create_element(Document&, DeprecatedFlyString local_name, DeprecatedFlyString namespace_, DeprecatedFlyString prefix = {}, Optional<String> is = Optional<String> {}, bool synchronous_custom_elements_flag = false);
}
diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp
index 4d7d29543f..0c43633a0d 100644
--- a/Userland/Libraries/LibWeb/DOM/Node.cpp
+++ b/Userland/Libraries/LibWeb/DOM/Node.cpp
@@ -30,6 +30,7 @@
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/DOM/StaticNodeList.h>
#include <LibWeb/HTML/BrowsingContextContainer.h>
+#include <LibWeb/HTML/CustomElements/CustomElementReactionNames.h>
#include <LibWeb/HTML/HTMLAnchorElement.h>
#include <LibWeb/HTML/HTMLStyleElement.h>
#include <LibWeb/HTML/Origin.h>
@@ -438,18 +439,29 @@ void Node::insert_before(JS::NonnullGCPtr<Node> node, JS::GCPtr<Node> child, boo
// FIXME: 5. If parent’s root is a shadow root, and parent is a slot whose assigned nodes is the empty list, then run signal a slot change for parent.
// FIXME: 6. Run assign slottables for a tree with node’s root.
- // FIXME: This should be shadow-including.
// 7. For each shadow-including inclusive descendant inclusiveDescendant of node, in shadow-including tree order:
- node_to_insert->for_each_in_inclusive_subtree([&](Node& inclusive_descendant) {
+ node_to_insert->for_each_shadow_including_inclusive_descendant([&](Node& inclusive_descendant) {
// 1. Run the insertion steps with inclusiveDescendant.
inclusive_descendant.inserted();
// 2. If inclusiveDescendant is connected, then:
- if (inclusive_descendant.is_connected()) {
- // FIXME: 1. If inclusiveDescendant is custom, then enqueue a custom element callback reaction with inclusiveDescendant, callback name "connectedCallback", and an empty argument list.
+ // NOTE: This is not specified here in the spec, but these steps can only be performed on an element.
+ if (inclusive_descendant.is_connected() && is<DOM::Element>(inclusive_descendant)) {
+ auto& element = static_cast<DOM::Element&>(inclusive_descendant);
+
+ // 1. If inclusiveDescendant is custom, then enqueue a custom element callback reaction with inclusiveDescendant,
+ // callback name "connectedCallback", and an empty argument list.
+ if (element.is_custom()) {
+ JS::MarkedVector<JS::Value> empty_arguments { vm().heap() };
+ element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::connectedCallback, move(empty_arguments));
+ }
- // FIXME: 2. Otherwise, try to upgrade inclusiveDescendant.
- // NOTE: If this successfully upgrades inclusiveDescendant, its connectedCallback will be enqueued automatically during the upgrade an element algorithm.
+ // 2. Otherwise, try to upgrade inclusiveDescendant.
+ // NOTE: If this successfully upgrades inclusiveDescendant, its connectedCallback will be enqueued automatically during
+ // the upgrade an element algorithm.
+ else {
+ element.try_to_upgrade();
+ }
}
return IterationDecision::Continue;
@@ -579,20 +591,37 @@ void Node::remove(bool suppress_observers)
// 15. Run the removing steps with node and parent.
removed_from(parent);
- // FIXME: 16. Let isParentConnected be parent’s connected. (Currently unused so not included)
+ // 16. Let isParentConnected be parent’s connected.
+ bool is_parent_connected = parent->is_connected();
- // FIXME: 17. If node is custom and isParentConnected is true, then enqueue a custom element callback reaction with node,
- // callback name "disconnectedCallback", and an empty argument list.
- // NOTE: It is intentional for now that custom elements do not get parent passed. This might change in the future if there is a need.
+ // 17. If node is custom and isParentConnected is true, then enqueue a custom element callback reaction with node,
+ // callback name "disconnectedCallback", and an empty argument list.
+ // Spec Note: It is intentional for now that custom elements do not get parent passed.
+ // This might change in the future if there is a need.
+ if (is<DOM::Element>(*this)) {
+ auto& element = static_cast<DOM::Element&>(*this);
+
+ if (element.is_custom() && is_parent_connected) {
+ JS::MarkedVector<JS::Value> empty_arguments { vm().heap() };
+ element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::disconnectedCallback, move(empty_arguments));
+ }
+ }
- // FIXME: This should be shadow-including.
// 18. For each shadow-including descendant descendant of node, in shadow-including tree order, then:
- for_each_in_subtree([&](Node& descendant) {
+ for_each_shadow_including_descendant([&](Node& descendant) {
// 1. Run the removing steps with descendant
descendant.removed_from(nullptr);
- // FIXME: 2. If descendant is custom and isParentConnected is true, then enqueue a custom element callback reaction with descendant,
- // callback name "disconnectedCallback", and an empty argument list.
+ // 2. If descendant is custom and isParentConnected is true, then enqueue a custom element callback reaction with descendant,
+ // callback name "disconnectedCallback", and an empty argument list.
+ if (is<DOM::Element>(descendant)) {
+ auto& element = static_cast<DOM::Element&>(descendant);
+
+ if (element.is_custom() && is_parent_connected) {
+ JS::MarkedVector<JS::Value> empty_arguments { vm().heap() };
+ element.enqueue_a_custom_element_callback_reaction(HTML::CustomElementReactionNames::disconnectedCallback, move(empty_arguments));
+ }
+ }
return IterationDecision::Continue;
});
@@ -727,7 +756,7 @@ JS::NonnullGCPtr<Node> Node::clone_node(Document* document, bool clone_children)
if (is<Element>(this)) {
// 1. Let copy be the result of creating an element, given document, node’s local name, node’s namespace, node’s namespace prefix, and node’s is value, with the synchronous custom elements flag unset.
auto& element = *verify_cast<Element>(this);
- auto element_copy = DOM::create_element(*document, element.local_name(), element.namespace_() /* FIXME: node’s namespace prefix, and node’s is value, with the synchronous custom elements flag unset */).release_value_but_fixme_should_propagate_errors();
+ auto element_copy = DOM::create_element(*document, element.local_name(), element.namespace_(), element.prefix(), element.is_value(), false).release_value_but_fixme_should_propagate_errors();
// 2. For each attribute in node’s attribute list:
element.for_each_attribute([&](auto& name, auto& value) {
diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h
index 82dd81b346..6832d4c372 100644
--- a/Userland/Libraries/LibWeb/DOM/Node.h
+++ b/Userland/Libraries/LibWeb/DOM/Node.h
@@ -241,6 +241,10 @@ public:
void queue_mutation_record(FlyString const& type, DeprecatedString attribute_name, DeprecatedString attribute_namespace, DeprecatedString old_value, JS::NonnullGCPtr<NodeList> added_nodes, JS::NonnullGCPtr<NodeList> removed_nodes, Node* previous_sibling, Node* next_sibling) const;
+ // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
+ template<typename Callback>
+ IterationDecision for_each_shadow_including_inclusive_descendant(Callback);
+
// https://dom.spec.whatwg.org/#concept-shadow-including-descendant
template<typename Callback>
IterationDecision for_each_shadow_including_descendant(Callback);
diff --git a/Userland/Libraries/LibWeb/DOM/QualifiedName.cpp b/Userland/Libraries/LibWeb/DOM/QualifiedName.cpp
index 46ee475ea8..28fb86d74a 100644
--- a/Userland/Libraries/LibWeb/DOM/QualifiedName.cpp
+++ b/Userland/Libraries/LibWeb/DOM/QualifiedName.cpp
@@ -70,4 +70,9 @@ void QualifiedName::Impl::make_internal_string()
as_string = DeprecatedString::formatted("{}:{}", prefix, local_name);
}
+void QualifiedName::set_prefix(DeprecatedFlyString const& value)
+{
+ m_impl->prefix = value;
+}
+
}
diff --git a/Userland/Libraries/LibWeb/DOM/QualifiedName.h b/Userland/Libraries/LibWeb/DOM/QualifiedName.h
index 14d69d60bc..4c90cbdd5b 100644
--- a/Userland/Libraries/LibWeb/DOM/QualifiedName.h
+++ b/Userland/Libraries/LibWeb/DOM/QualifiedName.h
@@ -32,6 +32,8 @@ public:
DeprecatedString as_string;
};
+ void set_prefix(DeprecatedFlyString const& value);
+
private:
NonnullRefPtr<Impl> m_impl;
};
diff --git a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
index 46a5b10338..db9a24ba61 100644
--- a/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
+++ b/Userland/Libraries/LibWeb/DOM/ShadowRoot.h
@@ -47,18 +47,34 @@ template<>
inline bool Node::fast_is<ShadowRoot>() const { return is_shadow_root(); }
template<typename Callback>
-inline IterationDecision Node::for_each_shadow_including_descendant(Callback callback)
+inline IterationDecision Node::for_each_shadow_including_inclusive_descendant(Callback callback)
{
if (callback(*this) == IterationDecision::Break)
return IterationDecision::Break;
for (auto* child = first_child(); child; child = child->next_sibling()) {
if (child->is_element()) {
if (JS::GCPtr<ShadowRoot> shadow_root = static_cast<Element*>(child)->shadow_root_internal()) {
- if (shadow_root->for_each_shadow_including_descendant(callback) == IterationDecision::Break)
+ if (shadow_root->for_each_shadow_including_inclusive_descendant(callback) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ }
+ if (child->for_each_shadow_including_inclusive_descendant(callback) == IterationDecision::Break)
+ return IterationDecision::Break;
+ }
+ return IterationDecision::Continue;
+}
+
+template<typename Callback>
+inline IterationDecision Node::for_each_shadow_including_descendant(Callback callback)
+{
+ for (auto* child = first_child(); child; child = child->next_sibling()) {
+ if (child->is_element()) {
+ if (JS::GCPtr<ShadowRoot> shadow_root = static_cast<Element*>(child)->shadow_root()) {
+ if (shadow_root->for_each_shadow_including_inclusive_descendant(callback) == IterationDecision::Break)
return IterationDecision::Break;
}
}
- if (child->for_each_shadow_including_descendant(callback) == IterationDecision::Break)
+ if (child->for_each_shadow_including_inclusive_descendant(callback) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;