use std::cmp::Ordering;
use js::context::{JSContext, NoGC};
use rustc_hash::FxHashMap;
use script_bindings::dom::UnrootedDom;
use script_bindings::inheritance::Castable;
use script_bindings::root::{Dom, DomRoot};
use servo_base::text::Utf32CodeUnitsOrNodeOffset;
use style::values::computed::UserSelect;
use crate::dom::comparator::compare_dom_positions;
use crate::dom::inputevent::HitTestResult;
use crate::dom::selection::UsedUserSelect;
use crate::dom::traversal::FlatTreeForSelectionNoGcTraversal;
use crate::dom::{Element, Node, NodeTraits};
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct DocumentSelectionDragHandler {
user_select_contain_node_for_selection_anchor: Option<Dom<Node>>,
}
impl DocumentSelectionDragHandler {
pub(crate) fn new(user_select_contain_node: Option<&Node>) -> Self {
Self {
user_select_contain_node_for_selection_anchor: user_select_contain_node
.map(Dom::from_ref),
}
}
pub(crate) fn still_connected(&self) -> bool {
self.user_select_contain_node_for_selection_anchor
.as_ref()
.is_none_or(|node| node.is_connected())
}
pub(crate) fn moved(&self, cx: &mut JSContext, hit_test_result: &HitTestResult) -> bool {
let Some((container, offset)) = hit_test_result.dom_position_for_selection.as_ref() else {
return true;
};
let Some(selection) = container.owner_document().selection() else {
return true;
};
let (container, offset) = adjust_focus_for_user_select(
cx,
selection.composed_anchor_position(),
container.clone(),
*offset,
self.user_select_contain_node_for_selection_anchor
.as_deref(),
);
selection.collapse_or_extend_to_dom_position(cx, &container, offset);
true
}
}
pub(crate) fn adjust_anchor_for_user_select(
no_gc: &NoGC,
mut anchor_container_candidate: DomRoot<Node>,
mut anchor_offset_candidate: Utf32CodeUnitsOrNodeOffset,
) -> Option<(
DomRoot<Node>,
Utf32CodeUnitsOrNodeOffset,
Option<DomRoot<Node>>,
)> {
let mut cache = Default::default();
if anchor_container_candidate.used_user_select(no_gc, &mut cache) == UsedUserSelect::None {
return None;
}
let mut nearest_with_user_select_contain = None;
let mut furthest_with_user_select_all = None;
let mut each_so_far_has_user_select_all = true;
for ancestor in anchor_container_candidate.inclusive_ancestors_in_flat_tree_unrooted(no_gc) {
match ancestor.used_user_select(no_gc, &mut cache) {
UsedUserSelect::Text | UsedUserSelect::None => {
each_so_far_has_user_select_all = false;
},
UsedUserSelect::Contain => {
nearest_with_user_select_contain = Some(ancestor.as_rooted());
break;
},
UsedUserSelect::All => {
if each_so_far_has_user_select_all {
furthest_with_user_select_all = Some(ancestor);
}
},
}
}
if let Some(atomic) = furthest_with_user_select_all {
anchor_container_candidate = atomic.as_rooted();
anchor_offset_candidate = Utf32CodeUnitsOrNodeOffset(0);
}
Some((
anchor_container_candidate,
anchor_offset_candidate,
nearest_with_user_select_contain,
))
}
pub(crate) fn adjust_focus_for_user_select(
no_gc: &NoGC,
anchor: Option<(DomRoot<Node>, u32)>,
mut focus_container_candidate: DomRoot<Node>,
mut focus_offset_candidate: Utf32CodeUnitsOrNodeOffset,
user_select_contain_node_for_anchor: Option<&Node>,
) -> (DomRoot<Node>, Utf32CodeUnitsOrNodeOffset) {
let mut cache = Default::default();
let mut user_select_contain_node_for_anchor_is_inclusive_ancestor = false;
let mut each_so_far_has_user_select_all = true;
let mut furthest_node_with_user_select_all = None;
let mut each_so_far_has_user_select_none = true;
let mut furthest_node_to_avoid = None;
for inclusive_ancestor in
focus_container_candidate.inclusive_ancestors_in_flat_tree_unrooted(no_gc)
{
if user_select_contain_node_for_anchor == Some(&**inclusive_ancestor) {
user_select_contain_node_for_anchor_is_inclusive_ancestor = true
}
match inclusive_ancestor.used_user_select(no_gc, &mut cache) {
UsedUserSelect::Text => {
each_so_far_has_user_select_all = false;
each_so_far_has_user_select_none = false;
},
UsedUserSelect::None => {
each_so_far_has_user_select_all = false;
if each_so_far_has_user_select_none {
furthest_node_to_avoid = Some(inclusive_ancestor)
}
},
UsedUserSelect::Contain => {
each_so_far_has_user_select_all = false;
each_so_far_has_user_select_none = false;
if !user_select_contain_node_for_anchor_is_inclusive_ancestor {
furthest_node_to_avoid = Some(inclusive_ancestor)
}
},
UsedUserSelect::All => {
each_so_far_has_user_select_none = false;
if each_so_far_has_user_select_all {
furthest_node_with_user_select_all = Some(inclusive_ancestor)
}
},
}
if !each_so_far_has_user_select_all &&
!each_so_far_has_user_select_none &&
user_select_contain_node_for_anchor_is_inclusive_ancestor
{
break;
}
}
let selection_is_backward = || {
anchor
.as_ref()
.is_some_and(|(anchor_container, anchor_offset)| {
let (ordering, _containment) = compare_dom_positions::<
FlatTreeForSelectionNoGcTraversal,
>(
no_gc,
anchor_container,
*anchor_offset,
&focus_container_candidate,
focus_container_candidate.to_sibling_or_utf16_offset(focus_offset_candidate),
);
ordering == Some(Ordering::Greater)
})
};
if let Some(contain_for_anchor) = user_select_contain_node_for_anchor &&
!user_select_contain_node_for_anchor_is_inclusive_ancestor
{
focus_offset_candidate = if selection_is_backward() {
Utf32CodeUnitsOrNodeOffset(0)
} else {
Utf32CodeUnitsOrNodeOffset(Node::len(contain_for_anchor))
};
focus_container_candidate = DomRoot::from_ref(contain_for_anchor);
} else if let Some(focus_ancestor_to_avoid) = furthest_node_to_avoid {
focus_offset_candidate = if selection_is_backward() {
Utf32CodeUnitsOrNodeOffset(Node::len(&focus_ancestor_to_avoid))
} else {
Utf32CodeUnitsOrNodeOffset(0)
};
focus_container_candidate = focus_ancestor_to_avoid.as_rooted();
} else if let Some(with_user_select_all) = furthest_node_with_user_select_all {
focus_offset_candidate = Utf32CodeUnitsOrNodeOffset(Node::len(&with_user_select_all));
focus_container_candidate = with_user_select_all.as_rooted();
}
(focus_container_candidate, focus_offset_candidate)
}
impl Node {
pub(crate) fn used_user_select<'no_gc>(
&self,
no_gc: &'no_gc NoGC,
cache: &mut FxHashMap<UnrootedDom<'no_gc, Node>, UsedUserSelect>,
) -> UsedUserSelect {
let cache_key = UnrootedDom::from_ref(self, no_gc);
if let Some(&used_value) = cache.get(&cache_key) {
return used_value;
}
if self.is_editing_host() {
let used_value = UsedUserSelect::Contain;
cache.insert(cache_key, used_value);
return used_value;
}
let computed_value = self
.downcast()
.and_then(Element::computed_user_select)
.unwrap_or(UserSelect::Auto);
let used_value = match computed_value {
UserSelect::Text => UsedUserSelect::Text,
UserSelect::None => UsedUserSelect::None,
UserSelect::Contain => UsedUserSelect::Contain,
UserSelect::All => UsedUserSelect::All,
UserSelect::Auto => {
let parent_used_value = self
.parent_in_flat_tree(no_gc)
.into_parent()
.map(|parent| parent.used_user_select(no_gc, cache));
match parent_used_value {
Some(UsedUserSelect::All) => UsedUserSelect::All,
Some(UsedUserSelect::None) => UsedUserSelect::None,
_ => UsedUserSelect::Text,
}
},
};
cache.insert(cache_key, used_value);
used_value
}
}