2020-06-07 14:40:38 +02:00
/*
2021-02-09 21:23:50 +01:00
* Copyright ( c ) 2020 - 2021 , Andreas Kling < kling @ serenityos . org >
2021-05-18 13:13:58 +02:00
* Copyright ( c ) 2021 , Max Wipfli < mail @ maxwipfli . ch >
2020-06-07 14:40:38 +02:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-06-07 14:40:38 +02:00
*/
# include <LibGUI/Event.h>
2020-12-06 19:51:55 +01:00
# include <LibWeb/DOM/Range.h>
2020-08-02 12:10:01 +02:00
# include <LibWeb/DOM/Text.h>
2021-11-18 15:01:28 +01:00
# include <LibWeb/HTML/BrowsingContext.h>
2022-11-05 14:34:24 +00:00
# include <LibWeb/HTML/Focus.h>
2020-07-28 17:21:23 +02:00
# include <LibWeb/HTML/HTMLAnchorElement.h>
2024-01-31 13:26:01 -05:00
# include <LibWeb/HTML/HTMLFormElement.h>
2020-07-28 17:21:23 +02:00
# include <LibWeb/HTML/HTMLIFrameElement.h>
2020-10-02 19:01:51 +02:00
# include <LibWeb/HTML/HTMLImageElement.h>
2023-11-30 12:54:30 -05:00
# include <LibWeb/HTML/HTMLInputElement.h>
2023-06-16 10:51:38 -04:00
# include <LibWeb/HTML/HTMLMediaElement.h>
2023-05-15 09:42:56 -04:00
# include <LibWeb/HTML/HTMLVideoElement.h>
2023-02-25 11:04:29 +01:00
# include <LibWeb/Layout/Viewport.h>
2020-07-28 19:27:41 +02:00
# include <LibWeb/Page/EventHandler.h>
2021-08-24 16:28:08 +02:00
# include <LibWeb/Page/Page.h>
2022-03-10 23:13:37 +01:00
# include <LibWeb/Painting/PaintableBox.h>
2020-11-21 19:15:57 +00:00
# include <LibWeb/UIEvents/EventNames.h>
2021-09-27 23:22:21 +02:00
# include <LibWeb/UIEvents/KeyboardEvent.h>
2020-07-28 17:21:23 +02:00
# include <LibWeb/UIEvents/MouseEvent.h>
2022-10-17 20:54:01 +03:00
# include <LibWeb/UIEvents/WheelEvent.h>
2020-06-07 14:40:38 +02:00
namespace Web {
2023-02-25 10:44:51 -07:00
static JS : : GCPtr < DOM : : Node > dom_node_for_event_dispatch ( Painting : : Paintable & paintable )
2022-10-23 17:53:02 +02:00
{
if ( auto node = paintable . mouse_event_target ( ) )
return node ;
if ( auto node = paintable . dom_node ( ) )
return node ;
2024-02-12 14:58:10 +01:00
auto * layout_parent = paintable . layout_node ( ) . parent ( ) ;
while ( layout_parent ) {
if ( auto * node = layout_parent - > dom_node ( ) )
return node ;
layout_parent = layout_parent - > parent ( ) ;
}
2022-10-23 17:53:02 +02:00
return nullptr ;
}
2023-02-25 10:44:51 -07:00
static bool parent_element_for_event_dispatch ( Painting : : Paintable & paintable , JS : : GCPtr < DOM : : Node > & node , Layout : : Node * & layout_node )
2022-10-24 03:24:19 +02:00
{
layout_node = & paintable . layout_node ( ) ;
while ( layout_node & & node & & ! node - > is_element ( ) & & layout_node - > parent ( ) ) {
layout_node = layout_node - > parent ( ) ;
2022-11-06 21:45:30 +01:00
if ( layout_node - > is_anonymous ( ) )
continue ;
2022-10-24 03:24:19 +02:00
node = layout_node - > dom_node ( ) ;
}
return node & & layout_node ;
}
2021-02-21 20:15:00 +00:00
static Gfx : : StandardCursor cursor_css_to_gfx ( Optional < CSS : : Cursor > cursor )
{
if ( ! cursor . has_value ( ) ) {
return Gfx : : StandardCursor : : None ;
}
switch ( cursor . value ( ) ) {
case CSS : : Cursor : : Crosshair :
case CSS : : Cursor : : Cell :
return Gfx : : StandardCursor : : Crosshair ;
case CSS : : Cursor : : Grab :
case CSS : : Cursor : : Grabbing :
return Gfx : : StandardCursor : : Drag ;
case CSS : : Cursor : : Pointer :
return Gfx : : StandardCursor : : Hand ;
case CSS : : Cursor : : Help :
return Gfx : : StandardCursor : : Help ;
case CSS : : Cursor : : None :
return Gfx : : StandardCursor : : Hidden ;
2023-04-13 20:06:21 -04:00
case CSS : : Cursor : : NotAllowed :
return Gfx : : StandardCursor : : Disallowed ;
2021-02-21 20:15:00 +00:00
case CSS : : Cursor : : Text :
case CSS : : Cursor : : VerticalText :
return Gfx : : StandardCursor : : IBeam ;
case CSS : : Cursor : : Move :
case CSS : : Cursor : : AllScroll :
return Gfx : : StandardCursor : : Move ;
case CSS : : Cursor : : Progress :
case CSS : : Cursor : : Wait :
return Gfx : : StandardCursor : : Wait ;
case CSS : : Cursor : : ColResize :
return Gfx : : StandardCursor : : ResizeColumn ;
case CSS : : Cursor : : EResize :
case CSS : : Cursor : : WResize :
case CSS : : Cursor : : EwResize :
return Gfx : : StandardCursor : : ResizeHorizontal ;
case CSS : : Cursor : : RowResize :
return Gfx : : StandardCursor : : ResizeRow ;
case CSS : : Cursor : : NResize :
case CSS : : Cursor : : SResize :
case CSS : : Cursor : : NsResize :
return Gfx : : StandardCursor : : ResizeVertical ;
case CSS : : Cursor : : NeResize :
case CSS : : Cursor : : SwResize :
case CSS : : Cursor : : NeswResize :
return Gfx : : StandardCursor : : ResizeDiagonalBLTR ;
case CSS : : Cursor : : NwResize :
case CSS : : Cursor : : SeResize :
case CSS : : Cursor : : NwseResize :
return Gfx : : StandardCursor : : ResizeDiagonalTLBR ;
2022-09-26 17:33:35 +01:00
case CSS : : Cursor : : ZoomIn :
case CSS : : Cursor : : ZoomOut :
return Gfx : : StandardCursor : : Zoom ;
2023-04-13 20:06:21 -04:00
case CSS : : Cursor : : ContextMenu :
case CSS : : Cursor : : Alias :
case CSS : : Cursor : : Copy :
case CSS : : Cursor : : NoDrop :
// FIXME: No corresponding GFX Standard Cursor, fallthrough to None
case CSS : : Cursor : : Auto :
case CSS : : Cursor : : Default :
2021-02-21 20:15:00 +00:00
default :
return Gfx : : StandardCursor : : None ;
}
}
2022-11-02 17:35:53 +00:00
static CSSPixelPoint compute_mouse_event_offset ( CSSPixelPoint position , Layout : : Node const & layout_node )
2020-06-07 14:40:38 +02:00
{
2024-01-14 11:14:20 +01:00
auto top_left_of_layout_node = layout_node . paintable ( ) - > box_type_agnostic_position ( ) ;
2020-06-07 14:40:38 +02:00
return {
2022-11-02 17:35:53 +00:00
position . x ( ) - top_left_of_layout_node . x ( ) ,
position . y ( ) - top_left_of_layout_node . y ( )
2020-06-07 14:40:38 +02:00
} ;
}
2022-02-06 14:41:29 +01:00
EventHandler : : EventHandler ( Badge < HTML : : BrowsingContext > , HTML : : BrowsingContext & browsing_context )
: m_browsing_context ( browsing_context )
, m_edit_event_handler ( make < EditEventHandler > ( browsing_context ) )
2020-06-07 14:40:38 +02:00
{
}
2022-03-14 13:21:51 -06:00
EventHandler : : ~ EventHandler ( ) = default ;
2020-06-07 14:40:38 +02:00
2022-03-11 00:03:28 +01:00
Painting : : PaintableBox * EventHandler : : paint_root ( )
{
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > active_document ( ) )
2022-03-11 00:03:28 +01:00
return nullptr ;
2023-08-07 00:59:23 +02:00
return m_browsing_context - > active_document ( ) - > paintable_box ( ) ;
2022-03-11 00:03:28 +01:00
}
Painting : : PaintableBox const * EventHandler : : paint_root ( ) const
{
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > active_document ( ) )
2022-03-11 00:03:28 +01:00
return nullptr ;
2023-08-07 00:59:23 +02:00
return m_browsing_context - > active_document ( ) - > paintable_box ( ) ;
2022-03-11 00:03:28 +01:00
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : handle_mousewheel ( CSSPixelPoint position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers , int wheel_delta_x , int wheel_delta_y )
2021-02-22 19:45:41 +01:00
{
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) )
return false ;
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
m_browsing_context - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2021-02-22 19:48:24 +01:00
return false ;
2022-01-20 22:35:01 +01:00
if ( modifiers & KeyModifier : : Mod_Shift )
swap ( wheel_delta_x , wheel_delta_y ) ;
2022-10-17 20:54:01 +03:00
bool handled_event = false ;
2021-02-22 19:48:24 +01:00
2023-01-11 12:51:49 +01:00
JS : : GCPtr < Painting : : Paintable > paintable ;
2023-04-27 09:19:20 -04:00
if ( auto result = target_for_mouse_position ( position ) ; result . has_value ( ) )
paintable = result - > paintable ;
2021-03-02 08:39:07 +11:00
2023-08-09 22:36:46 +00:00
if ( paintable ) {
auto * containing_block = paintable - > containing_block ( ) ;
while ( containing_block ) {
2024-03-01 15:30:44 +01:00
auto handled_scroll_event = containing_block - > handle_mousewheel ( { } , position , buttons , modifiers , wheel_delta_x , wheel_delta_y ) ;
2024-02-29 10:35:25 +01:00
if ( handled_scroll_event )
return true ;
2023-08-09 22:36:46 +00:00
containing_block = containing_block - > containing_block ( ) ;
2023-08-06 14:36:02 +02:00
}
2024-03-17 08:02:52 +01:00
if ( paintable - > handle_mousewheel ( { } , position , buttons , modifiers , wheel_delta_x , wheel_delta_y ) )
return true ;
2022-10-17 20:54:01 +03:00
2022-10-23 17:53:02 +02:00
auto node = dom_node_for_event_dispatch ( * paintable ) ;
2022-10-17 20:54:01 +03:00
if ( node ) {
// FIXME: Support wheel events in nested browsing contexts.
if ( is < HTML : : HTMLIFrameElement > ( * node ) ) {
2024-02-03 11:01:31 +01:00
auto & iframe = static_cast < HTML : : HTMLIFrameElement & > ( * node ) ;
auto position_in_iframe = position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) ;
iframe . nested_browsing_context ( ) - > event_handler ( ) . handle_mousewheel ( position_in_iframe , screen_position , button , buttons , modifiers , wheel_delta_x , wheel_delta_y ) ;
2022-10-17 20:54:01 +03:00
return false ;
}
// Search for the first parent of the hit target that's an element.
2023-02-25 10:44:51 -07:00
Layout : : Node * layout_node ;
2022-10-24 03:24:19 +02:00
if ( ! parent_element_for_event_dispatch ( * paintable , node , layout_node ) )
2022-10-17 20:54:01 +03:00
return false ;
auto offset = compute_mouse_event_offset ( position , * layout_node ) ;
2023-09-08 17:50:53 +02:00
auto client_offset = compute_mouse_event_client_offset ( position ) ;
auto page_offset = compute_mouse_event_page_offset ( client_offset ) ;
2023-09-08 18:57:06 +02:00
if ( node - > dispatch_event ( UIEvents : : WheelEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : wheel , screen_position , page_offset , client_offset , offset , wheel_delta_x , wheel_delta_y , button , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ) {
2024-02-03 11:01:31 +01:00
m_browsing_context - > active_window ( ) - > scroll_by ( wheel_delta_x , wheel_delta_y ) ;
2022-10-17 20:54:01 +03:00
}
handled_event = true ;
}
2021-02-22 19:48:24 +01:00
}
2022-10-17 20:54:01 +03:00
return handled_event ;
2021-02-22 19:45:41 +01:00
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : handle_mouseup ( CSSPixelPoint position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers )
2020-06-07 14:40:38 +02:00
{
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) )
return false ;
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
m_browsing_context - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2020-06-07 14:40:38 +02:00
return false ;
2020-09-11 18:15:47 +02:00
2020-06-07 14:40:38 +02:00
bool handled_event = false ;
2023-01-11 12:51:49 +01:00
JS : : GCPtr < Painting : : Paintable > paintable ;
2023-04-27 09:19:20 -04:00
if ( auto result = target_for_mouse_position ( position ) ; result . has_value ( ) )
paintable = result - > paintable ;
2020-09-11 18:15:47 +02:00
2022-03-14 23:05:55 +00:00
if ( paintable & & paintable - > wants_mouse_events ( ) ) {
if ( paintable - > handle_mouseup ( { } , position , button , modifiers ) = = Painting : : Paintable : : DispatchEventOfSameName : : No )
return false ;
2020-09-12 17:55:19 +02:00
2020-11-22 15:53:01 +01:00
// Things may have changed as a consequence of Layout::Node::handle_mouseup(). Hit test again.
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2020-09-12 17:55:19 +02:00
return true ;
2022-11-02 17:35:53 +00:00
if ( auto result = paint_root ( ) - > hit_test ( position , Painting : : HitTestType : : Exact ) ; result . has_value ( ) )
2022-03-21 11:11:05 +01:00
paintable = result - > paintable ;
2020-09-11 18:15:47 +02:00
}
2022-03-14 23:05:55 +00:00
if ( paintable ) {
2022-10-23 17:53:02 +02:00
auto node = dom_node_for_event_dispatch ( * paintable ) ;
2022-02-07 13:27:17 +01:00
2022-03-14 23:05:55 +00:00
if ( node ) {
if ( is < HTML : : HTMLIFrameElement > ( * node ) ) {
if ( auto * nested_browsing_context = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . nested_browsing_context ( ) )
2023-09-08 18:48:44 +02:00
return nested_browsing_context - > event_handler ( ) . handle_mouseup ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , button , buttons , modifiers ) ;
2022-03-14 23:05:55 +00:00
return false ;
}
2022-05-03 16:50:38 +02:00
// Search for the first parent of the hit target that's an element.
// "The click event type MUST be dispatched on the topmost event target indicated by the pointer." (https://www.w3.org/TR/uievents/#event-type-click)
// "The topmost event target MUST be the element highest in the rendering order which is capable of being an event target." (https://www.w3.org/TR/uievents/#topmost-event-target)
2023-02-25 10:44:51 -07:00
Layout : : Node * layout_node ;
2022-10-24 03:24:19 +02:00
if ( ! parent_element_for_event_dispatch ( * paintable , node , layout_node ) ) {
2022-05-03 16:50:38 +02:00
// FIXME: This is pretty ugly but we need to bail out here.
goto after_node_use ;
}
auto offset = compute_mouse_event_offset ( position , * layout_node ) ;
2022-12-31 16:04:48 +02:00
auto client_offset = compute_mouse_event_client_offset ( position ) ;
2022-12-31 16:40:36 +02:00
auto page_offset = compute_mouse_event_page_offset ( client_offset ) ;
2023-09-08 18:57:06 +02:00
node - > dispatch_event ( UIEvents : : MouseEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : mouseup , screen_position , page_offset , client_offset , offset , { } , button , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-03-14 23:05:55 +00:00
handled_event = true ;
2023-07-21 16:42:48 +02:00
bool run_activation_behavior = false ;
2023-05-21 20:29:30 +01:00
if ( node . ptr ( ) = = m_mousedown_target ) {
if ( button = = GUI : : MouseButton : : Primary )
2023-09-08 18:57:06 +02:00
run_activation_behavior = node - > dispatch_event ( UIEvents : : MouseEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : click , screen_position , page_offset , client_offset , offset , { } , 1 , button , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2023-05-21 20:29:30 +01:00
else if ( button = = GUI : : MouseButton : : Secondary & & ! ( modifiers & Mod_Shift ) ) // Allow the user to bypass custom context menus by holding shift, like Firefox.
2023-09-08 18:57:06 +02:00
run_activation_behavior = node - > dispatch_event ( UIEvents : : MouseEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : contextmenu , screen_position , page_offset , client_offset , offset , { } , 1 , button , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-04-08 21:48:00 -03:00
}
if ( run_activation_behavior ) {
2023-09-13 15:25:45 +02:00
// FIXME: Currently cannot spawn a new top-level
2022-04-08 21:48:00 -03:00
// browsing context for new tab operations, because the new
// top-level browsing context would be in another process. To
// fix this, there needs to be some way to be able to
// communicate with browsing contexts in remote WebContent
// processes, and then step 8 of this algorithm needs to be
2023-09-13 15:25:45 +02:00
// implemented in Navigable::choose_a_navigable:
2022-04-08 21:48:00 -03:00
//
2023-09-13 15:25:45 +02:00
// https://html.spec.whatwg.org/multipage/document-sequences.html#the-rules-for-choosing-a-navigable
2024-02-04 03:36:24 -07:00
auto top_level_position = m_browsing_context - > active_document ( ) - > navigable ( ) - > to_top_level_position ( position ) ;
2023-02-25 10:44:51 -07:00
if ( JS : : GCPtr < HTML : : HTMLAnchorElement const > link = node - > enclosing_link_element ( ) ) {
2023-02-26 16:09:02 -07:00
JS : : NonnullGCPtr < DOM : : Document > document = * m_browsing_context - > active_document ( ) ;
2022-04-08 21:48:00 -03:00
auto href = link - > href ( ) ;
auto url = document - > parse_url ( href ) ;
dbgln ( " Web::EventHandler: Clicking on a link to {} " , url ) ;
2023-11-18 14:46:24 +01:00
if ( button = = GUI : : MouseButton : : Middle ) {
2024-01-16 19:04:45 +01:00
m_browsing_context - > page ( ) . client ( ) . page_did_middle_click_link ( url , link - > target ( ) . to_byte_string ( ) , modifiers ) ;
2022-04-08 21:48:00 -03:00
} else if ( button = = GUI : : MouseButton : : Secondary ) {
2024-02-04 03:36:24 -07:00
m_browsing_context - > page ( ) . client ( ) . page_did_request_link_context_menu ( top_level_position , url , link - > target ( ) . to_byte_string ( ) , modifiers ) ;
2022-03-15 14:37:58 +00:00
}
2022-03-30 18:38:52 +00:00
} else if ( button = = GUI : : MouseButton : : Secondary ) {
2022-04-08 21:48:00 -03:00
if ( is < HTML : : HTMLImageElement > ( * node ) ) {
auto & image_element = verify_cast < HTML : : HTMLImageElement > ( * node ) ;
auto image_url = image_element . document ( ) . parse_url ( image_element . src ( ) ) ;
2024-02-04 03:36:24 -07:00
m_browsing_context - > page ( ) . client ( ) . page_did_request_image_context_menu ( top_level_position , image_url , " " , modifiers , image_element . bitmap ( ) ) ;
2023-06-16 10:51:38 -04:00
} else if ( is < HTML : : HTMLMediaElement > ( * node ) ) {
auto & media_element = verify_cast < HTML : : HTMLMediaElement > ( * node ) ;
2023-05-15 09:42:56 -04:00
2023-06-16 10:51:38 -04:00
Page : : MediaContextMenu menu {
. media_url = media_element . document ( ) . parse_url ( media_element . current_src ( ) ) ,
. is_video = is < HTML : : HTMLVideoElement > ( * node ) ,
. is_playing = media_element . potentially_playing ( ) ,
2023-06-16 11:29:54 -04:00
. is_muted = media_element . muted ( ) ,
2023-06-16 10:51:38 -04:00
. has_user_agent_controls = media_element . has_attribute ( HTML : : AttributeNames : : controls ) ,
. is_looping = media_element . has_attribute ( HTML : : AttributeNames : : loop ) ,
} ;
2023-05-15 09:42:56 -04:00
2024-02-04 03:36:24 -07:00
m_browsing_context - > page ( ) . did_request_media_context_menu ( media_element . unique_id ( ) , top_level_position , " " , modifiers , move ( menu ) ) ;
2023-12-03 20:29:37 +13:00
} else {
2024-02-04 03:36:24 -07:00
m_browsing_context - > page ( ) . client ( ) . page_did_request_context_menu ( top_level_position ) ;
2022-04-08 21:48:00 -03:00
}
2022-04-07 22:43:30 +01:00
}
2022-03-15 14:37:58 +00:00
}
2022-02-07 13:27:17 +01:00
}
2020-06-07 14:40:38 +02:00
}
2022-05-03 16:50:38 +02:00
after_node_use :
2021-10-27 13:20:27 +02:00
if ( button = = GUI : : MouseButton : : Primary )
2020-06-07 14:40:38 +02:00
m_in_mouse_selection = false ;
return handled_event ;
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : handle_mousedown ( CSSPixelPoint position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers )
2020-06-07 14:40:38 +02:00
{
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) )
return false ;
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
m_browsing_context - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2020-06-07 14:40:38 +02:00
return false ;
2020-09-11 18:15:47 +02:00
2023-02-26 16:09:02 -07:00
JS : : NonnullGCPtr < DOM : : Document > document = * m_browsing_context - > active_document ( ) ;
2022-08-28 13:42:07 +02:00
JS : : GCPtr < DOM : : Node > node ;
2020-06-07 14:40:38 +02:00
2020-11-29 16:39:56 +01:00
{
2023-01-11 12:51:49 +01:00
JS : : GCPtr < Painting : : Paintable > paintable ;
2023-04-27 09:19:20 -04:00
if ( auto result = target_for_mouse_position ( position ) ; result . has_value ( ) )
2022-03-21 11:11:05 +01:00
paintable = result - > paintable ;
2023-04-27 09:19:20 -04:00
else
return false ;
2020-06-07 14:40:38 +02:00
2022-03-14 23:05:55 +00:00
auto pointer_events = paintable - > computed_values ( ) . pointer_events ( ) ;
2022-02-07 17:19:33 +00:00
// FIXME: Handle other values for pointer-events.
2022-10-19 13:46:48 +03:00
VERIFY ( pointer_events ! = CSS : : PointerEvents : : None ) ;
2021-10-19 12:53:22 +02:00
2022-10-23 17:53:02 +02:00
node = dom_node_for_event_dispatch ( * paintable ) ;
2020-11-29 16:39:56 +01:00
document - > set_hovered_node ( node ) ;
2020-09-12 17:55:19 +02:00
2022-03-14 23:05:55 +00:00
if ( paintable - > wants_mouse_events ( ) ) {
if ( paintable - > handle_mousedown ( { } , position , button , modifiers ) = = Painting : : Paintable : : DispatchEventOfSameName : : No )
return false ;
2020-11-29 16:39:56 +01:00
}
2020-09-11 18:15:47 +02:00
2020-11-29 16:39:56 +01:00
if ( ! node )
return false ;
2020-06-07 14:40:38 +02:00
2020-11-29 16:39:56 +01:00
if ( is < HTML : : HTMLIFrameElement > ( * node ) ) {
2022-02-06 14:41:29 +01:00
if ( auto * nested_browsing_context = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . nested_browsing_context ( ) )
2023-09-08 18:48:44 +02:00
return nested_browsing_context - > event_handler ( ) . handle_mousedown ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , button , buttons , modifiers ) ;
2020-11-29 16:39:56 +01:00
return false ;
}
2020-06-07 14:40:38 +02:00
2023-12-03 20:29:37 +13:00
m_browsing_context - > page ( ) . set_focused_browsing_context ( { } , m_browsing_context ) ;
2020-08-14 11:33:20 +02:00
2022-05-03 16:50:38 +02:00
// Search for the first parent of the hit target that's an element.
// "The click event type MUST be dispatched on the topmost event target indicated by the pointer." (https://www.w3.org/TR/uievents/#event-type-click)
// "The topmost event target MUST be the element highest in the rendering order which is capable of being an event target." (https://www.w3.org/TR/uievents/#topmost-event-target)
2023-02-25 10:44:51 -07:00
Layout : : Node * layout_node ;
2022-10-24 03:24:19 +02:00
if ( ! parent_element_for_event_dispatch ( * paintable , node , layout_node ) )
2022-05-03 16:50:38 +02:00
return false ;
2022-08-28 13:42:07 +02:00
m_mousedown_target = node . ptr ( ) ;
2022-05-03 16:50:38 +02:00
auto offset = compute_mouse_event_offset ( position , * layout_node ) ;
2022-12-31 16:04:48 +02:00
auto client_offset = compute_mouse_event_client_offset ( position ) ;
2022-12-31 16:40:36 +02:00
auto page_offset = compute_mouse_event_page_offset ( client_offset ) ;
2023-09-08 18:57:06 +02:00
node - > dispatch_event ( UIEvents : : MouseEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : mousedown , screen_position , page_offset , client_offset , offset , { } , button , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2020-11-29 16:39:56 +01:00
}
// NOTE: Dispatching an event may have disturbed the world.
2023-04-20 16:01:16 +01:00
if ( ! paint_root ( ) | | paint_root ( ) ! = node - > document ( ) . paintable_box ( ) )
2020-07-10 23:43:25 +02:00
return true ;
2022-03-15 14:37:58 +00:00
if ( button = = GUI : : MouseButton : : Primary ) {
2022-11-02 17:35:53 +00:00
if ( auto result = paint_root ( ) - > hit_test ( position , Painting : : HitTestType : : TextCursor ) ; result . has_value ( ) ) {
2022-03-15 14:37:58 +00:00
auto paintable = result - > paintable ;
if ( paintable - > dom_node ( ) ) {
2022-02-06 19:28:09 +01:00
// See if we want to focus something.
bool did_focus_something = false ;
2023-03-18 20:26:56 +01:00
for ( auto candidate = node ; candidate ; candidate = candidate - > parent_or_shadow_host ( ) ) {
2022-02-06 19:28:09 +01:00
if ( candidate - > is_focusable ( ) ) {
2022-11-05 14:34:24 +00:00
// When a user activates a click focusable focusable area, the user agent must run the focusing steps on the focusable area with focus trigger set to "click".
// Spec Note: Note that focusing is not an activation behavior, i.e. calling the click() method on an element or dispatching a synthetic click event on it won't cause the element to get focused.
HTML : : run_focusing_steps ( candidate . ptr ( ) , nullptr , " click " sv ) ;
2022-02-06 19:28:09 +01:00
did_focus_something = true ;
break ;
}
}
if ( ! did_focus_something ) {
2023-12-03 09:54:26 -05:00
if ( auto * focused_element = document - > focused_element ( ) )
HTML : : run_unfocusing_steps ( focused_element ) ;
2024-02-08 21:01:25 +00:00
}
2023-12-03 09:54:26 -05:00
2024-02-08 21:01:25 +00:00
// If we didn't focus anything, place the document text cursor at the mouse position.
// FIXME: This is all rather strange. Find a better solution.
if ( ! did_focus_something | | paintable - > dom_node ( ) - > is_editable ( ) ) {
2023-09-26 19:34:21 +02:00
auto & realm = document - > realm ( ) ;
m_browsing_context - > set_cursor_position ( DOM : : Position : : create ( realm , * paintable - > dom_node ( ) , result - > index_in_node ) ) ;
2023-01-11 19:48:53 +01:00
if ( auto selection = document - > get_selection ( ) ) {
( void ) selection - > set_base_and_extent ( * paintable - > dom_node ( ) , result - > index_in_node , * paintable - > dom_node ( ) , result - > index_in_node ) ;
}
2022-02-06 19:28:09 +01:00
m_in_mouse_selection = true ;
}
2020-08-05 16:55:56 +02:00
}
2020-06-27 14:21:58 -06:00
}
2020-06-07 14:40:38 +02:00
}
return true ;
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : handle_mousemove ( CSSPixelPoint position , CSSPixelPoint screen_position , u32 buttons , u32 modifiers )
2020-06-07 14:40:38 +02:00
{
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) )
return false ;
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
m_browsing_context - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2020-06-07 14:40:38 +02:00
return false ;
2020-09-11 18:15:47 +02:00
2023-02-26 16:09:02 -07:00
auto & document = * m_browsing_context - > active_document ( ) ;
2023-09-26 19:34:21 +02:00
auto & realm = document . realm ( ) ;
2020-06-07 14:40:38 +02:00
bool hovered_node_changed = false ;
bool is_hovering_link = false ;
2021-02-21 20:15:00 +00:00
Gfx : : StandardCursor hovered_node_cursor = Gfx : : StandardCursor : : None ;
2022-03-14 23:05:55 +00:00
2023-01-11 12:51:49 +01:00
JS : : GCPtr < Painting : : Paintable > paintable ;
2022-03-14 23:05:55 +00:00
Optional < int > start_index ;
2023-04-27 09:19:20 -04:00
if ( auto result = target_for_mouse_position ( position ) ; result . has_value ( ) ) {
paintable = result - > paintable ;
start_index = result - > index_in_node ;
2022-03-14 23:05:55 +00:00
}
2020-07-28 18:20:36 +02:00
const HTML : : HTMLAnchorElement * hovered_link_element = nullptr ;
2022-03-14 23:05:55 +00:00
if ( paintable ) {
if ( paintable - > wants_mouse_events ( ) ) {
2022-03-21 11:19:02 +01:00
document . set_hovered_node ( paintable - > dom_node ( ) ) ;
2022-03-14 23:05:55 +00:00
if ( paintable - > handle_mousemove ( { } , position , buttons , modifiers ) = = Painting : : Paintable : : DispatchEventOfSameName : : No )
return false ;
2020-09-11 18:15:47 +02:00
// FIXME: It feels a bit aggressive to always update the cursor like this.
2023-12-03 20:29:37 +13:00
m_browsing_context - > page ( ) . client ( ) . page_did_request_cursor_change ( Gfx : : StandardCursor : : None ) ;
2020-09-11 18:15:47 +02:00
}
2022-10-23 17:53:02 +02:00
auto node = dom_node_for_event_dispatch ( * paintable ) ;
2020-06-07 14:40:38 +02:00
2020-07-28 18:20:36 +02:00
if ( node & & is < HTML : : HTMLIFrameElement > ( * node ) ) {
2022-02-06 14:41:29 +01:00
if ( auto * nested_browsing_context = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . nested_browsing_context ( ) )
2023-09-08 18:48:44 +02:00
return nested_browsing_context - > event_handler ( ) . handle_mousemove ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , buttons , modifiers ) ;
2020-06-07 14:40:38 +02:00
return false ;
}
2023-12-08 10:54:40 +01:00
auto const cursor = paintable - > computed_values ( ) . cursor ( ) ;
2022-03-14 23:05:55 +00:00
auto pointer_events = paintable - > computed_values ( ) . pointer_events ( ) ;
2022-02-07 17:19:33 +00:00
// FIXME: Handle other values for pointer-events.
2022-10-19 13:46:48 +03:00
VERIFY ( pointer_events ! = CSS : : PointerEvents : : None ) ;
2021-10-05 19:47:13 +01:00
2022-11-07 18:22:33 +01:00
// Search for the first parent of the hit target that's an element.
// "The click event type MUST be dispatched on the topmost event target indicated by the pointer." (https://www.w3.org/TR/uievents/#event-type-click)
// "The topmost event target MUST be the element highest in the rendering order which is capable of being an event target." (https://www.w3.org/TR/uievents/#topmost-event-target)
2023-02-25 10:44:51 -07:00
Layout : : Node * layout_node ;
2022-11-07 18:22:33 +01:00
bool found_parent_element = parent_element_for_event_dispatch ( * paintable , node , layout_node ) ;
2022-08-28 13:42:07 +02:00
hovered_node_changed = node . ptr ( ) ! = document . hovered_node ( ) ;
2020-06-07 14:40:38 +02:00
document . set_hovered_node ( node ) ;
2022-11-07 18:22:33 +01:00
if ( found_parent_element ) {
2020-06-07 14:40:38 +02:00
hovered_link_element = node - > enclosing_link_element ( ) ;
2020-11-19 22:21:16 +01:00
if ( hovered_link_element )
2020-06-07 14:40:38 +02:00
is_hovering_link = true ;
2021-02-21 20:15:00 +00:00
2024-02-12 18:34:06 +00:00
if ( paintable - > layout_node ( ) . is_text_node ( ) ) {
2021-10-05 19:47:13 +01:00
if ( cursor = = CSS : : Cursor : : Auto )
hovered_node_cursor = Gfx : : StandardCursor : : IBeam ;
else
2022-02-15 21:48:48 -05:00
hovered_node_cursor = cursor_css_to_gfx ( cursor ) ;
} else if ( node - > is_element ( ) ) {
if ( cursor = = CSS : : Cursor : : Auto )
hovered_node_cursor = Gfx : : StandardCursor : : Arrow ;
else
2021-10-05 19:47:13 +01:00
hovered_node_cursor = cursor_css_to_gfx ( cursor ) ;
}
2021-02-21 20:15:00 +00:00
2022-05-03 16:50:38 +02:00
auto offset = compute_mouse_event_offset ( position , * layout_node ) ;
2022-12-31 16:04:48 +02:00
auto client_offset = compute_mouse_event_client_offset ( position ) ;
2022-12-31 16:40:36 +02:00
auto page_offset = compute_mouse_event_page_offset ( client_offset ) ;
2023-09-08 18:48:44 +02:00
auto movement = compute_mouse_event_movement ( screen_position ) ;
2023-12-02 10:54:17 -05:00
2023-09-08 18:48:44 +02:00
m_mousemove_previous_screen_position = screen_position ;
2023-12-02 10:54:17 -05:00
bool continue_ = node - > dispatch_event ( UIEvents : : MouseEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : mousemove , screen_position , page_offset , client_offset , offset , movement , 1 , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
if ( ! continue_ )
return false ;
2020-11-29 16:39:56 +01:00
// NOTE: Dispatching an event may have disturbed the world.
2023-04-20 16:01:16 +01:00
if ( ! paint_root ( ) | | paint_root ( ) ! = node - > document ( ) . paintable_box ( ) )
2020-07-10 23:43:25 +02:00
return true ;
2020-06-07 14:40:38 +02:00
}
if ( m_in_mouse_selection ) {
2022-11-02 17:35:53 +00:00
auto hit = paint_root ( ) - > hit_test ( position , Painting : : HitTestType : : TextCursor ) ;
2022-03-21 11:16:02 +01:00
if ( start_index . has_value ( ) & & hit . has_value ( ) & & hit - > dom_node ( ) ) {
2023-09-26 19:34:21 +02:00
m_browsing_context - > set_cursor_position ( DOM : : Position : : create ( realm , * hit - > dom_node ( ) , * start_index ) ) ;
2023-01-11 19:48:53 +01:00
if ( auto selection = document . get_selection ( ) ) {
auto anchor_node = selection - > anchor_node ( ) ;
if ( anchor_node )
( void ) selection - > set_base_and_extent ( * anchor_node , selection - > anchor_offset ( ) , * hit - > paintable - > dom_node ( ) , hit - > index_in_node ) ;
else
( void ) selection - > set_base_and_extent ( * hit - > paintable - > dom_node ( ) , hit - > index_in_node , * hit - > paintable - > dom_node ( ) , hit - > index_in_node ) ;
}
2023-08-22 16:00:42 +02:00
document . navigable ( ) - > set_needs_display ( ) ;
2020-08-05 16:55:56 +02:00
}
2020-06-07 14:40:38 +02:00
}
}
2020-08-17 12:54:41 +02:00
2023-12-03 20:29:37 +13:00
auto & page = m_browsing_context - > page ( ) ;
2020-11-12 18:23:05 +01:00
2023-12-03 20:29:37 +13:00
page . client ( ) . page_did_request_cursor_change ( hovered_node_cursor ) ;
if ( hovered_node_changed ) {
JS : : GCPtr < HTML : : HTMLElement const > hovered_html_element = document . hovered_node ( ) ? document . hovered_node ( ) - > enclosing_html_element_with_attribute ( HTML : : AttributeNames : : title ) : nullptr ;
if ( hovered_html_element & & hovered_html_element - > title ( ) . has_value ( ) ) {
2024-02-04 03:36:24 -07:00
page . client ( ) . page_did_enter_tooltip_area ( m_browsing_context - > active_document ( ) - > navigable ( ) - > to_top_level_position ( position ) , hovered_html_element - > title ( ) - > to_byte_string ( ) ) ;
2023-12-03 20:29:37 +13:00
} else {
page . client ( ) . page_did_leave_tooltip_area ( ) ;
2020-11-12 18:23:05 +01:00
}
2023-12-03 20:29:37 +13:00
if ( is_hovering_link )
page . client ( ) . page_did_hover_link ( document . parse_url ( hovered_link_element - > href ( ) ) ) ;
else
page . client ( ) . page_did_unhover_link ( ) ;
2020-06-07 14:40:38 +02:00
}
2023-12-03 20:29:37 +13:00
2020-06-07 14:40:38 +02:00
return true ;
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : handle_doubleclick ( CSSPixelPoint position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers )
2022-06-14 19:38:00 +02:00
{
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) )
return false ;
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
m_browsing_context - > active_document ( ) - > update_layout ( ) ;
2022-06-14 19:38:00 +02:00
if ( ! paint_root ( ) )
return false ;
2023-01-11 12:51:49 +01:00
JS : : GCPtr < Painting : : Paintable > paintable ;
2023-04-27 09:19:20 -04:00
if ( auto result = target_for_mouse_position ( position ) ; result . has_value ( ) )
2022-06-14 19:38:00 +02:00
paintable = result - > paintable ;
2023-04-27 09:19:20 -04:00
else
return false ;
2022-06-14 19:38:00 +02:00
auto pointer_events = paintable - > computed_values ( ) . pointer_events ( ) ;
// FIXME: Handle other values for pointer-events.
if ( pointer_events = = CSS : : PointerEvents : : None )
return false ;
2022-10-23 17:53:02 +02:00
auto node = dom_node_for_event_dispatch ( * paintable ) ;
2022-06-14 19:38:00 +02:00
if ( paintable - > wants_mouse_events ( ) ) {
// FIXME: Handle double clicks.
}
if ( ! node )
return false ;
if ( is < HTML : : HTMLIFrameElement > ( * node ) ) {
if ( auto * nested_browsing_context = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . nested_browsing_context ( ) )
2023-09-08 18:48:44 +02:00
return nested_browsing_context - > event_handler ( ) . handle_doubleclick ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , button , buttons , modifiers ) ;
2022-06-14 19:38:00 +02:00
return false ;
}
// Search for the first parent of the hit target that's an element.
// "The topmost event target MUST be the element highest in the rendering order which is capable of being an event target." (https://www.w3.org/TR/uievents/#topmost-event-target)
2023-02-25 10:44:51 -07:00
Layout : : Node * layout_node ;
2022-10-24 03:24:19 +02:00
if ( ! parent_element_for_event_dispatch ( * paintable , node , layout_node ) )
2022-06-14 19:38:00 +02:00
return false ;
2022-06-14 19:39:33 +02:00
auto offset = compute_mouse_event_offset ( position , * layout_node ) ;
2022-12-31 16:04:48 +02:00
auto client_offset = compute_mouse_event_client_offset ( position ) ;
2022-12-31 16:40:36 +02:00
auto page_offset = compute_mouse_event_page_offset ( client_offset ) ;
2023-09-08 18:57:06 +02:00
node - > dispatch_event ( UIEvents : : MouseEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : dblclick , screen_position , page_offset , client_offset , offset , { } , button , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-06-14 19:38:00 +02:00
2022-06-14 21:57:29 +02:00
// NOTE: Dispatching an event may have disturbed the world.
2023-04-20 16:01:16 +01:00
if ( ! paint_root ( ) | | paint_root ( ) ! = node - > document ( ) . paintable_box ( ) )
2022-06-14 21:57:29 +02:00
return true ;
if ( button = = GUI : : MouseButton : : Primary ) {
2022-11-02 17:35:53 +00:00
if ( auto result = paint_root ( ) - > hit_test ( position , Painting : : HitTestType : : TextCursor ) ; result . has_value ( ) ) {
2022-10-19 12:56:39 +01:00
auto hit_paintable = result - > paintable ;
if ( ! hit_paintable - > dom_node ( ) )
2022-06-14 21:57:29 +02:00
return true ;
2022-10-19 12:56:39 +01:00
auto const & hit_layout_node = hit_paintable - > layout_node ( ) ;
if ( ! hit_layout_node . is_text_node ( ) )
2022-06-14 21:57:29 +02:00
return true ;
2023-05-23 07:42:01 +02:00
auto & hit_dom_node = verify_cast < DOM : : Text > ( * hit_paintable - > dom_node ( ) ) ;
2022-10-19 12:56:39 +01:00
auto const & text_for_rendering = verify_cast < Layout : : TextNode > ( hit_layout_node ) . text_for_rendering ( ) ;
2022-06-14 21:57:29 +02:00
int first_word_break_before = [ & ] {
// Start from one before the index position to prevent selecting only spaces between words, caused by the addition below.
// This also helps us dealing with cases where index is equal to the string length.
for ( int i = result - > index_in_node - 1 ; i > = 0 ; - - i ) {
2023-11-21 10:56:29 +13:00
if ( is_ascii_space ( text_for_rendering . bytes_as_string_view ( ) [ i ] ) ) {
2022-06-14 21:57:29 +02:00
// Don't include the space in the selection
return i + 1 ;
}
}
return 0 ;
} ( ) ;
int first_word_break_after = [ & ] {
2023-11-21 10:56:29 +13:00
for ( size_t i = result - > index_in_node ; i < text_for_rendering . bytes ( ) . size ( ) ; + + i ) {
if ( is_ascii_space ( text_for_rendering . bytes_as_string_view ( ) [ i ] ) )
2022-06-14 21:57:29 +02:00
return i ;
}
2023-11-21 10:56:29 +13:00
return text_for_rendering . bytes ( ) . size ( ) ;
2022-06-14 21:57:29 +02:00
} ( ) ;
2023-09-26 19:34:21 +02:00
auto & realm = node - > document ( ) . realm ( ) ;
m_browsing_context - > set_cursor_position ( DOM : : Position : : create ( realm , hit_dom_node , first_word_break_after ) ) ;
2023-01-11 19:48:53 +01:00
if ( auto selection = node - > document ( ) . get_selection ( ) ) {
2023-05-23 07:42:01 +02:00
( void ) selection - > set_base_and_extent ( hit_dom_node , first_word_break_before , hit_dom_node , first_word_break_after ) ;
2023-01-11 19:48:53 +01:00
}
2022-06-14 21:57:29 +02:00
}
}
2022-06-14 19:38:00 +02:00
return true ;
}
2020-08-14 19:40:37 +02:00
bool EventHandler : : focus_next_element ( )
2020-08-02 12:10:01 +02:00
{
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > active_document ( ) )
2020-08-14 19:40:37 +02:00
return false ;
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
2023-02-26 16:09:02 -07:00
auto * element = m_browsing_context - > active_document ( ) - > focused_element ( ) ;
2020-08-14 19:40:37 +02:00
if ( ! element ) {
2023-02-26 16:09:02 -07:00
element = m_browsing_context - > active_document ( ) - > first_child_of_type < DOM : : Element > ( ) ;
2020-08-14 19:40:37 +02:00
if ( element & & element - > is_focusable ( ) ) {
2023-02-26 16:09:02 -07:00
m_browsing_context - > active_document ( ) - > set_focused_element ( element ) ;
2020-08-14 19:40:37 +02:00
return true ;
}
}
for ( element = element - > next_element_in_pre_order ( ) ; element & & ! element - > is_focusable ( ) ; element = element - > next_element_in_pre_order ( ) )
;
2023-02-26 16:09:02 -07:00
m_browsing_context - > active_document ( ) - > set_focused_element ( element ) ;
2020-08-14 19:40:37 +02:00
return element ;
}
bool EventHandler : : focus_previous_element ( )
{
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > active_document ( ) )
2022-02-08 21:23:43 +01:00
return false ;
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
2023-02-26 16:09:02 -07:00
auto * element = m_browsing_context - > active_document ( ) - > focused_element ( ) ;
2022-02-08 21:23:43 +01:00
if ( ! element ) {
2023-02-26 16:09:02 -07:00
element = m_browsing_context - > active_document ( ) - > last_child_of_type < DOM : : Element > ( ) ;
2022-02-08 21:23:43 +01:00
if ( element & & element - > is_focusable ( ) ) {
2023-02-26 16:09:02 -07:00
m_browsing_context - > active_document ( ) - > set_focused_element ( element ) ;
2022-02-08 21:23:43 +01:00
return true ;
}
}
for ( element = element - > previous_element_in_pre_order ( ) ; element & & ! element - > is_focusable ( ) ; element = element - > previous_element_in_pre_order ( ) )
;
2023-02-26 16:09:02 -07:00
m_browsing_context - > active_document ( ) - > set_focused_element ( element ) ;
2022-02-08 21:23:43 +01:00
return element ;
2020-08-14 19:40:37 +02:00
}
2024-01-30 20:09:33 +00:00
constexpr bool should_ignore_keydown_event ( u32 code_point , u32 modifiers )
2021-05-18 13:13:58 +02:00
{
2024-03-16 19:50:39 -04:00
if ( modifiers & ( KeyModifier : : Mod_Ctrl | KeyModifier : : Mod_Alt | KeyModifier : : Mod_Super ) )
2024-01-30 20:09:33 +00:00
return true ;
2021-06-01 10:01:11 +02:00
// FIXME: There are probably also keys with non-zero code points that should be filtered out.
2022-02-19 22:22:45 +01:00
return code_point = = 0 | | code_point = = 27 ;
2021-05-18 13:13:58 +02:00
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : fire_keyboard_event ( FlyString const & event_name , HTML : : BrowsingContext & browsing_context , KeyCode key , u32 modifiers , u32 code_point )
2022-11-05 15:36:03 +00:00
{
2023-07-26 19:56:09 +02:00
JS : : GCPtr < DOM : : Document > document = browsing_context . active_document ( ) ;
2022-11-05 15:36:03 +00:00
if ( ! document )
return false ;
2023-10-03 15:35:07 +02:00
if ( ! document - > is_fully_active ( ) )
return false ;
2022-11-05 15:36:03 +00:00
if ( JS : : GCPtr < DOM : : Element > focused_element = document - > focused_element ( ) ) {
2022-12-12 12:20:02 +01:00
if ( is < HTML : : NavigableContainer > ( * focused_element ) ) {
auto & navigable_container = verify_cast < HTML : : NavigableContainer > ( * focused_element ) ;
if ( navigable_container . nested_browsing_context ( ) )
return fire_keyboard_event ( event_name , * navigable_container . nested_browsing_context ( ) , key , modifiers , code_point ) ;
2022-11-05 15:36:03 +00:00
}
2023-08-13 13:05:26 +02:00
auto event = UIEvents : : KeyboardEvent : : create_from_platform_event ( document - > realm ( ) , event_name , key , modifiers , code_point ) ;
2023-11-30 15:40:06 -05:00
return focused_element - > dispatch_event ( event ) ;
2022-11-05 15:36:03 +00:00
}
2022-11-09 10:56:12 -05:00
// FIXME: De-duplicate this. This is just to prevent wasting a KeyboardEvent allocation when recursing into an (i)frame.
2023-08-13 13:05:26 +02:00
auto event = UIEvents : : KeyboardEvent : : create_from_platform_event ( document - > realm ( ) , event_name , key , modifiers , code_point ) ;
2022-11-05 15:36:03 +00:00
if ( JS : : GCPtr < HTML : : HTMLElement > body = document - > body ( ) )
2023-11-30 15:40:06 -05:00
return body - > dispatch_event ( event ) ;
2022-11-05 15:36:03 +00:00
2023-11-30 15:40:06 -05:00
return document - > root ( ) . dispatch_event ( event ) ;
2022-11-05 15:36:03 +00:00
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : handle_keydown ( KeyCode key , u32 modifiers , u32 code_point )
2020-08-14 19:40:37 +02:00
{
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > active_document ( ) )
2021-10-12 14:44:00 +02:00
return false ;
2023-10-03 15:35:07 +02:00
if ( ! m_browsing_context - > active_document ( ) - > is_fully_active ( ) )
return false ;
2021-10-12 14:44:00 +02:00
2023-02-26 16:09:02 -07:00
JS : : NonnullGCPtr < DOM : : Document > document = * m_browsing_context - > active_document ( ) ;
2021-10-12 14:44:00 +02:00
if ( ! document - > layout_node ( ) )
return false ;
2020-08-14 19:40:37 +02:00
if ( key = = KeyCode : : Key_Tab ) {
if ( modifiers & KeyModifier : : Mod_Shift )
return focus_previous_element ( ) ;
2021-10-12 14:44:52 +02:00
return focus_next_element ( ) ;
2020-08-14 19:40:37 +02:00
}
2023-09-26 19:34:21 +02:00
auto & realm = document - > realm ( ) ;
2023-01-11 19:48:53 +01:00
if ( auto selection = document - > get_selection ( ) ) {
auto range = selection - > range ( ) ;
2024-02-24 02:09:02 +01:00
if ( range & & ! range - > collapsed ( ) & & range - > start_container ( ) - > is_editable ( ) ) {
2023-01-11 19:48:53 +01:00
selection - > remove_all_ranges ( ) ;
2020-12-01 23:36:12 +01:00
2020-12-14 10:58:10 +01:00
// FIXME: This doesn't work for some reason?
2023-09-26 19:34:21 +02:00
m_browsing_context - > set_cursor_position ( DOM : : Position : : create ( realm , * range - > start_container ( ) , range - > start_offset ( ) ) ) ;
2020-12-03 18:20:17 +01:00
2020-12-14 10:58:10 +01:00
if ( key = = KeyCode : : Key_Backspace | | key = = KeyCode : : Key_Delete ) {
2022-08-09 01:06:47 +02:00
m_edit_event_handler - > handle_delete ( * range ) ;
2020-12-01 23:36:12 +01:00
return true ;
2021-10-12 14:44:52 +02:00
}
2024-01-30 20:09:33 +00:00
// FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here.
if ( ! should_ignore_keydown_event ( code_point , modifiers ) ) {
2022-08-09 01:06:47 +02:00
m_edit_event_handler - > handle_delete ( * range ) ;
2023-09-26 19:34:21 +02:00
m_edit_event_handler - > handle_insert ( JS : : NonnullGCPtr { * m_browsing_context - > cursor_position ( ) } , code_point ) ;
2023-02-26 16:09:02 -07:00
m_browsing_context - > increment_cursor_position_offset ( ) ;
2020-12-14 10:58:10 +01:00
return true ;
}
2020-12-01 23:36:12 +01:00
}
}
2023-07-02 21:40:46 -07:00
if ( auto * element = m_browsing_context - > active_document ( ) - > focused_element ( ) ; is < HTML : : HTMLMediaElement > ( element ) ) {
auto & media_element = static_cast < HTML : : HTMLMediaElement & > ( * element ) ;
media_element . handle_keydown ( { } , key ) . release_value_but_fixme_should_propagate_errors ( ) ;
}
2023-11-30 15:42:57 -05:00
bool continue_ = fire_keyboard_event ( UIEvents : : EventNames : : keydown , m_browsing_context , key , modifiers , code_point ) ;
if ( ! continue_ )
return false ;
2023-09-26 19:34:21 +02:00
if ( m_browsing_context - > cursor_position ( ) & & m_browsing_context - > cursor_position ( ) - > node ( ) - > is_editable ( ) ) {
2020-12-01 23:35:47 +01:00
if ( key = = KeyCode : : Key_Backspace ) {
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > decrement_cursor_position_offset ( ) ) {
2021-05-18 13:08:25 +02:00
// FIXME: Move to the previous node and delete the last character there.
return true ;
}
2020-12-02 15:00:55 +01:00
2023-09-26 19:34:21 +02:00
m_edit_event_handler - > handle_delete_character_after ( * m_browsing_context - > cursor_position ( ) ) ;
2020-12-02 15:00:55 +01:00
return true ;
2021-10-12 14:44:52 +02:00
}
if ( key = = KeyCode : : Key_Delete ) {
2023-09-26 19:34:21 +02:00
if ( m_browsing_context - > cursor_position ( ) - > offset_is_at_end_of_node ( ) ) {
2021-05-18 13:08:25 +02:00
// FIXME: Move to the next node and delete the first character there.
return true ;
}
2023-09-26 19:34:21 +02:00
m_edit_event_handler - > handle_delete_character_after ( * m_browsing_context - > cursor_position ( ) ) ;
2020-12-02 15:00:55 +01:00
return true ;
2021-10-12 14:44:52 +02:00
}
if ( key = = KeyCode : : Key_Right ) {
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > increment_cursor_position_offset ( ) ) {
2021-05-18 13:08:25 +02:00
// FIXME: Move to the next node.
}
2020-12-02 15:00:55 +01:00
return true ;
2021-10-12 14:44:52 +02:00
}
if ( key = = KeyCode : : Key_Left ) {
2023-02-26 16:09:02 -07:00
if ( ! m_browsing_context - > decrement_cursor_position_offset ( ) ) {
2021-05-18 13:08:25 +02:00
// FIXME: Move to the previous node.
}
2020-08-02 16:05:59 +02:00
return true ;
2021-10-12 14:44:52 +02:00
}
2022-02-19 18:39:19 +01:00
if ( key = = KeyCode : : Key_Home ) {
2024-02-24 02:53:35 +01:00
auto & cursor_position_node = * m_browsing_context - > cursor_position ( ) - > node ( ) ;
if ( cursor_position_node . is_text ( ) )
m_browsing_context - > set_cursor_position ( DOM : : Position : : create ( realm , cursor_position_node , 0 ) ) ;
2022-02-19 18:39:19 +01:00
return true ;
}
2022-02-19 18:42:08 +01:00
if ( key = = KeyCode : : Key_End ) {
2024-02-24 02:53:35 +01:00
auto & cursor_position_node = * m_browsing_context - > cursor_position ( ) - > node ( ) ;
if ( cursor_position_node . is_text ( ) ) {
auto & text_node = static_cast < DOM : : Text & > ( cursor_position_node ) ;
m_browsing_context - > set_cursor_position ( DOM : : Position : : create ( realm , text_node , ( unsigned ) text_node . data ( ) . bytes ( ) . size ( ) ) ) ;
}
2022-02-19 18:42:08 +01:00
return true ;
}
2024-01-31 13:26:01 -05:00
if ( key = = KeyCode : : Key_Return ) {
2024-02-24 02:53:35 +01:00
HTML : : HTMLInputElement * input_element = nullptr ;
if ( auto node = m_browsing_context - > cursor_position ( ) - > node ( ) ) {
if ( node - > is_text ( ) ) {
auto & text_node = static_cast < DOM : : Text & > ( * node ) ;
if ( is < HTML : : HTMLInputElement > ( text_node . editable_text_node_owner ( ) ) )
input_element = static_cast < HTML : : HTMLInputElement * > ( text_node . editable_text_node_owner ( ) ) ;
} else if ( node - > is_html_input_element ( ) ) {
input_element = static_cast < HTML : : HTMLInputElement * > ( node . ptr ( ) ) ;
}
}
if ( input_element ) {
if ( auto * form = input_element - > form ( ) ) {
2024-01-31 13:26:01 -05:00
form - > implicitly_submit_form ( ) . release_value_but_fixme_should_propagate_errors ( ) ;
return true ;
}
2024-02-24 02:53:35 +01:00
input_element - > commit_pending_changes ( ) ;
2024-01-31 13:26:01 -05:00
return true ;
}
2023-11-30 12:54:30 -05:00
}
2024-01-30 20:09:33 +00:00
// FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here.
if ( ! should_ignore_keydown_event ( code_point , modifiers ) ) {
2023-09-26 19:34:21 +02:00
m_edit_event_handler - > handle_insert ( JS : : NonnullGCPtr { * m_browsing_context - > cursor_position ( ) } , code_point ) ;
2023-02-26 16:09:02 -07:00
m_browsing_context - > increment_cursor_position_offset ( ) ;
2021-05-18 13:13:58 +02:00
return true ;
2020-08-02 16:05:59 +02:00
}
2020-08-02 12:10:01 +02:00
}
2020-12-01 23:35:47 +01:00
2022-11-05 15:37:19 +00:00
// FIXME: Work out and implement the difference between this and keydown.
2024-02-12 18:19:09 +00:00
return ! fire_keyboard_event ( UIEvents : : EventNames : : keypress , m_browsing_context , key , modifiers , code_point ) ;
2020-08-02 12:10:01 +02:00
}
2024-01-09 19:17:24 +01:00
bool EventHandler : : handle_keyup ( KeyCode key , u32 modifiers , u32 code_point )
2021-09-28 15:39:35 +02:00
{
2024-02-12 18:19:09 +00:00
return ! fire_keyboard_event ( UIEvents : : EventNames : : keyup , m_browsing_context , key , modifiers , code_point ) ;
2021-09-28 15:39:35 +02:00
}
2024-01-14 10:14:36 +01:00
void EventHandler : : set_mouse_event_tracking_paintable ( Painting : : Paintable * paintable )
2020-09-11 18:15:47 +02:00
{
2024-01-14 10:14:36 +01:00
m_mouse_event_tracking_paintable = paintable ;
2020-09-11 18:15:47 +02:00
}
2020-12-03 18:46:56 +01:00
2022-12-31 16:04:48 +02:00
CSSPixelPoint EventHandler : : compute_mouse_event_client_offset ( CSSPixelPoint event_page_position ) const
{
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-mouseevent-clientx
// The clientX attribute must return the x-coordinate of the position where the event occurred relative to the origin of the viewport.
2022-12-31 16:40:36 +02:00
2023-08-22 16:00:42 +02:00
auto scroll_offset = m_browsing_context - > active_document ( ) - > navigable ( ) - > viewport_scroll_offset ( ) ;
2022-11-03 12:49:54 +00:00
return event_page_position . translated ( - scroll_offset ) ;
2022-12-31 16:04:48 +02:00
}
2022-12-31 16:40:36 +02:00
CSSPixelPoint EventHandler : : compute_mouse_event_page_offset ( CSSPixelPoint event_client_offset ) const
{
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-mouseevent-pagex
// FIXME: 1. If the event’ s dispatch flag is set, return the horizontal coordinate of the position where the event occurred relative to the origin of the initial containing block and terminate these steps.
// 2. Let offset be the value of the scrollX attribute of the event’ s associated Window object, if there is one, or zero otherwise.
2023-08-22 16:00:42 +02:00
auto scroll_offset = m_browsing_context - > active_document ( ) - > navigable ( ) - > viewport_scroll_offset ( ) ;
2022-12-31 16:40:36 +02:00
// 3. Return the sum of offset and the value of the event’ s clientX attribute.
2022-11-03 12:49:54 +00:00
return event_client_offset . translated ( scroll_offset ) ;
2022-12-31 16:40:36 +02:00
}
2023-04-27 09:19:20 -04:00
2023-09-08 18:48:44 +02:00
CSSPixelPoint EventHandler : : compute_mouse_event_movement ( CSSPixelPoint screen_position ) const
2023-08-24 17:47:13 +02:00
{
// https://w3c.github.io/pointerlock/#dom-mouseevent-movementx
// The attributes movementX movementY must provide the change in position of the pointer,
// as if the values of screenX, screenY, were stored between two subsequent mousemove events eNow and ePrevious and the difference taken movementX = eNow.screenX-ePrevious.screenX.
2023-09-08 18:48:44 +02:00
if ( ! m_mousemove_previous_screen_position . has_value ( ) )
2023-08-24 17:47:13 +02:00
// When unlocked, the system cursor can exit and re-enter the user agent window.
// If it does so and the user agent was not the target of operating system mouse move events
// then the most recent pointer position will be unknown to the user agent and movementX/movementY can not be computed and must be set to zero.
// FIXME: For this to actually work, m_mousemove_previous_client_offset needs to be cleared when the mouse leaves the window
return { 0 , 0 } ;
2023-09-08 18:48:44 +02:00
return { screen_position . x ( ) - m_mousemove_previous_screen_position . value ( ) . x ( ) , screen_position . y ( ) - m_mousemove_previous_screen_position . value ( ) . y ( ) } ;
2023-08-24 17:47:13 +02:00
}
2023-04-27 09:19:20 -04:00
Optional < EventHandler : : Target > EventHandler : : target_for_mouse_position ( CSSPixelPoint position )
{
2024-01-14 10:14:36 +01:00
if ( m_mouse_event_tracking_paintable ) {
if ( m_mouse_event_tracking_paintable - > wants_mouse_events ( ) )
return Target { m_mouse_event_tracking_paintable , { } } ;
2023-04-27 09:19:20 -04:00
2024-01-14 10:14:36 +01:00
m_mouse_event_tracking_paintable = nullptr ;
2023-04-27 09:19:20 -04:00
}
if ( auto result = paint_root ( ) - > hit_test ( position , Painting : : HitTestType : : Exact ) ; result . has_value ( ) )
return Target { result - > paintable . ptr ( ) , result - > index_in_node } ;
return { } ;
}
2024-01-14 10:14:36 +01:00
void EventHandler : : visit_edges ( JS : : Cell : : Visitor & visitor ) const
{
visitor . visit ( m_mouse_event_tracking_paintable ) ;
}
2020-06-07 14:40:38 +02:00
}