2020-01-18 09:38:21 +01:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
2019-09-29 16:22:15 +02:00
# include <AK/StringBuilder.h>
2020-03-18 15:22:31 +01:00
# include <LibJS/AST.h>
# include <LibJS/Runtime/Function.h>
2020-03-21 18:17:18 +01:00
# include <LibWeb/Bindings/EventWrapper.h>
2020-03-18 15:22:31 +01:00
# include <LibWeb/Bindings/NodeWrapper.h>
2020-06-20 22:09:38 +02:00
# include <LibWeb/Bindings/NodeWrapperFactory.h>
2021-04-06 19:34:49 +01:00
# include <LibWeb/DOM/Comment.h>
# include <LibWeb/DOM/DocumentType.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Element.h>
2020-03-21 18:17:18 +01:00
# include <LibWeb/DOM/Event.h>
2020-09-06 14:28:41 +02:00
# include <LibWeb/DOM/EventDispatcher.h>
2020-03-18 15:22:31 +01:00
# include <LibWeb/DOM/EventListener.h>
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Node.h>
2021-04-06 19:34:49 +01:00
# include <LibWeb/DOM/ProcessingInstruction.h>
2020-11-21 18:32:39 +00:00
# include <LibWeb/DOM/ShadowRoot.h>
2020-08-25 22:18:32 +04:30
# include <LibWeb/HTML/HTMLAnchorElement.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/InitialContainingBlockBox.h>
# include <LibWeb/Layout/Node.h>
# include <LibWeb/Layout/TextNode.h>
2019-06-15 18:55:47 +02:00
2020-07-26 19:37:56 +02:00
namespace Web : : DOM {
2020-03-07 10:27:02 +01:00
2019-09-29 11:43:07 +02:00
Node : : Node ( Document & document , NodeType type )
2020-09-20 19:22:44 +02:00
: EventTarget ( static_cast < Bindings : : ScriptExecutionContext & > ( document ) )
, m_document ( & document )
2019-09-29 11:43:07 +02:00
, m_type ( type )
2019-06-15 18:55:47 +02:00
{
2020-10-22 23:38:14 +02:00
if ( ! is_document ( ) )
m_document - > ref_from_node ( { } ) ;
2019-06-15 18:55:47 +02:00
}
Node : : ~ Node ( )
{
2021-02-23 20:42:32 +01:00
VERIFY ( m_deletion_has_begun ) ;
2020-03-25 18:48:32 +01:00
if ( layout_node ( ) & & layout_node ( ) - > parent ( ) )
layout_node ( ) - > parent ( ) - > remove_child ( * layout_node ( ) ) ;
2020-10-22 23:38:14 +02:00
if ( ! is_document ( ) )
m_document - > unref_from_node ( { } ) ;
2019-06-15 18:55:47 +02:00
}
2019-09-25 12:17:29 +03:00
2020-07-28 18:20:36 +02:00
const HTML : : HTMLAnchorElement * Node : : enclosing_link_element ( ) const
2019-09-29 11:59:38 +02:00
{
2019-10-19 21:21:29 +02:00
for ( auto * node = this ; node ; node = node - > parent ( ) ) {
2020-07-28 18:20:36 +02:00
if ( is < HTML : : HTMLAnchorElement > ( * node ) & & downcast < HTML : : HTMLAnchorElement > ( * node ) . has_attribute ( HTML : : AttributeNames : : href ) )
return downcast < HTML : : HTMLAnchorElement > ( node ) ;
2019-10-19 21:21:29 +02:00
}
return nullptr ;
2019-09-29 11:59:38 +02:00
}
2019-09-29 12:24:36 +02:00
2020-07-28 18:20:36 +02:00
const HTML : : HTMLElement * Node : : enclosing_html_element ( ) const
2019-09-29 12:24:36 +02:00
{
2020-07-28 18:20:36 +02:00
return first_ancestor_of_type < HTML : : HTMLElement > ( ) ;
2019-09-29 12:24:36 +02:00
}
2019-09-29 16:22:15 +02:00
2021-03-30 12:06:06 -04:00
const HTML : : HTMLElement * Node : : enclosing_html_element_with_attribute ( const FlyString & attribute ) const
{
for ( auto * node = this ; node ; node = node - > parent ( ) ) {
if ( is < HTML : : HTMLElement > ( * node ) & & downcast < HTML : : HTMLElement > ( * node ) . has_attribute ( attribute ) )
return downcast < HTML : : HTMLElement > ( node ) ;
}
return nullptr ;
}
2019-09-29 16:22:15 +02:00
String Node : : text_content ( ) const
{
StringBuilder builder ;
for ( auto * child = first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
2020-08-17 21:19:10 -04:00
builder . append ( child - > text_content ( ) ) ;
2019-09-29 16:22:15 +02:00
}
return builder . to_string ( ) ;
}
2019-10-06 19:54:50 +02:00
2020-08-17 12:36:00 -04:00
void Node : : set_text_content ( const String & content )
{
if ( is_text ( ) ) {
downcast < Text > ( this ) - > set_data ( content ) ;
} else {
remove_all_children ( ) ;
append_child ( document ( ) . create_text_node ( content ) ) ;
}
set_needs_style_update ( true ) ;
document ( ) . invalidate_layout ( ) ;
}
2021-01-06 14:27:40 +01:00
RefPtr < Layout : : Node > Node : : create_layout_node ( )
2019-10-09 20:17:01 +02:00
{
return nullptr ;
}
2019-10-14 18:32:02 +02:00
void Node : : invalidate_style ( )
{
2021-04-06 18:38:10 +01:00
for_each_in_inclusive_subtree_of_type < Element > ( [ & ] ( auto & element ) {
2019-12-18 21:34:03 +01:00
element . set_needs_style_update ( true ) ;
2019-10-21 12:01:30 +02:00
return IterationDecision : : Continue ;
2019-10-19 18:57:02 +02:00
} ) ;
document ( ) . schedule_style_update ( ) ;
2019-10-14 18:32:02 +02:00
}
2019-10-19 21:21:29 +02:00
bool Node : : is_link ( ) const
{
2020-06-13 00:22:41 +02:00
return enclosing_link_element ( ) ;
2019-10-19 21:21:29 +02:00
}
2020-03-07 10:27:02 +01:00
2020-11-21 18:32:39 +00:00
bool Node : : dispatch_event ( NonnullRefPtr < Event > event )
2020-03-18 15:22:31 +01:00
{
2020-11-21 18:32:39 +00:00
return EventDispatcher : : dispatch ( * this , event ) ;
2020-03-18 15:22:31 +01:00
}
2020-05-24 21:59:24 +02:00
String Node : : child_text_content ( ) const
{
if ( ! is < ParentNode > ( * this ) )
return String : : empty ( ) ;
StringBuilder builder ;
2020-07-26 17:16:18 +02:00
downcast < ParentNode > ( * this ) . for_each_child ( [ & ] ( auto & child ) {
2020-05-24 21:59:24 +02:00
if ( is < Text > ( child ) )
2020-07-26 17:16:18 +02:00
builder . append ( downcast < Text > ( child ) . text_content ( ) ) ;
2020-05-24 21:59:24 +02:00
} ) ;
return builder . build ( ) ;
}
2020-11-21 18:32:39 +00:00
Node * Node : : root ( )
2020-05-24 21:59:24 +02:00
{
2020-11-21 18:32:39 +00:00
Node * root = this ;
2020-05-24 21:59:24 +02:00
while ( root - > parent ( ) )
root = root - > parent ( ) ;
return root ;
}
2020-11-21 18:32:39 +00:00
Node * Node : : shadow_including_root ( )
{
auto node_root = root ( ) ;
if ( is < ShadowRoot > ( node_root ) )
return downcast < ShadowRoot > ( node_root ) - > host ( ) - > shadow_including_root ( ) ;
return node_root ;
}
2020-05-24 21:59:24 +02:00
bool Node : : is_connected ( ) const
{
2020-11-21 18:32:39 +00:00
return shadow_including_root ( ) & & shadow_including_root ( ) - > is_document ( ) ;
2020-05-24 21:59:24 +02:00
}
2020-06-20 22:26:54 +02:00
Element * Node : : parent_element ( )
{
if ( ! parent ( ) | | ! is < Element > ( parent ( ) ) )
return nullptr ;
2020-07-26 17:16:18 +02:00
return downcast < Element > ( parent ( ) ) ;
2020-06-20 22:26:54 +02:00
}
const Element * Node : : parent_element ( ) const
{
if ( ! parent ( ) | | ! is < Element > ( parent ( ) ) )
return nullptr ;
2020-07-26 17:16:18 +02:00
return downcast < Element > ( parent ( ) ) ;
2020-06-20 22:26:54 +02:00
}
2021-04-06 19:34:49 +01:00
// 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
2020-06-21 01:00:58 +02:00
{
2021-04-06 19:34:49 +01:00
if ( ! is < Document > ( this ) & & ! is < DocumentFragment > ( this ) & & ! is < Element > ( this ) )
return DOM : : HierarchyRequestError : : create ( " Can only insert into a document, document fragment or element " ) ;
if ( node - > is_host_including_inclusive_ancestor_of ( * this ) )
return DOM : : HierarchyRequestError : : create ( " New node is an ancestor of this node " ) ;
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.
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 " ) ;
if ( ( is < Text > ( * node ) & & is < Document > ( this ) ) | | ( is < DocumentType > ( * node ) & & ! is < Document > ( this ) ) )
return DOM : : HierarchyRequestError : : create ( " Invalid node type for insertion " ) ;
if ( is < Document > ( this ) ) {
if ( is < DocumentFragment > ( * node ) ) {
auto node_element_child_count = node - > element_child_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 ( ) ) /* FIXME: or child is non-null and a doctype is following child. */ ) ) ) {
return DOM : : HierarchyRequestError : : create ( " Invalid node type for insertion " ) ;
}
} else if ( is < Element > ( * node ) ) {
if ( has_child_of_type < Element > ( ) | | is < DocumentType > ( child . ptr ( ) ) /* FIXME: or child is non-null and a doctype is following child. */ )
return DOM : : HierarchyRequestError : : create ( " Invalid node type for insertion " ) ;
} else if ( is < DocumentType > ( * node ) ) {
if ( has_child_of_type < DocumentType > ( ) /* FIXME: or child is non-null and an element is preceding child */ | | ( ! child & & has_child_of_type < Element > ( ) ) )
return DOM : : HierarchyRequestError : : create ( " Invalid node type for insertion " ) ;
}
}
return { } ;
2020-06-21 01:00:58 +02:00
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-node-insert
void Node : : insert_before ( NonnullRefPtr < Node > node , RefPtr < Node > child , bool suppress_observers )
2021-01-28 08:57:37 +01:00
{
2021-04-06 19:34:49 +01:00
NonnullRefPtrVector < Node > nodes ;
if ( is < DocumentFragment > ( * node ) )
nodes = downcast < DocumentFragment > ( * node ) . child_nodes ( ) ;
else
nodes . append ( node ) ;
auto count = nodes . size ( ) ;
if ( count = = 0 )
return ;
if ( is < DocumentFragment > ( * node ) ) {
node - > remove_all_children ( true ) ;
// FIXME: Queue a tree mutation record for node with « », nodes, null, and null.
}
if ( child ) {
// FIXME: For each live range whose start node is parent and start offset is greater than child’ s index, increase its start offset by count.
// FIXME: For each live range whose end node is parent and end offset is greater than child’ s index, increase its end offset by count.
}
// 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
document ( ) . adopt_node ( node_to_insert ) ;
if ( ! child )
TreeNode < Node > : : append_child ( node ) ;
else
TreeNode < Node > : : insert_before ( node , 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: This should be shadow-including.
node_to_insert . for_each_in_inclusive_subtree ( [ & ] ( Node & inclusive_descendant ) {
inclusive_descendant . inserted ( ) ;
if ( inclusive_descendant . is_connected ( ) ) {
// 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.
}
return IterationDecision : : Continue ;
} ) ;
}
if ( ! suppress_observers ) {
// FIXME: queue a tree mutation record for parent with nodes, « », previousSibling, and child.
}
children_changed ( ) ;
2021-01-28 08:57:37 +01:00
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-node-pre-insert
NonnullRefPtr < Node > Node : : pre_insert ( NonnullRefPtr < Node > node , RefPtr < Node > child )
2020-06-21 16:45:21 +02:00
{
2021-04-06 19:34:49 +01:00
auto validity_result = ensure_pre_insertion_validity ( node , child ) ;
if ( validity_result . is_exception ( ) ) {
dbgln ( " Node::pre_insert: ensure_pre_insertion_validity failed: {}. (FIXME: throw as exception, see issue #6075) " , validity_result . exception ( ) . message ( ) ) ;
return node ;
2020-06-21 16:45:21 +02:00
}
2021-04-06 19:34:49 +01:00
auto reference_child = child ;
if ( reference_child = = node )
reference_child = node - > next_sibling ( ) ;
insert_before ( node , reference_child ) ;
2020-06-21 16:45:21 +02:00
return node ;
}
2021-04-06 19:34:49 +01:00
// https://dom.spec.whatwg.org/#concept-node-pre-remove
NonnullRefPtr < Node > Node : : pre_remove ( NonnullRefPtr < Node > child )
{
if ( child - > parent ( ) ! = this ) {
dbgln ( " Node::pre_remove: Child doesn't belong to this node. (FIXME: throw NotFoundError DOMException, see issue #6075) " ) ;
return child ;
}
child - > remove ( ) ;
return child ;
}
// https://dom.spec.whatwg.org/#concept-node-append
NonnullRefPtr < Node > Node : : append_child ( NonnullRefPtr < Node > node )
{
return pre_insert ( node , nullptr ) ;
}
// https://dom.spec.whatwg.org/#concept-node-remove
void Node : : remove ( bool suppress_observers )
{
auto * parent = TreeNode < Node > : : parent ( ) ;
VERIFY ( parent ) ;
// FIXME: Let index be node’ s index. (Currently unused so not included)
// FIXME: For each live range whose start node is an inclusive descendant of node, set its start to (parent, index).
// FIXME: For each live range whose end node is an inclusive descendant of node, set its end to (parent, index).
// FIXME: For each live range whose start node is parent and start offset is greater than index, decrease its start offset by 1.
// FIXME: For each live range whose end node is parent and end offset is greater than index, decrease its end offset by 1.
// FIXME: 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.
// 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)
parent - > remove_child ( * this ) ;
// 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.
// 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.
removed_from ( parent ) ;
// 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,
// callback name "disconnectedCallback", and an empty argument list.
// FIXME: This should be shadow-including.
for_each_in_subtree ( [ & ] ( Node & descendant ) {
descendant . removed_from ( nullptr ) ;
// 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 ;
} ) ;
if ( ! suppress_observers ) {
// FIXME: queue a tree mutation record for parent with « », « node », oldPreviousSibling, and oldNextSibling.
}
parent - > children_changed ( ) ;
}
2020-06-25 23:42:08 +02:00
void Node : : set_document ( Badge < Document > , Document & document )
{
2020-10-22 23:37:17 +02:00
if ( m_document = = & document )
return ;
document . ref_from_node ( { } ) ;
m_document - > unref_from_node ( { } ) ;
2020-06-25 23:42:08 +02:00
m_document = & document ;
}
2020-08-02 16:05:59 +02:00
bool Node : : is_editable ( ) const
{
return parent ( ) & & parent ( ) - > is_editable ( ) ;
}
2021-01-18 12:15:02 +01:00
JS : : Object * Node : : create_wrapper ( JS : : GlobalObject & global_object )
2020-09-06 14:28:41 +02:00
{
return wrap ( global_object , * this ) ;
}
2020-10-11 21:52:59 +02:00
void Node : : removed_last_ref ( )
{
if ( is < Document > ( * this ) ) {
downcast < Document > ( * this ) . removed_last_ref ( ) ;
return ;
}
2020-10-22 23:38:14 +02:00
m_deletion_has_begun = true ;
2020-10-11 21:52:59 +02:00
delete this ;
}
2020-11-22 15:53:01 +01:00
void Node : : set_layout_node ( Badge < Layout : : Node > , Layout : : Node * layout_node ) const
2020-10-22 20:26:32 +02:00
{
if ( layout_node )
m_layout_node = layout_node - > make_weak_ptr ( ) ;
else
m_layout_node = nullptr ;
}
2020-11-21 18:32:39 +00:00
EventTarget * Node : : get_parent ( const Event & )
{
// FIXME: returns the node’ s assigned slot, if node is assigned, and node’ s parent otherwise.
return parent ( ) ;
}
2020-12-13 15:19:42 +01:00
void Node : : set_needs_style_update ( bool value )
{
if ( m_needs_style_update = = value )
return ;
m_needs_style_update = value ;
2020-12-14 12:04:30 +01:00
if ( m_needs_style_update ) {
2021-04-06 19:34:49 +01:00
for ( auto * ancestor = parent ( ) ; ancestor ; ancestor = ancestor - > parent ( ) ) {
//dbgln("{}", ancestor->node_name());
2020-12-14 12:04:30 +01:00
ancestor - > m_child_needs_style_update = true ;
2021-04-06 19:34:49 +01:00
}
2020-12-13 15:19:42 +01:00
document ( ) . schedule_style_update ( ) ;
2020-12-14 12:04:30 +01:00
}
}
2021-04-06 19:34:49 +01:00
void Node : : inserted ( )
2020-12-14 12:04:30 +01:00
{
set_needs_style_update ( true ) ;
2020-12-13 15:19:42 +01:00
}
2021-02-10 18:22:20 +01:00
ParentNode * Node : : parent_or_shadow_host ( )
{
if ( is < ShadowRoot > ( * this ) )
return downcast < ShadowRoot > ( * this ) . host ( ) ;
return downcast < ParentNode > ( parent ( ) ) ;
}
2021-03-06 17:06:25 +00:00
NonnullRefPtrVector < Node > Node : : child_nodes ( ) const
{
NonnullRefPtrVector < Node > nodes ;
for_each_child ( [ & ] ( auto & child ) {
nodes . append ( child ) ;
} ) ;
return nodes ;
}
2021-04-06 19:34:49 +01:00
void Node : : remove_all_children ( bool suppress_observers )
{
while ( RefPtr < Node > child = first_child ( ) )
child - > remove ( suppress_observers ) ;
}
// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor
bool Node : : is_host_including_inclusive_ancestor_of ( const Node & other ) const
{
return is_inclusive_ancestor_of ( other ) | | ( is < DocumentFragment > ( other . root ( ) ) & & downcast < DocumentFragment > ( other . root ( ) ) - > host ( ) & & is_inclusive_ancestor_of ( * downcast < DocumentFragment > ( other . root ( ) ) - > host ( ) . ptr ( ) ) ) ;
}
size_t Node : : element_child_count ( ) const
{
size_t count = 0 ;
for ( auto * child = first_child ( ) ; child ; child = child - > next_sibling ( ) ) {
if ( is < Element > ( child ) )
+ + count ;
}
return count ;
}
2020-03-07 10:27:02 +01:00
}