LibWeb: Selecting an editing host should focus it

Unspecced but common behavior in the major browsers: if the selection
moves to an editing host, the editing host itself should become focused.
This commit is contained in:
Jelle Raaijmakers 2025-07-31 14:25:03 +02:00 committed by Jelle Raaijmakers
commit 0ce1571e71
Notes: github-actions[bot] 2025-08-01 08:10:36 +00:00
3 changed files with 35 additions and 7 deletions

View file

@ -525,26 +525,37 @@ GC::Ptr<DOM::Range> Selection::range() const
void Selection::set_range(GC::Ptr<DOM::Range> range)
{
if (m_range == range)
auto old_range = m_range;
if (old_range == range)
return;
if (m_range)
m_range->set_associated_selection({}, nullptr);
if (old_range)
old_range->set_associated_selection({}, nullptr);
auto range_changed = ((m_range == nullptr) != (range == nullptr)) || (m_range && *m_range != *range);
m_range = range;
if (m_range)
m_range->set_associated_selection({}, this);
if (range)
range->set_associated_selection({}, this);
// https://w3c.github.io/editing/docs/execCommand/#state-override
// Whenever the number of ranges in the selection changes to something different, and whenever a boundary point of
// the range at a given index in the selection changes to something different, the state override and value override
// must be unset for every command.
if (range_changed) {
if (((old_range == nullptr) != (range == nullptr)) || (old_range && *old_range != *range)) {
m_document->reset_command_state_overrides();
m_document->reset_command_value_overrides();
}
// https://developer.mozilla.org/en-US/docs/Web/API/Selection#behavior_of_selection_api_in_terms_of_editing_host_focus_changes
// AD-HOC: Focus editing host if the previous selection was outside of it. There seems to be no spec for this.
if (range && range->start_container()->is_editable_or_editing_host()) {
GC::Ptr old_editing_host = old_range ? old_range->start_container()->editing_host() : nullptr;
GC::Ref new_editing_host = *range->start_container()->editing_host();
if (new_editing_host != old_editing_host && document()->focused_element() != new_editing_host) {
// FIXME: Determine and propagate the right focus trigger.
HTML::run_focusing_steps(new_editing_host, nullptr, HTML::FocusTrigger::Other);
}
}
}
GC::Ptr<DOM::Position> Selection::cursor_position() const

View file

@ -0,0 +1,2 @@
Range: [object Text] 1 [object Text] 2
document.activeElement: [object HTMLDivElement]

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<div contenteditable>foo</div>
<script src="include.js"></script>
<script>
test(() => {
const divElm = document.querySelector('div[contenteditable]');
const range = document.createRange();
range.setStart(divElm.childNodes[0], 1);
range.setEnd(divElm.childNodes[0], 2);
window.getSelection().addRange(range);
println(`Range: ${range.startContainer} ${range.startOffset} ${range.endContainer} ${range.endOffset}`);
println(`document.activeElement: ${document.activeElement}`);
});
</script>