1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 07:07:45 +00:00

LibWeb: Introduce CustomElementRegistry and creating custom elements

The main missing feature here is form associated custom elements.
This commit is contained in:
Luke Wilde 2023-03-29 23:46:18 +01:00 committed by Andreas Kling
parent 083b547e97
commit 034aaf3f51
38 changed files with 1747 additions and 143 deletions

View file

@ -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 attributes local name, attributes 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 attributes local name, oldValue, newValue, and attributes namespace.
// 2. If element is custom, then enqueue a custom element callback reaction with element, callback name "attributeChangedCallback", and an argument list containing attributes local name, oldValue, newValue, and attributes 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, attributes local name, oldValue, newValue, and attributes namespace.
}

View file

@ -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<>

View file

@ -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));

View file

@ -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 thiss 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 nodes node document.
auto& old_document = node.document();
// 2. If nodes 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 nodes shadow-including inclusive descendants:
node.for_each_shadow_including_inclusive_descendant([&](DOM::Node& inclusive_descendant) {
// 1. Set inclusiveDescendants node document to document.
inclusive_descendant.set_document({}, *this);
// FIXME: If inclusiveDescendant is an element, then set the node document of each attribute in inclusiveDescendants attribute list to document.
// FIXME: 2. If inclusiveDescendant is an element, then set the node document of each attribute in inclusiveDescendants
// attribute list to document.
return IterationDecision::Continue;
});
// FIXME: For each inclusiveDescendant in nodes 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 nodes 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;
node.for_each_shadow_including_descendant([&](auto& inclusive_descendant) {
auto& element = static_cast<DOM::Element&>(inclusive_descendant);
if (element.is_custom()) {
auto& vm = this->vm();
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 nodes 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()
{

View file

@ -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; }

View file

@ -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;

View file

@ -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 thiss local name is a valid custom element name, or thiss is value is not null, then: ...
// 3. If thiss local name is a valid custom element name, or thiss 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 thiss 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 definitions 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 shadows delegates focus to init["delegatesFocus"].
shadow->set_delegates_focus(init.delegates_focus);
// FIXME: 7. If thiss custom element state is "precustomized" or "custom", then set shadows available to element internals to true.
// 7. If thiss custom element state is "precustomized" or "custom", then set shadows 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 shadows 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);
}
}

View file

@ -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 elements 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<>

View file

@ -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 definitions 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 results 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)
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))
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGClipPathElement>(realm, document, move(qualified_name)));
if (lowercase_tag_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))
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGDefsElement>(realm, document, move(qualified_name)));
if (lowercase_tag_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))
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGForeignObjectElement>(realm, document, move(qualified_name)));
if (lowercase_tag_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)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPathElement>(realm, document, move(qualified_name)));
if (lowercase_tag_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)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPolylineElement>(realm, document, move(qualified_name)));
if (lowercase_tag_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)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGGElement>(realm, document, move(qualified_name)));
if (lowercase_tag_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)) {
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 (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 (local_name == SVG::TagNames::circle)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGCircleElement>(realm, document, move(qualified_name)));
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 (local_name == SVG::TagNames::ellipse)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGEllipseElement>(realm, document, move(qualified_name)));
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 (local_name == SVG::TagNames::line)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGLineElement>(realm, document, move(qualified_name)));
if (local_name == SVG::TagNames::path)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPathElement>(realm, document, move(qualified_name)));
if (local_name == SVG::TagNames::polygon)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPolygonElement>(realm, document, move(qualified_name)));
if (local_name == SVG::TagNames::polyline)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGPolylineElement>(realm, document, move(qualified_name)));
if (local_name == SVG::TagNames::rect)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGRectElement>(realm, document, move(qualified_name)));
if (local_name == SVG::TagNames::g)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGGElement>(realm, document, move(qualified_name)));
if (local_name == SVG::TagNames::text)
return MUST_OR_THROW_OOM(realm.heap().allocate<SVG::SVGTextContentElement>(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 definitions 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 results 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;
}
// 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 definitions 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: results custom element state and custom element definition are initialized.
// FIXME: 4. Assert: results 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 results 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 results 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 results 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 results 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 results namespace prefix to prefix.
element->set_prefix(prefix);
// 11. Set results 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 results 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;
}
}

View file

@ -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);
}

View file

@ -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 parents 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 nodes 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);
// 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.
// 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));
}
// 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 parents connected. (Currently unused so not included)
// 16. Let isParentConnected be parents 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, nodes local name, nodes namespace, nodes namespace prefix, and nodes 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: nodes namespace prefix, and nodes 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 nodes attribute list:
element.for_each_attribute([&](auto& name, auto& value) {

View file

@ -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);

View file

@ -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;
}
}

View file

@ -32,6 +32,8 @@ public:
DeprecatedString as_string;
};
void set_prefix(DeprecatedFlyString const& value);
private:
NonnullRefPtr<Impl> m_impl;
};

View file

@ -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_descendant(callback) == 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_inclusive_descendant(callback) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;