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:
parent
083b547e97
commit
034aaf3f51
38 changed files with 1747 additions and 143 deletions
|
@ -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.
|
||||
}
|
||||
|
|
|
@ -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<>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
||||
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 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()
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<>
|
||||
|
|
|
@ -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)
|
||||
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 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
// 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 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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ public:
|
|||
DeprecatedString as_string;
|
||||
};
|
||||
|
||||
void set_prefix(DeprecatedFlyString const& value);
|
||||
|
||||
private:
|
||||
NonnullRefPtr<Impl> m_impl;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue