1use crate::events::{DragMode, ScrollAnimationState, handle_dom_event};
2use crate::font_metrics::BlitzFontMetricsProvider;
3use crate::layout::construct::ConstructionTask;
4use crate::layout::damage::ALL_DAMAGE;
5use crate::mutator::ViewportMut;
6use crate::net::{
7 Resource, ResourceHandler, ResourceLoadResponse, StylesheetHandler, StylesheetLoader,
8};
9use crate::node::{ImageData, NodeFlags, RasterImageData, SpecialElementData, Status, TextBrush};
10use crate::selection::TextSelection;
11use crate::stylo_to_cursor_icon::stylo_to_cursor_icon;
12use crate::traversal::TreeTraverser;
13use crate::url::DocumentUrl;
14use crate::util::ImageType;
15use crate::{
16 DEFAULT_CSS, DocumentConfig, DocumentMutator, DummyHtmlParserProvider, ElementData,
17 EventDriver, HtmlParserProvider, Node, NodeData, NoopEventHandler, TextNodeData,
18};
19use blitz_traits::devtools::DevtoolSettings;
20use blitz_traits::events::{BlitzScrollEvent, DomEvent, DomEventData, HitResult, UiEvent};
21use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
22use blitz_traits::net::{DummyNetProvider, NetProvider, Request};
23use blitz_traits::shell::{ColorScheme, DummyShellProvider, ShellProvider, Viewport};
24use cursor_icon::CursorIcon;
25use linebender_resource_handle::Blob;
26use markup5ever::local_name;
27use parley::{FontContext, PlainEditorDriver};
28use selectors::{Element, matching::QuirksMode};
29use slab::Slab;
30use std::any::Any;
31use std::cell::RefCell;
32use std::collections::{BTreeMap, Bound, HashMap, HashSet};
33use std::ops::{Deref, DerefMut};
34use std::rc::Rc;
35use std::str::FromStr;
36use std::sync::atomic::{AtomicUsize, Ordering};
37use std::sync::mpsc::{Receiver, Sender, channel};
38use std::sync::{Arc, Mutex, MutexGuard, RwLockReadGuard, RwLockWriteGuard};
39use std::task::Context as TaskContext;
40use std::time::Instant;
41use style::Atom;
42use style::animation::DocumentAnimationSet;
43use style::attr::{AttrIdentifier, AttrValue};
44use style::data::{ElementData as StyloElementData, ElementStyles};
45use style::media_queries::MediaType;
46use style::properties::ComputedValues;
47use style::properties::style_structs::Font;
48use style::queries::values::PrefersColorScheme;
49use style::selector_parser::ServoElementSnapshot;
50use style::servo_arc::Arc as ServoArc;
51use style::values::GenericAtomIdent;
52use style::values::computed::Overflow;
53use style::{
54 device::Device,
55 dom::{TDocument, TNode},
56 media_queries::MediaList,
57 selector_parser::SnapshotMap,
58 shared_lock::{SharedRwLock, StylesheetGuards},
59 stylesheets::{AllowImportRules, DocumentStyleSheet, Origin, Stylesheet},
60 stylist::Stylist,
61};
62use url::Url;
63
64#[cfg(feature = "parallel-construct")]
65use thread_local::ThreadLocal;
66
67pub enum DocGuard<'a> {
68 Ref(&'a BaseDocument),
69 RefCell(std::cell::Ref<'a, BaseDocument>),
70 RwLock(RwLockReadGuard<'a, BaseDocument>),
71 Mutex(MutexGuard<'a, BaseDocument>),
72}
73
74impl Deref for DocGuard<'_> {
75 type Target = BaseDocument;
76 #[inline(always)]
77 fn deref(&self) -> &Self::Target {
78 match self {
79 Self::Ref(base_document) => base_document,
80 Self::RefCell(refcell_guard) => refcell_guard,
81 Self::RwLock(rw_lock_read_guard) => rw_lock_read_guard,
82 Self::Mutex(mutex_guard) => mutex_guard,
83 }
84 }
85}
86
87pub enum DocGuardMut<'a> {
88 Ref(&'a mut BaseDocument),
89 RefCell(std::cell::RefMut<'a, BaseDocument>),
90 RwLock(RwLockWriteGuard<'a, BaseDocument>),
91 Mutex(MutexGuard<'a, BaseDocument>),
92}
93
94impl Deref for DocGuardMut<'_> {
95 type Target = BaseDocument;
96 #[inline(always)]
97 fn deref(&self) -> &Self::Target {
98 match self {
99 Self::Ref(base_document) => base_document,
100 Self::RefCell(refcell_guard) => refcell_guard,
101 Self::RwLock(rw_lock_read_guard) => rw_lock_read_guard,
102 Self::Mutex(mutex_guard) => mutex_guard,
103 }
104 }
105}
106
107impl DerefMut for DocGuardMut<'_> {
108 #[inline(always)]
109 fn deref_mut(&mut self) -> &mut Self::Target {
110 match self {
111 Self::Ref(base_document) => base_document,
112 Self::RefCell(refcell_guard) => &mut *refcell_guard,
113 Self::RwLock(rw_lock_read_guard) => &mut *rw_lock_read_guard,
114 Self::Mutex(mutex_guard) => &mut *mutex_guard,
115 }
116 }
117}
118
119pub trait Document: Any + 'static {
122 fn inner(&self) -> DocGuard<'_>;
123 fn inner_mut(&mut self) -> DocGuardMut<'_>;
124
125 fn handle_ui_event(&mut self, event: UiEvent) {
127 let mut doc = self.inner_mut();
128 let mut driver = EventDriver::new(&mut *doc, NoopEventHandler);
129 driver.handle_ui_event(event);
130 }
131
132 fn poll(&mut self, task_context: Option<TaskContext>) -> bool {
134 let _ = task_context;
136 false
137 }
138
139 fn id(&self) -> usize {
141 self.inner().id
142 }
143}
144
145pub struct PlainDocument(pub BaseDocument);
146impl Document for PlainDocument {
147 fn inner(&self) -> DocGuard<'_> {
148 DocGuard::Ref(&self.0)
149 }
150 fn inner_mut(&mut self) -> DocGuardMut<'_> {
151 DocGuardMut::Ref(&mut self.0)
152 }
153}
154
155impl Document for BaseDocument {
156 fn inner(&self) -> DocGuard<'_> {
157 DocGuard::Ref(self)
158 }
159 fn inner_mut(&mut self) -> DocGuardMut<'_> {
160 DocGuardMut::Ref(self)
161 }
162}
163
164impl Document for Rc<RefCell<BaseDocument>> {
165 fn inner(&self) -> DocGuard<'_> {
166 DocGuard::RefCell(self.borrow())
167 }
168
169 fn inner_mut(&mut self) -> DocGuardMut<'_> {
170 DocGuardMut::RefCell(self.borrow_mut())
171 }
172}
173
174pub enum DocumentEvent {
175 ResourceLoad(ResourceLoadResponse),
176}
177
178pub struct BaseDocument {
179 id: usize,
181
182 pub(crate) url: DocumentUrl,
185 pub(crate) devtool_settings: DevtoolSettings,
187 pub(crate) viewport: Viewport,
189 pub(crate) viewport_scroll: crate::Point<f64>,
191 pub(crate) media_type: MediaType,
193
194 pub(crate) tx: Sender<DocumentEvent>,
196 pub(crate) rx: Option<Receiver<DocumentEvent>>,
198
199 pub(crate) nodes: Box<Slab<Node>>,
204
205 pub(crate) stylist: Stylist,
208 pub(crate) animations: DocumentAnimationSet,
209 pub(crate) guard: SharedRwLock,
211 pub(crate) snapshots: SnapshotMap,
213
214 pub(crate) font_ctx: Arc<Mutex<parley::FontContext>>,
217 #[cfg(feature = "parallel-construct")]
218 pub(crate) thread_font_contexts: ThreadLocal<RefCell<Box<FontContext>>>,
220 pub(crate) layout_ctx: parley::LayoutContext<TextBrush>,
222
223 pub(crate) hover_node_id: Option<usize>,
225 pub(crate) hover_node_is_text: bool,
227 pub(crate) focus_node_id: Option<usize>,
229 pub(crate) active_node_id: Option<usize>,
231 pub(crate) mousedown_node_id: Option<usize>,
233 pub(crate) last_mousedown_time: Option<Instant>,
235 pub(crate) mousedown_position: taffy::Point<f32>,
237 pub(crate) click_count: u16,
239 pub(crate) drag_mode: DragMode,
241 pub(crate) scroll_animation: ScrollAnimationState,
243
244 pub(crate) text_selection: TextSelection,
246
247 pub(crate) has_active_animations: bool,
250 pub(crate) has_canvas: bool,
252 pub(crate) subdoc_is_animating: bool,
254
255 pub(crate) nodes_to_id: HashMap<String, usize>,
257 pub(crate) nodes_to_stylesheet: BTreeMap<usize, DocumentStyleSheet>,
259 pub(crate) ua_stylesheets: HashMap<String, DocumentStyleSheet>,
262 pub(crate) controls_to_form: HashMap<usize, usize>,
264 pub(crate) sub_document_nodes: HashSet<usize>,
266 pub(crate) changed_nodes: HashSet<usize>,
268 pub(crate) deferred_construction_nodes: Vec<ConstructionTask>,
270
271 pub(crate) image_cache: HashMap<String, ImageData>,
274
275 pub(crate) pending_images: HashMap<String, Vec<(usize, ImageType)>>,
279
280 pub(crate) pending_critical_resources: HashSet<usize>,
282
283 pub net_provider: Arc<dyn NetProvider>,
286 pub navigation_provider: Arc<dyn NavigationProvider>,
289 pub shell_provider: Arc<dyn ShellProvider>,
291 pub html_parser_provider: Arc<dyn HtmlParserProvider>,
293}
294
295pub(crate) fn make_device(
296 viewport: &Viewport,
297 media_type: MediaType,
298 font_ctx: Arc<Mutex<FontContext>>,
299) -> Device {
300 let width = viewport.window_size.0 as f32 / viewport.scale();
301 let height = viewport.window_size.1 as f32 / viewport.scale();
302 let viewport_size = euclid::Size2D::new(width, height);
303 let device_pixel_ratio = euclid::Scale::new(viewport.scale());
304
305 Device::new(
306 media_type,
307 selectors::matching::QuirksMode::NoQuirks,
308 viewport_size,
309 device_pixel_ratio,
310 Box::new(BlitzFontMetricsProvider { font_ctx }),
311 ComputedValues::initial_values_with_font_override(Font::initial_values()),
312 match viewport.color_scheme {
313 ColorScheme::Light => PrefersColorScheme::Light,
314 ColorScheme::Dark => PrefersColorScheme::Dark,
315 },
316 )
317}
318
319impl BaseDocument {
320 pub fn new(config: DocumentConfig) -> Self {
322 static ID_GENERATOR: AtomicUsize = AtomicUsize::new(1);
323
324 let id = ID_GENERATOR.fetch_add(1, Ordering::SeqCst);
325
326 let font_ctx = config
327 .font_ctx
328 .map(|mut font_ctx| {
329 font_ctx.source_cache.make_shared();
330 font_ctx
332 })
333 .unwrap_or_else(|| {
334 use parley::fontique::{Collection, CollectionOptions, SourceCache};
335 let mut font_ctx = FontContext {
336 source_cache: SourceCache::new_shared(),
337 collection: Collection::new(CollectionOptions {
338 shared: false,
339 system_fonts: true,
340 }),
341 };
342 font_ctx
343 .collection
344 .register_fonts(Blob::new(Arc::new(crate::BULLET_FONT) as _), None);
345 font_ctx
346 });
347 let font_ctx = Arc::new(Mutex::new(font_ctx));
348
349 style_config::set_pref!("layout.grid.enabled", true);
351 style_config::set_pref!("layout.unimplemented", true);
352 style_config::set_pref!("layout.columns.enabled", true);
353 style_config::set_pref!("layout.threads", -1);
354
355 let viewport = config.viewport.unwrap_or_default();
356 let media_type = config.media_type.unwrap_or_else(MediaType::screen);
357 let device = make_device(&viewport, media_type.clone(), font_ctx.clone());
358 let stylist = Stylist::new(device, QuirksMode::NoQuirks);
359 let snapshots = SnapshotMap::new();
360 let nodes = Box::new(Slab::new());
361 let guard = SharedRwLock::new();
362 let nodes_to_id = HashMap::new();
363
364 let base_url = config
365 .base_url
366 .and_then(|url| DocumentUrl::from_str(&url).ok())
367 .unwrap_or_default();
368
369 let net_provider = config
370 .net_provider
371 .unwrap_or_else(|| Arc::new(DummyNetProvider));
372 let navigation_provider = config
373 .navigation_provider
374 .unwrap_or_else(|| Arc::new(DummyNavigationProvider));
375 let shell_provider = config
376 .shell_provider
377 .unwrap_or_else(|| Arc::new(DummyShellProvider));
378 let html_parser_provider = config
379 .html_parser_provider
380 .unwrap_or_else(|| Arc::new(DummyHtmlParserProvider));
381
382 let (tx, rx) = channel();
383
384 let mut doc = Self {
385 id,
386 tx,
387 rx: Some(rx),
388
389 guard,
390 nodes,
391 stylist,
392 animations: DocumentAnimationSet::default(),
393 snapshots,
394 nodes_to_id,
395 viewport,
396 media_type,
397 devtool_settings: DevtoolSettings::default(),
398 viewport_scroll: crate::Point::ZERO,
399 url: base_url,
400 ua_stylesheets: HashMap::new(),
401 nodes_to_stylesheet: BTreeMap::new(),
402 font_ctx,
403 #[cfg(feature = "parallel-construct")]
404 thread_font_contexts: ThreadLocal::new(),
405 layout_ctx: parley::LayoutContext::new(),
406
407 hover_node_id: None,
408 hover_node_is_text: false,
409 focus_node_id: None,
410 active_node_id: None,
411 mousedown_node_id: None,
412 has_active_animations: false,
413 subdoc_is_animating: false,
414 has_canvas: false,
415 sub_document_nodes: HashSet::new(),
416 changed_nodes: HashSet::new(),
417 deferred_construction_nodes: Vec::new(),
418 image_cache: HashMap::new(),
419 pending_images: HashMap::new(),
420 pending_critical_resources: HashSet::new(),
421 controls_to_form: HashMap::new(),
422 net_provider,
423 navigation_provider,
424 shell_provider,
425 html_parser_provider,
426 last_mousedown_time: None,
427 mousedown_position: taffy::Point::ZERO,
428 click_count: 0,
429 drag_mode: DragMode::None,
430 scroll_animation: ScrollAnimationState::None,
431 text_selection: TextSelection::default(),
432 };
433
434 doc.create_node(NodeData::Document);
436 doc.root_node_mut().flags.insert(NodeFlags::IS_IN_DOCUMENT);
437
438 match config.ua_stylesheets {
439 Some(stylesheets) => {
440 for ss in &stylesheets {
441 doc.add_user_agent_stylesheet(ss);
442 }
443 }
444 None => doc.add_user_agent_stylesheet(DEFAULT_CSS),
445 }
446
447 let stylo_element_data = StyloElementData {
449 styles: ElementStyles {
450 primary: Some(
451 ComputedValues::initial_values_with_font_override(Font::initial_values())
452 .to_arc(),
453 ),
454 ..Default::default()
455 },
456 ..Default::default()
457 };
458 let stylo_data = &mut doc.root_node_mut().stylo_element_data;
459 *stylo_data.ensure_init_mut() = stylo_element_data;
460
461 doc
462 }
463
464 pub fn set_net_provider(&mut self, net_provider: Arc<dyn NetProvider>) {
466 self.net_provider = net_provider;
467 }
468
469 pub fn set_navigation_provider(&mut self, navigation_provider: Arc<dyn NavigationProvider>) {
471 self.navigation_provider = navigation_provider;
472 }
473
474 pub fn set_shell_provider(&mut self, shell_provider: Arc<dyn ShellProvider>) {
476 self.shell_provider = shell_provider;
477 }
478
479 pub fn set_html_parser_provider(&mut self, html_parser_provider: Arc<dyn HtmlParserProvider>) {
481 self.html_parser_provider = html_parser_provider;
482 }
483
484 pub fn set_base_url(&mut self, url: &str) {
486 self.url = DocumentUrl::from(Url::parse(url).unwrap());
487 }
488
489 pub fn guard(&self) -> &SharedRwLock {
490 &self.guard
491 }
492
493 pub fn tree(&self) -> &Slab<Node> {
494 &self.nodes
495 }
496
497 pub fn id(&self) -> usize {
498 self.id
499 }
500
501 pub fn get_node(&self, node_id: usize) -> Option<&Node> {
502 self.nodes.get(node_id)
503 }
504
505 pub fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node> {
506 self.nodes.get_mut(node_id)
507 }
508
509 pub fn get_focussed_node_id(&self) -> Option<usize> {
510 self.focus_node_id
511 .or(self.try_root_element().map(|el| el.id))
512 }
513
514 pub fn mutate<'doc>(&'doc mut self) -> DocumentMutator<'doc> {
515 DocumentMutator::new(self)
516 }
517
518 pub fn handle_dom_event<F: FnMut(DomEvent)>(
519 &mut self,
520 event: &mut DomEvent,
521 dispatch_event: F,
522 ) {
523 handle_dom_event(self, event, dispatch_event)
524 }
525
526 pub fn as_any_mut(&mut self) -> &mut dyn Any {
527 self
528 }
529
530 pub fn label_bound_input_element(&self, label_node_id: usize) -> Option<&Node> {
537 let label_element = self.nodes[label_node_id].element_data()?;
538 if let Some(target_element_dom_id) = label_element.attr(local_name!("for")) {
539 TreeTraverser::new(self)
540 .filter_map(|id| {
541 let node = self.get_node(id)?;
542 let element_data = node.element_data()?;
543 if element_data.name.local != local_name!("input") {
544 return None;
545 }
546 let id = element_data.id.as_ref()?;
547 if *id == *target_element_dom_id {
548 Some(node)
549 } else {
550 None
551 }
552 })
553 .next()
554 } else {
555 TreeTraverser::new_with_root(self, label_node_id)
556 .filter_map(|child_id| {
557 let node = self.get_node(child_id)?;
558 let element_data = node.element_data()?;
559 if element_data.name.local == local_name!("input") {
560 Some(node)
561 } else {
562 None
563 }
564 })
565 .next()
566 }
567 }
568
569 pub fn toggle_checkbox(el: &mut ElementData) -> bool {
570 let Some(is_checked) = el.checkbox_input_checked_mut() else {
571 return false;
572 };
573 *is_checked = !*is_checked;
574
575 *is_checked
576 }
577
578 pub fn toggle_radio(&mut self, radio_set_name: String, target_radio_id: usize) {
579 for i in 0..self.nodes.len() {
580 let node = &mut self.nodes[i];
581 if let Some(node_data) = node.data.downcast_element_mut() {
582 if node_data.attr(local_name!("name")) == Some(&radio_set_name) {
583 let was_clicked = i == target_radio_id;
584 let Some(is_checked) = node_data.checkbox_input_checked_mut() else {
585 continue;
586 };
587 *is_checked = was_clicked;
588 }
589 }
590 }
591 }
592
593 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
594 let node = &mut self.nodes[node_id];
595 let did_change = node.element_data_mut().unwrap().set_style_property(
596 name,
597 value,
598 &self.guard,
599 self.url.url_extra_data(),
600 );
601 if did_change {
602 node.mark_style_attr_updated();
603 }
604 }
605
606 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
607 let node = &mut self.nodes[node_id];
608 let did_change = node.element_data_mut().unwrap().remove_style_property(
609 name,
610 &self.guard,
611 self.url.url_extra_data(),
612 );
613 if did_change {
614 node.mark_style_attr_updated();
615 }
616 }
617
618 pub fn set_sub_document(&mut self, node_id: usize, sub_document: Box<dyn Document>) {
619 self.nodes[node_id]
620 .element_data_mut()
621 .unwrap()
622 .set_sub_document(sub_document);
623 self.sub_document_nodes.insert(node_id);
624 }
625
626 pub fn remove_sub_document(&mut self, node_id: usize) {
627 self.nodes[node_id]
628 .element_data_mut()
629 .unwrap()
630 .remove_sub_document();
631 self.sub_document_nodes.remove(&node_id);
632 }
633
634 pub fn root_node(&self) -> &Node {
635 &self.nodes[0]
636 }
637
638 pub fn root_node_mut(&mut self) -> &mut Node {
639 &mut self.nodes[0]
640 }
641
642 pub fn try_root_element(&self) -> Option<&Node> {
643 TDocument::as_node(&self.root_node()).first_element_child()
644 }
645
646 pub fn root_element(&self) -> &Node {
647 TDocument::as_node(&self.root_node())
648 .first_element_child()
649 .unwrap()
650 .as_element()
651 .unwrap()
652 }
653
654 pub fn create_node(&mut self, node_data: NodeData) -> usize {
655 let slab_ptr = self.nodes.as_mut() as *mut Slab<Node>;
656 let guard = self.guard.clone();
657
658 let entry = self.nodes.vacant_entry();
659 let id = entry.key();
660 entry.insert(Node::new(slab_ptr, id, guard, node_data));
661
662 self.changed_nodes.insert(id);
664 id
665 }
666
667 pub(crate) fn drop_node_ignoring_parent(&mut self, node_id: usize) -> Option<Node> {
668 let mut node = self.nodes.try_remove(node_id);
669 if let Some(node) = &mut node {
670 if let Some(before) = node.before {
671 self.drop_node_ignoring_parent(before);
672 }
673 if let Some(after) = node.after {
674 self.drop_node_ignoring_parent(after);
675 }
676
677 for &child in &node.children {
678 self.drop_node_ignoring_parent(child);
679 }
680 }
681 node
682 }
683
684 pub fn has_changes(&self) -> bool {
686 self.changed_nodes.is_empty()
687 }
688
689 pub fn create_text_node(&mut self, text: &str) -> usize {
690 let content = text.to_string();
691 let data = NodeData::Text(TextNodeData::new(content));
692 self.create_node(data)
693 }
694
695 pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
696 let node = &self.nodes[node_id];
698 let data = node.data.clone();
699 let children = node.children.clone();
700
701 let new_node_id = self.create_node(data);
703
704 let new_children: Vec<usize> = children
706 .into_iter()
707 .map(|child_id| self.deep_clone_node(child_id))
708 .collect();
709 for &child_id in &new_children {
710 self.nodes[child_id].parent = Some(new_node_id);
711 }
712 self.nodes[new_node_id].children = new_children;
713
714 new_node_id
715 }
716
717 pub(crate) fn remove_and_drop_pe(&mut self, node_id: usize) -> Option<Node> {
718 fn remove_pe_ignoring_parent(doc: &mut BaseDocument, node_id: usize) -> Option<Node> {
719 let mut node = doc.nodes.try_remove(node_id);
720 if let Some(node) = &mut node {
721 for &child in &node.children {
722 remove_pe_ignoring_parent(doc, child);
723 }
724 }
725 node
726 }
727
728 let node = remove_pe_ignoring_parent(self, node_id);
729
730 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
732 let parent = &mut self.nodes[parent_id];
733 parent.children.retain(|id| *id != node_id);
734 }
735
736 node
737 }
738
739 pub(crate) fn resolve_url(&self, raw: &str) -> url::Url {
740 self.url.resolve_relative(raw).unwrap_or_else(|| {
741 panic!(
742 "to be able to resolve {raw} with the base_url: {:?}",
743 *self.url
744 )
745 })
746 }
747
748 pub fn print_tree(&self) {
749 crate::util::walk_tree(0, self.root_node());
750 }
751
752 pub fn print_subtree(&self, node_id: usize) {
753 crate::util::walk_tree(0, &self.nodes[node_id]);
754 }
755
756 pub fn reload_resource_by_href(&mut self, href_to_reload: &str) {
757 for &node_id in self.nodes_to_stylesheet.keys() {
758 let node = &self.nodes[node_id];
759 let Some(element) = node.element_data() else {
760 continue;
761 };
762
763 if element.name.local == local_name!("link") {
764 if let Some(href) = element.attr(local_name!("href")) {
765 if href == href_to_reload {
767 let resolved_href = self.resolve_url(href);
768 self.net_provider.fetch(
769 self.id(),
770 Request::get(resolved_href.clone()),
771 ResourceHandler::boxed(
772 self.tx.clone(),
773 self.id,
774 Some(node_id),
775 self.shell_provider.clone(),
776 StylesheetHandler {
777 source_url: resolved_href,
778 guard: self.guard.clone(),
779 net_provider: self.net_provider.clone(),
780 },
781 ),
782 );
783 }
784 }
785 }
786 }
787 }
788
789 pub fn process_style_element(&mut self, target_id: usize) {
790 let css = self.nodes[target_id].text_content();
791 let css = html_escape::decode_html_entities(&css);
792 let sheet = self.make_stylesheet(&css, Origin::Author);
793 self.add_stylesheet_for_node(sheet, target_id);
794 }
795
796 pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
797 if let Some(sheet) = self.ua_stylesheets.remove(contents) {
798 self.stylist.remove_stylesheet(sheet, &self.guard.read());
799 }
800 }
801
802 pub fn add_user_agent_stylesheet(&mut self, css: &str) {
803 let sheet = self.make_stylesheet(css, Origin::UserAgent);
804 self.ua_stylesheets.insert(css.to_string(), sheet.clone());
805 self.stylist.append_stylesheet(sheet, &self.guard.read());
806 }
807
808 pub fn make_stylesheet(&self, css: impl AsRef<str>, origin: Origin) -> DocumentStyleSheet {
809 let data = Stylesheet::from_str(
810 css.as_ref(),
811 self.url.url_extra_data(),
812 origin,
813 ServoArc::new(self.guard.wrap(MediaList::empty())),
814 self.guard.clone(),
815 Some(&StylesheetLoader {
816 tx: self.tx.clone(),
817 doc_id: self.id,
818 net_provider: self.net_provider.clone(),
819 shell_provider: self.shell_provider.clone(),
820 }),
821 None,
822 QuirksMode::NoQuirks,
823 AllowImportRules::Yes,
824 );
825
826 DocumentStyleSheet(ServoArc::new(data))
827 }
828
829 pub fn upsert_stylesheet_for_node(&mut self, node_id: usize) {
830 let raw_styles = self.nodes[node_id].text_content();
831 let sheet = self.make_stylesheet(raw_styles, Origin::Author);
832 self.add_stylesheet_for_node(sheet, node_id);
833 }
834
835 pub fn add_stylesheet_for_node(&mut self, stylesheet: DocumentStyleSheet, node_id: usize) {
836 let old = self.nodes_to_stylesheet.insert(node_id, stylesheet.clone());
837
838 if let Some(old) = old {
839 self.stylist.remove_stylesheet(old, &self.guard.read())
840 }
841
842 crate::net::fetch_font_face(
844 self.tx.clone(),
845 self.id,
846 Some(node_id),
847 &stylesheet.0,
848 &self.net_provider,
849 &self.shell_provider,
850 &self.guard.read(),
851 );
852
853 let element = &mut self.nodes[node_id].element_data_mut().unwrap();
855 element.special_data = SpecialElementData::Stylesheet(stylesheet.clone());
856
857 let insertion_point = self
859 .nodes_to_stylesheet
860 .range((Bound::Excluded(node_id), Bound::Unbounded))
861 .next()
862 .map(|(_, sheet)| sheet);
863
864 if let Some(insertion_point) = insertion_point {
865 self.stylist.insert_stylesheet_before(
866 stylesheet,
867 insertion_point.clone(),
868 &self.guard.read(),
869 )
870 } else {
871 self.stylist
872 .append_stylesheet(stylesheet, &self.guard.read())
873 }
874 }
875
876 pub fn handle_messages(&mut self) {
877 let rx = self.rx.take().unwrap();
880
881 while let Ok(msg) = rx.try_recv() {
882 self.handle_message(msg);
883 }
884
885 self.rx = Some(rx);
887 }
888
889 pub fn handle_message(&mut self, msg: DocumentEvent) {
890 match msg {
891 DocumentEvent::ResourceLoad(resource) => self.load_resource(resource),
892 }
893 }
894
895 pub fn has_pending_critical_resources(&self) -> bool {
897 !self.pending_critical_resources.is_empty()
898 }
899
900 pub fn load_resource(&mut self, res: ResourceLoadResponse) {
901 self.pending_critical_resources.remove(&res.request_id);
902
903 let Ok(resource) = res.result else {
904 return;
906 };
907
908 match resource {
909 Resource::Css(css) => {
910 let node_id = res.node_id.unwrap();
911 self.add_stylesheet_for_node(css, node_id);
912 }
913 Resource::Image(_kind, width, height, image_data) => {
914 let image = ImageData::Raster(RasterImageData::new(width, height, image_data));
916
917 let Some(url) = res.resolved_url.as_ref() else {
918 return;
919 };
920
921 let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
923
924 #[cfg(feature = "tracing")]
925 tracing::info!(
926 "Image {url} loaded, applying to {} nodes",
927 waiting_nodes.len()
928 );
929
930 self.image_cache.insert(url.clone(), image.clone());
932
933 for (node_id, image_type) in waiting_nodes {
935 let Some(node) = self.get_node_mut(node_id) else {
936 continue;
937 };
938
939 match image_type {
940 ImageType::Image => {
941 node.element_data_mut().unwrap().special_data =
942 SpecialElementData::Image(Box::new(image.clone()));
943
944 node.cache.clear();
946 node.insert_damage(ALL_DAMAGE);
947 }
948 ImageType::Background(idx) => {
949 if let Some(Some(bg_image)) = node
950 .element_data_mut()
951 .and_then(|el| el.background_images.get_mut(idx))
952 {
953 bg_image.status = Status::Ok;
954 bg_image.image = image.clone();
955 }
956 }
957 }
958 }
959 }
960 #[cfg(feature = "svg")]
961 Resource::Svg(_kind, tree) => {
962 let image = ImageData::Svg(tree);
964
965 let Some(url) = res.resolved_url.as_ref() else {
966 return;
967 };
968
969 let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
971
972 #[cfg(feature = "tracing")]
973 tracing::info!(
974 "SVG {url} loaded, applying to {} nodes",
975 waiting_nodes.len()
976 );
977
978 self.image_cache.insert(url.clone(), image.clone());
980
981 for (node_id, image_type) in waiting_nodes {
983 let Some(node) = self.get_node_mut(node_id) else {
984 continue;
985 };
986
987 match image_type {
988 ImageType::Image => {
989 node.element_data_mut().unwrap().special_data =
990 SpecialElementData::Image(Box::new(image.clone()));
991
992 node.cache.clear();
994 node.insert_damage(ALL_DAMAGE);
995 }
996 ImageType::Background(idx) => {
997 if let Some(Some(bg_image)) = node
998 .element_data_mut()
999 .and_then(|el| el.background_images.get_mut(idx))
1000 {
1001 bg_image.status = Status::Ok;
1002 bg_image.image = image.clone();
1003 }
1004 }
1005 }
1006 }
1007 }
1008 Resource::Font(bytes) => {
1009 let font = Blob::new(Arc::new(bytes));
1010
1011 let mut global_font_ctx = self.font_ctx.lock().unwrap();
1014 global_font_ctx
1015 .collection
1016 .register_fonts(font.clone(), None);
1017
1018 #[cfg(feature = "parallel-construct")]
1019 {
1020 rayon::broadcast(|_ctx| {
1021 let mut font_ctx = self
1022 .thread_font_contexts
1023 .get_or(|| RefCell::new(Box::new(global_font_ctx.clone())))
1024 .borrow_mut();
1025 font_ctx.collection.register_fonts(font.clone(), None);
1026 });
1027 }
1028 drop(global_font_ctx);
1029
1030 self.invalidate_inline_contexts();
1032 }
1033 Resource::None => {
1034 }
1036 }
1037 }
1038
1039 pub fn snapshot_node(&mut self, node_id: usize) {
1040 let node = &mut self.nodes[node_id];
1041 let opaque_node_id = TNode::opaque(&&*node);
1042 node.has_snapshot = true;
1043 node.snapshot_handled
1044 .store(false, std::sync::atomic::Ordering::SeqCst);
1045
1046 if let Some(_existing_snapshot) = self.snapshots.get_mut(&opaque_node_id) {
1048 } else {
1051 let attrs: Option<Vec<_>> = node.attrs().map(|attrs| {
1052 attrs
1053 .iter()
1054 .map(|attr| {
1055 let ident = AttrIdentifier {
1056 local_name: GenericAtomIdent(attr.name.local.clone()),
1057 name: GenericAtomIdent(attr.name.local.clone()),
1058 namespace: GenericAtomIdent(attr.name.ns.clone()),
1059 prefix: None,
1060 };
1061
1062 let value = if attr.name.local == local_name!("id") {
1063 AttrValue::Atom(Atom::from(&*attr.value))
1064 } else if attr.name.local == local_name!("class") {
1065 let classes = attr
1066 .value
1067 .split_ascii_whitespace()
1068 .map(Atom::from)
1069 .collect();
1070 AttrValue::TokenList(attr.value.clone(), classes)
1071 } else {
1072 AttrValue::String(attr.value.clone())
1073 };
1074
1075 (ident, value)
1076 })
1077 .collect()
1078 });
1079
1080 let changed_attrs = attrs
1081 .as_ref()
1082 .map(|attrs| attrs.iter().map(|attr| attr.0.name.clone()).collect())
1083 .unwrap_or_default();
1084
1085 self.snapshots.insert(
1086 opaque_node_id,
1087 ServoElementSnapshot {
1088 state: Some(node.element_state),
1089 attrs,
1090 changed_attrs,
1091 class_changed: true,
1092 id_changed: true,
1093 other_attributes_changed: true,
1094 },
1095 );
1096 }
1097 }
1098
1099 pub fn snapshot_node_and(&mut self, node_id: usize, cb: impl FnOnce(&mut Node)) {
1100 self.snapshot_node(node_id);
1101 cb(&mut self.nodes[node_id]);
1102 }
1103
1104 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
1106 if TDocument::as_node(&&self.nodes[0])
1107 .first_element_child()
1108 .is_none()
1109 {
1110 #[cfg(feature = "tracing")]
1111 tracing::warn!("No DOM - not resolving hit test");
1112 return None;
1113 }
1114
1115 self.root_element().hit(x, y)
1116 }
1117
1118 pub fn focus_next_node(&mut self) -> Option<usize> {
1119 let focussed_node_id = self.get_focussed_node_id()?;
1120 let id = self.next_node(&self.nodes[focussed_node_id], |node| node.is_focussable())?;
1121 self.set_focus_to(id);
1122 Some(id)
1123 }
1124
1125 pub fn clear_focus(&mut self) {
1127 if let Some(id) = self.focus_node_id {
1128 let shell_provider = self.shell_provider.clone();
1129 self.snapshot_node_and(id, |node| node.blur(shell_provider));
1130 self.focus_node_id = None;
1131 }
1132 }
1133
1134 pub fn set_mousedown_node_id(&mut self, node_id: Option<usize>) {
1135 self.mousedown_node_id = node_id;
1136 }
1137 pub fn set_focus_to(&mut self, focus_node_id: usize) -> bool {
1138 if Some(focus_node_id) == self.focus_node_id {
1139 return false;
1140 }
1141
1142 #[cfg(feature = "tracing")]
1143 tracing::info!("Focussed node {focus_node_id}");
1144
1145 let shell_provider = self.shell_provider.clone();
1146
1147 if let Some(id) = self.focus_node_id {
1149 self.snapshot_node_and(id, |node| node.blur(shell_provider.clone()));
1150 }
1151
1152 self.snapshot_node_and(focus_node_id, |node| node.focus(shell_provider));
1154
1155 self.focus_node_id = Some(focus_node_id);
1156
1157 true
1158 }
1159
1160 pub fn active_node(&mut self) -> bool {
1161 let Some(hover_node_id) = self.get_hover_node_id() else {
1162 return false;
1163 };
1164
1165 if let Some(active_node_id) = self.active_node_id {
1166 if active_node_id == hover_node_id {
1167 return true;
1168 }
1169 self.unactive_node();
1170 }
1171
1172 let active_node_id = Some(hover_node_id);
1173
1174 let node_path = self.maybe_node_layout_ancestors(active_node_id);
1175 for &id in node_path.iter() {
1176 self.snapshot_node_and(id, |node| node.active());
1177 }
1178
1179 self.active_node_id = active_node_id;
1180
1181 true
1182 }
1183
1184 pub fn unactive_node(&mut self) -> bool {
1185 let Some(active_node_id) = self.active_node_id.take() else {
1186 return false;
1187 };
1188
1189 let node_path = self.maybe_node_layout_ancestors(Some(active_node_id));
1190 for &id in node_path.iter() {
1191 self.snapshot_node_and(id, |node| node.unactive());
1192 }
1193
1194 true
1195 }
1196
1197 pub fn set_hover_to(&mut self, x: f32, y: f32) -> bool {
1198 let hit = self.hit(x, y);
1199 let hover_node_id = hit.map(|hit| hit.node_id);
1200 let new_is_text = hit.map(|hit| hit.is_text).unwrap_or(false);
1201
1202 if hover_node_id == self.hover_node_id {
1204 return false;
1205 }
1206
1207 let old_node_path = self.maybe_node_layout_ancestors(self.hover_node_id);
1208 let new_node_path = self.maybe_node_layout_ancestors(hover_node_id);
1209 let same_count = old_node_path
1210 .iter()
1211 .zip(&new_node_path)
1212 .take_while(|(o, n)| o == n)
1213 .count();
1214 for &id in old_node_path.iter().skip(same_count) {
1215 self.snapshot_node_and(id, |node| node.unhover());
1216 }
1217 for &id in new_node_path.iter().skip(same_count) {
1218 self.snapshot_node_and(id, |node| node.hover());
1219 }
1220
1221 self.hover_node_id = hover_node_id;
1222 self.hover_node_is_text = new_is_text;
1223
1224 let cursor = self.get_cursor().unwrap_or_default();
1226 self.shell_provider.set_cursor(cursor);
1227
1228 self.shell_provider.request_redraw();
1230
1231 true
1232 }
1233
1234 pub fn clear_hover(&mut self) -> bool {
1235 let Some(hover_node_id) = self.hover_node_id else {
1236 return false;
1237 };
1238
1239 let old_node_path = self.maybe_node_layout_ancestors(Some(hover_node_id));
1240 for &id in old_node_path.iter() {
1241 self.snapshot_node_and(id, |node| node.unhover());
1242 }
1243
1244 self.hover_node_id = None;
1245 self.hover_node_is_text = false;
1246
1247 let cursor = self.get_cursor().unwrap_or_default();
1249 self.shell_provider.set_cursor(cursor);
1250
1251 self.shell_provider.request_redraw();
1253
1254 true
1255 }
1256
1257 pub fn get_hover_node_id(&self) -> Option<usize> {
1258 self.hover_node_id
1259 }
1260
1261 pub fn set_viewport(&mut self, viewport: Viewport) {
1262 let scale_has_changed = viewport.scale_f64() != self.viewport.scale_f64();
1263 self.viewport = viewport;
1264 self.set_stylist_device(make_device(
1265 &self.viewport,
1266 self.media_type.clone(),
1267 self.font_ctx.clone(),
1268 ));
1269 self.scroll_viewport_by(0.0, 0.0); if scale_has_changed {
1272 self.invalidate_inline_contexts();
1273 self.shell_provider.request_redraw();
1274 }
1275 }
1276
1277 pub fn media_type(&self) -> &MediaType {
1279 &self.media_type
1280 }
1281
1282 pub fn set_media_type(&mut self, media_type: MediaType) {
1285 if self.media_type == media_type {
1286 return;
1287 }
1288 self.media_type = media_type;
1289 self.set_stylist_device(make_device(
1290 &self.viewport,
1291 self.media_type.clone(),
1292 self.font_ctx.clone(),
1293 ));
1294 }
1295
1296 pub fn viewport(&self) -> &Viewport {
1297 &self.viewport
1298 }
1299
1300 pub fn viewport_mut(&mut self) -> ViewportMut<'_> {
1301 ViewportMut::new(self)
1302 }
1303
1304 pub fn zoom_by(&mut self, increment: f32) {
1305 *self.viewport.zoom_mut() += increment;
1306 self.set_viewport(self.viewport.clone());
1307 }
1308
1309 pub fn zoom_to(&mut self, zoom: f32) {
1310 *self.viewport.zoom_mut() = zoom;
1311 self.set_viewport(self.viewport.clone());
1312 }
1313
1314 pub fn get_viewport(&self) -> Viewport {
1315 self.viewport.clone()
1316 }
1317
1318 pub fn devtools(&self) -> &DevtoolSettings {
1319 &self.devtool_settings
1320 }
1321
1322 pub fn devtools_mut(&mut self) -> &mut DevtoolSettings {
1323 &mut self.devtool_settings
1324 }
1325
1326 pub fn is_animating(&self) -> bool {
1327 self.has_canvas
1328 | self.has_active_animations
1329 | self.subdoc_is_animating
1330 | (self.scroll_animation != ScrollAnimationState::None)
1331 }
1332
1333 pub fn set_stylist_device(&mut self, device: Device) {
1335 let origins = {
1336 let guard = &self.guard;
1337 let guards = StylesheetGuards {
1338 author: &guard.read(),
1339 ua_or_user: &guard.read(),
1340 };
1341 self.stylist.set_device(device, &guards)
1342 };
1343 self.stylist.force_stylesheet_origins_dirty(origins);
1344 }
1345
1346 pub fn stylist_device(&mut self) -> &Device {
1347 self.stylist.device()
1348 }
1349
1350 pub fn get_cursor(&self) -> Option<CursorIcon> {
1351 let node = &self.nodes[self.get_hover_node_id()?];
1352
1353 if let Some(subdoc) = node.subdoc().map(|doc| doc.inner()) {
1354 return subdoc.get_cursor();
1355 }
1356
1357 let style = node.primary_styles()?;
1358 let keyword = stylo_to_cursor_icon(style.clone_cursor().keyword);
1359
1360 if keyword != CursorIcon::Default {
1362 return Some(keyword);
1363 }
1364
1365 if node
1367 .element_data()
1368 .is_some_and(|e| e.text_input_data().is_some())
1369 {
1370 return Some(CursorIcon::Text);
1371 }
1372
1373 let mut maybe_node = Some(node);
1375 while let Some(node) = maybe_node {
1376 if node.is_link() {
1377 return Some(CursorIcon::Pointer);
1378 }
1379
1380 maybe_node = node.layout_parent.get().map(|node_id| node.with(node_id));
1381 }
1382
1383 if self.hover_node_is_text {
1385 return Some(CursorIcon::Text);
1386 }
1387
1388 Some(CursorIcon::Default)
1390 }
1391
1392 pub fn scroll_node_by<F: FnMut(DomEvent)>(
1393 &mut self,
1394 node_id: usize,
1395 x: f64,
1396 y: f64,
1397 dispatch_event: F,
1398 ) {
1399 self.scroll_node_by_has_changed(node_id, x, y, dispatch_event);
1400 }
1401
1402 pub fn scroll_node_by_has_changed<F: FnMut(DomEvent)>(
1406 &mut self,
1407 node_id: usize,
1408 x: f64,
1409 y: f64,
1410 mut dispatch_event: F,
1411 ) -> bool {
1412 let Some(node) = self.nodes.get_mut(node_id) else {
1413 return false;
1414 };
1415
1416 let is_html_or_body = node.data.downcast_element().is_some_and(|e| {
1417 let tag = &e.name.local;
1418 tag == "html" || tag == "body"
1419 });
1420
1421 let (can_x_scroll, can_y_scroll) = node
1422 .primary_styles()
1423 .map(|styles| {
1424 (
1425 matches!(styles.clone_overflow_x(), Overflow::Scroll | Overflow::Auto),
1426 matches!(styles.clone_overflow_y(), Overflow::Scroll | Overflow::Auto)
1427 || (styles.clone_overflow_y() == Overflow::Visible && is_html_or_body),
1428 )
1429 })
1430 .unwrap_or((false, false));
1431
1432 let initial = node.scroll_offset;
1433 let new_x = node.scroll_offset.x - x;
1434 let new_y = node.scroll_offset.y - y;
1435
1436 let mut bubble_x = 0.0;
1437 let mut bubble_y = 0.0;
1438
1439 let scroll_width = node.final_layout.scroll_width() as f64;
1440 let scroll_height = node.final_layout.scroll_height() as f64;
1441
1442 if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
1444 let has_changed = if let Some(hover_node_id) = sub_doc.get_hover_node_id() {
1445 sub_doc.scroll_node_by_has_changed(hover_node_id, x, y, dispatch_event)
1446 } else {
1447 sub_doc.scroll_viewport_by_has_changed(x, y)
1448 };
1449
1450 return has_changed;
1452 }
1453
1454 if !can_x_scroll {
1456 bubble_x = x
1457 } else if new_x < 0.0 {
1458 bubble_x = -new_x;
1459 node.scroll_offset.x = 0.0;
1460 } else if new_x > scroll_width {
1461 bubble_x = scroll_width - new_x;
1462 node.scroll_offset.x = scroll_width;
1463 } else {
1464 node.scroll_offset.x = new_x;
1465 }
1466
1467 if !can_y_scroll {
1468 bubble_y = y
1469 } else if new_y < 0.0 {
1470 bubble_y = -new_y;
1471 node.scroll_offset.y = 0.0;
1472 } else if new_y > scroll_height {
1473 bubble_y = scroll_height - new_y;
1474 node.scroll_offset.y = scroll_height;
1475 } else {
1476 node.scroll_offset.y = new_y;
1477 }
1478
1479 let has_changed = node.scroll_offset != initial;
1480
1481 if has_changed {
1482 let layout = node.final_layout;
1483 let event = BlitzScrollEvent {
1484 scroll_top: node.scroll_offset.y,
1485 scroll_left: node.scroll_offset.x,
1486 scroll_width: layout.scroll_width() as i32,
1487 scroll_height: layout.scroll_height() as i32,
1488 client_width: layout.size.width as i32,
1489 client_height: layout.size.height as i32,
1490 };
1491
1492 dispatch_event(DomEvent::new(node_id, DomEventData::Scroll(event)));
1493 }
1494
1495 if bubble_x != 0.0 || bubble_y != 0.0 {
1496 if let Some(parent) = node.parent {
1497 return self.scroll_node_by_has_changed(parent, bubble_x, bubble_y, dispatch_event)
1498 | has_changed;
1499 } else {
1500 return self.scroll_viewport_by_has_changed(bubble_x, bubble_y) | has_changed;
1501 }
1502 }
1503
1504 has_changed
1505 }
1506
1507 pub fn scroll_viewport_by(&mut self, x: f64, y: f64) {
1508 self.scroll_viewport_by_has_changed(x, y);
1509 }
1510
1511 pub fn scroll_viewport_by_has_changed(&mut self, x: f64, y: f64) -> bool {
1513 let content_size = self.root_element().final_layout.size;
1514 let new_scroll = (self.viewport_scroll.x - x, self.viewport_scroll.y - y);
1515 let window_width = self.viewport.window_size.0 as f64 / self.viewport.scale() as f64;
1516 let window_height = self.viewport.window_size.1 as f64 / self.viewport.scale() as f64;
1517
1518 let initial = self.viewport_scroll;
1519 self.viewport_scroll.x = f64::max(
1520 0.0,
1521 f64::min(new_scroll.0, content_size.width as f64 - window_width),
1522 );
1523 self.viewport_scroll.y = f64::max(
1524 0.0,
1525 f64::min(new_scroll.1, content_size.height as f64 - window_height),
1526 );
1527
1528 self.viewport_scroll != initial
1529 }
1530
1531 pub fn scroll_by(
1532 &mut self,
1533 anchor_node_id: Option<usize>,
1534 scroll_x: f64,
1535 scroll_y: f64,
1536 dispatch_event: &mut dyn FnMut(DomEvent),
1537 ) -> bool {
1538 if let Some(anchor_node_id) = anchor_node_id {
1539 self.scroll_node_by_has_changed(anchor_node_id, scroll_x, scroll_y, dispatch_event)
1540 } else {
1541 self.scroll_viewport_by_has_changed(scroll_x, scroll_y)
1542 }
1543 }
1544
1545 pub fn viewport_scroll(&self) -> crate::Point<f64> {
1546 self.viewport_scroll
1547 }
1548
1549 pub fn set_viewport_scroll(&mut self, scroll: crate::Point<f64>) {
1550 self.viewport_scroll = scroll;
1551 }
1552
1553 pub fn get_client_bounding_rect(&self, node_id: usize) -> Option<BoundingRect> {
1555 let node = self.get_node(node_id)?;
1556 let pos = node.absolute_position(0.0, 0.0);
1557
1558 Some(BoundingRect {
1559 x: pos.x as f64 - self.viewport_scroll.x,
1560 y: pos.y as f64 - self.viewport_scroll.y,
1561 width: node.unrounded_layout.size.width as f64,
1562 height: node.unrounded_layout.size.height as f64,
1563 })
1564 }
1565
1566 pub fn find_title_node(&self) -> Option<&Node> {
1567 TreeTraverser::new(self)
1568 .find(|node_id| {
1569 self.nodes[*node_id]
1570 .data
1571 .is_element_with_tag_name(&local_name!("title"))
1572 })
1573 .map(|node_id| &self.nodes[node_id])
1574 }
1575
1576 pub fn with_text_input(
1577 &mut self,
1578 node_id: usize,
1579 cb: impl FnOnce(PlainEditorDriver<TextBrush>),
1580 ) {
1581 let Some(node) = self.nodes.get_mut(node_id) else {
1582 return;
1583 };
1584
1585 if let Some(text_input) = node
1586 .element_data_mut()
1587 .and_then(|el| el.text_input_data_mut())
1588 {
1589 let mut font_ctx = self.font_ctx.lock().unwrap();
1590 let layout_ctx = &mut self.layout_ctx;
1591 let driver = text_input.editor.driver(&mut font_ctx, layout_ctx);
1592 cb(driver)
1593 }
1594 }
1595
1596 pub(crate) fn compute_has_canvas(&self) -> bool {
1597 TreeTraverser::new(self).any(|node_id| {
1598 let node = &self.nodes[node_id];
1599 let Some(element) = node.element_data() else {
1600 return false;
1601 };
1602 if element.name.local == local_name!("canvas") && element.has_attr(local_name!("src")) {
1603 return true;
1604 }
1605
1606 false
1607 })
1608 }
1609
1610 pub fn find_text_position(&self, x: f32, y: f32) -> Option<(usize, usize)> {
1616 let hit = self.hit(x, y)?;
1617 let hit_node = self.get_node(hit.node_id)?;
1618 let inline_root = hit_node.inline_root_ancestor()?;
1619 let byte_offset = inline_root.text_offset_at_point(hit.x, hit.y)?;
1620 Some((inline_root.id, byte_offset))
1621 }
1622
1623 pub fn set_text_selection(
1625 &mut self,
1626 anchor_node: usize,
1627 anchor_offset: usize,
1628 focus_node: usize,
1629 focus_offset: usize,
1630 ) {
1631 self.text_selection =
1632 TextSelection::new(anchor_node, anchor_offset, focus_node, focus_offset);
1633
1634 if let (Some(parent), Some(idx)) = self.anonymous_block_location(anchor_node) {
1636 self.text_selection
1637 .anchor
1638 .set_anonymous(parent, idx, anchor_offset);
1639 }
1640 if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1641 self.text_selection
1642 .focus
1643 .set_anonymous(parent, idx, focus_offset);
1644 }
1645 }
1646
1647 fn anonymous_block_location(&self, node_id: usize) -> (Option<usize>, Option<usize>) {
1650 let Some(node) = self.get_node(node_id) else {
1651 return (None, None);
1652 };
1653
1654 if !node.is_anonymous() {
1655 return (None, None);
1656 }
1657
1658 let Some(parent_id) = node.parent else {
1659 return (None, None);
1660 };
1661
1662 let Some(parent) = self.get_node(parent_id) else {
1663 return (Some(parent_id), None);
1664 };
1665
1666 let layout_children = parent.layout_children.borrow();
1667 let Some(children) = layout_children.as_ref() else {
1668 return (Some(parent_id), None);
1669 };
1670
1671 let mut anon_index = 0;
1673 for &child_id in children.iter() {
1674 if child_id == node_id {
1675 return (Some(parent_id), Some(anon_index));
1676 }
1677 if self.get_node(child_id).is_some_and(|n| n.is_anonymous()) {
1678 anon_index += 1;
1679 }
1680 }
1681
1682 (Some(parent_id), None)
1683 }
1684
1685 pub fn clear_text_selection(&mut self) {
1687 self.text_selection.clear();
1688 }
1689
1690 pub fn update_selection_focus(&mut self, focus_node: usize, focus_offset: usize) {
1692 if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1694 self.text_selection
1695 .focus
1696 .set_anonymous(parent, idx, focus_offset);
1697 } else {
1698 self.text_selection.set_focus(focus_node, focus_offset);
1699 }
1700 }
1701
1702 pub fn extend_text_selection_to_point(&mut self, x: f32, y: f32) -> bool {
1705 if !self.text_selection.anchor.is_some() {
1706 return false;
1707 }
1708
1709 if let Some((node, offset)) = self.find_text_position(x, y) {
1710 self.update_selection_focus(node, offset);
1711 self.shell_provider.request_redraw();
1712 true
1713 } else {
1714 false
1715 }
1716 }
1717
1718 fn find_anonymous_block_by_index(
1720 &self,
1721 parent_id: usize,
1722 target_index: usize,
1723 ) -> Option<usize> {
1724 let parent = self.get_node(parent_id)?;
1725 let layout_children = parent.layout_children.borrow();
1726 let children = layout_children.as_ref()?;
1727
1728 children
1729 .iter()
1730 .filter(|&&child_id| self.get_node(child_id).is_some_and(|n| n.is_anonymous()))
1731 .nth(target_index)
1732 .copied()
1733 }
1734
1735 pub fn has_text_selection(&self) -> bool {
1737 self.text_selection.is_active()
1738 }
1739
1740 pub fn get_selected_text(&self) -> Option<String> {
1742 let ranges = self.get_text_selection_ranges();
1743 if ranges.is_empty() {
1744 return None;
1745 }
1746
1747 let mut result = String::new();
1748 for (node_id, start, end) in &ranges {
1749 let node = self.get_node(*node_id)?;
1750 let element_data = node.element_data()?;
1751 let inline_layout = element_data.inline_layout_data.as_ref()?;
1752
1753 if *end > inline_layout.text.len() {
1754 continue;
1755 }
1756
1757 if !result.is_empty() {
1758 result.push(' ');
1759 }
1760 result.push_str(&inline_layout.text[*start..*end]);
1761 }
1762
1763 if result.is_empty() {
1764 None
1765 } else {
1766 Some(result)
1767 }
1768 }
1769
1770 pub fn get_text_selection_ranges(&self) -> Vec<(usize, usize, usize)> {
1773 let lookup = |parent_id, idx| self.find_anonymous_block_by_index(parent_id, idx);
1774
1775 let anchor_node = match self.text_selection.anchor.resolve_node_id(lookup) {
1776 Some(id) => id,
1777 None => return Vec::new(),
1778 };
1779 let focus_node = match self.text_selection.focus.resolve_node_id(lookup) {
1780 Some(id) => id,
1781 None => return Vec::new(),
1782 };
1783
1784 if anchor_node == focus_node {
1786 let start = self
1787 .text_selection
1788 .anchor
1789 .offset
1790 .min(self.text_selection.focus.offset);
1791 let end = self
1792 .text_selection
1793 .anchor
1794 .offset
1795 .max(self.text_selection.focus.offset);
1796
1797 if start == end {
1798 return Vec::new();
1799 }
1800 return vec![(anchor_node, start, end)];
1801 }
1802
1803 let inline_roots = self.collect_inline_roots_in_range(anchor_node, focus_node);
1805 if inline_roots.is_empty() {
1806 return Vec::new();
1807 }
1808
1809 let first_in_roots = inline_roots[0];
1812
1813 let (first_node, first_offset, last_node, last_offset) =
1814 if first_in_roots == anchor_node || (first_in_roots != focus_node) {
1815 (
1817 anchor_node,
1818 self.text_selection.anchor.offset,
1819 focus_node,
1820 self.text_selection.focus.offset,
1821 )
1822 } else {
1823 (
1825 focus_node,
1826 self.text_selection.focus.offset,
1827 anchor_node,
1828 self.text_selection.anchor.offset,
1829 )
1830 };
1831
1832 let mut ranges = Vec::with_capacity(inline_roots.len());
1833
1834 for &node_id in &inline_roots {
1835 let Some(node) = self.get_node(node_id) else {
1836 continue;
1837 };
1838 let Some(element_data) = node.element_data() else {
1839 continue;
1840 };
1841 let Some(inline_layout) = element_data.inline_layout_data.as_ref() else {
1842 continue;
1843 };
1844
1845 let text_len = inline_layout.text.len();
1846
1847 if node_id == first_node && node_id == last_node {
1848 let start = first_offset.min(last_offset);
1849 let end = first_offset.max(last_offset);
1850 if start < end && end <= text_len {
1851 ranges.push((node_id, start, end));
1852 }
1853 } else if node_id == first_node {
1854 if first_offset < text_len {
1855 ranges.push((node_id, first_offset, text_len));
1856 }
1857 } else if node_id == last_node {
1858 if last_offset > 0 && last_offset <= text_len {
1859 ranges.push((node_id, 0, last_offset));
1860 }
1861 } else if text_len > 0 {
1862 ranges.push((node_id, 0, text_len));
1863 }
1864 }
1865
1866 ranges
1867 }
1868}
1869
1870pub struct BoundingRect {
1871 pub x: f64,
1872 pub y: f64,
1873 pub width: f64,
1874 pub height: f64,
1875}
1876
1877impl AsRef<BaseDocument> for BaseDocument {
1878 fn as_ref(&self) -> &BaseDocument {
1879 self
1880 }
1881}
1882
1883impl AsMut<BaseDocument> for BaseDocument {
1884 fn as_mut(&mut self) -> &mut BaseDocument {
1885 self
1886 }
1887}