LibWeb: Work around a spec bug regarding cancelling dragenter events

The spec dictates that dragenter events must be cancelled in order for
drops to be accepted on the entered element. Web reality disagrees, as
all three major browsers do not have this requirement.
This commit is contained in:
Timothy Flynn 2026-04-04 20:17:37 -04:00 committed by Tim Flynn
parent dafb5cbe05
commit b7076c366d
5 changed files with 33 additions and 36 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -177,7 +177,6 @@ EventResult DragAndDropEventHandler::handle_drag_start(
// https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model:queue-a-task
EventResult DragAndDropEventHandler::handle_drag_move(
JS::Realm& realm,
GC::Ref<DOM::Document> document,
GC::Ref<DOM::Node> node,
CSSPixelPoint screen_position,
CSSPixelPoint page_offset,
@ -227,39 +226,27 @@ EventResult DragAndDropEventHandler::handle_drag_move(
// -> Otherwise
else {
// Fire a DND event named dragenter at the immediate user selection.
auto drag_event = fire_a_drag_and_drop_event(m_immediate_user_selection, HTML::EventNames::dragenter);
fire_a_drag_and_drop_event(m_immediate_user_selection, HTML::EventNames::dragenter);
// If the event is canceled, then set the current target element to the immediate user selection.
if (drag_event->cancelled()) {
m_current_target_element = m_immediate_user_selection;
}
// Otherwise, run the appropriate step from the following list:
else {
// -> If the immediate user selection is a text control (e.g., textarea, or an input element whose
// type attribute is in the Text state) or an editing host or editable element, and the drag data
// store item list has an item with the drag data item type string "text/plain" and the drag data
// item kind text
if (allow_text_drop(*m_immediate_user_selection)) {
// Set the current target element to the immediate user selection anyway.
m_current_target_element = m_immediate_user_selection;
}
// -> If the immediate user selection is the body element
else if (m_immediate_user_selection == document->body()) {
// Leave the current target element unchanged.
}
// -> Otherwise
else {
// Fire a DND event named dragenter at the body element, if there is one, or at the Document
// object, if not. Then, set the current target element to the body element, regardless of
// whether that event was canceled or not.
DOM::EventTarget* target = document->body();
if (!target)
target = document;
// -> If the immediate user selection is a text control (e.g., textarea, or an input element whose
// type attribute is in the Text state) or an editing host or editable element, and the drag data
// store item list has an item with the drag data item type string "text/plain" and the drag data
// item kind text
// Set the current target element to the immediate user selection anyway.
// -> If the immediate user selection is the body element
// Leave the current target element unchanged.
// -> Otherwise
// Fire a DND event named dragenter at the body element, if there is one, or at the Document
// object, if not. Then, set the current target element to the body element, regardless of
// whether that event was canceled or not.
fire_a_drag_and_drop_event(target, HTML::EventNames::dragenter);
m_current_target_element = document->body();
}
}
// FIXME: Spec isue: Contrary to the spec, all browsers do not require the user script to cancel the
// dragenter event. See: https://github.com/whatwg/html/issues/11608
m_current_target_element = is<DOM::Element>(*m_immediate_user_selection)
? m_immediate_user_selection
: m_immediate_user_selection->first_ancestor_of_type<DOM::Element>();
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -23,7 +23,7 @@ public:
bool has_ongoing_drag_and_drop_operation() const { return !m_drag_data_store.is_null(); }
EventResult handle_drag_start(JS::Realm&, CSSPixelPoint screen_position, CSSPixelPoint page_offset, CSSPixelPoint client_offset, CSSPixelPoint offset, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files);
EventResult handle_drag_move(JS::Realm&, GC::Ref<DOM::Document>, GC::Ref<DOM::Node>, CSSPixelPoint screen_position, CSSPixelPoint page_offset, CSSPixelPoint client_offset, CSSPixelPoint offset, unsigned button, unsigned buttons, unsigned modifiers);
EventResult handle_drag_move(JS::Realm&, GC::Ref<DOM::Node>, CSSPixelPoint screen_position, CSSPixelPoint page_offset, CSSPixelPoint client_offset, CSSPixelPoint offset, unsigned button, unsigned buttons, unsigned modifiers);
EventResult handle_drag_leave(JS::Realm&, CSSPixelPoint screen_position, CSSPixelPoint page_offset, CSSPixelPoint client_offset, CSSPixelPoint offset, unsigned button, unsigned buttons, unsigned modifiers);
EventResult handle_drop(JS::Realm&, CSSPixelPoint screen_position, CSSPixelPoint page_offset, CSSPixelPoint client_offset, CSSPixelPoint offset, unsigned button, unsigned buttons, unsigned modifiers);

View file

@ -1461,7 +1461,7 @@ EventResult EventHandler::handle_drag_and_drop_event(DragEvent::Type type, CSSPi
case DragEvent::Type::DragStart:
return m_drag_and_drop_event_handler->handle_drag_start(document.realm(), screen_position, page_offset, viewport_position, 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, viewport_position, offset, button, buttons, modifiers);
return m_drag_and_drop_event_handler->handle_drag_move(document.realm(), *node, screen_position, page_offset, viewport_position, offset, button, buttons, modifiers);
case DragEvent::Type::DragEnd:
return m_drag_and_drop_event_handler->handle_drag_leave(document.realm(), screen_position, page_offset, viewport_position, offset, button, buttons, modifiers);
case DragEvent::Type::Drop:

View file

@ -24,9 +24,17 @@ drop
files: 1
test2
Drag enter not accepted:
Drag enter not accepted (but still allowed):
dragenter
types: Files
dragover
types: Files
dragover
types: Files
drop
types: Files
files: 1
test3
Drag over not accepted:
dragenter

View file

@ -55,7 +55,9 @@
internals.simulateDragMove(100, 100);
internals.simulateDrop(100, 100);
println("\nDrag enter not accepted:");
// Contrary to the spec, we continue with the drag and drop even if the dragenter event is not cancelled.
// https://github.com/whatwg/html/issues/11608
println("\nDrag enter not accepted (but still allowed):");
internals.simulateDragStart(0, 0, "test3", "well hello friends :^)");
acceptDragEvents = false;
internals.simulateDragMove(100, 100);