2020-01-18 09:38:21 +01:00
/*
2021-03-16 18:03:43 +01:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
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
*/
2020-03-07 10:32:51 +01:00
# include <LibWeb/DOM/Document.h>
2021-04-22 21:11:20 +02:00
# include <LibWeb/DOM/HTMLCollection.h>
2021-03-16 18:03:43 +01:00
# include <LibWeb/DOM/Window.h>
2021-11-18 15:01:28 +01:00
# include <LibWeb/HTML/BrowsingContext.h>
2022-02-06 18:45:29 +01:00
# include <LibWeb/HTML/BrowsingContextContainer.h>
2021-10-03 16:42:03 +02:00
# include <LibWeb/HTML/EventLoop/EventLoop.h>
2020-07-26 15:08:16 +02:00
# include <LibWeb/HTML/HTMLAnchorElement.h>
2022-02-17 12:59:32 +01:00
# include <LibWeb/HTML/HTMLInputElement.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/BreakNode.h>
2021-09-08 11:27:46 +02:00
# include <LibWeb/Layout/InitialContainingBlock.h>
2020-11-22 15:53:01 +01:00
# include <LibWeb/Layout/TextNode.h>
2021-08-24 16:28:08 +02:00
# include <LibWeb/Page/Page.h>
2019-10-04 15:50:04 +02:00
2021-11-18 15:01:28 +01:00
namespace Web : : HTML {
2020-03-07 10:27:02 +01:00
2021-09-10 01:51:09 +02:00
BrowsingContext : : BrowsingContext ( Page & page , HTML : : BrowsingContextContainer * container )
2021-09-08 02:07:39 +02:00
: m_page ( page )
2020-06-06 15:08:36 +02:00
, m_loader ( * this )
2020-06-07 14:40:38 +02:00
, m_event_handler ( { } , * this )
2021-09-09 02:07:32 +02:00
, m_container ( container )
2020-06-05 23:36:02 +02:00
{
2021-09-08 02:07:39 +02: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 23:36:02 +02:00
}
2021-09-08 02:07:39 +02:00
BrowsingContext : : ~ BrowsingContext ( )
2020-08-02 11:52:35 +02:00
{
}
2021-05-30 12:36:53 +02:00
void BrowsingContext : : did_edit ( Badge < EditEventHandler > )
2021-01-04 20:48:27 +01:00
{
2021-02-09 21:23:50 +01:00
reset_cursor_blink_cycle ( ) ;
2022-02-17 12:59:32 +01:00
if ( m_cursor_position . node ( ) & & is < DOM : : Text > ( * m_cursor_position . node ( ) ) ) {
auto & text_node = static_cast < DOM : : Text & > ( * m_cursor_position . node ( ) ) ;
if ( auto * input_element = text_node . owner_input_element ( ) )
input_element - > did_edit_text_node ( { } ) ;
}
2021-02-09 21:23:50 +01:00
}
2021-05-30 12:36:53 +02:00
void BrowsingContext : : reset_cursor_blink_cycle ( )
2021-02-09 21:23:50 +01:00
{
2021-01-04 20:48:27 +01:00
m_cursor_blink_state = true ;
m_cursor_blink_timer - > restart ( ) ;
2021-05-18 21:56:20 +02:00
m_cursor_position . node ( ) - > layout_node ( ) - > set_needs_display ( ) ;
2021-01-04 20:48:27 +01:00
}
2022-03-01 21:17:53 +00:00
// https://html.spec.whatwg.org/multipage/browsers.html#top-level-browsing-context
bool BrowsingContext : : is_top_level ( ) const
{
// A browsing context that has no parent browsing context is the top-level browsing context for itself and all of the browsing contexts for which it is an ancestor browsing context.
return ! parent ( ) ;
}
2021-05-30 12:36:53 +02:00
bool BrowsingContext : : is_focused_context ( ) const
2020-08-14 11:33:20 +02:00
{
2021-05-30 12:36:53 +02:00
return m_page & & & m_page - > focused_context ( ) = = this ;
2020-08-14 11:33:20 +02:00
}
2021-09-09 13:14:32 +02:00
void BrowsingContext : : set_active_document ( DOM : : Document * document )
2019-10-04 15:50:04 +02:00
{
2021-09-09 13:14:32 +02:00
if ( m_active_document = = document )
2019-10-04 15:50:04 +02:00
return ;
2020-10-22 23:23:57 +02:00
m_cursor_position = { } ;
2021-09-09 13:14:32 +02:00
if ( m_active_document )
m_active_document - > detach_from_browsing_context ( { } , * this ) ;
2019-10-04 15:50:04 +02:00
2021-09-09 13:14:32 +02:00
m_active_document = document ;
2019-10-04 15:50:04 +02:00
2021-09-09 13:14:32 +02:00
if ( m_active_document ) {
m_active_document - > attach_to_browsing_context ( { } , * this ) ;
2021-05-30 12:36:53 +02:00
if ( m_page & & is_top_level ( ) )
2021-09-09 13:14:32 +02:00
m_page - > client ( ) . page_did_change_title ( m_active_document - > title ( ) ) ;
2020-10-08 21:04:32 +01:00
}
2020-06-06 13:02:44 +02:00
2020-11-12 18:23:05 +01:00
if ( m_page )
2021-09-09 13:14:32 +02:00
m_page - > client ( ) . page_did_set_document_in_top_level_browsing_context ( m_active_document ) ;
2019-10-04 15:50:04 +02:00
}
2021-09-08 10:43:20 +02:00
void BrowsingContext : : set_viewport_rect ( Gfx : : IntRect const & rect )
2021-02-09 22:40:16 +01:00
{
bool did_change = false ;
if ( m_size ! = rect . size ( ) ) {
m_size = rect . size ( ) ;
2021-10-05 22:30:53 +02:00
if ( auto * document = active_document ( ) )
document - > set_needs_layout ( ) ;
2021-02-09 22:40:16 +01: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 11:12:22 +02:00
client - > browsing_context_did_set_viewport_rect ( rect ) ;
2021-02-09 22:40:16 +01:00
}
2021-10-03 16:42:03 +02:00
// Schedule the HTML event loop to ensure that a `resize` event gets fired.
HTML : : main_thread_event_loop ( ) . schedule ( ) ;
2021-02-09 22:40:16 +01:00
}
2021-09-08 10:43:20 +02:00
void BrowsingContext : : set_size ( Gfx : : IntSize const & size )
2019-10-04 15:50:04 +02:00
{
if ( m_size = = size )
return ;
m_size = size ;
2021-01-30 12:25:33 +01:00
2021-10-05 22:30:53 +02:00
if ( auto * document = active_document ( ) )
document - > set_needs_layout ( ) ;
2021-01-30 12:25:33 +01:00
for ( auto * client : m_viewport_clients )
2021-09-08 11:12:22 +02:00
client - > browsing_context_did_set_viewport_rect ( viewport_rect ( ) ) ;
2021-10-03 16:42:03 +02:00
// Schedule the HTML event loop to ensure that a `resize` event gets fired.
HTML : : main_thread_event_loop ( ) . schedule ( ) ;
2019-10-04 15:50:04 +02:00
}
2019-10-09 21:25:29 +02:00
2021-09-08 10:43:20 +02:00
void BrowsingContext : : set_viewport_scroll_offset ( Gfx : : IntPoint const & offset )
2019-12-18 20:54:23 +01:00
{
2020-12-02 23:40:57 +01:00
if ( m_viewport_scroll_offset = = offset )
2019-12-18 20:54:23 +01:00
return ;
2020-12-02 23:40:57 +01:00
m_viewport_scroll_offset = offset ;
2019-12-18 20:57:18 +01:00
2021-01-30 12:25:33 +01:00
for ( auto * client : m_viewport_clients )
2021-09-08 11:12:22 +02:00
client - > browsing_context_did_set_viewport_rect ( viewport_rect ( ) ) ;
2019-12-18 20:54:23 +01:00
}
2022-02-15 13:01:48 +01:00
void BrowsingContext : : set_needs_display ( )
{
set_needs_display ( viewport_rect ( ) ) ;
}
2021-09-08 10:43:20 +02:00
void BrowsingContext : : set_needs_display ( Gfx : : IntRect const & rect )
2019-10-09 21:25:29 +02:00
{
2020-12-02 23:40:57 +01:00
if ( ! viewport_rect ( ) . intersects ( rect ) )
2019-12-18 22:16:27 +01:00
return ;
2021-05-30 12:36:53 +02:00
if ( is_top_level ( ) ) {
2020-11-12 18:23:05 +01:00
if ( m_page )
2021-05-30 12:36:53 +02:00
m_page - > client ( ) . page_did_invalidate ( to_top_level_rect ( rect ) ) ;
2019-10-09 21:25:29 +02:00
return ;
2020-06-07 14:56:29 +02:00
}
2021-09-09 02:07:32 +02:00
if ( container ( ) & & container ( ) - > layout_node ( ) )
container ( ) - > layout_node ( ) - > set_needs_display ( ) ;
2019-10-09 21:25:29 +02:00
}
2020-03-07 10:27:02 +01:00
2021-09-08 10:43:20 +02:00
void BrowsingContext : : scroll_to_anchor ( String const & fragment )
2020-06-06 13:02:44 +02:00
{
2021-09-09 13:14:32 +02:00
if ( ! active_document ( ) )
2020-07-05 14:50:38 +02:00
return ;
2021-09-09 13:14:32 +02:00
auto element = active_document ( ) - > get_element_by_id ( fragment ) ;
2020-07-05 14:50:38 +02:00
if ( ! element ) {
2021-09-09 13:14:32 +02:00
auto candidates = active_document ( ) - > get_elements_by_name ( fragment ) ;
2021-04-22 21:11:20 +02:00
for ( auto & candidate : candidates - > collect_matching_elements ( ) ) {
if ( is < HTML : : HTMLAnchorElement > ( * candidate ) ) {
2021-06-24 19:53:42 +02:00
element = verify_cast < HTML : : HTMLAnchorElement > ( * candidate ) ;
2020-07-05 14:50:38 +02:00
break ;
}
}
}
2022-02-15 14:12:23 +01:00
active_document ( ) - > force_layout ( ) ;
2020-12-13 17:38:03 +01:00
2020-07-05 14:50:38 +02: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 15:53:01 +01:00
if ( is < Layout : : Box > ( layout_node ) ) {
2021-06-24 19:53:42 +02:00
auto & layout_box = verify_cast < Layout : : Box > ( layout_node ) ;
2020-12-12 21:02:06 +01:00
auto padding_box = layout_box . box_model ( ) . padding_box ( ) ;
2021-04-12 11:47:09 -07:00
float_rect . translate_by ( - padding_box . left , - padding_box . top ) ;
2020-07-05 14:50:38 +02:00
}
2020-11-12 18:23:05 +01:00
if ( m_page )
m_page - > client ( ) . page_did_request_scroll_into_view ( enclosing_int_rect ( float_rect ) ) ;
2020-06-08 20:31:49 +02:00
}
2021-09-08 10:43:20 +02:00
Gfx : : IntRect BrowsingContext : : to_top_level_rect ( Gfx : : IntRect const & a_rect )
2020-06-08 20:31:49 +02:00
{
auto rect = a_rect ;
2021-05-30 12:36:53 +02:00
rect . set_location ( to_top_level_position ( a_rect . location ( ) ) ) ;
2020-06-08 20:31:49 +02:00
return rect ;
}
2021-09-08 10:43:20 +02:00
Gfx : : IntPoint BrowsingContext : : to_top_level_position ( Gfx : : IntPoint const & a_position )
2020-06-08 20:31:49 +02:00
{
auto position = a_position ;
for ( auto * ancestor = parent ( ) ; ancestor ; ancestor = ancestor - > parent ( ) ) {
2021-05-30 12:36:53 +02:00
if ( ancestor - > is_top_level ( ) )
2020-06-08 20:31:49 +02:00
break ;
2021-09-09 02:07:32 +02:00
if ( ! ancestor - > container ( ) )
2020-06-08 20:31:49 +02:00
return { } ;
2021-09-09 02:07:32 +02:00
if ( ! ancestor - > container ( ) - > layout_node ( ) )
2020-06-08 20:31:49 +02:00
return { } ;
2021-09-09 02:07:32 +02:00
position . translate_by ( ancestor - > container ( ) - > layout_node ( ) - > box_type_agnostic_position ( ) . to_type < int > ( ) ) ;
2020-06-08 20:31:49 +02:00
}
return position ;
2020-06-06 13:02:44 +02:00
}
2021-05-30 12:36:53 +02:00
void BrowsingContext : : set_cursor_position ( DOM : : Position position )
2020-08-02 11:52:35 +02: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 21:23:50 +01:00
m_cursor_position = move ( position ) ;
2020-08-02 11:52:35 +02:00
if ( m_cursor_position . node ( ) & & m_cursor_position . node ( ) - > layout_node ( ) )
m_cursor_position . node ( ) - > layout_node ( ) - > set_needs_display ( ) ;
2021-02-09 21:23:50 +01:00
reset_cursor_blink_cycle ( ) ;
2020-08-02 11:52:35 +02:00
}
2021-05-30 12:36:53 +02:00
String BrowsingContext : : selected_text ( ) const
2020-08-06 19:21:59 +02:00
{
StringBuilder builder ;
2021-09-09 13:14:32 +02:00
if ( ! active_document ( ) )
2020-08-06 19:21:59 +02:00
return { } ;
2021-09-09 13:14:32 +02:00
auto * layout_root = active_document ( ) - > layout_node ( ) ;
2020-08-06 19:21:59 +02: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 15:53:01 +01:00
if ( ! is < Layout : : TextNode > ( * selection . start ( ) . layout_node ) )
2020-08-06 19:21:59 +02:00
return " " ;
2021-06-24 19:53:42 +02: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 19:21:59 +02:00
}
// Start node
auto layout_node = selection . start ( ) . layout_node ;
2020-11-22 15:53:01 +01:00
if ( is < Layout : : TextNode > ( * layout_node ) ) {
2021-06-24 19:53:42 +02:00
auto & text = verify_cast < Layout : : TextNode > ( * layout_node ) . text_for_rendering ( ) ;
2020-08-06 19:21:59 +02: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 15:53:01 +01:00
if ( is < Layout : : TextNode > ( * layout_node ) )
2021-06-24 19:53:42 +02:00
builder . append ( verify_cast < Layout : : TextNode > ( * layout_node ) . text_for_rendering ( ) ) ;
2021-10-06 20:02:41 +02:00
else if ( is < Layout : : BreakNode > ( * layout_node ) | | is < Layout : : BlockContainer > ( * layout_node ) )
2020-08-06 19:21:59 +02:00
builder . append ( ' \n ' ) ;
layout_node = layout_node - > next_in_pre_order ( ) ;
}
// End node
2021-02-23 20:42:32 +01:00
VERIFY ( layout_node = = selection . end ( ) . layout_node ) ;
2020-11-22 15:53:01 +01:00
if ( is < Layout : : TextNode > ( * layout_node ) ) {
2021-06-24 19:53:42 +02:00
auto & text = verify_cast < Layout : : TextNode > ( * layout_node ) . text_for_rendering ( ) ;
2020-08-26 11:31:11 +12:00
builder . append ( text . substring ( 0 , selection . end ( ) . index_in_node ) ) ;
2020-08-06 19:21:59 +02:00
}
return builder . to_string ( ) ;
}
2021-07-14 08:38:10 -04:00
void BrowsingContext : : select_all ( )
{
2021-09-09 13:14:32 +02:00
if ( ! active_document ( ) )
2021-07-14 08:38:10 -04:00
return ;
2021-09-09 13:14:32 +02:00
auto * layout_root = active_document ( ) - > layout_node ( ) ;
2021-07-14 08:38:10 -04:00
if ( ! layout_root )
return ;
2021-09-08 10:43:20 +02:00
Layout : : Node const * first_layout_node = layout_root ;
2021-07-14 08:38:10 -04: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 10:43:20 +02:00
Layout : : Node const * last_layout_node = first_layout_node ;
2021-07-14 08:38:10 -04:00
2021-09-08 10:43:20 +02:00
for ( Layout : : Node const * layout_node = first_layout_node ; layout_node ; layout_node = layout_node - > next_in_pre_order ( ) ) {
2021-07-14 08:38:10 -04: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 09:11:34 -04: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 08:38:10 -04:00
layout_root - > set_selection ( { { first_layout_node , 0 } , { last_layout_node , last_layout_node_index_in_node } } ) ;
}
2021-05-30 12:36:53 +02:00
void BrowsingContext : : register_viewport_client ( ViewportClient & client )
2021-01-30 12:25:33 +01:00
{
auto result = m_viewport_clients . set ( & client ) ;
2021-02-23 20:42:32 +01:00
VERIFY ( result = = AK : : HashSetResult : : InsertedNewEntry ) ;
2021-01-30 12:25:33 +01:00
}
2021-05-30 12:36:53 +02:00
void BrowsingContext : : unregister_viewport_client ( ViewportClient & client )
2021-01-30 12:25:33 +01:00
{
bool was_removed = m_viewport_clients . remove ( & client ) ;
2021-02-23 20:42:32 +01:00
VERIFY ( was_removed ) ;
2021-01-30 12:25:33 +01:00
}
2021-09-13 00:33:23 +03:00
void BrowsingContext : : register_frame_nesting ( AK : : URL const & url )
2021-04-19 14:30:08 +02:00
{
m_frame_nesting_levels . ensure ( url ) + + ;
}
2021-09-13 00:33:23 +03:00
bool BrowsingContext : : is_frame_nesting_allowed ( AK : : URL const & url ) const
2021-04-19 14:30:08 +02:00
{
return m_frame_nesting_levels . get ( url ) . value_or ( 0 ) < 3 ;
}
2021-05-30 12:36:53 +02:00
bool BrowsingContext : : increment_cursor_position_offset ( )
2021-05-18 22:01:12 +02:00
{
if ( ! m_cursor_position . increment_offset ( ) )
return false ;
reset_cursor_blink_cycle ( ) ;
return true ;
}
2021-05-30 12:36:53 +02:00
bool BrowsingContext : : decrement_cursor_position_offset ( )
2021-05-18 22:01:12 +02:00
{
if ( ! m_cursor_position . decrement_offset ( ) )
return false ;
reset_cursor_blink_cycle ( ) ;
return true ;
}
2021-09-09 02:10:05 +02: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 17:05:58 +02: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 ;
}
2022-02-06 18:45:29 +01:00
// https://html.spec.whatwg.org/multipage/interaction.html#currently-focused-area-of-a-top-level-browsing-context
RefPtr < DOM : : Node > BrowsingContext : : currently_focused_area ( )
{
// 1. If topLevelBC does not have system focus, then return null.
if ( ! is_focused_context ( ) )
return nullptr ;
// 2. Let candidate be topLevelBC's active document.
auto * candidate = active_document ( ) ;
// 3. While candidate's focused area is a browsing context container with a non-null nested browsing context:
// set candidate to the active document of that browsing context container's nested browsing context.
while ( candidate - > focused_element ( )
& & is < HTML : : BrowsingContextContainer > ( candidate - > focused_element ( ) )
& & static_cast < HTML : : BrowsingContextContainer & > ( * candidate - > focused_element ( ) ) . nested_browsing_context ( ) ) {
candidate = static_cast < HTML : : BrowsingContextContainer & > ( * candidate - > focused_element ( ) ) . nested_browsing_context ( ) - > active_document ( ) ;
}
// 4. If candidate's focused area is non-null, set candidate to candidate's focused area.
if ( candidate - > focused_element ( ) ) {
// NOTE: We return right away here instead of assigning to candidate,
// since that would require compromising type safety.
return candidate - > focused_element ( ) ;
}
// 5. Return candidate.
return candidate ;
}
2020-03-07 10:27:02 +01:00
}