script/drag/
document_selection_drag.rs1use std::cmp::Ordering;
6
7use js::context::{JSContext, NoGC};
8use rustc_hash::FxHashMap;
9use script_bindings::dom::UnrootedDom;
10use script_bindings::inheritance::Castable;
11use script_bindings::root::{Dom, DomRoot};
12use style::values::computed::UserSelect;
13
14use crate::dom::inputevent::HitTestResult;
15use crate::dom::selection::UsedUserSelect;
16use crate::dom::selection_range::RootedSelectionBoundary;
17use crate::dom::{Element, Node, NodeTraits};
18
19#[derive(JSTraceable, MallocSizeOf)]
20#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
21pub(crate) struct DocumentSelectionDragHandler {
22 user_select_contain_node_for_selection_anchor: Option<Dom<Node>>,
24}
25
26impl DocumentSelectionDragHandler {
27 pub(crate) fn new(user_select_contain_node: Option<&Node>) -> Self {
28 Self {
29 user_select_contain_node_for_selection_anchor: user_select_contain_node
30 .map(Dom::from_ref),
31 }
32 }
33
34 pub(crate) fn still_connected(&self) -> bool {
35 self.user_select_contain_node_for_selection_anchor
36 .as_ref()
37 .is_none_or(|node| node.is_connected())
38 }
39
40 pub(crate) fn moved(&self, cx: &mut JSContext, hit_test_result: &HitTestResult) -> bool {
44 let Some(boundary) = hit_test_result.dom_position_for_selection.as_ref() else {
45 return true;
46 };
47 let Some(selection) = boundary.container.owner_document().selection() else {
48 return true;
49 };
50 let boundary = adjust_focus_for_user_select(
51 cx,
52 selection.composed_anchor_position().as_ref(),
53 boundary.clone(),
54 self.user_select_contain_node_for_selection_anchor
55 .as_deref(),
56 );
57 selection.collapse_or_extend_to_dom_position(cx, &boundary);
58 true
59 }
60}
61
62pub(crate) fn adjust_anchor_for_user_select(
73 no_gc: &NoGC,
74 mut anchor_candidate: RootedSelectionBoundary,
75) -> Option<(RootedSelectionBoundary, Option<DomRoot<Node>>)> {
76 let mut cache = Default::default();
77 if anchor_candidate
78 .container
79 .used_user_select(no_gc, &mut cache) ==
80 UsedUserSelect::None
81 {
82 return None;
83 }
84
85 let mut nearest_with_user_select_contain = None;
87
88 let mut furthest_with_user_select_all = None;
91 let mut each_so_far_has_user_select_all = true;
92
93 for ancestor in anchor_candidate
94 .container
95 .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
96 {
97 match ancestor.used_user_select(no_gc, &mut cache) {
98 UsedUserSelect::Text | UsedUserSelect::None => {
99 each_so_far_has_user_select_all = false;
100 },
101 UsedUserSelect::Contain => {
102 nearest_with_user_select_contain = Some(ancestor.as_rooted());
103 break;
106 },
107 UsedUserSelect::All => {
108 if each_so_far_has_user_select_all {
109 furthest_with_user_select_all = Some(ancestor);
110 }
111 },
112 }
113 }
114
115 if let Some(atomic) = furthest_with_user_select_all {
116 anchor_candidate = RootedSelectionBoundary::start_of(&atomic);
122 }
123 Some((anchor_candidate, nearest_with_user_select_contain))
124}
125
126pub(crate) fn adjust_focus_for_user_select(
131 no_gc: &NoGC,
132 anchor: Option<&RootedSelectionBoundary>,
133 mut focus_candidate: RootedSelectionBoundary,
134 user_select_contain_node_for_anchor: Option<&Node>,
135) -> RootedSelectionBoundary {
136 let mut cache = Default::default();
137 let mut user_select_contain_node_for_anchor_is_inclusive_ancestor = false;
138 let mut each_so_far_has_user_select_all = true;
139 let mut furthest_node_with_user_select_all = None;
140 let mut each_so_far_has_user_select_none = true;
141 let mut furthest_node_to_avoid = None;
143
144 for inclusive_ancestor in focus_candidate
145 .container
146 .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
147 {
148 if user_select_contain_node_for_anchor == Some(&**inclusive_ancestor) {
149 user_select_contain_node_for_anchor_is_inclusive_ancestor = true
150 }
151 match inclusive_ancestor.used_user_select(no_gc, &mut cache) {
152 UsedUserSelect::Text => {
153 each_so_far_has_user_select_all = false;
154 each_so_far_has_user_select_none = false;
155 },
156 UsedUserSelect::None => {
157 each_so_far_has_user_select_all = false;
158 if each_so_far_has_user_select_none {
159 furthest_node_to_avoid = Some(inclusive_ancestor)
160 }
161 },
162 UsedUserSelect::Contain => {
163 each_so_far_has_user_select_all = false;
164 each_so_far_has_user_select_none = false;
165 if !user_select_contain_node_for_anchor_is_inclusive_ancestor {
166 furthest_node_to_avoid = Some(inclusive_ancestor)
167 }
168 },
169 UsedUserSelect::All => {
170 each_so_far_has_user_select_none = false;
171 if each_so_far_has_user_select_all {
172 furthest_node_with_user_select_all = Some(inclusive_ancestor)
173 }
174 },
175 }
176 if !each_so_far_has_user_select_all &&
177 !each_so_far_has_user_select_none &&
178 user_select_contain_node_for_anchor_is_inclusive_ancestor
179 {
180 break;
182 }
183 }
184 let selection_is_backward = || {
185 anchor.is_some_and(|anchor| {
186 anchor.compare_dom_positions(no_gc, &focus_candidate) == Some(Ordering::Greater)
187 })
188 };
189 if let Some(contain_for_anchor) = user_select_contain_node_for_anchor &&
190 !user_select_contain_node_for_anchor_is_inclusive_ancestor
191 {
192 focus_candidate = if selection_is_backward() {
195 RootedSelectionBoundary::start_of(contain_for_anchor)
196 } else {
197 RootedSelectionBoundary::end_of(contain_for_anchor)
198 };
199 } else if let Some(focus_ancestor_to_avoid) = furthest_node_to_avoid {
200 focus_candidate = if selection_is_backward() {
201 RootedSelectionBoundary::end_of(&focus_ancestor_to_avoid)
202 } else {
203 RootedSelectionBoundary::start_of(&focus_ancestor_to_avoid)
204 };
205 } else if let Some(with_user_select_all) = furthest_node_with_user_select_all {
206 focus_candidate = RootedSelectionBoundary::end_of(&with_user_select_all);
209 }
210
211 focus_candidate
212}
213
214impl Node {
215 pub(crate) fn used_user_select<'no_gc>(
223 &self,
224 no_gc: &'no_gc NoGC,
225 cache: &mut FxHashMap<UnrootedDom<'no_gc, Node>, UsedUserSelect>,
226 ) -> UsedUserSelect {
227 let cache_key = UnrootedDom::from_ref(self, no_gc);
228 if let Some(&used_value) = cache.get(&cache_key) {
229 return used_value;
230 }
231 if self.is_editing_host() {
245 let used_value = UsedUserSelect::Contain;
246 cache.insert(cache_key, used_value);
247 return used_value;
248 }
249 let computed_value = self
250 .downcast()
251 .and_then(Element::computed_user_select)
252 .unwrap_or(UserSelect::Auto);
254 let used_value = match computed_value {
255 UserSelect::Text => UsedUserSelect::Text,
256 UserSelect::None => UsedUserSelect::None,
257 UserSelect::Contain => UsedUserSelect::Contain,
258 UserSelect::All => UsedUserSelect::All,
259 UserSelect::Auto => {
260 let parent_used_value = self
261 .parent_in_flat_tree(no_gc)
262 .into_parent()
263 .map(|parent| parent.used_user_select(no_gc, cache));
264 match parent_used_value {
273 Some(UsedUserSelect::All) => UsedUserSelect::All,
274 Some(UsedUserSelect::None) => UsedUserSelect::None,
275 _ => UsedUserSelect::Text,
276 }
277 },
278 };
279 cache.insert(cache_key, used_value);
280 used_value
281 }
282}