script/drag/
document_selection_drag.rs1use std::cmp::Ordering;
6
7use js::context::{JSContext, NoGC};
8use layout_api::SegmentGranularity;
9use rustc_hash::FxHashMap;
10use script_bindings::dom::UnrootedDom;
11use script_bindings::inheritance::Castable;
12use script_bindings::root::{Dom, DomRoot};
13use style::values::computed::UserSelect;
14
15use crate::dom::inputevent::HitTestResult;
16use crate::dom::selection::UsedUserSelect;
17use crate::dom::selection_range::{RootedSelectionBoundary, SelectionRange};
18use crate::dom::{Document, Element, Node};
19
20#[derive(JSTraceable, MallocSizeOf)]
23#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
24pub(crate) struct DocumentDragSegment {
25 #[no_trace]
27 pub granularity: SegmentGranularity,
28 pub range: SelectionRange,
30}
31
32#[derive(JSTraceable, MallocSizeOf)]
33#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
34pub(crate) struct DocumentSelectionDragHandler {
35 document: Dom<Document>,
37 user_select_contain_node_for_selection_anchor: Option<Dom<Node>>,
39 segment: Option<DocumentDragSegment>,
41}
42
43impl DocumentSelectionDragHandler {
44 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
45 pub(crate) fn new(
46 document: &Document,
47 user_select_contain_node: Option<&Node>,
48 segment: Option<DocumentDragSegment>,
49 ) -> Self {
50 Self {
51 document: Dom::from_ref(document),
52 user_select_contain_node_for_selection_anchor: user_select_contain_node
53 .map(Dom::from_ref),
54 segment,
55 }
56 }
57
58 pub(crate) fn still_connected(&self) -> bool {
59 self.user_select_contain_node_for_selection_anchor
60 .as_ref()
61 .is_none_or(|node| node.is_connected())
62 }
63
64 pub(crate) fn moved(&self, cx: &mut JSContext, hit_test_result: &HitTestResult) -> bool {
68 let Some(selection) = self.document.selection() else {
69 return true;
70 };
71
72 let Some(boundary) = hit_test_result.dom_position_for_selection.as_ref() else {
73 return true;
74 };
75
76 let adjust_for_user_select =
77 |anchor: Option<&RootedSelectionBoundary>, focus: RootedSelectionBoundary| {
78 adjust_focus_for_user_select(
79 cx,
80 anchor,
81 focus,
82 self.user_select_contain_node_for_selection_anchor
83 .as_deref(),
84 )
85 };
86
87 let Some(segment) = &self.segment else {
88 selection.collapse_or_extend_to_dom_position(
89 cx,
90 &adjust_for_user_select(
91 selection.composed_anchor_position().as_ref(),
92 boundary.clone(),
93 ),
94 );
95 return true;
96 };
97
98 let (start, end) = self
99 .document
100 .window()
101 .query_text_segment(
102 &boundary.container,
103 boundary.utf32_offset(),
104 segment.granularity,
105 )
106 .unwrap_or_else(|| (boundary.clone(), boundary.clone()));
107
108 let original_start = segment.range.start.rooted();
109 let original_end = segment.range.end.rooted();
110 let (anchor, focus) = if boundary.cmp(cx.no_gc(), &original_start) == Ordering::Less {
111 (original_end, start)
114 } else if boundary.cmp(cx.no_gc(), &original_end) == Ordering::Greater {
115 (original_start, end)
118 } else {
119 (original_start, original_end)
122 };
123
124 let focus = adjust_for_user_select(Some(&anchor), focus);
125 selection.set_anchor_and_focus(cx, &anchor, &focus);
126 true
127 }
128}
129
130pub(crate) fn adjust_anchor_for_user_select(
141 no_gc: &NoGC,
142 mut anchor_candidate: RootedSelectionBoundary,
143) -> Option<(RootedSelectionBoundary, Option<DomRoot<Node>>)> {
144 let mut cache = Default::default();
145 if anchor_candidate
146 .container
147 .used_user_select(no_gc, &mut cache) ==
148 UsedUserSelect::None
149 {
150 return None;
151 }
152
153 let mut nearest_with_user_select_contain = None;
155
156 let mut furthest_with_user_select_all = None;
159 let mut each_so_far_has_user_select_all = true;
160
161 for ancestor in anchor_candidate
162 .container
163 .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
164 {
165 match ancestor.used_user_select(no_gc, &mut cache) {
166 UsedUserSelect::Text | UsedUserSelect::None => {
167 each_so_far_has_user_select_all = false;
168 },
169 UsedUserSelect::Contain => {
170 nearest_with_user_select_contain = Some(ancestor.as_rooted());
171 break;
174 },
175 UsedUserSelect::All => {
176 if each_so_far_has_user_select_all {
177 furthest_with_user_select_all = Some(ancestor);
178 }
179 },
180 }
181 }
182
183 if let Some(atomic) = furthest_with_user_select_all {
184 anchor_candidate = RootedSelectionBoundary::start_of(&atomic);
190 }
191 Some((anchor_candidate, nearest_with_user_select_contain))
192}
193
194pub(crate) fn adjust_focus_for_user_select(
199 no_gc: &NoGC,
200 anchor: Option<&RootedSelectionBoundary>,
201 mut focus_candidate: RootedSelectionBoundary,
202 user_select_contain_node_for_anchor: Option<&Node>,
203) -> RootedSelectionBoundary {
204 let mut cache = Default::default();
205 let mut user_select_contain_node_for_anchor_is_inclusive_ancestor = false;
206 let mut each_so_far_has_user_select_all = true;
207 let mut furthest_node_with_user_select_all = None;
208 let mut each_so_far_has_user_select_none = true;
209 let mut furthest_node_to_avoid = None;
211
212 for inclusive_ancestor in focus_candidate
213 .container
214 .inclusive_ancestors_in_flat_tree_unrooted(no_gc)
215 {
216 if user_select_contain_node_for_anchor == Some(&**inclusive_ancestor) {
217 user_select_contain_node_for_anchor_is_inclusive_ancestor = true
218 }
219 match inclusive_ancestor.used_user_select(no_gc, &mut cache) {
220 UsedUserSelect::Text => {
221 each_so_far_has_user_select_all = false;
222 each_so_far_has_user_select_none = false;
223 },
224 UsedUserSelect::None => {
225 each_so_far_has_user_select_all = false;
226 if each_so_far_has_user_select_none {
227 furthest_node_to_avoid = Some(inclusive_ancestor)
228 }
229 },
230 UsedUserSelect::Contain => {
231 each_so_far_has_user_select_all = false;
232 each_so_far_has_user_select_none = false;
233 if !user_select_contain_node_for_anchor_is_inclusive_ancestor {
234 furthest_node_to_avoid = Some(inclusive_ancestor)
235 }
236 },
237 UsedUserSelect::All => {
238 each_so_far_has_user_select_none = false;
239 if each_so_far_has_user_select_all {
240 furthest_node_with_user_select_all = Some(inclusive_ancestor)
241 }
242 },
243 }
244 if !each_so_far_has_user_select_all &&
245 !each_so_far_has_user_select_none &&
246 user_select_contain_node_for_anchor_is_inclusive_ancestor
247 {
248 break;
250 }
251 }
252 let selection_is_backward = || {
253 anchor.is_some_and(|anchor| {
254 anchor.compare_dom_positions(no_gc, &focus_candidate) == Some(Ordering::Greater)
255 })
256 };
257 if let Some(contain_for_anchor) = user_select_contain_node_for_anchor &&
258 !user_select_contain_node_for_anchor_is_inclusive_ancestor
259 {
260 focus_candidate = if selection_is_backward() {
263 RootedSelectionBoundary::start_of(contain_for_anchor)
264 } else {
265 RootedSelectionBoundary::end_of(contain_for_anchor)
266 };
267 } else if let Some(focus_ancestor_to_avoid) = furthest_node_to_avoid {
268 focus_candidate = if selection_is_backward() {
269 RootedSelectionBoundary::end_of(&focus_ancestor_to_avoid)
270 } else {
271 RootedSelectionBoundary::start_of(&focus_ancestor_to_avoid)
272 };
273 } else if let Some(with_user_select_all) = furthest_node_with_user_select_all {
274 focus_candidate = RootedSelectionBoundary::end_of(&with_user_select_all);
277 }
278
279 focus_candidate
280}
281
282impl Node {
283 pub(crate) fn used_user_select<'no_gc>(
291 &self,
292 no_gc: &'no_gc NoGC,
293 cache: &mut FxHashMap<UnrootedDom<'no_gc, Node>, UsedUserSelect>,
294 ) -> UsedUserSelect {
295 let cache_key = UnrootedDom::from_ref(self, no_gc);
296 if let Some(&used_value) = cache.get(&cache_key) {
297 return used_value;
298 }
299 if self.is_inert() {
303 cache.insert(cache_key, UsedUserSelect::None);
304 return UsedUserSelect::None;
305 }
306 if self.is_editing_host() {
320 let used_value = UsedUserSelect::Contain;
321 cache.insert(cache_key, used_value);
322 return used_value;
323 }
324 let computed_value = self
325 .downcast()
326 .and_then(Element::computed_user_select)
327 .unwrap_or(UserSelect::Auto);
329 let used_value = match computed_value {
330 UserSelect::Text => UsedUserSelect::Text,
331 UserSelect::None => UsedUserSelect::None,
332 UserSelect::Contain => UsedUserSelect::Contain,
333 UserSelect::All => UsedUserSelect::All,
334 UserSelect::Auto => {
335 let parent_used_value = self
336 .parent_in_flat_tree(no_gc)
337 .into_parent()
338 .map(|parent| parent.used_user_select(no_gc, cache));
339 match parent_used_value {
348 Some(UsedUserSelect::All) => UsedUserSelect::All,
349 Some(UsedUserSelect::None) => UsedUserSelect::None,
350 _ => UsedUserSelect::Text,
351 }
352 },
353 };
354 cache.insert(cache_key, used_value);
355 used_value
356 }
357}