2020-01-18 05:38:21 -03:00
/*
2021-03-16 14:03:43 -03:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2020-01-18 05:38:21 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 05:38:21 -03:00
*/
2020-03-07 06:32:51 -03:00
# include <LibWeb/DOM/Document.h>
2021-04-22 16:11:20 -03:00
# include <LibWeb/DOM/HTMLCollection.h>
2021-03-16 14:03:43 -03:00
# include <LibWeb/DOM/Window.h>
2021-11-18 11:01:28 -03:00
# include <LibWeb/HTML/BrowsingContext.h>
2021-10-03 11:42:03 -03:00
# include <LibWeb/HTML/EventLoop/EventLoop.h>
2020-07-26 10:08:16 -03:00
# include <LibWeb/HTML/HTMLAnchorElement.h>
2020-11-22 11:53:01 -03:00
# include <LibWeb/Layout/BreakNode.h>
2021-09-08 06:27:46 -03:00
# include <LibWeb/Layout/InitialContainingBlock.h>
2020-11-22 11:53:01 -03:00
# include <LibWeb/Layout/TextNode.h>
2021-08-24 11:28:08 -03:00
# include <LibWeb/Page/Page.h>
2019-10-04 10:50:04 -03:00
2021-11-18 11:01:28 -03:00
namespace Web : : HTML {
2020-03-07 06:27:02 -03:00
2021-09-09 20:51:09 -03:00
BrowsingContext : : BrowsingContext ( Page & page , HTML : : BrowsingContextContainer * container )
2021-09-07 21:07:39 -03:00
: m_page ( page )
2020-06-06 10:08:36 -03:00
, m_loader ( * this )
2020-06-07 09:40:38 -03:00
, m_event_handler ( { } , * this )
2021-09-08 21:07:32 -03:00
, m_container ( container )
2020-06-05 18:36:02 -03:00
{
2021-09-07 21:07:39 -03:00
m_cursor_blink_timer = Core : : Timer : : construct ( 500 , [ this ] {
if ( ! is_focused_context ( ) )
return ;
if ( m_cursor_position . node ( ) & & m_cursor_position . node ( ) - > layout_node ( ) ) {
m_cursor_blink_state = ! m_cursor_blink_state ;
m_cursor_position . node ( ) - > layout_node ( ) - > set_needs_display ( ) ;
}
} ) ;
2020-06-05 18:36:02 -03:00
}
2021-09-07 21:07:39 -03:00
BrowsingContext : : ~ BrowsingContext ( )
2020-08-02 06:52:35 -03:00
{
}
2021-05-30 07:36:53 -03:00
void BrowsingContext : : did_edit ( Badge < EditEventHandler > )
2021-01-04 16:48:27 -03:00
{
2021-02-09 17:23:50 -03:00
reset_cursor_blink_cycle ( ) ;
}
2021-05-30 07:36:53 -03:00
void BrowsingContext : : reset_cursor_blink_cycle ( )
2021-02-09 17:23:50 -03:00
{
2021-01-04 16:48:27 -03:00
m_cursor_blink_state = true ;
m_cursor_blink_timer - > restart ( ) ;
2021-05-18 16:56:20 -03:00
m_cursor_position . node ( ) - > layout_node ( ) - > set_needs_display ( ) ;
2021-01-04 16:48:27 -03:00
}
2021-05-30 07:36:53 -03:00
bool BrowsingContext : : is_focused_context ( ) const
2020-08-14 06:33:20 -03:00
{
2021-05-30 07:36:53 -03:00
return m_page & & & m_page - > focused_context ( ) = = this ;
2020-08-14 06:33:20 -03:00
}
2021-09-09 08:14:32 -03:00
void BrowsingContext : : set_active_document ( DOM : : Document * document )
2019-10-04 10:50:04 -03:00
{
2021-09-09 08:14:32 -03:00
if ( m_active_document = = document )
2019-10-04 10:50:04 -03:00
return ;
2020-10-22 18:23:57 -03:00
m_cursor_position = { } ;
2021-09-09 08:14:32 -03:00
if ( m_active_document )
m_active_document - > detach_from_browsing_context ( { } , * this ) ;
2019-10-04 10:50:04 -03:00
2021-09-09 08:14:32 -03:00
m_active_document = document ;
2019-10-04 10:50:04 -03:00
2021-09-09 08:14:32 -03:00
if ( m_active_document ) {
m_active_document - > attach_to_browsing_context ( { } , * this ) ;
2021-05-30 07:36:53 -03:00
if ( m_page & & is_top_level ( ) )
2021-09-09 08:14:32 -03:00
m_page - > client ( ) . page_did_change_title ( m_active_document - > title ( ) ) ;
2020-10-08 17:04:32 -03:00
}
2020-06-06 08:02:44 -03:00
2020-11-12 14:23:05 -03:00
if ( m_page )
2021-09-09 08:14:32 -03:00
m_page - > client ( ) . page_did_set_document_in_top_level_browsing_context ( m_active_document ) ;
2019-10-04 10:50:04 -03:00
}
2021-09-08 05:43:20 -03:00
void BrowsingContext : : set_viewport_rect ( Gfx : : IntRect const & rect )
2021-02-09 18:40:16 -03:00
{
bool did_change = false ;
if ( m_size ! = rect . size ( ) ) {
m_size = rect . size ( ) ;
2021-10-05 17:30:53 -03:00
if ( auto * document = active_document ( ) )
document - > set_needs_layout ( ) ;
2021-02-09 18:40:16 -03:00
did_change = true ;
}
if ( m_viewport_scroll_offset ! = rect . location ( ) ) {
m_viewport_scroll_offset = rect . location ( ) ;
did_change = true ;
}
if ( did_change ) {
for ( auto * client : m_viewport_clients )
2021-09-08 06:12:22 -03:00
client - > browsing_context_did_set_viewport_rect ( rect ) ;
2021-02-09 18:40:16 -03:00
}
2021-10-03 11:42:03 -03:00
// Schedule the HTML event loop to ensure that a `resize` event gets fired.
HTML : : main_thread_event_loop ( ) . schedule ( ) ;
2021-02-09 18:40:16 -03:00
}
2021-09-08 05:43:20 -03:00
void BrowsingContext : : set_size ( Gfx : : IntSize const & size )
2019-10-04 10:50:04 -03:00
{
if ( m_size = = size )
return ;
m_size = size ;
2021-01-30 08:25:33 -03:00
2021-10-05 17:30:53 -03:00
if ( auto * document = active_document ( ) )
document - > set_needs_layout ( ) ;
2021-01-30 08:25:33 -03:00
for ( auto * client : m_viewport_clients )
2021-09-08 06:12:22 -03:00
client - > browsing_context_did_set_viewport_rect ( viewport_rect ( ) ) ;
2021-10-03 11:42:03 -03:00
// Schedule the HTML event loop to ensure that a `resize` event gets fired.
HTML : : main_thread_event_loop ( ) . schedule ( ) ;
2019-10-04 10:50:04 -03:00
}
2019-10-09 16:25:29 -03:00
2021-09-08 05:43:20 -03:00
void BrowsingContext : : set_viewport_scroll_offset ( Gfx : : IntPoint const & offset )
2019-12-18 16:54:23 -03:00
{
2020-12-02 19:40:57 -03:00
if ( m_viewport_scroll_offset = = offset )
2019-12-18 16:54:23 -03:00
return ;
2020-12-02 19:40:57 -03:00
m_viewport_scroll_offset = offset ;
2019-12-18 16:57:18 -03:00
2021-01-30 08:25:33 -03:00
for ( auto * client : m_viewport_clients )
2021-09-08 06:12:22 -03:00
client - > browsing_context_did_set_viewport_rect ( viewport_rect ( ) ) ;
2019-12-18 16:54:23 -03:00
}
2021-09-08 05:43:20 -03:00
void BrowsingContext : : set_needs_display ( Gfx : : IntRect const & rect )
2019-10-09 16:25:29 -03:00
{
2020-12-02 19:40:57 -03:00
if ( ! viewport_rect ( ) . intersects ( rect ) )
2019-12-18 18:16:27 -03:00
return ;
2021-05-30 07:36:53 -03:00
if ( is_top_level ( ) ) {
2020-11-12 14:23:05 -03:00
if ( m_page )
2021-05-30 07:36:53 -03:00
m_page - > client ( ) . page_did_invalidate ( to_top_level_rect ( rect ) ) ;
2019-10-09 16:25:29 -03:00
return ;
2020-06-07 09:56:29 -03:00
}
2021-09-08 21:07:32 -03:00
if ( container ( ) & & container ( ) - > layout_node ( ) )
container ( ) - > layout_node ( ) - > set_needs_display ( ) ;
2019-10-09 16:25:29 -03:00
}
2020-03-07 06:27:02 -03:00
2021-09-08 05:43:20 -03:00
void BrowsingContext : : scroll_to_anchor ( String const & fragment )
2020-06-06 08:02:44 -03:00
{
2021-09-09 08:14:32 -03:00
if ( ! active_document ( ) )
2020-07-05 09:50:38 -03:00
return ;
2021-09-09 08:14:32 -03:00
auto element = active_document ( ) - > get_element_by_id ( fragment ) ;
2020-07-05 09:50:38 -03:00
if ( ! element ) {
2021-09-09 08:14:32 -03:00
auto candidates = active_document ( ) - > get_elements_by_name ( fragment ) ;
2021-04-22 16:11:20 -03:00
for ( auto & candidate : candidates - > collect_matching_elements ( ) ) {
if ( is < HTML : : HTMLAnchorElement > ( * candidate ) ) {
2021-06-24 14:53:42 -03:00
element = verify_cast < HTML : : HTMLAnchorElement > ( * candidate ) ;
2020-07-05 09:50:38 -03:00
break ;
}
}
}
2021-10-18 05:17:01 -03:00
active_document ( ) - > update_layout ( ) ;
2020-12-13 13:38:03 -03:00
2020-07-05 09:50:38 -03:00
if ( ! element | | ! element - > layout_node ( ) )
return ;
auto & layout_node = * element - > layout_node ( ) ;
Gfx : : FloatRect float_rect { layout_node . box_type_agnostic_position ( ) , { ( float ) viewport_rect ( ) . width ( ) , ( float ) viewport_rect ( ) . height ( ) } } ;
2020-11-22 11:53:01 -03:00
if ( is < Layout : : Box > ( layout_node ) ) {
2021-06-24 14:53:42 -03:00
auto & layout_box = verify_cast < Layout : : Box > ( layout_node ) ;
2020-12-12 17:02:06 -03:00
auto padding_box = layout_box . box_model ( ) . padding_box ( ) ;
2021-04-12 15:47:09 -03:00
float_rect . translate_by ( - padding_box . left , - padding_box . top ) ;
2020-07-05 09:50:38 -03:00
}
2020-11-12 14:23:05 -03:00
if ( m_page )
m_page - > client ( ) . page_did_request_scroll_into_view ( enclosing_int_rect ( float_rect ) ) ;
2020-06-08 15:31:49 -03:00
}
2021-09-08 05:43:20 -03:00
Gfx : : IntRect BrowsingContext : : to_top_level_rect ( Gfx : : IntRect const & a_rect )
2020-06-08 15:31:49 -03:00
{
auto rect = a_rect ;
2021-05-30 07:36:53 -03:00
rect . set_location ( to_top_level_position ( a_rect . location ( ) ) ) ;
2020-06-08 15:31:49 -03:00
return rect ;
}
2021-09-08 05:43:20 -03:00
Gfx : : IntPoint BrowsingContext : : to_top_level_position ( Gfx : : IntPoint const & a_position )
2020-06-08 15:31:49 -03:00
{
auto position = a_position ;
for ( auto * ancestor = parent ( ) ; ancestor ; ancestor = ancestor - > parent ( ) ) {
2021-05-30 07:36:53 -03:00
if ( ancestor - > is_top_level ( ) )
2020-06-08 15:31:49 -03:00
break ;
2021-09-08 21:07:32 -03:00
if ( ! ancestor - > container ( ) )
2020-06-08 15:31:49 -03:00
return { } ;
2021-09-08 21:07:32 -03:00
if ( ! ancestor - > container ( ) - > layout_node ( ) )
2020-06-08 15:31:49 -03:00
return { } ;
2021-09-08 21:07:32 -03:00
position . translate_by ( ancestor - > container ( ) - > layout_node ( ) - > box_type_agnostic_position ( ) . to_type < int > ( ) ) ;
2020-06-08 15:31:49 -03:00
}
return position ;
2020-06-06 08:02:44 -03:00
}
2021-05-30 07:36:53 -03:00
void BrowsingContext : : set_cursor_position ( DOM : : Position position )
2020-08-02 06:52:35 -03:00
{
if ( m_cursor_position = = position )
return ;
if ( m_cursor_position . node ( ) & & m_cursor_position . node ( ) - > layout_node ( ) )
m_cursor_position . node ( ) - > layout_node ( ) - > set_needs_display ( ) ;
2021-02-09 17:23:50 -03:00
m_cursor_position = move ( position ) ;
2020-08-02 06:52:35 -03:00
if ( m_cursor_position . node ( ) & & m_cursor_position . node ( ) - > layout_node ( ) )
m_cursor_position . node ( ) - > layout_node ( ) - > set_needs_display ( ) ;
2021-02-09 17:23:50 -03:00
reset_cursor_blink_cycle ( ) ;
2020-08-02 06:52:35 -03:00
}
2021-05-30 07:36:53 -03:00
String BrowsingContext : : selected_text ( ) const
2020-08-06 14:21:59 -03:00
{
StringBuilder builder ;
2021-09-09 08:14:32 -03:00
if ( ! active_document ( ) )
2020-08-06 14:21:59 -03:00
return { } ;
2021-09-09 08:14:32 -03:00
auto * layout_root = active_document ( ) - > layout_node ( ) ;
2020-08-06 14:21:59 -03:00
if ( ! layout_root )
return { } ;
if ( ! layout_root - > selection ( ) . is_valid ( ) )
return { } ;
auto selection = layout_root - > selection ( ) . normalized ( ) ;
if ( selection . start ( ) . layout_node = = selection . end ( ) . layout_node ) {
2020-11-22 11:53:01 -03:00
if ( ! is < Layout : : TextNode > ( * selection . start ( ) . layout_node ) )
2020-08-06 14:21:59 -03:00
return " " ;
2021-06-24 14:53:42 -03:00
return verify_cast < Layout : : TextNode > ( * selection . start ( ) . layout_node ) . text_for_rendering ( ) . substring ( selection . start ( ) . index_in_node , selection . end ( ) . index_in_node - selection . start ( ) . index_in_node ) ;
2020-08-06 14:21:59 -03:00
}
// Start node
auto layout_node = selection . start ( ) . layout_node ;
2020-11-22 11:53:01 -03:00
if ( is < Layout : : TextNode > ( * layout_node ) ) {
2021-06-24 14:53:42 -03:00
auto & text = verify_cast < Layout : : TextNode > ( * layout_node ) . text_for_rendering ( ) ;
2020-08-06 14:21:59 -03:00
builder . append ( text . substring ( selection . start ( ) . index_in_node , text . length ( ) - selection . start ( ) . index_in_node ) ) ;
}
// Middle nodes
layout_node = layout_node - > next_in_pre_order ( ) ;
while ( layout_node & & layout_node ! = selection . end ( ) . layout_node ) {
2020-11-22 11:53:01 -03:00
if ( is < Layout : : TextNode > ( * layout_node ) )
2021-06-24 14:53:42 -03:00
builder . append ( verify_cast < Layout : : TextNode > ( * layout_node ) . text_for_rendering ( ) ) ;
2021-10-06 15:02:41 -03:00
else if ( is < Layout : : BreakNode > ( * layout_node ) | | is < Layout : : BlockContainer > ( * layout_node ) )
2020-08-06 14:21:59 -03:00
builder . append ( ' \n ' ) ;
layout_node = layout_node - > next_in_pre_order ( ) ;
}
// End node
2021-02-23 16:42:32 -03:00
VERIFY ( layout_node = = selection . end ( ) . layout_node ) ;
2020-11-22 11:53:01 -03:00
if ( is < Layout : : TextNode > ( * layout_node ) ) {
2021-06-24 14:53:42 -03:00
auto & text = verify_cast < Layout : : TextNode > ( * layout_node ) . text_for_rendering ( ) ;
2020-08-25 20:31:11 -03:00
builder . append ( text . substring ( 0 , selection . end ( ) . index_in_node ) ) ;
2020-08-06 14:21:59 -03:00
}
return builder . to_string ( ) ;
}
2021-07-14 09:38:10 -03:00
void BrowsingContext : : select_all ( )
{
2021-09-09 08:14:32 -03:00
if ( ! active_document ( ) )
2021-07-14 09:38:10 -03:00
return ;
2021-09-09 08:14:32 -03:00
auto * layout_root = active_document ( ) - > layout_node ( ) ;
2021-07-14 09:38:10 -03:00
if ( ! layout_root )
return ;
2021-09-08 05:43:20 -03:00
Layout : : Node const * first_layout_node = layout_root ;
2021-07-14 09:38:10 -03:00
for ( ; ; ) {
auto * next = first_layout_node - > next_in_pre_order ( ) ;
if ( ! next )
break ;
first_layout_node = next ;
if ( is < Layout : : TextNode > ( * first_layout_node ) )
break ;
}
2021-09-08 05:43:20 -03:00
Layout : : Node const * last_layout_node = first_layout_node ;
2021-07-14 09:38:10 -03:00
2021-09-08 05:43:20 -03:00
for ( Layout : : Node const * layout_node = first_layout_node ; layout_node ; layout_node = layout_node - > next_in_pre_order ( ) ) {
2021-07-14 09:38:10 -03:00
if ( is < Layout : : TextNode > ( * layout_node ) )
last_layout_node = layout_node ;
}
VERIFY ( first_layout_node ) ;
VERIFY ( last_layout_node ) ;
int last_layout_node_index_in_node = 0 ;
2021-07-14 10:11:34 -03:00
if ( is < Layout : : TextNode > ( * last_layout_node ) ) {
auto const & text_for_rendering = verify_cast < Layout : : TextNode > ( * last_layout_node ) . text_for_rendering ( ) ;
if ( ! text_for_rendering . is_empty ( ) )
last_layout_node_index_in_node = text_for_rendering . length ( ) - 1 ;
}
2021-07-14 09:38:10 -03:00
layout_root - > set_selection ( { { first_layout_node , 0 } , { last_layout_node , last_layout_node_index_in_node } } ) ;
}
2021-05-30 07:36:53 -03:00
void BrowsingContext : : register_viewport_client ( ViewportClient & client )
2021-01-30 08:25:33 -03:00
{
auto result = m_viewport_clients . set ( & client ) ;
2021-02-23 16:42:32 -03:00
VERIFY ( result = = AK : : HashSetResult : : InsertedNewEntry ) ;
2021-01-30 08:25:33 -03:00
}
2021-05-30 07:36:53 -03:00
void BrowsingContext : : unregister_viewport_client ( ViewportClient & client )
2021-01-30 08:25:33 -03:00
{
bool was_removed = m_viewport_clients . remove ( & client ) ;
2021-02-23 16:42:32 -03:00
VERIFY ( was_removed ) ;
2021-01-30 08:25:33 -03:00
}
2021-09-12 18:33:23 -03:00
void BrowsingContext : : register_frame_nesting ( AK : : URL const & url )
2021-04-19 09:30:08 -03:00
{
m_frame_nesting_levels . ensure ( url ) + + ;
}
2021-09-12 18:33:23 -03:00
bool BrowsingContext : : is_frame_nesting_allowed ( AK : : URL const & url ) const
2021-04-19 09:30:08 -03:00
{
return m_frame_nesting_levels . get ( url ) . value_or ( 0 ) < 3 ;
}
2021-05-30 07:36:53 -03:00
bool BrowsingContext : : increment_cursor_position_offset ( )
2021-05-18 17:01:12 -03:00
{
if ( ! m_cursor_position . increment_offset ( ) )
return false ;
reset_cursor_blink_cycle ( ) ;
return true ;
}
2021-05-30 07:36:53 -03:00
bool BrowsingContext : : decrement_cursor_position_offset ( )
2021-05-18 17:01:12 -03:00
{
if ( ! m_cursor_position . decrement_offset ( ) )
return false ;
reset_cursor_blink_cycle ( ) ;
return true ;
}
2021-09-08 21:10:05 -03:00
DOM : : Document * BrowsingContext : : container_document ( )
{
if ( auto * container = this - > container ( ) )
return & container - > document ( ) ;
return nullptr ;
}
DOM : : Document const * BrowsingContext : : container_document ( ) const
{
if ( auto * container = this - > container ( ) )
return & container - > document ( ) ;
return nullptr ;
}
2021-10-03 12:05:58 -03:00
// https://html.spec.whatwg.org/#rendering-opportunity
bool BrowsingContext : : has_a_rendering_opportunity ( ) const
{
// A browsing context has a rendering opportunity if the user agent is currently able to present the contents of the browsing context to the user,
// accounting for hardware refresh rate constraints and user agent throttling for performance reasons, but considering content presentable even if it's outside the viewport.
// FIXME: We should at the very least say `false` here if we're an inactive browser tab.
return true ;
}
2020-03-07 06:27:02 -03:00
}