mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 13:38:11 +00:00
LibWeb: Add spec comments to Node
This also implements a few more steps of compare_document_position, and removes an invalid step from clone_node
This commit is contained in:
parent
62bd88dc14
commit
1c0d29b8da
1 changed files with 244 additions and 58 deletions
|
@ -125,60 +125,80 @@ String Node::descendant_text_content() const
|
|||
// https://dom.spec.whatwg.org/#dom-node-textcontent
|
||||
String Node::text_content() const
|
||||
{
|
||||
// The textContent getter steps are to return the following, switching on the interface this implements:
|
||||
// If DocumentFragment or Element, return the descendant text content of this.
|
||||
if (is<DocumentFragment>(this) || is<Element>(this))
|
||||
return descendant_text_content();
|
||||
else if (is<CharacterData>(this))
|
||||
// If CharacterData, return this’s data.
|
||||
return verify_cast<CharacterData>(this)->data();
|
||||
|
||||
// FIXME: Else if this is an Attr node, return this's value.
|
||||
// FIXME: If this is an Attr node, return this's value.
|
||||
|
||||
// Otherwise, return null
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#ref-for-dom-node-textcontent%E2%91%A0
|
||||
void Node::set_text_content(String const& content)
|
||||
{
|
||||
// The textContent setter steps are to, if the given value is null, act as if it was the empty string instead,
|
||||
// and then do as described below, switching on the interface this implements:
|
||||
|
||||
// If DocumentFragment or Element, string replace all with the given value within this.
|
||||
if (is<DocumentFragment>(this) || is<Element>(this)) {
|
||||
string_replace_all(content);
|
||||
} else if (is<CharacterData>(this)) {
|
||||
// FIXME: CharacterData::set_data is not spec compliant. Make this match the spec when set_data becomes spec compliant.
|
||||
// Do note that this will make this function able to throw an exception.
|
||||
|
||||
// If CharacterData, replace data with node this, offset 0, count this’s length, and data the given value.
|
||||
auto* character_data_node = verify_cast<CharacterData>(this);
|
||||
character_data_node->set_data(content);
|
||||
} else {
|
||||
// FIXME: Else if this is an Attr node, set an existing attribute value with this and the given value.
|
||||
|
||||
// FIXME: CharacterData::set_data is not spec compliant. Make this match the spec when set_data becomes spec compliant.
|
||||
// Do note that this will make this function able to throw an exception.
|
||||
} else {
|
||||
// FIXME: If this is an Attr node, set an existing attribute value with this and the given value.
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, do nothing.
|
||||
|
||||
set_needs_style_update(true);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-node-nodevalue
|
||||
String Node::node_value() const
|
||||
{
|
||||
// The nodeValue getter steps are to return the following, switching on the interface this implements:
|
||||
|
||||
// If Attr, return this’s value.
|
||||
if (is<Attribute>(this)) {
|
||||
return verify_cast<Attribute>(this)->value();
|
||||
}
|
||||
|
||||
// If CharacterData, return this’s data.
|
||||
if (is<CharacterData>(this)) {
|
||||
return verify_cast<CharacterData>(this)->data();
|
||||
}
|
||||
|
||||
// Otherwise, return null.
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#ref-for-dom-node-nodevalue%E2%91%A0
|
||||
void Node::set_node_value(String const& value)
|
||||
{
|
||||
// The nodeValue setter steps are to, if the given value is null, act as if it was the empty string instead,
|
||||
// and then do as described below, switching on the interface this implements:
|
||||
|
||||
// If Attr, set an existing attribute value with this and the given value.
|
||||
if (is<Attribute>(this)) {
|
||||
verify_cast<Attribute>(this)->set_value(value);
|
||||
} else if (is<CharacterData>(this)) {
|
||||
// If CharacterData, replace data with node this, offset 0, count this’s length, and data the given value.
|
||||
verify_cast<CharacterData>(this)->set_data(value);
|
||||
}
|
||||
|
||||
// Otherwise: Do nothing.
|
||||
// Otherwise, do nothing.
|
||||
}
|
||||
|
||||
void Node::invalidate_style()
|
||||
|
@ -228,6 +248,8 @@ String Node::child_text_content() const
|
|||
// https://dom.spec.whatwg.org/#concept-tree-root
|
||||
Node& Node::root()
|
||||
{
|
||||
// The root of an object is itself, if its parent is null, or else it is the root of its parent.
|
||||
// The root of a tree is any object participating in that tree whose parent is null.
|
||||
Node* root = this;
|
||||
while (root->parent())
|
||||
root = root->parent();
|
||||
|
@ -237,6 +259,8 @@ Node& Node::root()
|
|||
// https://dom.spec.whatwg.org/#concept-shadow-including-root
|
||||
Node& Node::shadow_including_root()
|
||||
{
|
||||
// The shadow-including root of an object is its root’s host’s shadow-including root,
|
||||
// if the object’s root is a shadow root; otherwise its root.
|
||||
auto& node_root = root();
|
||||
if (is<ShadowRoot>(node_root))
|
||||
return verify_cast<ShadowRoot>(node_root).host()->shadow_including_root();
|
||||
|
@ -246,6 +270,7 @@ Node& Node::shadow_including_root()
|
|||
// https://dom.spec.whatwg.org/#connected
|
||||
bool Node::is_connected() const
|
||||
{
|
||||
// An element is connected if its shadow-including root is a document.
|
||||
return shadow_including_root().is_document();
|
||||
}
|
||||
|
||||
|
@ -266,34 +291,46 @@ Element const* Node::parent_element() const
|
|||
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
|
||||
ExceptionOr<void> Node::ensure_pre_insertion_validity(NonnullRefPtr<Node> node, RefPtr<Node> child) const
|
||||
{
|
||||
// 1. If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
|
||||
if (!is<Document>(this) && !is<DocumentFragment>(this) && !is<Element>(this))
|
||||
return DOM::HierarchyRequestError::create("Can only insert into a document, document fragment or element");
|
||||
|
||||
// 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException.
|
||||
if (node->is_host_including_inclusive_ancestor_of(*this))
|
||||
return DOM::HierarchyRequestError::create("New node is an ancestor of this node");
|
||||
|
||||
// 3. If child is non-null and its parent is not parent, then throw a "NotFoundError" DOMException.
|
||||
if (child && child->parent() != this)
|
||||
return DOM::NotFoundError::create("This node is not the parent of the given child");
|
||||
|
||||
// FIXME: All the following "Invalid node type for insertion" messages could be more descriptive.
|
||||
|
||||
// 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
|
||||
if (!is<DocumentFragment>(*node) && !is<DocumentType>(*node) && !is<Element>(*node) && !is<Text>(*node) && !is<Comment>(*node) && !is<ProcessingInstruction>(*node))
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
|
||||
// 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException.
|
||||
if ((is<Text>(*node) && is<Document>(this)) || (is<DocumentType>(*node) && !is<Document>(this)))
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
|
||||
// 6. If parent is a document, and any of the statements below, switched on the interface node implements, are true, then throw a "HierarchyRequestError" DOMException.
|
||||
if (is<Document>(this)) {
|
||||
// DocumentFragment
|
||||
if (is<DocumentFragment>(*node)) {
|
||||
// If node has more than one element child or has a Text node child.
|
||||
// Otherwise, if node has one element child and either parent has an element child, child is a doctype, or child is non-null and a doctype is following child.
|
||||
auto node_element_child_count = verify_cast<DocumentFragment>(*node).child_element_count();
|
||||
if ((node_element_child_count > 1 || node->has_child_of_type<Text>())
|
||||
|| (node_element_child_count == 1 && (has_child_of_type<Element>() || is<DocumentType>(child.ptr()) || (child && child->has_following_node_of_type_in_tree_order<DocumentType>())))) {
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
}
|
||||
} else if (is<Element>(*node)) {
|
||||
// Element
|
||||
// If parent has an element child, child is a doctype, or child is non-null and a doctype is following child.
|
||||
if (has_child_of_type<Element>() || is<DocumentType>(child.ptr()) || (child && child->has_following_node_of_type_in_tree_order<DocumentType>()))
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
} else if (is<DocumentType>(*node)) {
|
||||
// DocumentType
|
||||
// parent has a doctype child, child is non-null and an element is preceding child, or child is null and parent has an element child.
|
||||
if (has_child_of_type<DocumentType>() || (child && child->has_preceding_node_of_type_in_tree_order<Element>()) || (!child && has_child_of_type<Element>()))
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
}
|
||||
|
@ -305,68 +342,88 @@ ExceptionOr<void> Node::ensure_pre_insertion_validity(NonnullRefPtr<Node> node,
|
|||
// https://dom.spec.whatwg.org/#concept-node-insert
|
||||
void Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool suppress_observers)
|
||||
{
|
||||
// 1. Let nodes be node’s children, if node is a DocumentFragment node; otherwise « node ».
|
||||
NonnullRefPtrVector<Node> nodes;
|
||||
if (is<DocumentFragment>(*node))
|
||||
nodes = verify_cast<DocumentFragment>(*node).children_as_vector();
|
||||
else
|
||||
nodes.append(node);
|
||||
|
||||
// 2. Let count be nodes’s size.
|
||||
auto count = nodes.size();
|
||||
|
||||
// 3. If count is 0, then return.
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
// 4. If node is a DocumentFragment node, then:
|
||||
if (is<DocumentFragment>(*node)) {
|
||||
// 4.1 Remove its children with the suppress observers flag set.
|
||||
node->remove_all_children(true);
|
||||
// FIXME: Queue a tree mutation record for node with « », nodes, null, and null.
|
||||
|
||||
// 4.2 FIXME: Queue a tree mutation record for node with « », nodes, null, and null.
|
||||
// NOTE: This step intentionally does not pay attention to the suppress observers flag.
|
||||
}
|
||||
|
||||
// 5. If child is non-null, then:
|
||||
if (child) {
|
||||
// 1. For each live range whose start node is parent and start offset is greater than child’s index, increase its start offset by count.
|
||||
// 5.1 For each live range whose start node is parent and start offset is greater than child’s index, increase its start offset by count.
|
||||
for (auto& range : Range::live_ranges()) {
|
||||
if (range->start_container() == this && range->start_offset() > child->index())
|
||||
range->set_start(*range->start_container(), range->start_offset() + count);
|
||||
}
|
||||
|
||||
// 2. For each live range whose end node is parent and end offset is greater than child’s index, increase its end offset by count.
|
||||
// 5.2 For each live range whose end node is parent and end offset is greater than child’s index, increase its end offset by count.
|
||||
for (auto& range : Range::live_ranges()) {
|
||||
if (range->end_container() == this && range->end_offset() > child->index())
|
||||
range->set_end(*range->end_container(), range->end_offset() + count);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Let previousSibling be child’s previous sibling or parent’s last child if child is null. (Currently unused so not included)
|
||||
// 6. FIXME: Let previousSibling be child’s previous sibling or parent’s last child if child is null. (Currently unused so not included)
|
||||
|
||||
for (auto& node_to_insert : nodes) { // FIXME: In tree order
|
||||
// 7. For each node in nodes, in tree order:
|
||||
// FIXME: In tree order
|
||||
for (auto& node_to_insert : nodes) {
|
||||
// 7.1 Adopt node into parent’s node document.
|
||||
document().adopt_node(node_to_insert);
|
||||
|
||||
// 7.2 If child is null, then append node to parent’s children.
|
||||
if (!child)
|
||||
TreeNode<Node>::append_child(node_to_insert);
|
||||
else
|
||||
// 7.3 Otherwise, insert node into parent’s children before child’s index.
|
||||
TreeNode<Node>::insert_before(node_to_insert, child);
|
||||
|
||||
// FIXME: If parent is a shadow host and node is a slottable, then assign a slot for node.
|
||||
// FIXME: 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: Run assign slottables for a tree with node’s root.
|
||||
// FIXME: 7.4 If parent is a shadow host and node is a slottable, then assign a slot for node.
|
||||
// FIXME: 7.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: 7.6 Run assign slottables for a tree with node’s root.
|
||||
|
||||
// FIXME: This should be shadow-including.
|
||||
// 7.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) {
|
||||
// 7.7.1 Run the insertion steps with inclusiveDescendant.
|
||||
inclusive_descendant.inserted();
|
||||
|
||||
// 7.7.2 If inclusiveDescendant is connected, then:
|
||||
if (inclusive_descendant.is_connected()) {
|
||||
// FIXME: If inclusiveDescendant is custom, then enqueue a custom element callback reaction with inclusiveDescendant,
|
||||
// 7.7.2.1 FIXME: If inclusiveDescendant is custom, then enqueue a custom element callback reaction with inclusiveDescendant,
|
||||
// callback name "connectedCallback", and an empty argument list.
|
||||
|
||||
// FIXME: Otherwise, try to upgrade inclusiveDescendant.
|
||||
// 7.7.2.2 FIXME: Otherwise, try to upgrade inclusiveDescendant.
|
||||
// NOTE: If this successfully upgrades inclusiveDescendant, its connectedCallback will be enqueued automatically during the upgrade an element algorithm.
|
||||
}
|
||||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
// 8. If suppress observers flag is unset, then queue a tree mutation record for parent with nodes, « », previousSibling, and child.
|
||||
if (!suppress_observers) {
|
||||
// FIXME: queue a tree mutation record for parent with nodes, « », previousSibling, and child.
|
||||
}
|
||||
|
||||
// 9. Run the children changed steps for parent.
|
||||
children_changed();
|
||||
|
||||
document().invalidate_style();
|
||||
|
@ -375,43 +432,58 @@ void Node::insert_before(NonnullRefPtr<Node> node, RefPtr<Node> child, bool supp
|
|||
// https://dom.spec.whatwg.org/#concept-node-pre-insert
|
||||
ExceptionOr<NonnullRefPtr<Node>> Node::pre_insert(NonnullRefPtr<Node> node, RefPtr<Node> child)
|
||||
{
|
||||
// 1. Ensure pre-insertion validity of node into parent before child.
|
||||
TRY(ensure_pre_insertion_validity(node, child));
|
||||
|
||||
// 2. Let referenceChild be child.
|
||||
auto reference_child = child;
|
||||
|
||||
// 3. If referenceChild is node, then set referenceChild to node’s next sibling.
|
||||
if (reference_child == node)
|
||||
reference_child = node->next_sibling();
|
||||
|
||||
// 4. Insert node into parent before referenceChild.
|
||||
insert_before(node, reference_child);
|
||||
|
||||
// 5. Return node.
|
||||
return node;
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-node-removechild
|
||||
ExceptionOr<NonnullRefPtr<Node>> Node::remove_child(NonnullRefPtr<Node> child)
|
||||
{
|
||||
// The removeChild(child) method steps are to return the result of pre-removing child from this.
|
||||
return pre_remove(child);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-node-pre-remove
|
||||
ExceptionOr<NonnullRefPtr<Node>> Node::pre_remove(NonnullRefPtr<Node> child)
|
||||
{
|
||||
// 1. If child’s parent is not parent, then throw a "NotFoundError" DOMException.
|
||||
if (child->parent() != this)
|
||||
return DOM::NotFoundError::create("Child does not belong to this node");
|
||||
|
||||
// 2. Remove child.
|
||||
child->remove();
|
||||
|
||||
// 3. Return child.
|
||||
return child;
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-node-append
|
||||
ExceptionOr<NonnullRefPtr<Node>> Node::append_child(NonnullRefPtr<Node> node)
|
||||
{
|
||||
// To append a node to a parent, pre-insert node into parent before null.
|
||||
return pre_insert(node, nullptr);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-node-remove
|
||||
void Node::remove(bool suppress_observers)
|
||||
{
|
||||
// 1. Let parent be node’s parent
|
||||
auto* parent = TreeNode<Node>::parent();
|
||||
|
||||
// 2. Assert: parent is non-null.
|
||||
VERIFY(parent);
|
||||
|
||||
// 3. Let index be node’s index.
|
||||
|
@ -441,45 +513,56 @@ void Node::remove(bool suppress_observers)
|
|||
range->set_end(*range->end_container(), range->end_offset() - 1);
|
||||
}
|
||||
|
||||
// For each NodeIterator object iterator whose root’s node document is node’s node document, run the NodeIterator pre-removing steps given node and iterator.
|
||||
// 8. For each NodeIterator object iterator whose root’s node document is node’s node document, run the NodeIterator pre-removing steps given node and iterator.
|
||||
document().for_each_node_iterator([&](NodeIterator& node_iterator) {
|
||||
node_iterator.run_pre_removing_steps(*this);
|
||||
});
|
||||
|
||||
// FIXME: Let oldPreviousSibling be node’s previous sibling. (Currently unused so not included)
|
||||
// FIXME: Let oldNextSibling be node’s next sibling. (Currently unused so not included)
|
||||
// 9. FIXME: Let oldPreviousSibling be node’s previous sibling. (Currently unused so not included)
|
||||
// 10. FIXME: Let oldNextSibling be node’s next sibling. (Currently unused so not included)
|
||||
|
||||
// 11. Remove node from its parent’s children.
|
||||
parent->TreeNode::remove_child(*this);
|
||||
|
||||
// FIXME: If node is assigned, then run assign slottables for node’s assigned slot.
|
||||
// 12. FIXME: If node is assigned, then run assign slottables for node’s assigned slot.
|
||||
|
||||
// FIXME: 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.
|
||||
// 13. FIXME: 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: If node has an inclusive descendant that is a slot, then:
|
||||
// Run assign slottables for a tree with parent’s root.
|
||||
// Run assign slottables for a tree with node.
|
||||
// 14. FIXME: If node has an inclusive descendant that is a slot, then:
|
||||
// 14.1 Run assign slottables for a tree with parent’s root.
|
||||
// 14.2 Run assign slottables for a tree with node.
|
||||
|
||||
// 15. Run the removing steps with node and parent.
|
||||
removed_from(parent);
|
||||
|
||||
// FIXME: Let isParentConnected be parent’s connected. (Currently unused so not included)
|
||||
// 16. FIXME: Let isParentConnected be parent’s connected. (Currently unused so not included)
|
||||
|
||||
// FIXME: If node is custom and isParentConnected is true, then enqueue a custom element callback reaction with node,
|
||||
// 17. FIXME: 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.
|
||||
|
||||
// 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) {
|
||||
// 18.1 Run the removing steps with descendant
|
||||
descendant.removed_from(nullptr);
|
||||
|
||||
// FIXME: If descendant is custom and isParentConnected is true, then enqueue a custom element callback reaction with descendant,
|
||||
// 18.2 FIXME: 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.
|
||||
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
// 19. FIXME: For each inclusive ancestor inclusiveAncestor of parent, and then for each registered of inclusiveAncestor’s registered observer list,
|
||||
// if registered’s options["subtree"] is true, then append a new transient registered observer
|
||||
// whose observer is registered’s observer, options is registered’s options, and source is registered to node’s registered observer list.
|
||||
|
||||
// 20. If suppress observers flag is unset, then queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
|
||||
if (!suppress_observers) {
|
||||
// FIXME: queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
|
||||
}
|
||||
|
||||
// 21. Run the children changed steps for parent.
|
||||
parent->children_changed();
|
||||
|
||||
document().invalidate_style();
|
||||
|
@ -488,126 +571,181 @@ void Node::remove(bool suppress_observers)
|
|||
// https://dom.spec.whatwg.org/#concept-node-replace
|
||||
ExceptionOr<NonnullRefPtr<Node>> Node::replace_child(NonnullRefPtr<Node> node, NonnullRefPtr<Node> child)
|
||||
{
|
||||
// NOTE: This differs slightly from ensure_pre_insertion_validity.
|
||||
// If parent is not a Document, DocumentFragment, or Element node, then throw a "HierarchyRequestError" DOMException.
|
||||
if (!is<Document>(this) && !is<DocumentFragment>(this) && !is<Element>(this))
|
||||
return DOM::HierarchyRequestError::create("Can only insert into a document, document fragment or element");
|
||||
|
||||
// 2. If node is a host-including inclusive ancestor of parent, then throw a "HierarchyRequestError" DOMException.
|
||||
if (node->is_host_including_inclusive_ancestor_of(*this))
|
||||
return DOM::HierarchyRequestError::create("New node is an ancestor of this node");
|
||||
|
||||
// 3. If child’s parent is not parent, then throw a "NotFoundError" DOMException.
|
||||
if (child->parent() != this)
|
||||
return DOM::NotFoundError::create("This node is not the parent of the given child");
|
||||
|
||||
// FIXME: All the following "Invalid node type for insertion" messages could be more descriptive.
|
||||
|
||||
// 4. If node is not a DocumentFragment, DocumentType, Element, or CharacterData node, then throw a "HierarchyRequestError" DOMException.
|
||||
if (!is<DocumentFragment>(*node) && !is<DocumentType>(*node) && !is<Element>(*node) && !is<Text>(*node) && !is<Comment>(*node) && !is<ProcessingInstruction>(*node))
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
|
||||
// 5. If either node is a Text node and parent is a document, or node is a doctype and parent is not a document, then throw a "HierarchyRequestError" DOMException.
|
||||
if ((is<Text>(*node) && is<Document>(this)) || (is<DocumentType>(*node) && !is<Document>(this)))
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
|
||||
// If parent is a document, and any of the statements below, switched on the interface node implements, are true, then throw a "HierarchyRequestError" DOMException.
|
||||
if (is<Document>(this)) {
|
||||
// DocumentFragment
|
||||
if (is<DocumentFragment>(*node)) {
|
||||
// If node has more than one element child or has a Text node child.
|
||||
// Otherwise, if node has one element child and either parent has an element child that is not child or a doctype is following child.
|
||||
auto node_element_child_count = verify_cast<DocumentFragment>(*node).child_element_count();
|
||||
if ((node_element_child_count > 1 || node->has_child_of_type<Text>())
|
||||
|| (node_element_child_count == 1 && (first_child_of_type<Element>() != child || child->has_following_node_of_type_in_tree_order<DocumentType>()))) {
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
}
|
||||
} else if (is<Element>(*node)) {
|
||||
// Element
|
||||
// parent has an element child that is not child or a doctype is following child.
|
||||
if (first_child_of_type<Element>() != child || child->has_following_node_of_type_in_tree_order<DocumentType>())
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
} else if (is<DocumentType>(*node)) {
|
||||
// DocumentType
|
||||
// parent has a doctype child that is not child, or an element is preceding child.
|
||||
if (first_child_of_type<DocumentType>() != node || child->has_preceding_node_of_type_in_tree_order<Element>())
|
||||
return DOM::HierarchyRequestError::create("Invalid node type for insertion");
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Let referenceChild be child’s next sibling.
|
||||
auto reference_child = child->next_sibling();
|
||||
|
||||
// 8. If referenceChild is node, then set referenceChild to node’s next sibling.
|
||||
if (reference_child == node)
|
||||
reference_child = node->next_sibling();
|
||||
|
||||
// FIXME: Let previousSibling be child’s previous sibling. (Currently unused so not included)
|
||||
// FIXME: Let removedNodes be the empty set. (Currently unused so not included)
|
||||
// 9. FIXME: Let previousSibling be child’s previous sibling. (Currently unused so not included)
|
||||
// 10. FIXME: Let removedNodes be the empty set. (Currently unused so not included)
|
||||
|
||||
// 11. If child’s parent is non-null, then:
|
||||
// NOTE: The above can only be false if child is node.
|
||||
if (child->parent()) {
|
||||
// FIXME: Set removedNodes to « child ».
|
||||
// 11.1 FIXME: Set removedNodes to « child ».
|
||||
|
||||
// 11.2 Remove child with the suppress observers flag set.
|
||||
child->remove(true);
|
||||
}
|
||||
|
||||
// FIXME: Let nodes be node’s children if node is a DocumentFragment node; otherwise « node ». (Currently unused so not included)
|
||||
// 12. FIXME: Let nodes be node’s children if node is a DocumentFragment node; otherwise « node ». (Currently unused so not included)
|
||||
|
||||
// 13. Insert node into parent before referenceChild with the suppress observers flag set.
|
||||
insert_before(node, reference_child, true);
|
||||
|
||||
// FIXME: Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild.
|
||||
// 14. FIXME: Queue a tree mutation record for parent with nodes, removedNodes, previousSibling, and referenceChild.
|
||||
|
||||
// 15. Return child.
|
||||
return child;
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-node-clone
|
||||
NonnullRefPtr<Node> Node::clone_node(Document* document, bool clone_children)
|
||||
{
|
||||
// 1. If document is not given, let document be node’s node document.
|
||||
if (!document)
|
||||
document = m_document;
|
||||
RefPtr<Node> copy;
|
||||
|
||||
// 2. If node is an element, then:
|
||||
if (is<Element>(this)) {
|
||||
// 2.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 */);
|
||||
|
||||
// 2.2 For each attribute in node’s attribute list:
|
||||
element.for_each_attribute([&](auto& name, auto& value) {
|
||||
// 2.2.1 Let copyAttribute be a clone of attribute.
|
||||
// 2.2.2 Append copyAttribute to copy.
|
||||
element_copy->set_attribute(name, value);
|
||||
});
|
||||
copy = move(element_copy);
|
||||
|
||||
} else if (is<Document>(this)) {
|
||||
// 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:
|
||||
// Document
|
||||
auto document_ = verify_cast<Document>(this);
|
||||
auto document_copy = Document::create(document_->url());
|
||||
|
||||
// Set copy’s encoding, content type, URL, origin, type, and mode to those of node.
|
||||
document_copy->set_encoding(document_->encoding());
|
||||
document_copy->set_content_type(document_->content_type());
|
||||
document_copy->set_url(document_->url());
|
||||
document_copy->set_origin(document_->origin());
|
||||
document_copy->set_quirks_mode(document_->mode());
|
||||
// FIXME: Set type ("xml" or "html")
|
||||
document_copy->set_quirks_mode(document_->mode());
|
||||
copy = move(document_copy);
|
||||
} else if (is<DocumentType>(this)) {
|
||||
// DocumentType
|
||||
auto document_type = verify_cast<DocumentType>(this);
|
||||
auto document_type_copy = adopt_ref(*new DocumentType(*document));
|
||||
|
||||
// Set copy’s name, public ID, and system ID to those of node.
|
||||
document_type_copy->set_name(document_type->name());
|
||||
document_type_copy->set_public_id(document_type->public_id());
|
||||
document_type_copy->set_system_id(document_type->system_id());
|
||||
copy = move(document_type_copy);
|
||||
} else if (is<Attribute>(this)) {
|
||||
// FIXME:
|
||||
// Attr
|
||||
// Set copy’s namespace, namespace prefix, local name, and value to those of node.
|
||||
dbgln("clone_node() not implemented for Attribute");
|
||||
} else if (is<Text>(this)) {
|
||||
// Text
|
||||
auto text = verify_cast<Text>(this);
|
||||
|
||||
// Set copy’s data to that of node.
|
||||
auto text_copy = adopt_ref(*new Text(*document, text->data()));
|
||||
copy = move(text_copy);
|
||||
} else if (is<Comment>(this)) {
|
||||
// Comment
|
||||
auto comment = verify_cast<Comment>(this);
|
||||
|
||||
// Set copy’s data to that of node.
|
||||
auto comment_copy = adopt_ref(*new Comment(*document, comment->data()));
|
||||
copy = move(comment_copy);
|
||||
} else if (is<ProcessingInstruction>(this)) {
|
||||
// ProcessingInstruction
|
||||
auto processing_instruction = verify_cast<ProcessingInstruction>(this);
|
||||
|
||||
// Set copy’s target and data to those of node.
|
||||
auto processing_instruction_copy = adopt_ref(*new ProcessingInstruction(*document, processing_instruction->data(), processing_instruction->target()));
|
||||
copy = move(processing_instruction_copy);
|
||||
} else if (is<DocumentFragment>(this)) {
|
||||
auto document_fragment_copy = adopt_ref(*new DocumentFragment(*document));
|
||||
copy = move(document_fragment_copy);
|
||||
} else {
|
||||
dbgln("clone_node() not implemented for NodeType {}", (u16)m_type);
|
||||
TODO();
|
||||
}
|
||||
// Otherwise, Do nothing.
|
||||
|
||||
// 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.
|
||||
|
||||
// 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.
|
||||
cloned(*copy, clone_children);
|
||||
|
||||
// 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.
|
||||
if (clone_children) {
|
||||
for_each_child([&](auto& child) {
|
||||
copy->append_child(child.clone_node(document, true));
|
||||
});
|
||||
}
|
||||
|
||||
// 7. Return copy.
|
||||
return copy.release_nonnull();
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-node-clonenode
|
||||
ExceptionOr<NonnullRefPtr<Node>> Node::clone_node_binding(bool deep)
|
||||
{
|
||||
// 1. If this is a shadow root, then throw a "NotSupportedError" DOMException.
|
||||
if (is<ShadowRoot>(*this))
|
||||
return NotSupportedError::create("Cannot clone shadow root");
|
||||
|
||||
// 2. Return a clone of this, with the clone children flag set if deep is true.
|
||||
return clone_node(nullptr, deep);
|
||||
}
|
||||
|
||||
|
@ -728,35 +866,68 @@ u16 Node::compare_document_position(RefPtr<Node> other)
|
|||
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32,
|
||||
};
|
||||
|
||||
// 1. If this is other, then return zero.
|
||||
if (this == other)
|
||||
return DOCUMENT_POSITION_EQUAL;
|
||||
|
||||
// 2. Let node1 be other and node2 be this.
|
||||
Node* node1 = other.ptr();
|
||||
Node* node2 = this;
|
||||
|
||||
// FIXME: Once LibWeb supports attribute nodes fix to follow the specification.
|
||||
VERIFY(node1->type() != NodeType::ATTRIBUTE_NODE && node2->type() != NodeType::ATTRIBUTE_NODE);
|
||||
// 3. Let attr1 and attr2 be null.
|
||||
Attribute* attr1;
|
||||
Attribute* attr2;
|
||||
|
||||
// 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’s element.
|
||||
if (is<Attribute>(node1)) {
|
||||
attr1 = verify_cast<Attribute>(node1);
|
||||
node1 = const_cast<Element*>(attr1->owner_element());
|
||||
}
|
||||
|
||||
// 5. If node2 is an attribute, then:
|
||||
if (is<Attribute>(node2)) {
|
||||
// 5.1 Set attr2 to node2 and node2 to attr2’s element.
|
||||
attr2 = verify_cast<Attribute>(node2);
|
||||
node2 = const_cast<Element*>(attr2->owner_element());
|
||||
|
||||
// 5.2 If attr1 and node1 are non-null, and node2 is node1, then:
|
||||
if (attr1 && node1 && node2 == node1) {
|
||||
// 5.2.1 FIXME: For each attr in node2’s attribute list:
|
||||
// 5.2.1.1 FIXME: If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
|
||||
// 5.2.1.2 FIXME: If attr equals attr2, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_FOLLOWING.
|
||||
}
|
||||
}
|
||||
|
||||
// 6. If node1 or node2 is null, or node1’s root is not node2’s root, then return the result of adding
|
||||
// DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either DOCUMENT_POSITION_PRECEDING or DOCUMENT_POSITION_FOLLOWING, with the constraint that this is to be consistent, together.
|
||||
if ((node1 == nullptr || node2 == nullptr) || (&node1->root() != &node2->root()))
|
||||
return DOCUMENT_POSITION_DISCONNECTED | DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | (node1 > node2 ? DOCUMENT_POSITION_PRECEDING : DOCUMENT_POSITION_FOLLOWING);
|
||||
|
||||
if (node1->is_ancestor_of(*node2))
|
||||
// 7. If node1 is an ancestor of node2 and attr1 is null, or node1 is node2 and attr2 is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINS to DOCUMENT_POSITION_PRECEDING.
|
||||
if ((node1->is_ancestor_of(*node2) && !attr1) || (node1 == node2 && attr2))
|
||||
return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING;
|
||||
|
||||
if (node2->is_ancestor_of(*node1))
|
||||
// 8. If node1 is a descendant of node2 and attr2 is null, or node1 is node2 and attr1 is non-null, then return the result of adding DOCUMENT_POSITION_CONTAINED_BY to DOCUMENT_POSITION_FOLLOWING.
|
||||
if ((node2->is_ancestor_of(*node1) && !attr2) || (node1 == node2 && attr1))
|
||||
return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING;
|
||||
|
||||
// 9. If node1 is preceding node2, then return DOCUMENT_POSITION_PRECEDING.
|
||||
if (node1->is_before(*node2))
|
||||
return DOCUMENT_POSITION_PRECEDING;
|
||||
else
|
||||
return DOCUMENT_POSITION_FOLLOWING;
|
||||
|
||||
// 10. Return DOCUMENT_POSITION_FOLLOWING.
|
||||
return DOCUMENT_POSITION_FOLLOWING;
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor
|
||||
bool Node::is_host_including_inclusive_ancestor_of(Node const& other) const
|
||||
{
|
||||
// An object A is a host-including inclusive ancestor of an object B,
|
||||
// if either A is an inclusive ancestor of B,
|
||||
if (is_inclusive_ancestor_of(other))
|
||||
return true;
|
||||
|
||||
// or if B’s root has a non-null host and A is a host-including inclusive ancestor of B’s root’s host
|
||||
if (is<DocumentFragment>(other.root())
|
||||
&& static_cast<DocumentFragment const&>(other.root()).host()
|
||||
&& is_inclusive_ancestor_of(*static_cast<DocumentFragment const&>(other.root()).host())) {
|
||||
|
@ -768,6 +939,7 @@ bool Node::is_host_including_inclusive_ancestor_of(Node const& other) const
|
|||
// https://dom.spec.whatwg.org/#dom-node-ownerdocument
|
||||
RefPtr<Document> Node::owner_document() const
|
||||
{
|
||||
// The ownerDocument getter steps are to return null, if this is a document; otherwise this’s node document.
|
||||
if (is_document())
|
||||
return nullptr;
|
||||
return m_document;
|
||||
|
@ -867,20 +1039,24 @@ bool Node::is_scripting_disabled() const
|
|||
// https://dom.spec.whatwg.org/#dom-node-contains
|
||||
bool Node::contains(RefPtr<Node> other) const
|
||||
{
|
||||
// The contains(other) method steps are to return true if other is an inclusive descendant of this; otherwise false (including when other is null).
|
||||
return other && other->is_inclusive_descendant_of(*this);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-shadow-including-descendant
|
||||
bool Node::is_shadow_including_descendant_of(Node const& other) const
|
||||
{
|
||||
// An object A is a shadow-including descendant of an object B,
|
||||
// if A is a descendant of B,
|
||||
if (is_descendant_of(other))
|
||||
return true;
|
||||
|
||||
// or A’s root is a shadow root
|
||||
if (!is<ShadowRoot>(root()))
|
||||
return false;
|
||||
|
||||
// and A’s root’s host is a shadow-including inclusive descendant of B.
|
||||
auto& shadow_root = verify_cast<ShadowRoot>(root());
|
||||
|
||||
// NOTE: While host is nullable because of inheriting from DocumentFragment, shadow roots always have a host.
|
||||
return shadow_root.host()->is_shadow_including_inclusive_descendant_of(other);
|
||||
}
|
||||
|
@ -888,63 +1064,72 @@ bool Node::is_shadow_including_descendant_of(Node const& other) const
|
|||
// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant
|
||||
bool Node::is_shadow_including_inclusive_descendant_of(Node const& other) const
|
||||
{
|
||||
// A shadow-including inclusive descendant is an object or one of its shadow-including descendants.
|
||||
return &other == this || is_shadow_including_descendant_of(other);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-shadow-including-ancestor
|
||||
bool Node::is_shadow_including_ancestor_of(Node const& other) const
|
||||
{
|
||||
// An object A is a shadow-including ancestor of an object B, if and only if B is a shadow-including descendant of A.
|
||||
return other.is_shadow_including_descendant_of(*this);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor
|
||||
bool Node::is_shadow_including_inclusive_ancestor_of(Node const& other) const
|
||||
{
|
||||
// A shadow-including inclusive ancestor is an object or one of its shadow-including ancestors.
|
||||
return other.is_shadow_including_inclusive_descendant_of(*this);
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-node-replace-all
|
||||
void Node::replace_all(RefPtr<Node> node)
|
||||
{
|
||||
// FIXME: Let removedNodes be parent’s children. (Current unused so not included)
|
||||
// FIXME: Let addedNodes be the empty set. (Currently unused so not included)
|
||||
// FIXME: If node is a DocumentFragment node, then set addedNodes to node’s children.
|
||||
// FIXME: Otherwise, if node is non-null, set addedNodes to « node ».
|
||||
// 1. FIXME: Let removedNodes be parent’s children. (Current unused so not included)
|
||||
// 2. FIXME: Let addedNodes be the empty set. (Currently unused so not included)
|
||||
// 3. FIXME: If node is a DocumentFragment node, then set addedNodes to node’s children.
|
||||
// 4. FIXME: Otherwise, if node is non-null, set addedNodes to « node ».
|
||||
|
||||
// 5. Remove all parent’s children, in tree order, with the suppress observers flag set.
|
||||
remove_all_children(true);
|
||||
|
||||
// 6. If node is non-null, then insert node into parent before null with the suppress observers flag set.
|
||||
if (node)
|
||||
insert_before(*node, nullptr, true);
|
||||
|
||||
// FIXME: If either addedNodes or removedNodes is not empty, then queue a tree mutation record for parent with addedNodes, removedNodes, null, and null.
|
||||
// 7. FIXME: If either addedNodes or removedNodes is not empty, then queue a tree mutation record for parent with addedNodes, removedNodes, null, and null.
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#string-replace-all
|
||||
void Node::string_replace_all(String const& string)
|
||||
{
|
||||
// 1. Let node be null.
|
||||
RefPtr<Node> node;
|
||||
|
||||
// 2. If string is not the empty string, then set node to a new Text node whose data is string and node document is parent’s node document.
|
||||
if (!string.is_empty())
|
||||
node = make_ref_counted<Text>(document(), string);
|
||||
|
||||
// 3. Replace all with node within parent.
|
||||
replace_all(node);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/DOM-Parsing/#dfn-fragment-serializing-algorithm
|
||||
String Node::serialize_fragment(/* FIXME: Requires well-formed flag */) const
|
||||
{
|
||||
// FIXME: Let context document be the value of node's node document.
|
||||
// 1. FIXME: Let context document be the value of node's node document.
|
||||
|
||||
// FIXME: If context document is an HTML document, return an HTML serialization of node.
|
||||
// 2. FIXME: If context document is an HTML document, return an HTML serialization of node.
|
||||
// (We currently always do this)
|
||||
return HTML::HTMLParser::serialize_html_fragment(*this);
|
||||
|
||||
// FIXME: Otherwise, context document is an XML document; return an XML serialization of node passing the flag require well-formed.
|
||||
// 3. FIXME: Otherwise, context document is an XML document; return an XML serialization of node passing the flag require well-formed.
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#dom-node-issamenode
|
||||
bool Node::is_same_node(Node const* other_node) const
|
||||
{
|
||||
// The isSameNode(otherNode) method steps are to return true if otherNode is this; otherwise false.
|
||||
return this == other_node;
|
||||
}
|
||||
|
||||
|
@ -1042,10 +1227,11 @@ bool Node::in_a_document_tree() const
|
|||
// https://dom.spec.whatwg.org/#dom-node-getrootnode
|
||||
NonnullRefPtr<Node> Node::get_root_node(GetRootNodeOptions const& options)
|
||||
{
|
||||
// The getRootNode(options) method steps are to return this’s shadow-including root if options["composed"] is true; otherwise this’s root.
|
||||
// The getRootNode(options) method steps are to return this’s shadow-including root if options["composed"] is true;
|
||||
if (options.composed)
|
||||
return shadow_including_root();
|
||||
|
||||
// otherwise this’s root.
|
||||
return root();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue