LibWeb: Do not scroll cursor into view on programmatic selection changes

We were mimicking Firefox' behavior that whenever a programmatic change
to an <input>'s or <textarea>'s selection happened, the new selection
focus is brought into view by scrolling. Currently we run a layout
update synchronously for that to make sure we have the fragment's
correct dimensions, which caused a significant performance regression in
Speedometer.

Since this is non-standard behavior, let's mimic Chromium instead which
does not scroll at all - only for direct user initiated input such as
typing.

Relevant issues:

* https://github.com/whatwg/html/issues/6217
* https://bugzilla.mozilla.org/show_bug.cgi?id=232405
* https://issues.chromium.org/issues/41081857
This commit is contained in:
Jelle Raaijmakers
2026-02-16 13:51:24 +01:00
committed by Andreas Kling
parent fa04c8db83
commit ded42e649b
Notes: github-actions[bot] 2026-02-17 09:25:08 +00:00
14 changed files with 124 additions and 69 deletions

View File

@@ -841,7 +841,7 @@ void FormAssociatedTextControlElement::set_the_selection_range(Optional<WebIDL::
});
}
selection_was_changed();
selection_was_changed(source);
}
}
@@ -872,6 +872,7 @@ void FormAssociatedTextControlElement::handle_insert(FlyString const& input_type
data_for_input_event = data_for_insertion;
did_edit_text_node(input_type, data_for_input_event);
scroll_cursor_into_view();
}
void FormAssociatedTextControlElement::handle_delete(FlyString const& input_type)
@@ -897,6 +898,7 @@ void FormAssociatedTextControlElement::handle_delete(FlyString const& input_type
text_node->invalidate_style(DOM::StyleInvalidationReason::EditingDeletion);
did_edit_text_node(input_type, {});
scroll_cursor_into_view();
}
Optional<Utf16String> FormAssociatedTextControlElement::selected_text_for_stringifier() const
@@ -920,7 +922,7 @@ void FormAssociatedTextControlElement::collapse_selection_to_offset(size_t posit
void FormAssociatedTextControlElement::scroll_cursor_into_view()
{
auto& element = form_associated_element_to_html_element();
element.document().update_layout(DOM::UpdateLayoutReason::ScrollFocusIntoView);
element.document().update_layout(DOM::UpdateLayoutReason::ScrollCursorIntoView);
auto text_node = form_associated_element_to_text_node();
if (!text_node)
@@ -933,7 +935,7 @@ void FormAssociatedTextControlElement::scroll_cursor_into_view()
paintable->scroll_ancestor_to_offset_into_view(m_selection_end);
}
void FormAssociatedTextControlElement::selection_was_changed()
void FormAssociatedTextControlElement::selection_was_changed(SelectionSource source)
{
auto& element = form_associated_element_to_html_element();
if (auto* input_element = as_if<HTMLInputElement>(element)) {
@@ -959,10 +961,14 @@ void FormAssociatedTextControlElement::selection_was_changed()
}
text_paintable->set_needs_display();
// AD-HOC: Skip scroll-into-view during mouse selection, since the user controls the viewport.
auto navigable = element.document().cached_navigable();
if (!(navigable && navigable->event_handler().is_handling_mouse_selection()))
scroll_cursor_into_view();
// AD-HOC: Only scroll the cursor into view for UI-driven selection changes (like keyboard input). Programmatic
// changes (input.value, setSelectionRange) do not cause the cursor to scroll into view. This matches the
// behavior of other browsers.
if (source == SelectionSource::UI) {
auto navigable = element.document().navigable();
if (navigable && !navigable->event_handler().is_handling_mouse_selection())
scroll_cursor_into_view();
}
}
void FormAssociatedTextControlElement::select_all()
@@ -971,7 +977,7 @@ void FormAssociatedTextControlElement::select_all()
if (!text_node)
return;
set_the_selection_range(0, text_node->length());
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::set_selection_anchor(GC::Ref<DOM::Node> anchor_node, size_t anchor_offset)
@@ -982,7 +988,7 @@ void FormAssociatedTextControlElement::set_selection_anchor(GC::Ref<DOM::Node> a
if (!text_node || anchor_node != text_node)
return;
collapse_selection_to_offset(anchor_offset);
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::set_selection_focus(GC::Ref<DOM::Node> focus_node, size_t focus_offset)
@@ -993,7 +999,7 @@ void FormAssociatedTextControlElement::set_selection_focus(GC::Ref<DOM::Node> fo
if (!text_node || focus_node != text_node)
return;
m_selection_end = focus_offset;
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::move_cursor_to_start(CollapseSelection collapse)
@@ -1006,7 +1012,7 @@ void FormAssociatedTextControlElement::move_cursor_to_start(CollapseSelection co
} else {
m_selection_end = 0;
}
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::move_cursor_to_end(CollapseSelection collapse)
@@ -1019,7 +1025,7 @@ void FormAssociatedTextControlElement::move_cursor_to_end(CollapseSelection coll
} else {
m_selection_end = text_node->length();
}
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::increment_cursor_position_offset(CollapseSelection collapse)
@@ -1034,7 +1040,7 @@ void FormAssociatedTextControlElement::increment_cursor_position_offset(Collapse
m_selection_end = *offset;
}
}
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::decrement_cursor_position_offset(CollapseSelection collapse)
@@ -1049,7 +1055,7 @@ void FormAssociatedTextControlElement::decrement_cursor_position_offset(Collapse
m_selection_end = *offset;
}
}
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::increment_cursor_position_to_next_word(CollapseSelection collapse)
@@ -1072,7 +1078,7 @@ void FormAssociatedTextControlElement::increment_cursor_position_to_next_word(Co
break;
}
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_word(CollapseSelection collapse)
@@ -1095,7 +1101,7 @@ void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_wor
break;
}
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::increment_cursor_position_to_next_line(CollapseSelection collapse)
@@ -1113,7 +1119,7 @@ void FormAssociatedTextControlElement::increment_cursor_position_to_next_line(Co
else
m_selection_end = *new_offset;
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_line(CollapseSelection collapse)
@@ -1131,7 +1137,7 @@ void FormAssociatedTextControlElement::decrement_cursor_position_to_previous_lin
else
m_selection_end = *new_offset;
selection_was_changed();
selection_was_changed(SelectionSource::UI);
}
GC::Ptr<DOM::Position> FormAssociatedTextControlElement::cursor_position() const