Skip to content

Commit aec2439

Browse files
sideshowbarkerAtkinsSJ
authored andcommitted
LibWeb: Skip user-select:none when computing selection state
Problem: Select All draws the selection highlight across user-select:none content — breaking compat with Chrome and Firefox, which leave such content unhighlighted. Cause: ViewportPaintable::recompute_selection_states walks the Range and assigns SelectionState::Start|::Full|::End to each layout node within. The walk filters out is_inert() nodes — but not user-select: none nodes. Fix: Extend the existing inert-only guards into a helper that also rejects nodes whose used value of user-select is ‘none’. Such nodes stay at SelectionState::None from the initial reset — so the selection highlight skips them. Fixes #9695
1 parent 03f6f25 commit aec2439

1 file changed

Lines changed: 15 additions & 4 deletions

File tree

Libraries/LibWeb/Painting/ViewportPaintable.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,17 @@ void ViewportPaintable::recompute_selection_states(DOM::Range& range)
312312
}
313313
};
314314

315+
// https://drafts.csswg.org/css-ui/#valdef-user-select-none
316+
// "The content of the element must be excluded from selection by [...] the selection methods of the Selection API
317+
// and the like." We honor this by leaving such nodes at SelectionState::None — even when they fall inside the
318+
// range. So, the selection highlight skips them.
319+
auto is_excluded_from_selection = [](DOM::Node const& node) {
320+
if (node.is_inert())
321+
return true;
322+
auto const* layout = node.unsafe_layout_node();
323+
return layout && layout->user_select_used_value() == CSS::UserSelect::None;
324+
};
325+
315326
auto start_container = range.start_container();
316327
auto end_container = range.end_container();
317328

@@ -324,14 +335,14 @@ void ViewportPaintable::recompute_selection_states(DOM::Range& range)
324335
}
325336

326337
// 2. If it's a text node, mark it as StartAndEnd and return.
327-
if (is<DOM::Text>(*start_container) && !range.start().node->is_inert()) {
338+
if (is<DOM::Text>(*start_container) && !is_excluded_from_selection(*start_container)) {
328339
set_selection_state_on_all_slices(*start_container, SelectionState::StartAndEnd);
329340
return;
330341
}
331342
}
332343

333344
// 3. Mark the selection start node as Start (if text) or Full (if anything else).
334-
if (!range.start().node->is_inert() && start_container->unsafe_layout_node()) {
345+
if (!is_excluded_from_selection(*start_container) && start_container->unsafe_layout_node()) {
335346
if (is<DOM::Text>(*start_container))
336347
set_selection_state_on_all_slices(*start_container, SelectionState::Start);
337348
else
@@ -352,13 +363,13 @@ void ViewportPaintable::recompute_selection_states(DOM::Range& range)
352363
DOM::Node* stop_at = end_container->child_at_index(range.end_offset());
353364
// Only stop at the end container if it has no children that may need to be included.
354365
for (auto* node = start_at; node && (node != stop_at && !(node == end_container && !end_container->has_children())); node = node->next_in_pre_order(end_container)) {
355-
if (node->is_inert())
366+
if (is_excluded_from_selection(*node))
356367
continue;
357368
set_selection_state_on_all_slices(*node, SelectionState::Full);
358369
}
359370

360371
// 5. Mark the selection end node as End if it is a text node.
361-
if (!range.end().node->is_inert() && is<DOM::Text>(*end_container) && end_container->unsafe_layout_node()) {
372+
if (!is_excluded_from_selection(*end_container) && is<DOM::Text>(*end_container) && end_container->unsafe_layout_node()) {
362373
set_selection_state_on_all_slices(*end_container, SelectionState::End);
363374
}
364375
}

0 commit comments

Comments
 (0)