2020-06-07 14:40:38 +02:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2020 - 2021 , Andreas Kling < andreas @ ladybird . 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
*/
2024-10-08 17:15:55 -04:00
# include <LibUnicode/CharacterTypes.h>
2024-08-27 08:19:03 -04:00
# include <LibUnicode/Segmenter.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>
2024-06-21 22:53:05 +01:00
# include <LibWeb/HTML/CloseWatcherManager.h>
2022-11-05 14:34:24 +00:00
# include <LibWeb/HTML/Focus.h>
2024-08-26 13:07:57 +02:00
# include <LibWeb/HTML/FormAssociatedElement.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>
2024-08-26 13:07:57 +02:00
# include <LibWeb/HTML/HTMLTextAreaElement.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>
2024-08-17 13:29:55 -04:00
# include <LibWeb/Page/DragAndDropEventHandler.h>
2024-08-17 12:58:58 -04:00
# include <LibWeb/Page/EditEventHandler.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>
2024-03-18 10:25:57 +01:00
# include <LibWeb/Painting/TextPaintable.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>
2024-06-02 19:00:42 +02:00
# include <LibWeb/UIEvents/MouseButton.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
{
2024-07-12 11:06:08 +01:00
auto * current_ancestor_node = node . ptr ( ) ;
do {
if ( is < HTML : : FormAssociatedElement > ( current_ancestor_node ) & & ! dynamic_cast < HTML : : FormAssociatedElement * > ( current_ancestor_node ) - > enabled ( ) ) {
return false ;
}
} while ( ( current_ancestor_node = current_ancestor_node - > parent ( ) ) ) ;
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
} ;
}
2024-04-26 16:59:04 +02:00
EventHandler : : EventHandler ( Badge < HTML : : Navigable > , HTML : : Navigable & navigable )
: m_navigable ( navigable )
2024-08-02 07:31:40 -04:00
, m_edit_event_handler ( make < EditEventHandler > ( ) )
2024-08-17 13:29:55 -04:00
, m_drag_and_drop_event_handler ( make < DragAndDropEventHandler > ( ) )
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 ( )
{
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2022-03-11 00:03:28 +01:00
return nullptr ;
2024-04-26 16:59:04 +02:00
return m_navigable - > active_document ( ) - > paintable_box ( ) ;
2022-03-11 00:03:28 +01:00
}
Painting : : PaintableBox const * EventHandler : : paint_root ( ) const
{
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2022-03-11 00:03:28 +01:00
return nullptr ;
2024-04-26 16:59:04 +02:00
return m_navigable - > active_document ( ) - > paintable_box ( ) ;
2022-03-11 00:03:28 +01:00
}
2024-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_mousewheel ( CSSPixelPoint viewport_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
{
2024-08-17 13:29:55 -04:00
if ( should_ignore_device_input_event ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2023-10-03 15:35:07 +02:00
2024-08-06 15:32:31 +03:00
auto position = viewport_position ;
2024-06-03 17:53:55 +03:00
2024-04-26 16:59:04 +02:00
m_navigable - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2021-02-22 19:48:24 +01:00
2024-06-06 13:29:08 -06:00
if ( modifiers & UIEvents : : KeyModifier : : Mod_Shift )
2022-01-20 22:35:01 +01:00
swap ( wheel_delta_x , wheel_delta_y ) ;
2024-09-12 12:56:31 -04:00
auto handled_event = EventResult : : Dropped ;
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 )
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
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 ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
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 ( ) ) ) ;
2024-04-26 16:59:04 +02:00
iframe . content_navigable ( ) - > event_handler ( ) . handle_mousewheel ( position_in_iframe , screen_position , button , buttons , modifiers , wheel_delta_x , wheel_delta_y ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-10-17 20:54:01 +03:00
}
// 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 ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-10-17 20:54:01 +03:00
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-04-26 16:59:04 +02:00
m_navigable - > active_window ( ) - > scroll_by ( wheel_delta_x , wheel_delta_y ) ;
2022-10-17 20:54:01 +03:00
}
2024-09-12 12:56:31 -04:00
handled_event = EventResult : : Handled ;
2022-10-17 20:54:01 +03:00
}
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-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_mouseup ( CSSPixelPoint viewport_position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers )
2020-06-07 14:40:38 +02:00
{
2024-08-17 13:29:55 -04:00
if ( should_ignore_device_input_event ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2023-10-03 15:35:07 +02:00
2024-08-06 15:32:31 +03:00
auto position = viewport_position ;
2024-06-03 17:53:55 +03:00
2024-04-26 16:59:04 +02:00
m_navigable - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2020-06-07 14:40:38 +02: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 ;
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 )
2024-09-12 12:56:31 -04:00
return EventResult : : Cancelled ;
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 ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
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
}
2024-09-12 12:56:31 -04:00
auto handled_event = EventResult : : Dropped ;
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 ) ) {
2024-04-26 16:59:04 +02:00
if ( auto content_navigable = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . content_navigable ( ) )
return content_navigable - > event_handler ( ) . handle_mouseup ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , button , buttons , modifiers ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-03-14 23:05:55 +00: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
// 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 ( ) ) ;
2024-09-12 12:56:31 -04:00
handled_event = EventResult : : Handled ;
2022-03-14 23:05:55 +00:00
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 ) {
2024-06-02 19:00:42 +02:00
if ( button = = UIEvents : : MouseButton : : Primary ) {
2024-09-29 08:54:29 -04: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 , { } , button , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2024-06-21 22:00:19 +01:00
} else if ( button = = UIEvents : : MouseButton : : Middle ) {
2024-09-29 08:54:29 -04:00
run_activation_behavior = node - > dispatch_event ( UIEvents : : MouseEvent : : create_from_platform_event ( node - > realm ( ) , UIEvents : : EventNames : : auxclick , screen_position , page_offset , client_offset , offset , { } , button , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2024-06-02 19:00:42 +02:00
} else if ( button = = UIEvents : : MouseButton : : Secondary ) {
2024-04-25 10:04:11 -04:00
// Allow the user to bypass custom context menus by holding shift, like Firefox.
2024-06-06 13:29:08 -06:00
if ( ( modifiers & UIEvents : : Mod_Shift ) = = 0 )
2024-09-29 08:54:29 -04: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 , { } , button , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2024-04-25 10:04:11 -04:00
else
run_activation_behavior = true ;
}
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
2023-02-25 10:44:51 -07:00
if ( JS : : GCPtr < HTML : : HTMLAnchorElement const > link = node - > enclosing_link_element ( ) ) {
2024-04-26 16:59:04 +02:00
JS : : NonnullGCPtr < DOM : : Document > document = * m_navigable - > active_document ( ) ;
2022-04-08 21:48:00 -03:00
auto href = link - > href ( ) ;
auto url = document - > parse_url ( href ) ;
2024-08-01 13:43:06 -04:00
2024-08-01 13:41:20 -04:00
if ( button = = UIEvents : : MouseButton : : Primary & & ( modifiers & UIEvents : : Mod_PlatformCtrl ) ! = 0 ) {
m_navigable - > page ( ) . client ( ) . page_did_click_link ( url , link - > target ( ) . to_byte_string ( ) , modifiers ) ;
} else if ( button = = UIEvents : : MouseButton : : Middle ) {
2024-04-26 16:59:04 +02:00
m_navigable - > page ( ) . client ( ) . page_did_middle_click_link ( url , link - > target ( ) . to_byte_string ( ) , modifiers ) ;
2024-06-02 19:00:42 +02:00
} else if ( button = = UIEvents : : MouseButton : : Secondary ) {
2024-06-03 17:53:55 +03:00
m_navigable - > page ( ) . client ( ) . page_did_request_link_context_menu ( viewport_position , url , link - > target ( ) . to_byte_string ( ) , modifiers ) ;
2022-03-15 14:37:58 +00:00
}
2024-06-02 19:00:42 +02:00
} else if ( button = = UIEvents : : 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-06-03 17:53:55 +03:00
m_navigable - > page ( ) . client ( ) . page_did_request_image_context_menu ( viewport_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-06-03 17:53:55 +03:00
m_navigable - > page ( ) . did_request_media_context_menu ( media_element . unique_id ( ) , viewport_position , " " , modifiers , move ( menu ) ) ;
2023-12-03 20:29:37 +13:00
} else {
2024-06-03 17:53:55 +03:00
m_navigable - > page ( ) . client ( ) . page_did_request_context_menu ( viewport_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 :
2024-08-26 13:07:57 +02:00
if ( button = = UIEvents : : MouseButton : : Primary ) {
2020-06-07 14:40:38 +02:00
m_in_mouse_selection = false ;
2024-08-26 13:07:57 +02:00
update_selection_range_for_input_or_textarea ( ) ;
}
2020-06-07 14:40:38 +02:00
return handled_event ;
}
2024-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_mousedown ( CSSPixelPoint viewport_position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers )
2020-06-07 14:40:38 +02:00
{
2024-08-17 13:29:55 -04:00
if ( should_ignore_device_input_event ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2023-10-03 15:35:07 +02:00
2024-08-06 15:32:31 +03:00
auto position = viewport_position ;
2024-06-03 17:53:55 +03:00
2024-04-26 16:59:04 +02:00
m_navigable - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2020-09-11 18:15:47 +02:00
2024-04-26 16:59:04 +02:00
JS : : NonnullGCPtr < DOM : : Document > document = * m_navigable - > 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
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
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 )
2024-09-12 12:56:31 -04:00
return EventResult : : Cancelled ;
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 )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2020-06-07 14:40:38 +02:00
2020-11-29 16:39:56 +01:00
if ( is < HTML : : HTMLIFrameElement > ( * node ) ) {
2024-04-26 16:59:04 +02:00
if ( auto content_navigable = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . content_navigable ( ) )
return content_navigable - > event_handler ( ) . handle_mousedown ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , button , buttons , modifiers ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2020-11-29 16:39:56 +01:00
}
2020-06-07 14:40:38 +02:00
2024-04-26 16:59:04 +02:00
m_navigable - > page ( ) . set_focused_navigable ( { } , m_navigable ) ;
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 ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-05-03 16:50:38 +02:00
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 ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Accepted ;
2020-07-10 23:43:25 +02:00
2024-06-02 19:00:42 +02:00
if ( button = = UIEvents : : 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 ;
2024-08-19 16:22:31 -03:00
auto dom_node = paintable - > dom_node ( ) ;
if ( dom_node ) {
2022-02-06 19:28:09 +01:00
// See if we want to focus something.
2024-08-26 13:07:57 +02:00
JS : : GCPtr < DOM : : Node > focus_candidate ;
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 ( ) ) {
2024-08-26 13:07:57 +02:00
focus_candidate = candidate ;
2022-02-06 19:28:09 +01:00
break ;
}
}
2024-08-26 12:23:21 -04: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.
2024-08-26 13:07:57 +02:00
if ( ! focus_candidate | | dom_node - > is_editable ( ) ) {
2023-09-26 19:34:21 +02:00
auto & realm = document - > realm ( ) ;
2024-08-19 16:22:31 -03:00
document - > set_cursor_position ( DOM : : Position : : create ( realm , * dom_node , result - > index_in_node ) ) ;
2023-01-11 19:48:53 +01:00
if ( auto selection = document - > get_selection ( ) ) {
2024-06-05 17:20:58 +01:00
auto anchor_node = selection - > anchor_node ( ) ;
2024-06-06 13:29:08 -06:00
if ( anchor_node & & modifiers & UIEvents : : KeyModifier : : Mod_Shift ) {
2024-08-19 16:22:31 -03:00
( void ) selection - > set_base_and_extent ( * anchor_node , selection - > anchor_offset ( ) , * dom_node , result - > index_in_node ) ;
2024-06-05 16:01:31 +01:00
} else {
2024-08-19 16:22:31 -03:00
( void ) selection - > set_base_and_extent ( * dom_node , result - > index_in_node , * dom_node , result - > index_in_node ) ;
2024-06-05 16:01:31 +01:00
}
2023-01-11 19:48:53 +01:00
}
2024-08-26 13:07:57 +02:00
update_selection_range_for_input_or_textarea ( ) ;
2022-02-06 19:28:09 +01:00
m_in_mouse_selection = true ;
}
2024-08-26 13:07:57 +02: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.
if ( focus_candidate )
HTML : : run_focusing_steps ( focus_candidate , nullptr , " click " sv ) ;
else if ( auto * focused_element = document - > focused_element ( ) )
HTML : : run_unfocusing_steps ( focused_element ) ;
2020-08-05 16:55:56 +02:00
}
2020-06-27 14:21:58 -06:00
}
2020-06-07 14:40:38 +02:00
}
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2020-06-07 14:40:38 +02:00
}
2024-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_mousemove ( CSSPixelPoint viewport_position , CSSPixelPoint screen_position , u32 buttons , u32 modifiers )
2020-06-07 14:40:38 +02:00
{
2024-08-17 13:29:55 -04:00
if ( should_ignore_device_input_event ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2023-10-03 15:35:07 +02:00
2024-08-06 15:32:31 +03:00
auto position = viewport_position ;
2024-06-03 17:53:55 +03:00
2024-04-26 16:59:04 +02:00
m_navigable - > active_document ( ) - > update_layout ( ) ;
2022-03-16 18:50:56 +01:00
2022-03-11 00:03:28 +01:00
if ( ! paint_root ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2020-09-11 18:15:47 +02:00
2024-04-26 16:59:04 +02:00
auto & document = * m_navigable - > 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 )
2024-09-12 12:56:31 -04:00
return EventResult : : Cancelled ;
2022-03-14 23:05:55 +00:00
2020-09-11 18:15:47 +02:00
// FIXME: It feels a bit aggressive to always update the cursor like this.
2024-04-26 16:59:04 +02:00
m_navigable - > 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 ) ) {
2024-04-26 16:59:04 +02:00
if ( auto content_navigable = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . content_navigable ( ) )
return content_navigable - > event_handler ( ) . handle_mousemove ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , buttons , modifiers ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2020-06-07 14:40:38 +02:00
}
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
2024-09-29 08:53:43 -04: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 , UIEvents : : MouseButton : : Primary , buttons , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2023-12-02 10:54:17 -05:00
if ( ! continue_ )
2024-09-12 12:56:31 -04:00
return EventResult : : Cancelled ;
2023-12-02 10:54:17 -05:00
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 ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Accepted ;
2020-06-07 14:40:38 +02:00
}
2024-09-12 12:56:31 -04:00
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 ) ;
2024-07-02 10:17:47 +01:00
auto should_set_cursor_position = true ;
2022-03-21 11:16:02 +01:00
if ( start_index . has_value ( ) & & hit . has_value ( ) & & hit - > dom_node ( ) ) {
2023-01-11 19:48:53 +01:00
if ( auto selection = document . get_selection ( ) ) {
auto anchor_node = selection - > anchor_node ( ) ;
2024-07-02 10:17:47 +01:00
if ( anchor_node ) {
if ( & anchor_node - > root ( ) = = & hit - > dom_node ( ) - > root ( ) )
( void ) selection - > set_base_and_extent ( * anchor_node , selection - > anchor_offset ( ) , * hit - > paintable - > dom_node ( ) , hit - > index_in_node ) ;
else
should_set_cursor_position = false ;
} else {
2023-01-11 19:48:53 +01:00
( void ) selection - > set_base_and_extent ( * hit - > paintable - > dom_node ( ) , hit - > index_in_node , * hit - > paintable - > dom_node ( ) , hit - > index_in_node ) ;
2024-07-02 10:17:47 +01:00
}
2023-01-11 19:48:53 +01:00
}
2024-07-02 10:17:47 +01:00
if ( should_set_cursor_position )
2024-08-02 07:31:40 -04:00
document . set_cursor_position ( DOM : : Position : : create ( realm , * hit - > dom_node ( ) , * start_index ) ) ;
2024-07-02 10:17:47 +01:00
2024-08-19 02:06:52 +02:00
document . 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
2024-04-26 16:59:04 +02:00
auto & page = m_navigable - > 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-07-02 14:16:24 +02:00
page . client ( ) . page_did_enter_tooltip_area ( 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
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2020-06-07 14:40:38 +02:00
}
2024-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_doubleclick ( CSSPixelPoint viewport_position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers )
2022-06-14 19:38:00 +02:00
{
2024-08-17 13:29:55 -04:00
if ( should_ignore_device_input_event ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2023-10-03 15:35:07 +02:00
2024-08-02 07:31:40 -04:00
auto & document = * m_navigable - > active_document ( ) ;
auto scroll_offset = document . navigable ( ) - > viewport_scroll_offset ( ) ;
2024-06-03 17:53:55 +03:00
auto position = viewport_position . translated ( scroll_offset ) ;
2024-08-02 07:31:40 -04:00
document . update_layout ( ) ;
2022-06-14 19:38:00 +02:00
if ( ! paint_root ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-06-14 19:38:00 +02: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-06-14 19:38:00 +02:00
paintable = result - > paintable ;
2023-04-27 09:19:20 -04:00
else
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
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 )
2024-09-12 12:56:31 -04:00
return EventResult : : Cancelled ;
2022-06-14 19:38:00 +02:00
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 )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-06-14 19:38:00 +02:00
if ( is < HTML : : HTMLIFrameElement > ( * node ) ) {
2024-04-26 16:59:04 +02:00
if ( auto content_navigable = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . content_navigable ( ) )
return content_navigable - > event_handler ( ) . handle_doubleclick ( position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , button , buttons , modifiers ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-06-14 19:38:00 +02:00
}
// 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 ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2022-06-14 19:38:00 +02:00
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 ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Accepted ;
2022-06-14 21:57:29 +02:00
2024-06-02 19:00:42 +02:00
if ( button = = UIEvents : : MouseButton : : Primary ) {
2022-11-02 17:35:53 +00:00
if ( auto result = paint_root ( ) - > hit_test ( position , Painting : : HitTestType : : TextCursor ) ; result . has_value ( ) ) {
2024-03-18 10:25:57 +01:00
if ( ! result - > paintable - > dom_node ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Accepted ;
2024-03-18 10:25:57 +01:00
if ( ! is < Painting : : TextPaintable > ( * result - > paintable ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Accepted ;
2024-03-18 10:25:57 +01:00
auto & hit_paintable = static_cast < Painting : : TextPaintable const & > ( * result - > paintable ) ;
2023-05-23 07:42:01 +02:00
2024-03-18 10:25:57 +01:00
auto & hit_dom_node = const_cast < DOM : : Text & > ( verify_cast < DOM : : Text > ( * hit_paintable . dom_node ( ) ) ) ;
auto const & text_for_rendering = hit_paintable . text_for_rendering ( ) ;
2022-06-14 21:57:29 +02:00
2024-08-27 08:19:03 -04:00
auto & segmenter = word_segmenter ( ) ;
segmenter . set_segmented_text ( text_for_rendering ) ;
2022-06-14 21:57:29 +02:00
2024-08-27 08:19:03 -04:00
auto previous_boundary = segmenter . previous_boundary ( result - > index_in_node , Unicode : : Segmenter : : Inclusive : : Yes ) . value_or ( 0 ) ;
auto next_boundary = segmenter . next_boundary ( result - > index_in_node ) . value_or ( text_for_rendering . byte_count ( ) ) ;
2022-06-14 21:57:29 +02:00
2023-09-26 19:34:21 +02:00
auto & realm = node - > document ( ) . realm ( ) ;
2024-08-27 08:19:03 -04:00
document . set_cursor_position ( DOM : : Position : : create ( realm , hit_dom_node , next_boundary ) ) ;
2023-01-11 19:48:53 +01:00
if ( auto selection = node - > document ( ) . get_selection ( ) ) {
2024-08-27 08:19:03 -04:00
( void ) selection - > set_base_and_extent ( hit_dom_node , previous_boundary , hit_dom_node , next_boundary ) ;
2023-01-11 19:48:53 +01:00
}
2024-08-26 13:07:57 +02:00
update_selection_range_for_input_or_textarea ( ) ;
2022-06-14 21:57:29 +02:00
}
}
2022-06-14 19:38:00 +02:00
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2022-06-14 19:38:00 +02:00
}
2024-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_drag_and_drop_event ( DragEvent : : Type type , CSSPixelPoint viewport_position , CSSPixelPoint screen_position , u32 button , u32 buttons , u32 modifiers , Vector < HTML : : SelectedFile > files )
2024-08-17 13:29:55 -04:00
{
if ( ! m_navigable - > active_document ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
auto & document = * m_navigable - > active_document ( ) ;
document . update_layout ( ) ;
if ( ! paint_root ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
JS : : GCPtr < Painting : : Paintable > paintable ;
if ( auto result = target_for_mouse_position ( viewport_position ) ; result . has_value ( ) )
paintable = result - > paintable ;
else
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
auto node = dom_node_for_event_dispatch ( * paintable ) ;
if ( ! node )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
if ( is < HTML : : HTMLIFrameElement > ( * node ) ) {
if ( auto content_navigable = static_cast < HTML : : HTMLIFrameElement & > ( * node ) . content_navigable ( ) )
return content_navigable - > event_handler ( ) . handle_drag_and_drop_event ( type , viewport_position . translated ( compute_mouse_event_offset ( { } , paintable - > layout_node ( ) ) ) , screen_position , button , buttons , modifiers , move ( files ) ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-08-17 13:29:55 -04:00
}
auto offset = compute_mouse_event_offset ( viewport_position , paintable - > layout_node ( ) ) ;
auto client_offset = compute_mouse_event_client_offset ( viewport_position ) ;
auto page_offset = compute_mouse_event_page_offset ( client_offset ) ;
switch ( type ) {
case DragEvent : : Type : : DragStart :
return m_drag_and_drop_event_handler - > handle_drag_start ( document . realm ( ) , screen_position , page_offset , client_offset , offset , button , buttons , modifiers , move ( files ) ) ;
case DragEvent : : Type : : DragMove :
return m_drag_and_drop_event_handler - > handle_drag_move ( document . realm ( ) , document , * node , screen_position , page_offset , client_offset , offset , button , buttons , modifiers ) ;
case DragEvent : : Type : : DragEnd :
return m_drag_and_drop_event_handler - > handle_drag_leave ( document . realm ( ) , screen_position , page_offset , client_offset , offset , button , buttons , modifiers ) ;
case DragEvent : : Type : : Drop :
return m_drag_and_drop_event_handler - > handle_drop ( document . realm ( ) , screen_position , page_offset , client_offset , offset , button , buttons , modifiers ) ;
}
VERIFY_NOT_REACHED ( ) ;
}
2020-08-14 19:40:37 +02:00
bool EventHandler : : focus_next_element ( )
2020-08-02 12:10:01 +02:00
{
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2020-08-14 19:40:37 +02:00
return false ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2023-10-03 15:35:07 +02:00
return false ;
2024-04-26 16:59:04 +02:00
auto * element = m_navigable - > active_document ( ) - > focused_element ( ) ;
2020-08-14 19:40:37 +02:00
if ( ! element ) {
2024-04-26 16:59:04 +02:00
element = m_navigable - > active_document ( ) - > first_child_of_type < DOM : : Element > ( ) ;
2020-08-14 19:40:37 +02:00
if ( element & & element - > is_focusable ( ) ) {
2024-04-26 16:59:04 +02:00
m_navigable - > 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 ( ) )
;
2024-04-26 16:59:04 +02:00
m_navigable - > active_document ( ) - > set_focused_element ( element ) ;
2020-08-14 19:40:37 +02:00
return element ;
}
bool EventHandler : : focus_previous_element ( )
{
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2022-02-08 21:23:43 +01:00
return false ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2023-10-03 15:35:07 +02:00
return false ;
2024-04-26 16:59:04 +02:00
auto * element = m_navigable - > active_document ( ) - > focused_element ( ) ;
2022-02-08 21:23:43 +01:00
if ( ! element ) {
2024-04-26 16:59:04 +02:00
element = m_navigable - > active_document ( ) - > last_child_of_type < DOM : : Element > ( ) ;
2022-02-08 21:23:43 +01:00
if ( element & & element - > is_focusable ( ) ) {
2024-04-26 16:59:04 +02:00
m_navigable - > 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 ( ) )
;
2024-04-26 16:59:04 +02:00
m_navigable - > 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-06-06 13:29:08 -06:00
if ( modifiers & ( UIEvents : : KeyModifier : : Mod_Ctrl | UIEvents : : KeyModifier : : Mod_Alt | UIEvents : : 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-09-12 12:56:31 -04:00
EventResult EventHandler : : fire_keyboard_event ( FlyString const & event_name , HTML : : Navigable & navigable , UIEvents : : KeyCode key , u32 modifiers , u32 code_point )
2022-11-05 15:36:03 +00:00
{
2024-04-26 16:59:04 +02:00
JS : : GCPtr < DOM : : Document > document = navigable . active_document ( ) ;
2022-11-05 15:36:03 +00:00
if ( ! document )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2023-10-03 15:35:07 +02:00
if ( ! document - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
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 ) ;
2024-04-26 16:59:04 +02:00
if ( navigable_container . content_navigable ( ) )
return fire_keyboard_event ( event_name , * navigable_container . content_navigable ( ) , 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 ) ;
2024-09-12 12:56:31 -04:00
return focused_element - > dispatch_event ( event ) ? EventResult : : Accepted : EventResult : : Cancelled ;
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
2024-09-12 12:56:31 -04:00
JS : : GCPtr target = document - > body ( ) ? : & document - > root ( ) ;
return target - > dispatch_event ( event ) ? EventResult : : Accepted : EventResult : : Cancelled ;
2022-11-05 15:36:03 +00:00
}
2024-10-08 17:15:55 -04:00
// https://w3c.github.io/uievents/#unicode-character-categories
static bool produces_character_value ( u32 code_point )
{
// A subset of the General Category values that are defined for each Unicode code point. This subset contains all
// the Letter (Ll, Lm, Lo, Lt, Lu), Number (Nd, Nl, No), Punctuation (Pc, Pd, Pe, Pf, Pi, Po, Ps) and Symbol (Sc,
// Sk, Sm, So) category values.
return Unicode : : code_point_has_letter_general_category ( code_point )
| | Unicode : : code_point_has_number_general_category ( code_point )
| | Unicode : : code_point_has_punctuation_general_category ( code_point )
| | Unicode : : code_point_has_symbol_general_category ( code_point ) ;
}
2024-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_keydown ( UIEvents : : KeyCode key , u32 modifiers , u32 code_point )
2020-08-14 19:40:37 +02:00
{
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2024-04-26 16:59:04 +02:00
if ( ! m_navigable - > active_document ( ) - > is_fully_active ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Dropped ;
2021-10-12 14:44:00 +02:00
2024-10-09 07:39:08 -04:00
auto dispatch_result = fire_keyboard_event ( UIEvents : : EventNames : : keydown , m_navigable , key , modifiers , code_point ) ;
if ( dispatch_result ! = EventResult : : Accepted )
return dispatch_result ;
2024-10-08 17:15:55 -04:00
// https://w3c.github.io/uievents/#event-type-keypress
// If supported by a user agent, this event MUST be dispatched when a key is pressed down, if and only if that key
// normally produces a character value.
if ( produces_character_value ( code_point ) ) {
dispatch_result = fire_keyboard_event ( UIEvents : : EventNames : : keypress , m_navigable , key , modifiers , code_point ) ;
if ( dispatch_result ! = EventResult : : Accepted )
return dispatch_result ;
}
2024-10-09 07:39:08 -04:00
2024-04-26 16:59:04 +02:00
JS : : NonnullGCPtr < DOM : : Document > document = * m_navigable - > active_document ( ) ;
2021-10-12 14:44:00 +02:00
2024-06-06 13:29:08 -06:00
if ( key = = UIEvents : : KeyCode : : Key_Tab ) {
if ( modifiers & UIEvents : : KeyModifier : : Mod_Shift )
2024-09-12 12:56:31 -04:00
return focus_previous_element ( ) ? EventResult : : Handled : EventResult : : Dropped ;
return focus_next_element ( ) ? EventResult : : Handled : EventResult : : Dropped ;
2020-08-14 19:40:37 +02:00
}
2024-08-19 22:50:55 -04:00
// https://html.spec.whatwg.org/multipage/interaction.html#close-requests
if ( key = = UIEvents : : KeyCode : : Key_Escape ) {
// 7. Let closedSomething be the result of processing close watchers on document's relevant global object.
auto closed_something = document - > window ( ) - > close_watcher_manager ( ) - > process_close_watchers ( ) ;
// 8. If closedSomething is true, then return.
if ( closed_something )
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-08-19 22:50:55 -04:00
// 9. Alternative processing: Otherwise, there was nothing watching for a close request. The user agent may
// instead interpret this interaction as some other action, instead of interpreting it as a close request.
}
2024-06-21 22:53:05 +01:00
2023-09-26 19:34:21 +02:00
auto & realm = document - > realm ( ) ;
2024-09-04 20:03:50 -04:00
auto selection = document - > get_selection ( ) ;
auto range = [ & ] ( ) - > JS : : GCPtr < DOM : : Range > {
if ( selection ) {
if ( auto range = selection - > range ( ) ; range & & ! range - > collapsed ( ) )
return range ;
}
return nullptr ;
} ( ) ;
if ( selection & & range & & range - > start_container ( ) - > is_editable ( ) ) {
auto clear_selection = [ & ] ( ) {
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?
2024-08-02 07:31:40 -04:00
document - > set_cursor_position ( DOM : : Position : : create ( realm , * range - > start_container ( ) , range - > start_offset ( ) ) ) ;
2024-09-04 20:03:50 -04:00
} ;
2020-12-03 18:20:17 +01:00
2024-09-04 20:03:50 -04:00
if ( key = = UIEvents : : KeyCode : : Key_Backspace | | key = = UIEvents : : KeyCode : : Key_Delete ) {
clear_selection ( ) ;
m_edit_event_handler - > handle_delete ( document , * range ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-09-04 20:03:50 -04:00
}
2024-09-12 12:56:31 -04:00
2024-09-04 20:03:50 -04:00
// FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here.
if ( ! should_ignore_keydown_event ( code_point , modifiers ) ) {
clear_selection ( ) ;
m_edit_event_handler - > handle_delete ( document , * range ) ;
m_edit_event_handler - > handle_insert ( document , JS : : NonnullGCPtr { * document - > cursor_position ( ) } , code_point ) ;
document - > increment_cursor_position_offset ( ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2020-12-01 23:36:12 +01:00
}
}
2024-04-26 16:59:04 +02:00
if ( auto * element = m_navigable - > active_document ( ) - > focused_element ( ) ; is < HTML : : HTMLMediaElement > ( element ) ) {
2023-07-02 21:40:46 -07:00
auto & media_element = static_cast < HTML : : HTMLMediaElement & > ( * element ) ;
2024-09-04 04:30:44 +01:00
if ( media_element . handle_keydown ( { } , key , modifiers ) . release_value_but_fixme_should_propagate_errors ( ) )
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2023-07-02 21:40:46 -07:00
}
2024-09-07 17:56:32 -04:00
if ( document - > cursor_position ( ) ) {
2024-09-04 20:03:50 -04:00
auto & node = * document - > cursor_position ( ) - > node ( ) ;
2024-09-07 17:56:32 -04:00
if ( key = = UIEvents : : KeyCode : : Key_Backspace & & node . is_editable ( ) ) {
2024-08-02 07:31:40 -04:00
if ( ! document - > decrement_cursor_position_offset ( ) ) {
2021-05-18 13:08:25 +02:00
// FIXME: Move to the previous node and delete the last character there.
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2021-05-18 13:08:25 +02:00
}
2020-12-02 15:00:55 +01:00
2024-08-02 07:31:40 -04:00
m_edit_event_handler - > handle_delete_character_after ( document , * document - > cursor_position ( ) ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2021-10-12 14:44:52 +02:00
}
2024-09-07 17:56:32 -04:00
if ( key = = UIEvents : : KeyCode : : Key_Delete & & node . is_editable ( ) ) {
2024-08-02 07:31:40 -04:00
if ( document - > 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.
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2021-05-18 13:08:25 +02:00
}
2024-09-12 12:56:31 -04:00
2024-08-02 07:31:40 -04:00
m_edit_event_handler - > handle_delete_character_after ( document , * document - > cursor_position ( ) ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2021-10-12 14:44:52 +02:00
}
2024-09-04 20:03:50 -04:00
2024-09-05 12:43:07 -04:00
# if defined(AK_OS_MACOS)
if ( ( modifiers & UIEvents : : Mod_Super ) ! = 0 ) {
if ( key = = UIEvents : : KeyCode : : Key_Left ) {
key = UIEvents : : KeyCode : : Key_Home ;
modifiers & = ~ UIEvents : : Mod_Super ;
}
if ( key = = UIEvents : : KeyCode : : Key_Right ) {
key = UIEvents : : KeyCode : : Key_End ;
modifiers & = ~ UIEvents : : Mod_Super ;
}
}
# endif
2024-09-04 20:03:50 -04:00
if ( key = = UIEvents : : KeyCode : : Key_Left | | key = = UIEvents : : KeyCode : : Key_Right ) {
auto increment_or_decrement_cursor = [ & ] ( ) {
2024-09-05 12:10:57 -04:00
if ( ( modifiers & UIEvents : : Mod_PlatformWordJump ) = = 0 ) {
if ( key = = UIEvents : : KeyCode : : Key_Left )
return document - > decrement_cursor_position_offset ( ) ;
return document - > increment_cursor_position_offset ( ) ;
}
2024-09-04 20:03:50 -04:00
if ( key = = UIEvents : : KeyCode : : Key_Left )
2024-09-05 12:10:57 -04:00
return document - > decrement_cursor_position_to_previous_word ( ) ;
return document - > increment_cursor_position_to_next_word ( ) ;
2024-09-04 20:03:50 -04:00
} ;
2024-09-07 17:56:32 -04:00
if ( ( modifiers & UIEvents : : Mod_Shift ) ! = 0 ) {
2024-09-04 20:03:50 -04:00
auto previous_position = document - > cursor_position ( ) - > offset ( ) ;
auto should_udpdate_selection = increment_or_decrement_cursor ( ) ;
if ( should_udpdate_selection & & selection ) {
auto selection_start = range ? selection - > anchor_offset ( ) : previous_position ;
auto selection_end = document - > cursor_position ( ) - > offset ( ) ;
( void ) selection - > set_base_and_extent ( node , selection_start , node , selection_end ) ;
}
2024-09-07 17:56:32 -04:00
} else if ( node . is_editable ( ) ) {
if ( selection & & range ) {
auto cursor_edge = key = = UIEvents : : KeyCode : : Key_Left ? range - > start_offset ( ) : range - > end_offset ( ) ;
document - > set_cursor_position ( DOM : : Position : : create ( document - > realm ( ) , node , cursor_edge ) ) ;
selection - > remove_all_ranges ( ) ;
} else {
increment_or_decrement_cursor ( ) ;
}
2021-05-18 13:08:25 +02:00
}
2024-09-04 20:03:50 -04:00
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2021-10-12 14:44:52 +02:00
}
2024-09-04 20:03:50 -04:00
if ( key = = UIEvents : : KeyCode : : Key_Home | | key = = UIEvents : : KeyCode : : Key_End ) {
auto cursor_edge = key = = UIEvents : : KeyCode : : Key_Home ? 0u z : node . length ( ) ;
2024-09-07 17:56:32 -04:00
if ( ( modifiers & UIEvents : : Mod_Shift ) ! = 0 ) {
2024-09-04 20:03:50 -04:00
auto previous_position = document - > cursor_position ( ) - > offset ( ) ;
auto should_udpdate_selection = previous_position ! = cursor_edge ;
if ( should_udpdate_selection & & selection ) {
auto selection_start = range ? selection - > anchor_offset ( ) : previous_position ;
( void ) selection - > set_base_and_extent ( node , selection_start , node , cursor_edge ) ;
}
2024-09-07 17:56:32 -04:00
} else if ( node . is_editable ( ) ) {
if ( selection & & range )
selection - > remove_all_ranges ( ) ;
2024-02-24 02:53:35 +01:00
}
2024-09-04 20:03:50 -04:00
document - > set_cursor_position ( DOM : : Position : : create ( realm , node , cursor_edge ) ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2022-02-19 18:42:08 +01:00
}
2024-09-04 20:03:50 -04:00
2024-09-07 17:56:32 -04:00
if ( key = = UIEvents : : KeyCode : : Key_Return & & node . is_editable ( ) ) {
2024-02-24 02:53:35 +01:00
HTML : : HTMLInputElement * input_element = nullptr ;
2024-08-02 07:31:40 -04:00
if ( auto node = document - > cursor_position ( ) - > node ( ) ) {
2024-02-24 02:53:35 +01:00
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 ( ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-01-31 13:26:01 -05:00
}
2024-02-24 02:53:35 +01:00
input_element - > commit_pending_changes ( ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-01-31 13:26:01 -05:00
}
2023-11-30 12:54:30 -05:00
}
2024-09-04 20:03:50 -04:00
2024-01-30 20:09:33 +00:00
// FIXME: Text editing shortcut keys (copy/paste etc.) should be handled here.
2024-09-07 17:56:32 -04:00
if ( ! should_ignore_keydown_event ( code_point , modifiers ) & & node . is_editable ( ) ) {
2024-08-02 07:31:40 -04:00
m_edit_event_handler - > handle_insert ( document , JS : : NonnullGCPtr { * document - > cursor_position ( ) } , code_point ) ;
document - > increment_cursor_position_offset ( ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2020-08-02 16:05:59 +02:00
}
2020-08-02 12:10:01 +02:00
}
2020-12-01 23:35:47 +01:00
2024-08-26 13:07:57 +02:00
update_selection_range_for_input_or_textarea ( ) ;
2024-07-01 16:03:50 -04:00
// FIXME: Implement scroll by line and by page instead of approximating the behavior of other browsers.
auto arrow_key_scroll_distance = 100 ;
auto page_scroll_distance = document - > window ( ) - > inner_height ( ) - ( document - > window ( ) - > outer_height ( ) - document - > window ( ) - > inner_height ( ) ) ;
switch ( key ) {
case UIEvents : : KeyCode : : Key_Up :
case UIEvents : : KeyCode : : Key_Down :
if ( modifiers & & modifiers ! = UIEvents : : KeyModifier : : Mod_Ctrl )
break ;
if ( modifiers )
key = = UIEvents : : KeyCode : : Key_Up ? document - > scroll_to_the_beginning_of_the_document ( ) : document - > window ( ) - > scroll_by ( 0 , INT64_MAX ) ;
else
document - > window ( ) - > scroll_by ( 0 , key = = UIEvents : : KeyCode : : Key_Up ? - arrow_key_scroll_distance : arrow_key_scroll_distance ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-07-01 16:03:50 -04:00
case UIEvents : : KeyCode : : Key_Left :
case UIEvents : : KeyCode : : Key_Right :
2024-10-09 08:56:55 -04:00
if ( modifiers & & modifiers ! = UIEvents : : KeyModifier : : Mod_Alt )
2024-07-01 16:03:50 -04:00
break ;
if ( modifiers )
document - > page ( ) . traverse_the_history_by_delta ( key = = UIEvents : : KeyCode : : Key_Left ? - 1 : 1 ) ;
else
document - > window ( ) - > scroll_by ( key = = UIEvents : : KeyCode : : Key_Left ? - arrow_key_scroll_distance : arrow_key_scroll_distance , 0 ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-07-01 16:03:50 -04:00
case UIEvents : : KeyCode : : Key_PageUp :
case UIEvents : : KeyCode : : Key_PageDown :
2024-10-09 08:56:55 -04:00
if ( modifiers ! = UIEvents : : KeyModifier : : Mod_None )
2024-07-01 16:03:50 -04:00
break ;
document - > window ( ) - > scroll_by ( 0 , key = = UIEvents : : KeyCode : : Key_PageUp ? - page_scroll_distance : page_scroll_distance ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-07-01 16:03:50 -04:00
case UIEvents : : KeyCode : : Key_Home :
document - > scroll_to_the_beginning_of_the_document ( ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-07-01 16:03:50 -04:00
case UIEvents : : KeyCode : : Key_End :
document - > window ( ) - > scroll_by ( 0 , INT64_MAX ) ;
2024-09-12 12:56:31 -04:00
return EventResult : : Handled ;
2024-07-01 16:03:50 -04:00
default :
break ;
}
2024-10-09 07:39:08 -04:00
return EventResult : : Accepted ;
2020-08-02 12:10:01 +02:00
}
2024-09-12 12:56:31 -04:00
EventResult EventHandler : : handle_keyup ( UIEvents : : KeyCode key , u32 modifiers , u32 code_point )
2021-09-28 15:39:35 +02:00
{
2024-09-12 12:56:31 -04:00
return fire_keyboard_event ( UIEvents : : EventNames : : keyup , m_navigable , key , modifiers , code_point ) ;
2021-09-28 15:39:35 +02:00
}
2024-03-22 11:56:49 +01:00
void EventHandler : : handle_paste ( String const & text )
{
2024-04-26 16:59:04 +02:00
auto active_document = m_navigable - > active_document ( ) ;
2024-03-22 11:56:49 +01:00
if ( ! active_document )
return ;
if ( ! active_document - > is_fully_active ( ) )
return ;
2024-08-02 07:31:40 -04:00
if ( auto cursor_position = active_document - > cursor_position ( ) ) {
2024-06-21 16:07:14 +01:00
if ( ! cursor_position - > node ( ) - > is_editable ( ) )
return ;
2024-03-22 11:56:49 +01:00
active_document - > update_layout ( ) ;
2024-08-02 07:31:40 -04:00
m_edit_event_handler - > handle_insert ( * active_document , * cursor_position , text ) ;
2024-03-22 11:56:49 +01:00
cursor_position - > set_offset ( cursor_position - > offset ( ) + text . code_points ( ) . length ( ) ) ;
}
}
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
2024-04-26 16:59:04 +02:00
auto scroll_offset = m_navigable - > 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.
2024-04-26 16:59:04 +02:00
auto scroll_offset = m_navigable - > 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-08-17 13:29:55 -04:00
bool EventHandler : : should_ignore_device_input_event ( ) const
{
// From the moment that the user agent is to initiate the drag-and-drop operation, until the end of the drag-and-drop
// operation, device input events (e.g. mouse and keyboard events) must be suppressed.
return m_drag_and_drop_event_handler - > has_ongoing_drag_and_drop_operation ( ) ;
}
2024-01-14 10:14:36 +01:00
void EventHandler : : visit_edges ( JS : : Cell : : Visitor & visitor ) const
{
2024-08-17 13:29:55 -04:00
m_drag_and_drop_event_handler - > visit_edges ( visitor ) ;
2024-01-14 10:14:36 +01:00
visitor . visit ( m_mouse_event_tracking_paintable ) ;
}
2024-08-26 13:07:57 +02:00
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#textFieldSelection:set-the-selection-range
void EventHandler : : update_selection_range_for_input_or_textarea ( )
{
// Where possible, user interface features for changing the text selection in input and
// textarea elements must be implemented using the set the selection range algorithm so that,
// e.g., all the same events fire.
// NOTE: It seems like only new selections are registered with the respective elements. I.e.
// existing selections in other elements are not cleared, so we only need to set the
// selection range for the element with the current selection.
// Get the active selection
auto active_document = m_navigable - > active_document ( ) ;
if ( ! active_document )
return ;
auto selection = active_document - > get_selection ( ) ;
if ( ! selection )
return ;
// Do we have a range within the same node?
auto range = selection - > range ( ) ;
if ( ! range | | range - > start_container ( ) ! = range - > end_container ( ) )
return ;
// We are only interested in text nodes with a shadow root
auto & node = * range - > start_container ( ) ;
if ( ! node . is_text ( ) )
return ;
auto & root = node . root ( ) ;
if ( ! root . is_shadow_root ( ) )
return ;
2024-08-29 12:21:26 -04:00
auto * shadow_host = root . parent_or_shadow_host ( ) ;
if ( ! shadow_host )
return ;
2024-08-26 13:07:57 +02:00
// Invoke "set the selection range" on the form associated element
auto selection_start = range - > start_offset ( ) ;
auto selection_end = range - > end_offset ( ) ;
// FIXME: support selection directions other than ::Forward
auto direction = HTML : : SelectionDirection : : Forward ;
2024-08-30 17:22:58 +01:00
Optional < HTML : : FormAssociatedTextControlElement & > target { } ;
2024-08-29 12:21:26 -04:00
if ( is < HTML : : HTMLInputElement > ( * shadow_host ) )
target = static_cast < HTML : : HTMLInputElement & > ( * shadow_host ) ;
else if ( is < HTML : : HTMLTextAreaElement > ( * shadow_host ) )
target = static_cast < HTML : : HTMLTextAreaElement & > ( * shadow_host ) ;
2024-08-26 13:07:57 +02:00
if ( target . has_value ( ) )
2024-09-10 11:28:44 +10:00
target . value ( ) . set_the_selection_range ( selection_start , selection_end , direction , HTML : : SelectionSource : : UI ) ;
2024-08-26 13:07:57 +02:00
}
2024-08-27 08:19:03 -04:00
Unicode : : Segmenter & EventHandler : : word_segmenter ( )
{
if ( ! m_word_segmenter )
2024-09-22 10:03:23 -04:00
m_word_segmenter = m_navigable - > active_document ( ) - > word_segmenter ( ) . clone ( ) ;
2024-08-27 08:19:03 -04:00
return * m_word_segmenter ;
}
2020-06-07 14:40:38 +02:00
}