Skip to content

Commit 1ab22a6

Browse files
committed
LibWeb: Implement scoped custom element registries
This corresponds to a couple of spec changes: whatwg/html#10869 whatwg/dom#1341 The regressions are unfortunate and need looking into. The spec issues I've reported mostly have fixes PRed too. Just going to wait for those.
1 parent d616ab0 commit 1ab22a6

27 files changed

+1064
-666
lines changed

Libraries/LibWeb/DOM/Document.cpp

+119-57
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,9 @@ WebIDL::ExceptionOr<GC::Ref<Document>> Document::create_and_initialize(Type type
354354
// current document readiness: "loading"
355355
// about base URL: navigationParams's about base URL
356356
// allow declarative shadow roots: true
357-
auto document = HTML::HTMLDocument::create(window->realm());
357+
// custom element registry: A new CustomElementRegistry object.
358+
auto& realm = window->realm();
359+
auto document = HTML::HTMLDocument::create(realm);
358360
document->m_type = type;
359361
document->m_content_type = move(content_type);
360362
document->set_origin(navigation_params.origin);
@@ -367,6 +369,7 @@ WebIDL::ExceptionOr<GC::Ref<Document>> Document::create_and_initialize(Type type
367369
document->m_readiness = HTML::DocumentReadyState::Loading;
368370
document->m_about_base_url = navigation_params.about_base_url;
369371
document->set_allow_declarative_shadow_roots(true);
372+
document->set_custom_element_registry(realm.create<HTML::CustomElementRegistry>(realm));
370373

371374
document->m_window = window;
372375

@@ -597,6 +600,7 @@ void Document::visit_edges(Cell::Visitor& visitor)
597600
visitor.visit(m_session_storage_holder);
598601
visitor.visit(m_render_blocking_elements);
599602
visitor.visit(m_policy_container);
603+
visitor.visit(m_custom_element_registry);
600604
}
601605

602606
// https://w3c.github.io/selection-api/#dom-document-getselection
@@ -2084,23 +2088,16 @@ WebIDL::ExceptionOr<GC::Ref<Element>> Document::create_element(String const& loc
20842088
? local_name.to_ascii_lowercase()
20852089
: local_name;
20862090

2087-
// 3. Let is be null.
2088-
Optional<String> is_value;
2091+
// 3. Let registry and is be the result of flattening element creation options given options and this.
2092+
auto [registry, is_value] = TRY(flatten_element_creation_options(options));
20892093

2090-
// 4. If options is a dictionary and options["is"] exists, then set is to it.
2091-
if (options.has<ElementCreationOptions>()) {
2092-
auto const& element_creation_options = options.get<ElementCreationOptions>();
2093-
if (element_creation_options.is.has_value())
2094-
is_value = element_creation_options.is.value();
2095-
}
2096-
2097-
// 5. Let namespace be the HTML namespace, if this is an HTML document or this’s content type is "application/xhtml+xml"; otherwise null.
2094+
// 4. Let namespace be the HTML namespace, if this is an HTML document or this’s content type is "application/xhtml+xml"; otherwise null.
20982095
Optional<FlyString> namespace_;
20992096
if (document_type() == Type::HTML || content_type() == "application/xhtml+xml"sv)
21002097
namespace_ = Namespace::HTML;
21012098

2102-
// 6. Return the result of creating an element given this, localName, namespace, null, is, and with the synchronous custom elements flag set.
2103-
return TRY(DOM::create_element(*this, FlyString::from_utf8_without_validation(local_name_lower.bytes()), move(namespace_), {}, move(is_value), true));
2099+
// 5. Return the result of creating an element given this, localName, namespace, null, is, true, and registry.
2100+
return TRY(DOM::create_element(*this, FlyString::from_utf8_without_validation(local_name_lower.bytes()), move(namespace_), {}, move(is_value), true, registry));
21042101
}
21052102

21062103
// https://dom.spec.whatwg.org/#dom-document-createelementns
@@ -2110,18 +2107,11 @@ WebIDL::ExceptionOr<GC::Ref<Element>> Document::create_element_ns(Optional<FlySt
21102107
// 1. Let namespace, prefix, and localName be the result of passing namespace and qualifiedName to validate and extract.
21112108
auto extracted_qualified_name = TRY(validate_and_extract(realm(), namespace_, qualified_name));
21122109

2113-
// 2. Let is be null.
2114-
Optional<String> is_value;
2110+
// 2. Let registry and is be the result of flattening element creation options given options and this.
2111+
auto [registry, is_value] = TRY(flatten_element_creation_options(options));
21152112

2116-
// 3. If options is a dictionary and options["is"] exists, then set is to it.
2117-
if (options.has<ElementCreationOptions>()) {
2118-
auto const& element_creation_options = options.get<ElementCreationOptions>();
2119-
if (element_creation_options.is.has_value())
2120-
is_value = element_creation_options.is.value();
2121-
}
2122-
2123-
// 4. Return the result of creating an element given document, localName, namespace, prefix, is, and with the synchronous custom elements flag set.
2124-
return TRY(DOM::create_element(*this, extracted_qualified_name.local_name(), extracted_qualified_name.namespace_(), extracted_qualified_name.prefix(), move(is_value), true));
2113+
// 3. Return the result of creating an element given document, localName, namespace, prefix, is, true, and registry.
2114+
return TRY(DOM::create_element(*this, extracted_qualified_name.local_name(), extracted_qualified_name.namespace_(), extracted_qualified_name.prefix(), move(is_value), true, registry));
21252115
}
21262116

21272117
GC::Ref<DocumentFragment> Document::create_document_fragment()
@@ -2305,14 +2295,39 @@ Vector<GC::Root<HTML::HTMLScriptElement>> Document::take_scripts_to_execute_in_o
23052295
}
23062296

23072297
// https://dom.spec.whatwg.org/#dom-document-importnode
2308-
WebIDL::ExceptionOr<GC::Ref<Node>> Document::import_node(GC::Ref<Node> node, bool deep)
2298+
WebIDL::ExceptionOr<GC::Ref<Node>> Document::import_node(GC::Ref<Node> node, Variant<bool, ImportNodeOptions> options)
23092299
{
23102300
// 1. If node is a document or shadow root, then throw a "NotSupportedError" DOMException.
23112301
if (is<Document>(*node) || is<ShadowRoot>(*node))
23122302
return WebIDL::NotSupportedError::create(realm(), "Cannot import a document or shadow root."_string);
23132303

2314-
// 2. Return a clone of node, with this and the clone children flag set if deep is true.
2315-
return node->clone_node(this, deep);
2304+
// 2. Let subtree be false.
2305+
bool subtree = false;
2306+
2307+
// 3. Let registry be null.
2308+
GC::Ptr<HTML::CustomElementRegistry> registry;
2309+
2310+
options.visit(
2311+
// 4. If options is a boolean, then set subtree to options.
2312+
[&subtree](bool const& value) {
2313+
subtree = value;
2314+
},
2315+
// 5. Otherwise:
2316+
[&subtree, &registry](ImportNodeOptions const& options) {
2317+
// 1. Set subtree to the negation of options["selfOnly"].
2318+
subtree = !options.self_only;
2319+
2320+
// 2. If options["customElementRegistry"] exists, then set registry to it.
2321+
if (options.custom_element_registry)
2322+
registry = options.custom_element_registry;
2323+
});
2324+
2325+
// 6. If registry is null, then set registry to the result of looking up a custom element registry given this.
2326+
if (!registry)
2327+
registry = HTML::look_up_a_custom_element_registry(*this);
2328+
2329+
// 7. Return the result of cloning a node given node with document set to this, subtree set to subtree, and fallbackRegistry set to registry.
2330+
return node->clone_node(this, subtree, nullptr, registry);
23162331
}
23172332

23182333
// https://dom.spec.whatwg.org/#concept-node-adopt
@@ -3690,36 +3705,6 @@ void Document::set_window(HTML::Window& window)
36903705
m_window = &window;
36913706
}
36923707

3693-
// https://html.spec.whatwg.org/multipage/custom-elements.html#look-up-a-custom-element-definition
3694-
GC::Ptr<HTML::CustomElementDefinition> Document::lookup_custom_element_definition(Optional<FlyString> const& namespace_, FlyString const& local_name, Optional<String> const& is) const
3695-
{
3696-
// 1. If namespace is not the HTML namespace, then return null.
3697-
if (namespace_ != Namespace::HTML)
3698-
return nullptr;
3699-
3700-
// 2. If document's browsing context is null, then return null.
3701-
if (!browsing_context())
3702-
return nullptr;
3703-
3704-
// 3. Let registry be document's relevant global object's custom element registry.
3705-
auto registry = as<HTML::Window>(relevant_global_object(*this)).custom_elements();
3706-
3707-
// 4. If registry's custom element definition set contains an item with name and local name both equal to localName, then return that item.
3708-
auto converted_local_name = local_name.to_string();
3709-
auto maybe_definition = registry->get_definition_with_name_and_local_name(converted_local_name, converted_local_name);
3710-
if (maybe_definition)
3711-
return maybe_definition;
3712-
3713-
// 5. If registry's custom element definition set contains an item with name equal to is and local name equal to localName, then return that item.
3714-
// 6. Return null.
3715-
3716-
// 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>)
3717-
if (!is.has_value())
3718-
return nullptr;
3719-
3720-
return registry->get_definition_with_name_and_local_name(is.value(), converted_local_name);
3721-
}
3722-
37233708
CSS::StyleSheetList& Document::style_sheets()
37243709
{
37253710
if (!m_style_sheets)
@@ -6440,6 +6425,38 @@ void Document::run_csp_initialization() const
64406425
}
64416426
}
64426427

6428+
// https://dom.spec.whatwg.org/#flatten-element-creation-options
6429+
WebIDL::ExceptionOr<Document::RegistryAndIs> Document::flatten_element_creation_options(Variant<String, ElementCreationOptions> options) const
6430+
{
6431+
// 1. Let registry be null.
6432+
GC::Ptr<HTML::CustomElementRegistry> registry;
6433+
6434+
// 2. Let is be null.
6435+
Optional<String> is;
6436+
6437+
// 3. If options is a dictionary:
6438+
if (auto* dictionary = options.get_pointer<ElementCreationOptions>()) {
6439+
// 1. If options["customElementRegistry"] exists, then set registry to it.
6440+
if (dictionary->custom_element_registry)
6441+
registry = dictionary->custom_element_registry;
6442+
6443+
// 2. If options["is"] exists, then set is to it.
6444+
if (dictionary->is.has_value())
6445+
is = dictionary->is.value();
6446+
6447+
// 3. If registry is non-null and is is non-null, then throw a "NotSupportedError" DOMException.
6448+
if (registry && is.has_value())
6449+
return WebIDL::NotSupportedError::create(realm(), "Cannot provide both 'is' and 'customElementRegistry' in ElementCreationOptions."_string);
6450+
}
6451+
6452+
// 4. If registry is null, then set registry to the result of looking up a custom element registry given document.
6453+
if (!registry)
6454+
registry = HTML::look_up_a_custom_element_registry(*this);
6455+
6456+
// 5. Return registry and is.
6457+
return RegistryAndIs { registry, move(is) };
6458+
}
6459+
64436460
WebIDL::CallbackType* Document::onreadystatechange()
64446461
{
64456462
return event_handler_attribute(HTML::EventNames::readystatechange);
@@ -6460,6 +6477,51 @@ void Document::set_onvisibilitychange(WebIDL::CallbackType* value)
64606477
set_event_handler_attribute(HTML::EventNames::visibilitychange, value);
64616478
}
64626479

6480+
// https://dom.spec.whatwg.org/#dom-documentorshadowroot-customelementregistry
6481+
GC::Ptr<HTML::CustomElementRegistry> Document::custom_element_registry() const
6482+
{
6483+
// 1. If this is a document, then return this’s custom element registry.
6484+
// NB: Always true.
6485+
return m_custom_element_registry;
6486+
6487+
// 2. Assert: this is a ShadowRoot node.
6488+
// 3. Return this’s custom element registry.
6489+
}
6490+
6491+
// https://html.spec.whatwg.org/multipage/custom-elements.html#upgrade-particular-elements-within-a-document
6492+
void Document::upgrade_particular_elements(GC::Ref<HTML::CustomElementDefinition> definition, String local_name, Optional<String> maybe_name)
6493+
{
6494+
// To upgrade particular elements within a document given a Document object document, a string localName, and
6495+
// optionally a string name (default localName):
6496+
auto name = maybe_name.value_or(local_name);
6497+
6498+
// FIXME: We pass in the definition but the spec doesn't. Spec issue: https://github.com/whatwg/html/issues/11222
6499+
6500+
// 1. Let upgradeCandidates be all elements that are shadow-including descendants of document, whose namespace is
6501+
// the HTML namespace and whose local name is localName, in shadow-including tree order.
6502+
// Additionally, if name is not localName, only include elements whose is value is equal to name.
6503+
Vector<GC::Root<Element>> upgrade_candidates;
6504+
for_each_shadow_including_descendant([&](Node& inclusive_descendant) {
6505+
auto* element = as_if<Element>(inclusive_descendant);
6506+
if (!element)
6507+
return TraversalDecision::Continue;
6508+
6509+
if (element->namespace_uri() != Namespace::HTML || element->local_name() != local_name)
6510+
return TraversalDecision::Continue;
6511+
6512+
if (name != local_name && element->is_value() != name)
6513+
return TraversalDecision::Continue;
6514+
6515+
upgrade_candidates.append(GC::make_root(element));
6516+
return TraversalDecision::Continue;
6517+
});
6518+
6519+
// 2. For each element element of upgradeCandidates:
6520+
// enqueue a custom element upgrade reaction given element and definition.
6521+
for (auto& element : upgrade_candidates)
6522+
element->enqueue_a_custom_element_upgrade_reaction(definition);
6523+
}
6524+
64636525
ElementByIdMap& Document::element_by_id() const
64646526
{
64656527
if (!m_element_by_id)

Libraries/LibWeb/DOM/Document.h

+23-3
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,18 @@ struct DocumentUnloadTimingInfo {
147147
double unload_event_end_time { 0 };
148148
};
149149

150+
// https://dom.spec.whatwg.org/#dictdef-elementcreationoptions
150151
struct ElementCreationOptions {
152+
GC::Ptr<HTML::CustomElementRegistry> custom_element_registry;
151153
Optional<String> is;
152154
};
153155

156+
// https://dom.spec.whatwg.org/#dictdef-importnodeoptions
157+
struct ImportNodeOptions {
158+
GC::Ptr<HTML::CustomElementRegistry> custom_element_registry;
159+
bool self_only = false;
160+
};
161+
154162
enum class PolicyControlledFeature : u8 {
155163
Autoplay,
156164
FocusWithoutUserActivation,
@@ -415,7 +423,7 @@ class Document
415423
// https://dom.spec.whatwg.org/#xml-document
416424
bool is_xml_document() const { return m_type == Type::XML; }
417425

418-
WebIDL::ExceptionOr<GC::Ref<Node>> import_node(GC::Ref<Node> node, bool deep);
426+
WebIDL::ExceptionOr<GC::Ref<Node>> import_node(GC::Ref<Node> node, Variant<bool, ImportNodeOptions>);
419427
void adopt_node(Node&);
420428
WebIDL::ExceptionOr<GC::Ref<Node>> adopt_node_binding(GC::Ref<Node>);
421429

@@ -591,8 +599,6 @@ class Document
591599
bool has_active_favicon() const { return m_active_favicon; }
592600
void check_favicon_after_loading_link_resource();
593601

594-
GC::Ptr<HTML::CustomElementDefinition> lookup_custom_element_definition(Optional<FlyString> const& namespace_, FlyString const& local_name, Optional<String> const& is) const;
595-
596602
void increment_throw_on_dynamic_markup_insertion_counter(Badge<HTML::HTMLParser>);
597603
void decrement_throw_on_dynamic_markup_insertion_counter(Badge<HTML::HTMLParser>);
598604

@@ -896,6 +902,11 @@ class Document
896902

897903
ElementByIdMap& element_by_id() const;
898904

905+
GC::Ptr<HTML::CustomElementRegistry> custom_element_registry() const;
906+
void set_custom_element_registry(GC::Ptr<HTML::CustomElementRegistry> custom_element_registry) { m_custom_element_registry = custom_element_registry; }
907+
908+
void upgrade_particular_elements(GC::Ref<HTML::CustomElementDefinition>, String local_name, Optional<String> name = {});
909+
899910
protected:
900911
virtual void initialize(JS::Realm&) override;
901912
virtual void visit_edges(Cell::Visitor&) override;
@@ -947,6 +958,12 @@ class Document
947958

948959
void run_csp_initialization() const;
949960

961+
struct RegistryAndIs {
962+
GC::Ptr<HTML::CustomElementRegistry> registry;
963+
Optional<String> is;
964+
};
965+
WebIDL::ExceptionOr<RegistryAndIs> flatten_element_creation_options(Variant<String, ElementCreationOptions>) const;
966+
950967
GC::Ref<Page> m_page;
951968
OwnPtr<CSS::StyleComputer> m_style_computer;
952969
GC::Ptr<CSS::StyleSheetList> m_style_sheets;
@@ -1248,6 +1265,9 @@ class Document
12481265
HashTable<GC::Ref<Element>> m_render_blocking_elements;
12491266

12501267
HashTable<WeakPtr<Node>> m_pending_nodes_for_style_invalidation_due_to_presence_of_has;
1268+
1269+
// https://dom.spec.whatwg.org/#document-custom-element-registry
1270+
GC::Ptr<HTML::CustomElementRegistry> m_custom_element_registry;
12511271
};
12521272

12531273
template<>

Libraries/LibWeb/DOM/Document.idl

+10-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ interface Document : Node {
109109
Range createRange();
110110
Event createEvent(DOMString interface);
111111

112-
[CEReactions, NewObject] Node importNode(Node node, optional boolean deep = false);
112+
[CEReactions, NewObject] Node importNode(Node node, optional (boolean or ImportNodeOptions) options = false);
113113
[CEReactions, ImplementedAs=adopt_node_binding] Node adoptNode(Node node);
114114

115115
readonly attribute DOMString compatMode;
@@ -158,9 +158,18 @@ interface Document : Node {
158158
attribute EventHandler onvisibilitychange;
159159
};
160160

161+
// https://dom.spec.whatwg.org/#dictdef-elementcreationoptions
161162
dictionary ElementCreationOptions {
163+
CustomElementRegistry customElementRegistry;
162164
DOMString is;
163165
};
166+
167+
// https://dom.spec.whatwg.org/#dictdef-importnodeoptions
168+
dictionary ImportNodeOptions {
169+
CustomElementRegistry customElementRegistry;
170+
boolean selfOnly = false;
171+
};
172+
164173
Document includes ParentNode;
165174
Document includes GlobalEventHandlers;
166175
Document includes DocumentOrShadowRoot;

0 commit comments

Comments
 (0)