Skip to content

Commit c60ad5b

Browse files
AtkinsSJtcl3
authored andcommitted
LibWeb/DOM: Update node cloning to latest spec
Main difference is that a chunk of the "clone a node" steps are pulled out into a "clone a single node" algorithm. Reflects these spec PRs: whatwg/dom#1332 whatwg/dom#1334 Though this code is quite old so there may also be older spec changes included here.
1 parent 331b1b2 commit c60ad5b

File tree

2 files changed

+162
-115
lines changed

2 files changed

+162
-115
lines changed

Libraries/LibWeb/DOM/Node.cpp

Lines changed: 157 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,150 +1026,194 @@ WebIDL::ExceptionOr<GC::Ref<Node>> Node::replace_child(GC::Ref<Node> node, GC::R
10261026
}
10271027

10281028
// https://dom.spec.whatwg.org/#concept-node-clone
1029-
WebIDL::ExceptionOr<GC::Ref<Node>> Node::clone_node(Document* document, bool clone_children)
1029+
WebIDL::ExceptionOr<GC::Ref<Node>> Node::clone_node(Document* document, bool subtree, Node* parent)
10301030
{
1031-
// 1. If document is not given, let document be node’s node document.
1031+
// To clone a node given a node node and an optional document document (default node’s node document),
1032+
// boolean subtree (default false), and node-or-null parent (default null):
10321033
if (!document)
1033-
document = m_document.ptr();
1034-
GC::Ptr<Node> copy;
1034+
document = m_document;
10351035

1036-
// 2. If node is an element, then:
1037-
if (is<Element>(this)) {
1038-
// 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.
1039-
auto& element = *verify_cast<Element>(this);
1040-
auto element_copy = DOM::create_element(*document, element.local_name(), element.namespace_uri(), element.prefix(), element.is_value(), false).release_value_but_fixme_should_propagate_errors();
1036+
// 1. Assert: node is not a document or node is document.
1037+
VERIFY(!is_document() || this == document);
10411038

1042-
// 2. For each attribute in node’s attribute list:
1043-
element.for_each_attribute([&](auto& name, auto& value) {
1044-
// 1. Let copyAttribute be a clone of attribute.
1045-
// 2. Append copyAttribute to copy.
1046-
element_copy->append_attribute(name, value);
1047-
});
1048-
copy = move(element_copy);
1039+
// 2. Let copy be the result of cloning a single node given node and document.
1040+
auto copy = TRY(clone_single_node(*document));
10491041

1050-
}
1051-
// 3. Otherwise, let copy be a node that implements the same interfaces as node, and fulfills these additional requirements, switching on the interface node implements:
1052-
else if (is<Document>(this)) {
1053-
// Document
1054-
auto document_ = verify_cast<Document>(this);
1055-
auto document_copy = [&] -> GC::Ref<Document> {
1056-
switch (document_->document_type()) {
1057-
case Document::Type::XML:
1058-
return XMLDocument::create(realm(), document_->url());
1059-
case Document::Type::HTML:
1060-
return HTML::HTMLDocument::create(realm(), document_->url());
1061-
default:
1062-
return Document::create(realm(), document_->url());
1063-
}
1064-
}();
1065-
1066-
// Set copy’s encoding, content type, URL, origin, type, and mode to those of node.
1067-
document_copy->set_encoding(document_->encoding());
1068-
document_copy->set_content_type(document_->content_type());
1069-
document_copy->set_url(document_->url());
1070-
document_copy->set_origin(document_->origin());
1071-
document_copy->set_document_type(document_->document_type());
1072-
document_copy->set_quirks_mode(document_->mode());
1073-
copy = move(document_copy);
1074-
} else if (is<DocumentType>(this)) {
1075-
// DocumentType
1076-
auto document_type = verify_cast<DocumentType>(this);
1077-
auto document_type_copy = realm().create<DocumentType>(*document);
1078-
1079-
// Set copy’s name, public ID, and system ID to those of node.
1080-
document_type_copy->set_name(document_type->name());
1081-
document_type_copy->set_public_id(document_type->public_id());
1082-
document_type_copy->set_system_id(document_type->system_id());
1083-
copy = move(document_type_copy);
1084-
} else if (is<Attr>(this)) {
1085-
// Attr
1086-
// Set copy’s namespace, namespace prefix, local name, and value to those of node.
1087-
auto& attr = static_cast<Attr&>(*this);
1088-
copy = attr.clone(*document);
1089-
} else if (is<Text>(this)) {
1090-
// Text
1091-
auto& text = static_cast<Text&>(*this);
1092-
1093-
// Set copy’s data to that of node.
1094-
copy = [&]() -> GC::Ref<Text> {
1095-
switch (type()) {
1096-
case NodeType::TEXT_NODE:
1097-
return realm().create<Text>(*document, text.data());
1098-
case NodeType::CDATA_SECTION_NODE:
1099-
return realm().create<CDATASection>(*document, text.data());
1100-
default:
1101-
VERIFY_NOT_REACHED();
1102-
}
1103-
}();
1104-
} else if (is<Comment>(this)) {
1105-
// Comment
1106-
auto comment = verify_cast<Comment>(this);
1042+
// 3. Run any cloning steps defined for node in other applicable specifications and pass node, copy, and subtree as parameters.
1043+
TRY(cloned(*copy, subtree));
11071044

1108-
// Set copy’s data to that of node.
1109-
auto comment_copy = realm().create<Comment>(*document, comment->data());
1110-
copy = move(comment_copy);
1111-
} else if (is<ProcessingInstruction>(this)) {
1112-
// ProcessingInstruction
1113-
auto processing_instruction = verify_cast<ProcessingInstruction>(this);
1045+
// 4. If parent is non-null, then append copy to parent.
1046+
if (parent)
1047+
TRY(parent->append_child(copy));
11141048

1115-
// Set copy’s target and data to those of node.
1116-
auto processing_instruction_copy = realm().create<ProcessingInstruction>(*document, processing_instruction->data(), processing_instruction->target());
1117-
copy = processing_instruction_copy;
1118-
}
1119-
// Otherwise, Do nothing.
1120-
else if (is<DocumentFragment>(this)) {
1121-
copy = realm().create<DocumentFragment>(*document);
1049+
// 5. If subtree is true, then for each child of node’s children, in tree order:
1050+
// clone a node given child with document set to document, subtree set to subtree, and parent set to copy.
1051+
if (subtree) {
1052+
for (auto child = first_child(); child; child = child->next_sibling()) {
1053+
TRY(child->clone_node(document, subtree, copy));
1054+
}
11221055
}
11231056

1124-
// FIXME: 4. Set copy’s node document and document to copy, if copy is a document, and set copy’s node document to document otherwise.
1125-
1126-
// 5. Run any cloning steps defined for node in other applicable specifications and pass copy, node, document and the clone children flag if set, as parameters.
1127-
TRY(cloned(*copy, clone_children));
1128-
1129-
// 6. If the clone children flag is set, clone all the children of node and append them to copy, with document as specified and the clone children flag being set.
1130-
if (clone_children) {
1131-
for (auto child = first_child(); child; child = child->next_sibling()) {
1132-
TRY(copy->append_child(TRY(child->clone_node(document, true))));
1057+
// 6. If node is an element, node is a shadow host, and node’s shadow root’s clonable is true:
1058+
if (is_element()) {
1059+
auto& node_element = verify_cast<Element>(*this);
1060+
if (node_element.is_shadow_host() && node_element.shadow_root()->clonable()) {
1061+
// 1. Assert: copy is not a shadow host.
1062+
auto& copy_element = verify_cast<Element>(*copy);
1063+
VERIFY(!copy_element.is_shadow_host());
1064+
1065+
// 2. Attach a shadow root with copy, node’s shadow root’s mode, true, node’s shadow root’s serializable, node’s shadow root’s delegates focus, and node’s shadow root’s slot assignment.
1066+
TRY(copy_element.attach_a_shadow_root(node_element.shadow_root()->mode(), true, node_element.shadow_root()->serializable(), node_element.shadow_root()->delegates_focus(), node_element.shadow_root()->slot_assignment()));
1067+
1068+
// 3. Set copy’s shadow root’s declarative to node’s shadow root’s declarative.
1069+
copy_element.shadow_root()->set_declarative(node_element.shadow_root()->declarative());
1070+
1071+
// 4. For each child of node’s shadow root’s children, in tree order:
1072+
// clone a node given child with document set to document, subtree set to subtree, and parent set to copy’s shadow root.
1073+
for (auto child = node_element.shadow_root()->first_child(); child; child = child->next_sibling()) {
1074+
TRY(child->clone_node(document, subtree, copy_element.shadow_root()));
1075+
}
11331076
}
11341077
}
11351078

1136-
// 7. If node is a shadow host whose shadow root’s clonable is true:
1137-
if (is_element() && static_cast<Element const&>(*this).is_shadow_host() && static_cast<Element const&>(*this).shadow_root()->clonable()) {
1138-
// 1. Assert: copy is not a shadow host.
1139-
VERIFY(!copy->is_element() || !static_cast<Element const&>(*copy).is_shadow_host());
1079+
// 7. Return copy.
1080+
return GC::Ref { *copy };
1081+
}
1082+
1083+
// https://dom.spec.whatwg.org/#clone-a-single-node
1084+
WebIDL::ExceptionOr<GC::Ref<Node>> Node::clone_single_node(Document& document)
1085+
{
1086+
// To clone a single node given a node node and document document:
11401087

1141-
// 2. Run attach a shadow root with copy, node’s shadow root’s mode, true, node’s shadow root’s serializable,
1142-
// node’s shadow root’s delegates focus, and node’s shadow root’s slot assignment.
1143-
auto& node_shadow_root = *static_cast<Element&>(*this).shadow_root();
1144-
TRY(static_cast<Element&>(*copy).attach_a_shadow_root(node_shadow_root.mode(), true, node_shadow_root.serializable(), node_shadow_root.delegates_focus(), node_shadow_root.slot_assignment()));
1088+
// 1. Let copy be null.
1089+
GC::Ptr<Node> copy = nullptr;
11451090

1146-
// 3. Set copy’s shadow root’s declarative to node’s shadow root’s declarative.
1147-
static_cast<Element&>(*copy).shadow_root()->set_declarative(node_shadow_root.declarative());
1091+
// 2. If node is an element:
1092+
if (is_element()) {
1093+
// 1. Set copy to the result of creating an element, given document, node’s local name, node’s namespace, node’s namespace prefix, and node’s is value.
1094+
auto& element = *verify_cast<Element>(this);
1095+
auto element_copy = TRY(DOM::create_element(document, element.local_name(), element.namespace_uri(), element.prefix(), element.is_value()));
11481096

1149-
// 4. For each child child of node’s shadow root, in tree order:
1150-
// append the result of cloning child with document and the clone children flag set, to copy’s shadow root.
1151-
for (auto child = node_shadow_root.first_child(); child; child = child->next_sibling()) {
1152-
TRY(static_cast<Element&>(*copy).shadow_root()->append_child(TRY(child->clone_node(document, true))));
1097+
// 2. For each attribute of node’s attribute list:
1098+
element.for_each_attribute([&](auto& name, auto& value) {
1099+
// FIXME: 1. Let copyAttribute be the result of cloning a single node given attribute and document.
1100+
// 2. Append copyAttribute to copy.
1101+
element_copy->append_attribute(name, value);
1102+
});
1103+
copy = move(element_copy);
1104+
}
1105+
1106+
// 3. Otherwise, set copy to a node that implements the same interfaces as node, and fulfills these additional requirements, switching on the interface node implements:
1107+
else {
1108+
if (is_document()) {
1109+
// -> Document
1110+
auto& document_ = verify_cast<Document>(*this);
1111+
auto document_copy = [&] -> GC::Ref<Document> {
1112+
switch (document_.document_type()) {
1113+
case Document::Type::XML:
1114+
return XMLDocument::create(realm(), document_.url());
1115+
case Document::Type::HTML:
1116+
return HTML::HTMLDocument::create(realm(), document_.url());
1117+
default:
1118+
return Document::create(realm(), document_.url());
1119+
}
1120+
}();
1121+
1122+
// Set copy’s encoding, content type, URL, origin, type, and mode to those of node.
1123+
document_copy->set_encoding(document_.encoding());
1124+
document_copy->set_content_type(document_.content_type());
1125+
document_copy->set_url(document_.url());
1126+
document_copy->set_origin(document_.origin());
1127+
document_copy->set_document_type(document_.document_type());
1128+
document_copy->set_quirks_mode(document_.mode());
1129+
copy = move(document_copy);
1130+
} else if (is_document_type()) {
1131+
// -> DocumentType
1132+
auto& document_type = verify_cast<DocumentType>(*this);
1133+
auto document_type_copy = realm().create<DocumentType>(document);
1134+
1135+
// Set copy’s name, public ID, and system ID to those of node.
1136+
document_type_copy->set_name(document_type.name());
1137+
document_type_copy->set_public_id(document_type.public_id());
1138+
document_type_copy->set_system_id(document_type.system_id());
1139+
copy = move(document_type_copy);
1140+
} else if (is_attribute()) {
1141+
// -> Attr
1142+
// Set copy’s namespace, namespace prefix, local name, and value to those of node.
1143+
auto& attr = verify_cast<Attr>(*this);
1144+
copy = attr.clone(document);
1145+
} else if (is_text()) {
1146+
// -> Text
1147+
auto& text = verify_cast<Text>(*this);
1148+
1149+
// Set copy’s data to that of node.
1150+
copy = [&]() -> GC::Ref<Text> {
1151+
switch (type()) {
1152+
case NodeType::TEXT_NODE:
1153+
return realm().create<Text>(document, text.data());
1154+
case NodeType::CDATA_SECTION_NODE:
1155+
return realm().create<CDATASection>(document, text.data());
1156+
default:
1157+
VERIFY_NOT_REACHED();
1158+
}
1159+
}();
1160+
} else if (is_comment()) {
1161+
// -> Comment
1162+
auto& comment = verify_cast<Comment>(*this);
1163+
1164+
// Set copy’s data to that of node.
1165+
auto comment_copy = realm().create<Comment>(document, comment.data());
1166+
copy = move(comment_copy);
1167+
} else if (is<ProcessingInstruction>(this)) {
1168+
// -> ProcessingInstruction
1169+
auto& processing_instruction = verify_cast<ProcessingInstruction>(*this);
1170+
1171+
// Set copy’s target and data to those of node.
1172+
auto processing_instruction_copy = realm().create<ProcessingInstruction>(document, processing_instruction.data(), processing_instruction.target());
1173+
copy = move(processing_instruction_copy);
1174+
}
1175+
// -> Otherwise
1176+
// Do nothing.
1177+
else if (is<DocumentFragment>(this)) {
1178+
copy = realm().create<DocumentFragment>(document);
1179+
} else {
1180+
dbgln("Missing code for cloning a '{}' node. Please add it to Node::clone_single_node()", class_name());
1181+
VERIFY_NOT_REACHED();
11531182
}
11541183
}
11551184

1156-
// 7. Return copy.
1185+
// 4. Assert: copy is a node.
11571186
VERIFY(copy);
1187+
1188+
// 5. If node is a document, then set document to copy.
1189+
Document& document_to_use = is_document()
1190+
? static_cast<Document&>(*copy)
1191+
: document;
1192+
1193+
// 6. Set copy’s node document to document.
1194+
copy->set_document(document_to_use);
1195+
1196+
// 7. Return copy.
11581197
return GC::Ref { *copy };
11591198
}
11601199

11611200
// https://dom.spec.whatwg.org/#dom-node-clonenode
1162-
WebIDL::ExceptionOr<GC::Ref<Node>> Node::clone_node_binding(bool deep)
1201+
WebIDL::ExceptionOr<GC::Ref<Node>> Node::clone_node_binding(bool subtree)
11631202
{
11641203
// 1. If this is a shadow root, then throw a "NotSupportedError" DOMException.
11651204
if (is<ShadowRoot>(*this))
11661205
return WebIDL::NotSupportedError::create(realm(), "Cannot clone shadow root"_string);
11671206

1168-
// 2. Return a clone of this, with the clone children flag set if deep is true.
1169-
return clone_node(nullptr, deep);
1207+
// 2. Return the result of cloning a node given this with subtree set to subtree.
1208+
return clone_node(nullptr, subtree);
11701209
}
11711210

11721211
void Node::set_document(Badge<Document>, Document& document)
1212+
{
1213+
set_document(document);
1214+
}
1215+
1216+
void Node::set_document(Document& document)
11731217
{
11741218
if (m_document.ptr() == &document)
11751219
return;

Libraries/LibWeb/DOM/Node.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,9 @@ class Node : public EventTarget {
192192

193193
WebIDL::ExceptionOr<GC::Ref<Node>> replace_child(GC::Ref<Node> node, GC::Ref<Node> child);
194194

195-
WebIDL::ExceptionOr<GC::Ref<Node>> clone_node(Document* document = nullptr, bool clone_children = false);
196-
WebIDL::ExceptionOr<GC::Ref<Node>> clone_node_binding(bool deep);
195+
WebIDL::ExceptionOr<GC::Ref<Node>> clone_node(Document* document = nullptr, bool subtree = false, Node* parent = nullptr);
196+
WebIDL::ExceptionOr<GC::Ref<Node>> clone_single_node(Document&);
197+
WebIDL::ExceptionOr<GC::Ref<Node>> clone_node_binding(bool subtree);
197198

198199
// NOTE: This is intended for the JS bindings.
199200
bool has_child_nodes() const { return has_children(); }
@@ -742,6 +743,8 @@ class Node : public EventTarget {
742743
Node(JS::Realm&, Document&, NodeType);
743744
Node(Document&, NodeType);
744745

746+
void set_document(Document&);
747+
745748
virtual void visit_edges(Cell::Visitor&) override;
746749
virtual void finalize() override;
747750

0 commit comments

Comments
 (0)