2020-01-18 09:38:21 +01:00
/*
2021-11-02 19:32:26 +01:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2021-08-24 17:04:30 +01:00
* Copyright ( c ) 2018 - 2020 , Adam Hodgen < ant1441 @ gmail . com >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2019-11-09 11:31:03 +01:00
# include "DOMTreeModel.h"
2021-08-24 17:04:30 +01:00
# include <AK/JsonObject.h>
2019-11-09 11:31:03 +01:00
# include <AK/StringBuilder.h>
2021-11-02 19:32:26 +01:00
# include <LibGUI/TreeView.h>
# include <LibGfx/Palette.h>
2019-11-09 11:31:03 +01:00
# include <ctype.h>
2022-04-30 11:13:33 +02:00
namespace WebView {
2020-03-07 10:27:02 +01:00
2022-09-25 12:11:02 +02:00
DOMTreeModel : : DOMTreeModel ( JsonObject dom_tree , GUI : : TreeView * tree_view )
2021-11-02 19:32:26 +01:00
: m_tree_view ( tree_view )
, m_dom_tree ( move ( dom_tree ) )
2019-11-09 11:31:03 +01:00
{
2022-09-25 12:11:02 +02:00
// FIXME: Get these from the outside somehow instead of hard-coding paths here.
2022-10-09 15:23:23 -06:00
# ifdef AK_OS_SERENITY
2022-07-11 17:32:29 +00:00
m_document_icon . set_bitmap_for_size ( 16 , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/filetype-html.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
m_element_icon . set_bitmap_for_size ( 16 , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/inspector-object.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
m_text_icon . set_bitmap_for_size ( 16 , Gfx : : Bitmap : : try_load_from_file ( " /res/icons/16x16/filetype-unknown.png " sv ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-09-25 12:11:02 +02:00
# endif
2021-08-24 17:04:30 +01:00
map_dom_nodes_to_parent ( nullptr , & m_dom_tree ) ;
2019-11-09 11:31:03 +01:00
}
2022-03-14 13:21:51 -06:00
DOMTreeModel : : ~ DOMTreeModel ( ) = default ;
2019-11-09 11:31:03 +01:00
2020-02-02 15:07:41 +01:00
GUI : : ModelIndex DOMTreeModel : : index ( int row , int column , const GUI : : ModelIndex & parent ) const
2019-11-09 11:31:03 +01:00
{
if ( ! parent . is_valid ( ) ) {
2021-08-24 17:04:30 +01:00
return create_index ( row , column , & m_dom_tree ) ;
2019-11-09 11:31:03 +01:00
}
2021-08-24 17:04:30 +01:00
auto const & parent_node = * static_cast < JsonObject const * > ( parent . internal_data ( ) ) ;
auto const * children = get_children ( parent_node ) ;
if ( ! children )
return create_index ( row , column , & m_dom_tree ) ;
auto const & child_node = children - > at ( row ) . as_object ( ) ;
return create_index ( row , column , & child_node ) ;
2019-11-09 11:31:03 +01:00
}
2020-02-02 15:07:41 +01:00
GUI : : ModelIndex DOMTreeModel : : parent_index ( const GUI : : ModelIndex & index ) const
2019-11-09 11:31:03 +01:00
{
2021-08-24 17:04:30 +01:00
// FIXME: Handle the template element (child elements are not stored in it, all of its children are in its document fragment "content")
// Probably in the JSON generation in Node.cpp?
2019-11-09 11:31:03 +01:00
if ( ! index . is_valid ( ) )
return { } ;
2021-08-24 17:04:30 +01:00
auto const & node = * static_cast < JsonObject const * > ( index . internal_data ( ) ) ;
auto const * parent_node = get_parent ( node ) ;
if ( ! parent_node )
return { } ;
2020-08-19 22:30:33 +01:00
2021-08-24 17:04:30 +01:00
// If the parent is the root document, we know it has index 0, 0
if ( parent_node = = & m_dom_tree ) {
return create_index ( 0 , 0 , parent_node ) ;
2019-11-09 11:31:03 +01:00
}
2021-08-24 17:04:30 +01:00
// Otherwise, we need to find the grandparent, to find the index of parent within that
auto const * grandparent_node = get_parent ( * parent_node ) ;
VERIFY ( grandparent_node ) ;
auto const * grandparent_children = get_children ( * grandparent_node ) ;
if ( ! grandparent_children )
return { } ;
for ( size_t grandparent_child_index = 0 ; grandparent_child_index < grandparent_children - > size ( ) ; + + grandparent_child_index ) {
auto const & child = grandparent_children - > at ( grandparent_child_index ) . as_object ( ) ;
if ( & child = = parent_node )
return create_index ( grandparent_child_index , 0 , parent_node ) ;
2019-11-09 11:31:03 +01:00
}
return { } ;
}
2020-02-02 15:07:41 +01:00
int DOMTreeModel : : row_count ( const GUI : : ModelIndex & index ) const
2019-11-09 11:31:03 +01:00
{
if ( ! index . is_valid ( ) )
return 1 ;
2021-08-24 17:04:30 +01:00
auto const & node = * static_cast < JsonObject const * > ( index . internal_data ( ) ) ;
auto const * children = get_children ( node ) ;
return children ? children - > size ( ) : 0 ;
2019-11-09 11:31:03 +01:00
}
2020-02-02 15:07:41 +01:00
int DOMTreeModel : : column_count ( const GUI : : ModelIndex & ) const
2019-11-09 11:31:03 +01:00
{
return 1 ;
}
2022-12-04 18:02:33 +00:00
static DeprecatedString with_whitespace_collapsed ( StringView string )
2019-11-09 11:31:03 +01:00
{
StringBuilder builder ;
2019-12-09 17:45:40 +01:00
for ( size_t i = 0 ; i < string . length ( ) ; + + i ) {
2019-11-09 11:31:03 +01:00
if ( isspace ( string [ i ] ) ) {
builder . append ( ' ' ) ;
while ( i < string . length ( ) ) {
if ( isspace ( string [ i ] ) ) {
+ + i ;
continue ;
}
builder . append ( string [ i ] ) ;
break ;
}
continue ;
}
builder . append ( string [ i ] ) ;
}
2022-12-06 01:12:49 +00:00
return builder . to_deprecated_string ( ) ;
2019-11-09 11:31:03 +01:00
}
2020-08-16 16:00:07 +02:00
GUI : : Variant DOMTreeModel : : data ( const GUI : : ModelIndex & index , GUI : : ModelRole role ) const
2019-11-09 11:31:03 +01:00
{
2021-08-24 17:04:30 +01:00
auto const & node = * static_cast < JsonObject const * > ( index . internal_data ( ) ) ;
2022-12-21 11:42:06 +00:00
auto node_name = node . get_deprecated ( " name " sv ) . as_string ( ) ;
auto type = node . get_deprecated ( " type " sv ) . as_string_or ( " unknown " sv ) ;
2021-08-24 17:04:30 +01:00
2022-09-25 12:11:02 +02:00
// FIXME: This FIXME can go away when we fix the one below.
2022-10-09 15:23:23 -06:00
# ifdef AK_OS_SERENITY
2021-11-02 19:32:26 +01:00
if ( role = = GUI : : ModelRole : : ForegroundColor ) {
// FIXME: Allow models to return a foreground color *role*.
// Then we won't need to have a GUI::TreeView& member anymore.
if ( type = = " comment " sv )
2022-09-25 12:11:02 +02:00
return m_tree_view - > palette ( ) . syntax_comment ( ) ;
2022-03-03 17:50:12 +00:00
if ( type = = " pseudo-element " sv )
2022-09-25 12:11:02 +02:00
return m_tree_view - > palette ( ) . syntax_type ( ) ;
2022-12-21 11:42:06 +00:00
if ( ! node . get_deprecated ( " visible " sv ) . to_bool ( true ) )
2022-09-25 12:11:02 +02:00
return m_tree_view - > palette ( ) . syntax_comment ( ) ;
2021-11-02 19:32:26 +01:00
return { } ;
}
2022-09-25 12:11:02 +02:00
# endif
2021-11-02 19:32:26 +01:00
2022-09-25 12:11:02 +02:00
// FIXME: This FIXME can go away when the icons are provided from the outside (see constructor).
2022-10-09 15:23:23 -06:00
# ifdef AK_OS_SERENITY
2020-08-16 16:00:07 +02:00
if ( role = = GUI : : ModelRole : : Icon ) {
2021-08-24 17:04:30 +01:00
if ( type = = " document " )
2019-11-09 11:58:20 +01:00
return m_document_icon ;
2021-08-24 17:04:30 +01:00
if ( type = = " element " )
2019-11-09 11:31:03 +01:00
return m_element_icon ;
// FIXME: More node type icons?
return m_text_icon ;
}
2022-09-25 12:11:02 +02:00
# endif
2020-08-16 16:00:07 +02:00
if ( role = = GUI : : ModelRole : : Display ) {
2021-08-24 17:04:30 +01:00
if ( type = = " text " )
2022-12-21 11:42:06 +00:00
return with_whitespace_collapsed ( node . get_deprecated ( " text " sv ) . as_string ( ) ) ;
2021-11-02 19:25:15 +01:00
if ( type = = " comment " sv )
2022-12-21 11:42:06 +00:00
return DeprecatedString : : formatted ( " <!--{}--> " , node . get_deprecated ( " data " sv ) . as_string ( ) ) ;
2021-08-24 17:04:30 +01:00
if ( type ! = " element " )
return node_name ;
2020-01-02 14:53:38 +01:00
StringBuilder builder ;
builder . append ( ' < ' ) ;
2021-08-24 17:04:30 +01:00
builder . append ( node_name . to_lowercase ( ) ) ;
2022-07-11 17:32:29 +00:00
if ( node . has ( " attributes " sv ) ) {
2022-12-21 11:42:06 +00:00
auto attributes = node . get_deprecated ( " attributes " sv ) . as_object ( ) ;
2021-08-24 17:04:30 +01:00
attributes . for_each_member ( [ & builder ] ( auto & name , JsonValue const & value ) {
builder . append ( ' ' ) ;
builder . append ( name ) ;
builder . append ( ' = ' ) ;
builder . append ( ' " ' ) ;
2022-12-06 01:12:49 +00:00
builder . append ( value . to_deprecated_string ( ) ) ;
2021-08-24 17:04:30 +01:00
builder . append ( ' " ' ) ;
} ) ;
}
2020-01-02 14:53:38 +01:00
builder . append ( ' > ' ) ;
2022-12-06 01:12:49 +00:00
return builder . to_deprecated_string ( ) ;
2019-11-09 11:31:03 +01:00
}
return { } ;
}
2021-08-24 17:04:30 +01:00
void DOMTreeModel : : map_dom_nodes_to_parent ( JsonObject const * parent , JsonObject const * node )
2021-08-17 16:33:07 +01:00
{
2021-08-24 17:04:30 +01:00
m_dom_node_to_parent_map . set ( node , parent ) ;
2022-12-21 11:42:06 +00:00
m_node_id_to_dom_node_map . set ( node - > get_deprecated ( " id " sv ) . to_i32 ( ) , node ) ;
2021-08-17 16:33:07 +01:00
2021-08-24 17:04:30 +01:00
auto const * children = get_children ( * node ) ;
if ( ! children )
return ;
2021-08-17 16:33:07 +01:00
2021-08-24 17:04:30 +01:00
children - > for_each ( [ & ] ( auto const & child ) {
auto const & child_node = child . as_object ( ) ;
map_dom_nodes_to_parent ( node , & child_node ) ;
} ) ;
2021-08-17 16:33:07 +01:00
}
2022-03-04 16:29:05 +00:00
GUI : : ModelIndex DOMTreeModel : : index_for_node ( i32 node_id , Optional < Web : : CSS : : Selector : : PseudoElement > pseudo_element ) const
2021-08-27 17:36:10 +01:00
{
auto node = m_node_id_to_dom_node_map . get ( node_id ) . value_or ( nullptr ) ;
if ( node ) {
2022-03-04 16:29:05 +00:00
if ( pseudo_element . has_value ( ) ) {
// Find pseudo-element child of the node.
auto node_children = get_children ( * node ) ;
for ( size_t i = 0 ; i < node_children - > size ( ) ; i + + ) {
auto & child = node_children - > at ( i ) . as_object ( ) ;
2022-07-11 17:32:29 +00:00
if ( ! child . has ( " pseudo-element " sv ) )
2022-03-04 16:29:05 +00:00
continue ;
2022-12-21 11:42:06 +00:00
auto child_pseudo_element = child . get_deprecated ( " pseudo-element " sv ) ;
2022-03-04 16:29:05 +00:00
if ( ! child_pseudo_element . is_i32 ( ) )
continue ;
if ( child_pseudo_element . as_i32 ( ) = = to_underlying ( pseudo_element . value ( ) ) )
return create_index ( i , 0 , & child ) ;
}
} else {
auto * parent = get_parent ( * node ) ;
if ( ! parent )
return { } ;
auto parent_children = get_children ( * parent ) ;
for ( size_t i = 0 ; i < parent_children - > size ( ) ; i + + ) {
if ( & parent_children - > at ( i ) . as_object ( ) = = node ) {
return create_index ( i , 0 , node ) ;
}
2021-08-27 17:36:10 +01:00
}
}
}
2022-07-11 17:32:29 +00:00
dbgln ( " Didn't find index for node {}, pseudo-element {}! " , node_id , pseudo_element . has_value ( ) ? Web : : CSS : : pseudo_element_name ( pseudo_element . value ( ) ) : " NONE " sv ) ;
2021-08-27 17:36:10 +01:00
return { } ;
}
2020-03-07 10:27:02 +01:00
}