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, StyleThreading,
18 TextNodeData,
19};
20use blitz_traits::devtools::DevtoolSettings;
21use blitz_traits::events::{BlitzScrollEvent, DomEvent, DomEventData, HitResult, UiEvent};
22use blitz_traits::navigation::{DummyNavigationProvider, NavigationProvider};
23use blitz_traits::net::{AbortSignal, DummyNetProvider, NetProvider, Request};
24use blitz_traits::shell::{ColorScheme, DummyShellProvider, ShellProvider, Viewport};
25use cursor_icon::CursorIcon;
26use linebender_resource_handle::Blob;
27use markup5ever::local_name;
28use parley::{FontContext, PlainEditorDriver};
29use selectors::{Element, matching::QuirksMode};
30use slab::Slab;
31use std::any::Any;
32use std::cell::RefCell;
33use std::collections::{BTreeMap, Bound, HashMap, HashSet};
34use std::ops::{Deref, DerefMut};
35use std::rc::Rc;
36use std::str::FromStr;
37use std::sync::atomic::{AtomicUsize, Ordering};
38use std::sync::mpsc::{Receiver, Sender, channel};
39use std::sync::{Arc, Mutex, MutexGuard, OnceLock, RwLockReadGuard, RwLockWriteGuard};
40use std::task::Context as TaskContext;
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, UserSelect};
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;
63use web_time::Instant;
64
65#[cfg(feature = "parallel-construct")]
66use thread_local::ThreadLocal;
67
68pub enum DocGuard<'a> {
69 Ref(&'a BaseDocument),
70 RefCell(std::cell::Ref<'a, BaseDocument>),
71 RwLock(RwLockReadGuard<'a, BaseDocument>),
72 Mutex(MutexGuard<'a, BaseDocument>),
73}
74
75impl Deref for DocGuard<'_> {
76 type Target = BaseDocument;
77 #[inline(always)]
78 fn deref(&self) -> &Self::Target {
79 match self {
80 Self::Ref(base_document) => base_document,
81 Self::RefCell(refcell_guard) => refcell_guard,
82 Self::RwLock(rw_lock_read_guard) => rw_lock_read_guard,
83 Self::Mutex(mutex_guard) => mutex_guard,
84 }
85 }
86}
87
88pub enum DocGuardMut<'a> {
89 Ref(&'a mut BaseDocument),
90 RefCell(std::cell::RefMut<'a, BaseDocument>),
91 RwLock(RwLockWriteGuard<'a, BaseDocument>),
92 Mutex(MutexGuard<'a, BaseDocument>),
93}
94
95impl Deref for DocGuardMut<'_> {
96 type Target = BaseDocument;
97 #[inline(always)]
98 fn deref(&self) -> &Self::Target {
99 match self {
100 Self::Ref(base_document) => base_document,
101 Self::RefCell(refcell_guard) => refcell_guard,
102 Self::RwLock(rw_lock_read_guard) => rw_lock_read_guard,
103 Self::Mutex(mutex_guard) => mutex_guard,
104 }
105 }
106}
107
108impl DerefMut for DocGuardMut<'_> {
109 #[inline(always)]
110 fn deref_mut(&mut self) -> &mut Self::Target {
111 match self {
112 Self::Ref(base_document) => base_document,
113 Self::RefCell(refcell_guard) => &mut *refcell_guard,
114 Self::RwLock(rw_lock_read_guard) => &mut *rw_lock_read_guard,
115 Self::Mutex(mutex_guard) => &mut *mutex_guard,
116 }
117 }
118}
119
120pub trait Document: Any + 'static {
123 fn inner(&self) -> DocGuard<'_>;
124 fn inner_mut(&mut self) -> DocGuardMut<'_>;
125
126 fn handle_ui_event(&mut self, event: UiEvent) {
128 let mut doc = self.inner_mut();
129 let mut driver = EventDriver::new(&mut *doc, NoopEventHandler);
130 driver.handle_ui_event(event);
131 }
132
133 fn poll(&mut self, task_context: Option<TaskContext>) -> bool {
135 let _ = task_context;
137 false
138 }
139
140 fn id(&self) -> usize {
142 self.inner().id
143 }
144}
145
146pub struct PlainDocument(pub BaseDocument);
147impl Document for PlainDocument {
148 fn inner(&self) -> DocGuard<'_> {
149 DocGuard::Ref(&self.0)
150 }
151 fn inner_mut(&mut self) -> DocGuardMut<'_> {
152 DocGuardMut::Ref(&mut self.0)
153 }
154}
155
156impl Document for BaseDocument {
157 fn inner(&self) -> DocGuard<'_> {
158 DocGuard::Ref(self)
159 }
160 fn inner_mut(&mut self) -> DocGuardMut<'_> {
161 DocGuardMut::Ref(self)
162 }
163}
164
165impl Document for Rc<RefCell<BaseDocument>> {
166 fn inner(&self) -> DocGuard<'_> {
167 DocGuard::RefCell(self.borrow())
168 }
169
170 fn inner_mut(&mut self) -> DocGuardMut<'_> {
171 DocGuardMut::RefCell(self.borrow_mut())
172 }
173}
174
175pub enum DocumentEvent {
176 ResourceLoad(ResourceLoadResponse),
177}
178
179pub struct BaseDocument {
180 id: usize,
182
183 pub(crate) url: DocumentUrl,
186 pub(crate) devtool_settings: DevtoolSettings,
188 pub(crate) viewport: Viewport,
190 pub(crate) viewport_scroll: crate::Point<f64>,
192 pub(crate) media_type: MediaType,
194 pub(crate) style_threading: StyleThreading,
196
197 pub(crate) tx: Sender<DocumentEvent>,
199 pub(crate) rx: Option<Receiver<DocumentEvent>>,
201
202 pub(crate) nodes: Box<Slab<Node>>,
207
208 pub(crate) stylist: Stylist,
211 pub(crate) animations: DocumentAnimationSet,
212 pub(crate) guard: SharedRwLock,
214 pub(crate) snapshots: SnapshotMap,
216
217 pub(crate) font_ctx: Arc<Mutex<parley::FontContext>>,
220 #[cfg(feature = "parallel-construct")]
221 pub(crate) thread_font_contexts: ThreadLocal<RefCell<Box<FontContext>>>,
223 pub(crate) layout_ctx: parley::LayoutContext<TextBrush>,
225
226 pub(crate) hover_node_id: Option<usize>,
228 pub(crate) hover_node_is_text: bool,
230 pub(crate) focus_node_id: Option<usize>,
232 pub(crate) active_node_id: Option<usize>,
234 pub(crate) mousedown_node_id: Option<usize>,
236 pub(crate) last_mousedown_time: Option<Instant>,
238 pub(crate) mousedown_position: taffy::Point<f32>,
240 pub(crate) click_count: u16,
242 pub(crate) drag_mode: DragMode,
244 pub(crate) scroll_animation: ScrollAnimationState,
246
247 pub(crate) text_selection: TextSelection,
249
250 pub(crate) has_active_animations: bool,
253 pub(crate) has_canvas: bool,
255 pub(crate) subdoc_is_animating: bool,
257
258 pub(crate) nodes_to_id: HashMap<String, usize>,
260 pub(crate) nodes_to_stylesheet: BTreeMap<usize, DocumentStyleSheet>,
262 pub(crate) ua_stylesheets: HashMap<String, DocumentStyleSheet>,
265 pub(crate) controls_to_form: HashMap<usize, usize>,
267 pub(crate) sub_document_nodes: HashSet<usize>,
269 pub(crate) changed_nodes: HashSet<usize>,
271 pub(crate) deferred_construction_nodes: Vec<ConstructionTask>,
273
274 #[cfg(feature = "custom-widget")]
276 pub(crate) custom_widget_nodes: HashSet<usize>,
277 #[cfg(feature = "custom-widget")]
279 pub(crate) pending_resource_deallocations: Vec<anyrender::ResourceId>,
280
281 pub(crate) image_cache: HashMap<String, ImageData>,
284
285 pub(crate) pending_images: HashMap<String, Vec<(usize, ImageType)>>,
289
290 pub(crate) pending_critical_resources: HashSet<usize>,
292
293 pub net_provider: Arc<dyn NetProvider>,
296 pub navigation_provider: Arc<dyn NavigationProvider>,
299 pub shell_provider: Arc<dyn ShellProvider>,
301 pub html_parser_provider: Arc<dyn HtmlParserProvider>,
303 pub(crate) abort_signal: Option<AbortSignal>,
307}
308
309pub(crate) fn make_device(
310 viewport: &Viewport,
311 media_type: MediaType,
312 font_ctx: Arc<Mutex<FontContext>>,
313) -> Device {
314 let width = viewport.window_size.0 as f32 / viewport.scale();
315 let height = viewport.window_size.1 as f32 / viewport.scale();
316 let viewport_size = euclid::Size2D::new(width, height);
317 let device_pixel_ratio = euclid::Scale::new(viewport.scale());
318
319 Device::new(
320 media_type,
321 selectors::matching::QuirksMode::NoQuirks,
322 viewport_size,
323 device_pixel_ratio,
324 Box::new(BlitzFontMetricsProvider { font_ctx }),
325 ComputedValues::initial_values_with_font_override(Font::initial_values()),
326 match viewport.color_scheme {
327 ColorScheme::Light => PrefersColorScheme::Light,
328 ColorScheme::Dark => PrefersColorScheme::Dark,
329 },
330 )
331}
332
333impl BaseDocument {
334 pub fn new(config: DocumentConfig) -> Self {
336 static ID_GENERATOR: AtomicUsize = AtomicUsize::new(1);
337
338 let id = ID_GENERATOR.fetch_add(1, Ordering::SeqCst);
339
340 let font_ctx = config
341 .font_ctx
342 .map(|mut font_ctx| {
343 font_ctx.source_cache.make_shared();
344 font_ctx
346 })
347 .unwrap_or_else(|| {
348 use parley::fontique::{Collection, CollectionOptions, SourceCache};
349 let mut font_ctx = FontContext {
350 source_cache: SourceCache::new_shared(),
351 collection: Collection::new(CollectionOptions {
352 shared: false,
353 system_fonts: cfg!(all(
354 feature = "system_fonts",
355 not(target_arch = "wasm32")
356 )),
357 }),
358 };
359 font_ctx
360 .collection
361 .register_fonts(Blob::new(Arc::new(crate::BULLET_FONT) as _), None);
362 font_ctx
363 });
364 let font_ctx = Arc::new(Mutex::new(font_ctx));
365
366 style_config::set_pref!("layout.grid.enabled", true);
368 style_config::set_pref!("layout.unimplemented", true);
369 style_config::set_pref!("layout.columns.enabled", true);
370 style_config::set_pref!("layout.css.basic-shape-shape.enabled", true);
371 style_config::set_pref!("layout.threads", -1);
372
373 let viewport = config.viewport.unwrap_or_default();
374 let media_type = config.media_type.unwrap_or_else(MediaType::screen);
375 let device = make_device(&viewport, media_type.clone(), font_ctx.clone());
376 let stylist = Stylist::new(device, QuirksMode::NoQuirks);
377 let snapshots = SnapshotMap::new();
378 let nodes = Box::new(Slab::new());
379 let guard = SharedRwLock::new();
380 let nodes_to_id = HashMap::new();
381
382 let base_url = config
383 .base_url
384 .and_then(|url| DocumentUrl::from_str(&url).ok())
385 .unwrap_or_default();
386
387 let net_provider = config
388 .net_provider
389 .unwrap_or_else(|| Arc::new(DummyNetProvider));
390 let navigation_provider = config
391 .navigation_provider
392 .unwrap_or_else(|| Arc::new(DummyNavigationProvider));
393 let shell_provider = config
394 .shell_provider
395 .unwrap_or_else(|| Arc::new(DummyShellProvider));
396 let html_parser_provider = config
397 .html_parser_provider
398 .unwrap_or_else(|| Arc::new(DummyHtmlParserProvider));
399
400 let (tx, rx) = channel();
401
402 let mut doc = Self {
403 id,
404 tx,
405 rx: Some(rx),
406
407 guard,
408 nodes,
409 stylist,
410 animations: DocumentAnimationSet::default(),
411 snapshots,
412 nodes_to_id,
413 viewport,
414 media_type,
415 style_threading: config.style_threading,
416 devtool_settings: DevtoolSettings::default(),
417 viewport_scroll: crate::Point::ZERO,
418 url: base_url,
419 ua_stylesheets: HashMap::new(),
420 nodes_to_stylesheet: BTreeMap::new(),
421 font_ctx,
422 #[cfg(feature = "parallel-construct")]
423 thread_font_contexts: ThreadLocal::new(),
424 layout_ctx: parley::LayoutContext::new(),
425
426 hover_node_id: None,
427 hover_node_is_text: false,
428 focus_node_id: None,
429 active_node_id: None,
430 mousedown_node_id: None,
431 has_active_animations: false,
432 subdoc_is_animating: false,
433 has_canvas: false,
434 sub_document_nodes: HashSet::new(),
435
436 #[cfg(feature = "custom-widget")]
437 custom_widget_nodes: HashSet::new(),
438 #[cfg(feature = "custom-widget")]
439 pending_resource_deallocations: Vec::new(),
440
441 changed_nodes: HashSet::new(),
442 deferred_construction_nodes: Vec::new(),
443 image_cache: HashMap::new(),
444 pending_images: HashMap::new(),
445 pending_critical_resources: HashSet::new(),
446 controls_to_form: HashMap::new(),
447 net_provider,
448 navigation_provider,
449 shell_provider,
450 html_parser_provider,
451 abort_signal: config.abort_signal,
452 last_mousedown_time: None,
453 mousedown_position: taffy::Point::ZERO,
454 click_count: 0,
455 drag_mode: DragMode::None,
456 scroll_animation: ScrollAnimationState::None,
457 text_selection: TextSelection::default(),
458 };
459
460 doc.create_node(NodeData::Document);
462 doc.root_node_mut().flags.insert(NodeFlags::IS_IN_DOCUMENT);
463
464 match config.ua_stylesheets {
465 Some(stylesheets) => {
466 for ss in &stylesheets {
467 doc.add_user_agent_stylesheet(ss);
468 }
469 }
470 None => doc.add_user_agent_stylesheet(DEFAULT_CSS),
471 }
472
473 let stylo_element_data = StyloElementData {
475 styles: ElementStyles {
476 primary: Some(
477 ComputedValues::initial_values_with_font_override(Font::initial_values())
478 .to_arc(),
479 ),
480 ..Default::default()
481 },
482 ..Default::default()
483 };
484 let stylo_data = &mut doc.root_node_mut().stylo_element_data;
485 *stylo_data.ensure_init_mut() = stylo_element_data;
486
487 doc
488 }
489
490 pub fn set_net_provider(&mut self, net_provider: Arc<dyn NetProvider>) {
492 self.net_provider = net_provider;
493 }
494
495 pub fn set_navigation_provider(&mut self, navigation_provider: Arc<dyn NavigationProvider>) {
497 self.navigation_provider = navigation_provider;
498 }
499
500 pub fn set_shell_provider(&mut self, shell_provider: Arc<dyn ShellProvider>) {
502 self.shell_provider = shell_provider;
503 }
504
505 pub fn set_html_parser_provider(&mut self, html_parser_provider: Arc<dyn HtmlParserProvider>) {
507 self.html_parser_provider = html_parser_provider;
508 }
509
510 pub fn set_base_url(&mut self, url: &str) {
512 self.url = DocumentUrl::from(Url::parse(url).unwrap());
513 }
514
515 pub fn guard(&self) -> &SharedRwLock {
516 &self.guard
517 }
518
519 pub fn tree(&self) -> &Slab<Node> {
520 &self.nodes
521 }
522
523 pub fn id(&self) -> usize {
524 self.id
525 }
526
527 pub(crate) fn build_request(&self, url: url::Url) -> Request {
530 crate::net::stamped_request(url, self.abort_signal.as_ref())
531 }
532
533 pub fn favicon_url(&self) -> Option<String> {
534 self.tree().iter().find_map(|(_, node)| {
535 let data = &node.data;
536 if !data.is_element_with_tag_name(&local_name!("link")) {
537 return None;
538 }
539 let rel = data.attr(local_name!("rel"))?;
540 if !rel
541 .split_ascii_whitespace()
542 .any(|v| v.eq_ignore_ascii_case("icon"))
543 {
544 return None;
545 }
546 data.attr(local_name!("href")).map(|s| s.to_string())
547 })
548 }
549
550 pub fn get_node(&self, node_id: usize) -> Option<&Node> {
551 self.nodes.get(node_id)
552 }
553
554 pub fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node> {
555 self.nodes.get_mut(node_id)
556 }
557
558 pub fn get_focussed_node_id(&self) -> Option<usize> {
559 self.focus_node_id
560 .or(self.try_root_element().map(|el| el.id))
561 }
562
563 pub fn mutate<'doc>(&'doc mut self) -> DocumentMutator<'doc> {
564 DocumentMutator::new(self)
565 }
566
567 pub fn handle_dom_event<F: FnMut(DomEvent)>(
568 &mut self,
569 event: &mut DomEvent,
570 dispatch_event: F,
571 ) {
572 handle_dom_event(self, event, dispatch_event)
573 }
574
575 pub fn as_any_mut(&mut self) -> &mut dyn Any {
576 self
577 }
578
579 pub fn label_bound_input_element(&self, label_node_id: usize) -> Option<&Node> {
586 let label_element = self.nodes[label_node_id].element_data()?;
587 if let Some(target_element_dom_id) = label_element.attr(local_name!("for")) {
588 TreeTraverser::new(self)
589 .filter_map(|id| {
590 let node = self.get_node(id)?;
591 let element_data = node.element_data()?;
592 if element_data.name.local != local_name!("input") {
593 return None;
594 }
595 let id = element_data.id.as_ref()?;
596 if *id == *target_element_dom_id {
597 Some(node)
598 } else {
599 None
600 }
601 })
602 .next()
603 } else {
604 TreeTraverser::new_with_root(self, label_node_id)
605 .filter_map(|child_id| {
606 let node = self.get_node(child_id)?;
607 let element_data = node.element_data()?;
608 if element_data.name.local == local_name!("input") {
609 Some(node)
610 } else {
611 None
612 }
613 })
614 .next()
615 }
616 }
617
618 pub fn toggle_checkbox(el: &mut ElementData) -> bool {
619 let Some(is_checked) = el.checkbox_input_checked_mut() else {
620 return false;
621 };
622 *is_checked = !*is_checked;
623
624 *is_checked
625 }
626
627 pub fn toggle_radio(&mut self, radio_set_name: String, target_radio_id: usize) {
628 for i in 0..self.nodes.len() {
629 let node = &mut self.nodes[i];
630 if let Some(node_data) = node.data.downcast_element_mut() {
631 if node_data.attr(local_name!("name")) == Some(&radio_set_name) {
632 let was_clicked = i == target_radio_id;
633 let Some(is_checked) = node_data.checkbox_input_checked_mut() else {
634 continue;
635 };
636 *is_checked = was_clicked;
637 }
638 }
639 }
640 }
641
642 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
643 let node = &mut self.nodes[node_id];
644 let did_change = node.element_data_mut().unwrap().set_style_property(
645 name,
646 value,
647 &self.guard,
648 self.url.url_extra_data(),
649 );
650 if did_change {
651 node.mark_style_attr_updated();
652 }
653 }
654
655 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
656 let node = &mut self.nodes[node_id];
657 let did_change = node.element_data_mut().unwrap().remove_style_property(
658 name,
659 &self.guard,
660 self.url.url_extra_data(),
661 );
662 if did_change {
663 node.mark_style_attr_updated();
664 }
665 }
666
667 pub fn sub_document_node_ids(&self) -> Vec<usize> {
668 self.sub_document_nodes.iter().copied().collect()
669 }
670
671 pub fn set_sub_document(&mut self, node_id: usize, sub_document: Box<dyn Document>) {
672 self.nodes[node_id]
673 .element_data_mut()
674 .unwrap()
675 .set_sub_document(sub_document);
676 self.sub_document_nodes.insert(node_id);
677 }
678
679 pub fn remove_sub_document(&mut self, node_id: usize) {
680 self.nodes[node_id]
681 .element_data_mut()
682 .unwrap()
683 .remove_sub_document();
684 self.sub_document_nodes.remove(&node_id);
685 }
686
687 #[cfg(feature = "custom-widget")]
688 pub fn custom_widget_node_ids(&self) -> Vec<usize> {
689 self.custom_widget_nodes.iter().copied().collect()
690 }
691
692 #[cfg(feature = "custom-widget")]
693 pub fn take_pending_resource_deallocations(&mut self) -> Vec<anyrender::ResourceId> {
694 std::mem::take(&mut self.pending_resource_deallocations)
695 }
696
697 #[cfg(feature = "custom-widget")]
698 pub fn set_custom_widget(&mut self, node_id: usize, widget: Box<dyn crate::Widget>) {
699 self.nodes[node_id]
700 .element_data_mut()
701 .unwrap()
702 .set_custom_widget(widget);
703 self.custom_widget_nodes.insert(node_id);
704 }
705
706 #[cfg(feature = "custom-widget")]
707 pub fn remove_custom_widget(&mut self, node_id: usize) {
708 let resources_to_deallocate = self.nodes[node_id]
709 .element_data_mut()
710 .unwrap()
711 .remove_custom_widget();
712 self.pending_resource_deallocations
713 .extend_from_slice(&resources_to_deallocate);
714 self.custom_widget_nodes.remove(&node_id);
715 }
716
717 pub fn root_node(&self) -> &Node {
718 &self.nodes[0]
719 }
720
721 pub fn root_node_mut(&mut self) -> &mut Node {
722 &mut self.nodes[0]
723 }
724
725 pub fn try_root_element(&self) -> Option<&Node> {
726 TDocument::as_node(&self.root_node()).first_element_child()
727 }
728
729 pub fn root_element(&self) -> &Node {
730 TDocument::as_node(&self.root_node())
731 .first_element_child()
732 .unwrap()
733 .as_element()
734 .unwrap()
735 }
736
737 pub fn create_node(&mut self, node_data: NodeData) -> usize {
738 let slab_ptr = self.nodes.as_mut() as *mut Slab<Node>;
739 let guard = self.guard.clone();
740
741 let entry = self.nodes.vacant_entry();
742 let id = entry.key();
743 entry.insert(Node::new(slab_ptr, id, guard, node_data));
744
745 self.changed_nodes.insert(id);
747 id
748 }
749
750 pub(crate) fn drop_node_ignoring_parent(&mut self, node_id: usize) -> Option<Node> {
751 let mut node = self.nodes.try_remove(node_id);
752 if let Some(node) = &mut node {
753 if let Some(before) = node.before {
754 self.drop_node_ignoring_parent(before);
755 }
756 if let Some(after) = node.after {
757 self.drop_node_ignoring_parent(after);
758 }
759
760 for &child in &node.children {
761 self.drop_node_ignoring_parent(child);
762 }
763 }
764 node
765 }
766
767 pub fn has_changes(&self) -> bool {
769 self.changed_nodes.is_empty()
770 }
771
772 pub fn create_text_node(&mut self, text: &str) -> usize {
773 let content = text.to_string();
774 let data = NodeData::Text(TextNodeData::new(content));
775 self.create_node(data)
776 }
777
778 pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
779 let node = &self.nodes[node_id];
781 let data = node.data.clone();
782 let children = node.children.clone();
783
784 let new_node_id = self.create_node(data);
786
787 let new_children: Vec<usize> = children
789 .into_iter()
790 .map(|child_id| self.deep_clone_node(child_id))
791 .collect();
792 for &child_id in &new_children {
793 self.nodes[child_id].parent = Some(new_node_id);
794 }
795 self.nodes[new_node_id].children = new_children;
796
797 new_node_id
798 }
799
800 pub(crate) fn remove_and_drop_pe(&mut self, node_id: usize) -> Option<Node> {
801 fn remove_pe_ignoring_parent(doc: &mut BaseDocument, node_id: usize) -> Option<Node> {
802 let mut node = doc.nodes.try_remove(node_id);
803 if let Some(node) = &mut node {
804 for &child in &node.children {
805 remove_pe_ignoring_parent(doc, child);
806 }
807 }
808 node
809 }
810
811 let node = remove_pe_ignoring_parent(self, node_id);
812
813 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
815 let parent = &mut self.nodes[parent_id];
816 parent.children.retain(|id| *id != node_id);
817 }
818
819 node
820 }
821
822 pub(crate) fn resolve_url(&self, raw: &str) -> url::Url {
823 self.url.resolve_relative(raw).unwrap_or_else(|| {
824 panic!(
825 "to be able to resolve {raw} with the base_url: {:?}",
826 *self.url
827 )
828 })
829 }
830
831 pub fn print_tree(&self) {
832 crate::util::walk_tree(0, self.root_node());
833 }
834
835 pub fn print_subtree(&self, node_id: usize) {
836 crate::util::walk_tree(0, &self.nodes[node_id]);
837 }
838
839 pub fn reload_resource_by_href(&mut self, href_to_reload: &str) {
840 for &node_id in self.nodes_to_stylesheet.keys() {
841 let node = &self.nodes[node_id];
842 let Some(element) = node.element_data() else {
843 continue;
844 };
845
846 if element.name.local == local_name!("link") {
847 if let Some(href) = element.attr(local_name!("href")) {
848 if href == href_to_reload {
850 let resolved_href = self.resolve_url(href);
851 self.net_provider.fetch(
852 self.id(),
853 self.build_request(resolved_href.clone()),
854 ResourceHandler::boxed(
855 self.tx.clone(),
856 self.id,
857 Some(node_id),
858 self.shell_provider.clone(),
859 StylesheetHandler {
860 source_url: resolved_href,
861 guard: self.guard.clone(),
862 net_provider: self.net_provider.clone(),
863 abort_signal: self.abort_signal.clone(),
864 },
865 ),
866 );
867 }
868 }
869 }
870 }
871 }
872
873 pub fn process_style_element(&mut self, target_id: usize) {
874 let css = self.nodes[target_id].text_content();
875 let css = html_escape::decode_html_entities(&css);
876 let sheet = self.make_stylesheet(&css, Origin::Author);
877 self.add_stylesheet_for_node(sheet, target_id);
878 }
879
880 pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
881 if let Some(sheet) = self.ua_stylesheets.remove(contents) {
882 self.stylist.remove_stylesheet(sheet, &self.guard.read());
883 }
884 }
885
886 pub fn add_user_agent_stylesheet(&mut self, css: &str) {
887 let sheet = self.make_stylesheet(css, Origin::UserAgent);
888 self.ua_stylesheets.insert(css.to_string(), sheet.clone());
889 self.stylist.append_stylesheet(sheet, &self.guard.read());
890 }
891
892 pub fn make_stylesheet(&self, css: impl AsRef<str>, origin: Origin) -> DocumentStyleSheet {
893 let data = Stylesheet::from_str(
894 css.as_ref(),
895 self.url.url_extra_data(),
896 origin,
897 ServoArc::new(self.guard.wrap(MediaList::empty())),
898 self.guard.clone(),
899 Some(&StylesheetLoader {
900 tx: self.tx.clone(),
901 doc_id: self.id,
902 net_provider: self.net_provider.clone(),
903 shell_provider: self.shell_provider.clone(),
904 abort_signal: self.abort_signal.clone(),
905 }),
906 None,
907 QuirksMode::NoQuirks,
908 AllowImportRules::Yes,
909 );
910
911 DocumentStyleSheet(ServoArc::new(data))
912 }
913
914 pub fn upsert_stylesheet_for_node(&mut self, node_id: usize) {
915 let raw_styles = self.nodes[node_id].text_content();
916 let sheet = self.make_stylesheet(raw_styles, Origin::Author);
917 self.add_stylesheet_for_node(sheet, node_id);
918 }
919
920 pub fn add_stylesheet_for_node(&mut self, stylesheet: DocumentStyleSheet, node_id: usize) {
921 let old = self.nodes_to_stylesheet.insert(node_id, stylesheet.clone());
922
923 if let Some(old) = old {
924 self.stylist.remove_stylesheet(old, &self.guard.read())
925 }
926
927 crate::net::fetch_font_face(
929 self.tx.clone(),
930 self.id,
931 Some(node_id),
932 &stylesheet.0,
933 &self.net_provider,
934 &self.shell_provider,
935 &self.guard.read(),
936 self.abort_signal.as_ref(),
937 );
938
939 let element = &mut self.nodes[node_id].element_data_mut().unwrap();
941 element.special_data = SpecialElementData::Stylesheet(stylesheet.clone());
942
943 let insertion_point = self
945 .nodes_to_stylesheet
946 .range((Bound::Excluded(node_id), Bound::Unbounded))
947 .next()
948 .map(|(_, sheet)| sheet);
949
950 if let Some(insertion_point) = insertion_point {
951 self.stylist.insert_stylesheet_before(
952 stylesheet,
953 insertion_point.clone(),
954 &self.guard.read(),
955 )
956 } else {
957 self.stylist
958 .append_stylesheet(stylesheet, &self.guard.read())
959 }
960 }
961
962 pub fn handle_messages(&mut self) {
963 let rx = self.rx.take().unwrap();
966
967 while let Ok(msg) = rx.try_recv() {
968 self.handle_message(msg);
969 }
970
971 self.rx = Some(rx);
973 }
974
975 pub fn handle_message(&mut self, msg: DocumentEvent) {
976 match msg {
977 DocumentEvent::ResourceLoad(resource) => self.load_resource(resource),
978 }
979 }
980
981 pub fn has_pending_critical_resources(&self) -> bool {
983 !self.pending_critical_resources.is_empty()
984 }
985
986 pub fn load_resource(&mut self, res: ResourceLoadResponse) {
987 self.pending_critical_resources.remove(&res.request_id);
988
989 let resource = match res.result {
990 Ok(resource) => resource,
991 Err(err) => {
992 if let Some(url) = res.resolved_url.as_ref() {
993 let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
994 #[cfg(feature = "tracing")]
995 tracing::warn!(
996 url = url.as_str(),
997 waiting_nodes = waiting_nodes.len(),
998 error = err.as_str(),
999 "Resource load failed"
1000 );
1001 #[cfg(not(feature = "tracing"))]
1002 let _ = (waiting_nodes, err);
1003 } else {
1004 #[cfg(feature = "tracing")]
1005 tracing::warn!(error = err.as_str(), "Resource load failed (no url)");
1006 #[cfg(not(feature = "tracing"))]
1007 let _ = err;
1008 }
1009 return;
1010 }
1011 };
1012
1013 match resource {
1014 Resource::Css(css) => {
1015 let node_id = res.node_id.unwrap();
1016 self.add_stylesheet_for_node(css, node_id);
1017 }
1018 Resource::Image(_kind, width, height, image_data) => {
1019 let image = ImageData::Raster(RasterImageData::new(width, height, image_data));
1021
1022 let Some(url) = res.resolved_url.as_ref() else {
1023 return;
1024 };
1025
1026 self.apply_loaded_image(url, image);
1027 }
1028 #[cfg(feature = "svg")]
1029 Resource::Svg(_kind, tree) => {
1030 let image = ImageData::Svg(tree);
1032
1033 let Some(url) = res.resolved_url.as_ref() else {
1034 return;
1035 };
1036
1037 self.apply_loaded_image(url, image);
1038 }
1039 Resource::Font(bytes, overrides) => {
1040 let font = Blob::new(Arc::new(bytes));
1041
1042 let weight_override = overrides.weight.map(parley::fontique::FontWeight::new);
1048 let info_override = parley::fontique::FontInfoOverride {
1049 family_name: overrides.family_name.as_deref(),
1050 weight: weight_override,
1051 style: overrides.style,
1052 ..Default::default()
1053 };
1054
1055 let mut global_font_ctx = self.font_ctx.lock().unwrap();
1057 global_font_ctx
1058 .collection
1059 .register_fonts(font.clone(), Some(info_override));
1060
1061 #[cfg(feature = "parallel-construct")]
1062 {
1063 rayon::broadcast(|_ctx| {
1064 let mut font_ctx = self
1065 .thread_font_contexts
1066 .get_or(|| RefCell::new(Box::new(global_font_ctx.clone())))
1067 .borrow_mut();
1068 font_ctx
1069 .collection
1070 .register_fonts(font.clone(), Some(info_override));
1071 });
1072 }
1073 drop(global_font_ctx);
1074
1075 self.invalidate_inline_contexts();
1077 }
1078 Resource::None => {
1079 }
1081 }
1082 }
1083
1084 fn apply_loaded_image(&mut self, url: &str, image: ImageData) {
1087 let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
1089
1090 #[cfg(feature = "tracing")]
1091 tracing::info!(
1092 "Image {url} loaded, applying to {} nodes",
1093 waiting_nodes.len()
1094 );
1095
1096 self.image_cache.insert(url.to_string(), image.clone());
1098
1099 for (node_id, image_type) in waiting_nodes {
1101 let Some(node) = self.get_node_mut(node_id) else {
1102 continue;
1103 };
1104
1105 match image_type {
1106 ImageType::Image => {
1107 node.element_data_mut().unwrap().special_data =
1108 SpecialElementData::Image(Box::new(image.clone()));
1109
1110 node.cache.clear();
1112 node.insert_damage(ALL_DAMAGE);
1113 }
1114 ImageType::Background(idx) | ImageType::Mask(idx) => {
1115 let layer_image = node.element_data_mut().and_then(|el| {
1116 let images = match image_type {
1117 ImageType::Background(_) => &mut el.background_images,
1118 ImageType::Mask(_) => &mut el.mask_images,
1119 ImageType::Image => unreachable!(),
1120 };
1121 images.get_mut(idx)
1122 });
1123 if let Some(Some(layer_image)) = layer_image {
1124 layer_image.status = Status::Ok;
1125 layer_image.image = image.clone();
1126 }
1127 }
1128 }
1129 }
1130 }
1131
1132 pub fn snapshot_node(&mut self, node_id: usize) {
1133 let node = &mut self.nodes[node_id];
1134 let opaque_node_id = TNode::opaque(&&*node);
1135 node.has_snapshot = true;
1136 node.snapshot_handled
1137 .store(false, std::sync::atomic::Ordering::SeqCst);
1138
1139 if let Some(_existing_snapshot) = self.snapshots.get_mut(&opaque_node_id) {
1141 } else {
1144 let attrs: Option<Vec<_>> = node.attrs().map(|attrs| {
1145 attrs
1146 .iter()
1147 .map(|attr| {
1148 let ident = AttrIdentifier {
1149 local_name: GenericAtomIdent(attr.name.local.clone()),
1150 name: GenericAtomIdent(attr.name.local.clone()),
1151 namespace: GenericAtomIdent(attr.name.ns.clone()),
1152 prefix: None,
1153 };
1154
1155 let value = if attr.name.local == local_name!("id") {
1156 AttrValue::Atom(Atom::from(&*attr.value))
1157 } else if attr.name.local == local_name!("class") {
1158 let classes = attr
1159 .value
1160 .split_ascii_whitespace()
1161 .map(Atom::from)
1162 .collect();
1163 AttrValue::TokenList(OnceLock::from(attr.value.clone()), classes)
1164 } else {
1165 AttrValue::String(attr.value.clone())
1166 };
1167
1168 (ident, value)
1169 })
1170 .collect()
1171 });
1172
1173 let changed_attrs = attrs
1174 .as_ref()
1175 .map(|attrs| attrs.iter().map(|attr| attr.0.name.clone()).collect())
1176 .unwrap_or_default();
1177
1178 self.snapshots.insert(
1179 opaque_node_id,
1180 ServoElementSnapshot {
1181 state: Some(node.element_state),
1182 attrs,
1183 changed_attrs,
1184 class_changed: true,
1185 id_changed: true,
1186 other_attributes_changed: true,
1187 },
1188 );
1189 }
1190 }
1191
1192 pub fn snapshot_node_and(&mut self, node_id: usize, cb: impl FnOnce(&mut Node)) {
1193 self.snapshot_node(node_id);
1194 cb(&mut self.nodes[node_id]);
1195 }
1196
1197 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
1199 if TDocument::as_node(&&self.nodes[0])
1200 .first_element_child()
1201 .is_none()
1202 {
1203 #[cfg(feature = "tracing")]
1204 tracing::warn!("No DOM - not resolving hit test");
1205 return None;
1206 }
1207
1208 self.root_element().hit(x, y, self.viewport().scale_f64())
1209 }
1210
1211 pub fn focus_next_node(&mut self) -> Option<usize> {
1212 let focussed_node_id = self.get_focussed_node_id()?;
1213 let id = self.next_node(&self.nodes[focussed_node_id], |node| node.is_focussable())?;
1214 self.set_focus_to(id);
1215 Some(id)
1216 }
1217
1218 pub fn clear_focus(&mut self) {
1220 if let Some(id) = self.focus_node_id {
1221 let shell_provider = self.shell_provider.clone();
1222 self.snapshot_node_and(id, |node| node.blur(shell_provider));
1223 self.focus_node_id = None;
1224 }
1225 }
1226
1227 pub fn set_mousedown_node_id(&mut self, node_id: Option<usize>) {
1228 self.mousedown_node_id = node_id;
1229 }
1230 pub fn set_focus_to(&mut self, focus_node_id: usize) -> bool {
1231 if Some(focus_node_id) == self.focus_node_id {
1232 return false;
1233 }
1234
1235 #[cfg(feature = "tracing")]
1236 tracing::info!("Focussed node {focus_node_id}");
1237
1238 let shell_provider = self.shell_provider.clone();
1239
1240 if let Some(id) = self.focus_node_id {
1242 self.snapshot_node_and(id, |node| node.blur(shell_provider.clone()));
1243 }
1244
1245 self.snapshot_node_and(focus_node_id, |node| node.focus(shell_provider));
1247
1248 self.focus_node_id = Some(focus_node_id);
1249
1250 true
1251 }
1252
1253 pub fn active_node(&mut self) -> bool {
1254 let Some(hover_node_id) = self.get_hover_node_id() else {
1255 return false;
1256 };
1257
1258 if let Some(active_node_id) = self.active_node_id {
1259 if active_node_id == hover_node_id {
1260 return true;
1261 }
1262 self.unactive_node();
1263 }
1264
1265 let active_node_id = Some(hover_node_id);
1266
1267 let node_path = self.maybe_node_layout_ancestors(active_node_id);
1268 for &id in node_path.iter() {
1269 self.snapshot_node_and(id, |node| node.active());
1270 }
1271
1272 self.active_node_id = active_node_id;
1273
1274 true
1275 }
1276
1277 pub fn unactive_node(&mut self) -> bool {
1278 let Some(active_node_id) = self.active_node_id.take() else {
1279 return false;
1280 };
1281
1282 let node_path = self.maybe_node_layout_ancestors(Some(active_node_id));
1283 for &id in node_path.iter() {
1284 self.snapshot_node_and(id, |node| node.unactive());
1285 }
1286
1287 true
1288 }
1289
1290 pub fn set_hover_to(&mut self, x: f32, y: f32) -> bool {
1291 let hit = self.hit(x, y);
1292 let hover_node_id = hit.map(|hit| hit.node_id);
1293 let new_is_text = hit.map(|hit| hit.is_text).unwrap_or(false);
1294
1295 if hover_node_id == self.hover_node_id {
1297 return false;
1298 }
1299
1300 let old_node_path = self.maybe_node_layout_ancestors(self.hover_node_id);
1301 let new_node_path = self.maybe_node_layout_ancestors(hover_node_id);
1302 let same_count = old_node_path
1303 .iter()
1304 .zip(&new_node_path)
1305 .take_while(|(o, n)| o == n)
1306 .count();
1307 for &id in old_node_path.iter().skip(same_count) {
1308 self.snapshot_node_and(id, |node| node.unhover());
1309 }
1310 for &id in new_node_path.iter().skip(same_count) {
1311 self.snapshot_node_and(id, |node| node.hover());
1312 }
1313
1314 self.hover_node_id = hover_node_id;
1315 self.hover_node_is_text = new_is_text;
1316
1317 let cursor = self.get_cursor().unwrap_or_default();
1319 self.shell_provider.set_cursor(cursor);
1320
1321 self.shell_provider.request_redraw();
1323
1324 true
1325 }
1326
1327 pub fn clear_hover(&mut self) -> bool {
1328 let Some(hover_node_id) = self.hover_node_id else {
1329 return false;
1330 };
1331
1332 let old_node_path = self.maybe_node_layout_ancestors(Some(hover_node_id));
1333 for &id in old_node_path.iter() {
1334 self.snapshot_node_and(id, |node| node.unhover());
1335 }
1336
1337 self.hover_node_id = None;
1338 self.hover_node_is_text = false;
1339
1340 let cursor = self.get_cursor().unwrap_or_default();
1342 self.shell_provider.set_cursor(cursor);
1343
1344 self.shell_provider.request_redraw();
1346
1347 true
1348 }
1349
1350 pub fn get_hover_node_id(&self) -> Option<usize> {
1351 self.hover_node_id
1352 }
1353
1354 pub fn set_viewport(&mut self, viewport: Viewport) {
1355 let scale_has_changed = viewport.scale_f64() != self.viewport.scale_f64();
1356 self.viewport = viewport;
1357 self.set_stylist_device(make_device(
1358 &self.viewport,
1359 self.media_type.clone(),
1360 self.font_ctx.clone(),
1361 ));
1362 self.scroll_viewport_by(0.0, 0.0); if scale_has_changed {
1365 self.invalidate_inline_contexts();
1366 self.shell_provider.request_redraw();
1367 }
1368 }
1369
1370 pub fn media_type(&self) -> &MediaType {
1372 &self.media_type
1373 }
1374
1375 pub fn set_media_type(&mut self, media_type: MediaType) {
1378 if self.media_type == media_type {
1379 return;
1380 }
1381 self.media_type = media_type;
1382 self.set_stylist_device(make_device(
1383 &self.viewport,
1384 self.media_type.clone(),
1385 self.font_ctx.clone(),
1386 ));
1387 }
1388
1389 pub fn viewport(&self) -> &Viewport {
1390 &self.viewport
1391 }
1392
1393 pub fn viewport_mut(&mut self) -> ViewportMut<'_> {
1394 ViewportMut::new(self)
1395 }
1396
1397 pub fn zoom_by(&mut self, increment: f32) {
1398 *self.viewport.zoom_mut() += increment;
1399 self.set_viewport(self.viewport.clone());
1400 }
1401
1402 pub fn zoom_to(&mut self, zoom: f32) {
1403 *self.viewport.zoom_mut() = zoom;
1404 self.set_viewport(self.viewport.clone());
1405 }
1406
1407 pub fn get_viewport(&self) -> Viewport {
1408 self.viewport.clone()
1409 }
1410
1411 pub fn devtools(&self) -> &DevtoolSettings {
1412 &self.devtool_settings
1413 }
1414
1415 pub fn devtools_mut(&mut self) -> &mut DevtoolSettings {
1416 &mut self.devtool_settings
1417 }
1418
1419 pub fn is_animating(&self) -> bool {
1420 #[cfg(feature = "custom-widget")]
1421 let has_custom_widgets = !self.custom_widget_nodes.is_empty();
1422 #[cfg(not(feature = "custom-widget"))]
1423 let has_custom_widgets = false;
1424
1425 self.has_canvas
1426 | self.has_active_animations
1427 | self.subdoc_is_animating
1428 | has_custom_widgets
1429 | (self.scroll_animation != ScrollAnimationState::None)
1430 }
1431
1432 pub fn set_stylist_device(&mut self, device: Device) {
1434 let origins = {
1435 let guard = &self.guard;
1436 let guards = StylesheetGuards {
1437 author: &guard.read(),
1438 ua_or_user: &guard.read(),
1439 };
1440 self.stylist.set_device(device, &guards)
1441 };
1442 self.stylist.force_stylesheet_origins_dirty(origins);
1443 }
1444
1445 pub fn stylist_device(&mut self) -> &Device {
1446 self.stylist.device()
1447 }
1448
1449 pub fn get_cursor(&self) -> Option<CursorIcon> {
1450 let node = &self.nodes[self.get_hover_node_id()?];
1451
1452 if let Some(subdoc) = node.subdoc().map(|doc| doc.inner()) {
1453 return subdoc.get_cursor();
1454 }
1455
1456 let style = node.primary_styles()?;
1457 let user_select = style.clone_user_select();
1458 let keyword = stylo_to_cursor_icon(style.clone_cursor().keyword);
1459
1460 if let Some(cursor) = keyword {
1462 return Some(cursor);
1463 }
1464
1465 if node
1467 .element_data()
1468 .is_some_and(|e| e.text_input_data().is_some())
1469 {
1470 return Some(CursorIcon::Text);
1471 }
1472
1473 let mut maybe_node = Some(node);
1475 while let Some(node) = maybe_node {
1476 if node.is_link() {
1477 return Some(CursorIcon::Pointer);
1478 }
1479
1480 maybe_node = node.layout_parent.get().map(|node_id| node.with(node_id));
1481 }
1482
1483 if self.hover_node_is_text {
1485 return Some(match user_select {
1486 UserSelect::Text => CursorIcon::Text,
1487 _ => CursorIcon::Default,
1488 });
1489 }
1490
1491 Some(CursorIcon::Default)
1493 }
1494
1495 pub fn scroll_node_by<F: FnMut(DomEvent)>(
1496 &mut self,
1497 node_id: usize,
1498 x: f64,
1499 y: f64,
1500 dispatch_event: F,
1501 ) {
1502 self.scroll_node_by_has_changed(node_id, x, y, dispatch_event);
1503 }
1504
1505 pub fn scroll_node_by_has_changed<F: FnMut(DomEvent)>(
1509 &mut self,
1510 node_id: usize,
1511 x: f64,
1512 y: f64,
1513 mut dispatch_event: F,
1514 ) -> bool {
1515 let Some(node) = self.nodes.get_mut(node_id) else {
1516 return false;
1517 };
1518
1519 let is_html_or_body = node.data.downcast_element().is_some_and(|e| {
1520 let tag = &e.name.local;
1521 tag == "html" || tag == "body"
1522 });
1523
1524 let (can_x_scroll, can_y_scroll) = node
1525 .primary_styles()
1526 .map(|styles| {
1527 (
1528 matches!(styles.clone_overflow_x(), Overflow::Scroll | Overflow::Auto),
1529 matches!(styles.clone_overflow_y(), Overflow::Scroll | Overflow::Auto)
1530 || (styles.clone_overflow_y() == Overflow::Visible && is_html_or_body),
1531 )
1532 })
1533 .unwrap_or((false, false));
1534
1535 let initial = node.scroll_offset;
1536 let new_x = node.scroll_offset.x - x;
1537 let new_y = node.scroll_offset.y - y;
1538
1539 let mut bubble_x = 0.0;
1540 let mut bubble_y = 0.0;
1541
1542 let scroll_width = node.final_layout.scroll_width() as f64;
1543 let scroll_height = node.final_layout.scroll_height() as f64;
1544
1545 if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
1547 let has_changed = if let Some(hover_node_id) = sub_doc.get_hover_node_id() {
1548 sub_doc.scroll_node_by_has_changed(hover_node_id, x, y, dispatch_event)
1549 } else {
1550 sub_doc.scroll_viewport_by_has_changed(x, y)
1551 };
1552
1553 return has_changed;
1555 }
1556
1557 if !can_x_scroll {
1559 bubble_x = x
1560 } else if new_x < 0.0 {
1561 bubble_x = -new_x;
1562 node.scroll_offset.x = 0.0;
1563 } else if new_x > scroll_width {
1564 bubble_x = scroll_width - new_x;
1565 node.scroll_offset.x = scroll_width;
1566 } else {
1567 node.scroll_offset.x = new_x;
1568 }
1569
1570 if !can_y_scroll {
1571 bubble_y = y
1572 } else if new_y < 0.0 {
1573 bubble_y = -new_y;
1574 node.scroll_offset.y = 0.0;
1575 } else if new_y > scroll_height {
1576 bubble_y = scroll_height - new_y;
1577 node.scroll_offset.y = scroll_height;
1578 } else {
1579 node.scroll_offset.y = new_y;
1580 }
1581
1582 let has_changed = node.scroll_offset != initial;
1583
1584 if has_changed {
1585 let layout = node.final_layout;
1586 let event = BlitzScrollEvent {
1587 scroll_top: node.scroll_offset.y,
1588 scroll_left: node.scroll_offset.x,
1589 scroll_width: layout.scroll_width() as i32,
1590 scroll_height: layout.scroll_height() as i32,
1591 client_width: layout.size.width as i32,
1592 client_height: layout.size.height as i32,
1593 };
1594
1595 dispatch_event(DomEvent::new(node_id, DomEventData::Scroll(event)));
1596 }
1597
1598 if bubble_x != 0.0 || bubble_y != 0.0 {
1599 if let Some(parent) = node.parent {
1600 return self.scroll_node_by_has_changed(parent, bubble_x, bubble_y, dispatch_event)
1601 | has_changed;
1602 } else {
1603 return self.scroll_viewport_by_has_changed(bubble_x, bubble_y) | has_changed;
1604 }
1605 }
1606
1607 has_changed
1608 }
1609
1610 pub fn scroll_viewport_by(&mut self, x: f64, y: f64) {
1611 self.scroll_viewport_by_has_changed(x, y);
1612 }
1613
1614 pub fn scroll_viewport_by_has_changed(&mut self, x: f64, y: f64) -> bool {
1616 let content_size = self.root_element().final_layout.size;
1617 let new_scroll = (self.viewport_scroll.x - x, self.viewport_scroll.y - y);
1618 let window_width = self.viewport.window_size.0 as f64 / self.viewport.scale() as f64;
1619 let window_height = self.viewport.window_size.1 as f64 / self.viewport.scale() as f64;
1620
1621 let initial = self.viewport_scroll;
1622 self.viewport_scroll.x = f64::max(
1623 0.0,
1624 f64::min(new_scroll.0, content_size.width as f64 - window_width),
1625 );
1626 self.viewport_scroll.y = f64::max(
1627 0.0,
1628 f64::min(new_scroll.1, content_size.height as f64 - window_height),
1629 );
1630
1631 self.viewport_scroll != initial
1632 }
1633
1634 pub fn scroll_by(
1635 &mut self,
1636 anchor_node_id: Option<usize>,
1637 scroll_x: f64,
1638 scroll_y: f64,
1639 dispatch_event: &mut dyn FnMut(DomEvent),
1640 ) -> bool {
1641 if let Some(anchor_node_id) = anchor_node_id {
1642 self.scroll_node_by_has_changed(anchor_node_id, scroll_x, scroll_y, dispatch_event)
1643 } else {
1644 self.scroll_viewport_by_has_changed(scroll_x, scroll_y)
1645 }
1646 }
1647
1648 pub fn viewport_scroll(&self) -> crate::Point<f64> {
1649 self.viewport_scroll
1650 }
1651
1652 pub fn set_viewport_scroll(&mut self, scroll: crate::Point<f64>) {
1653 self.viewport_scroll = scroll;
1654 }
1655
1656 pub fn get_client_bounding_rect(&self, node_id: usize) -> Option<BoundingRect> {
1658 let node = self.get_node(node_id)?;
1659 let pos = node.absolute_position(0.0, 0.0);
1660
1661 Some(BoundingRect {
1662 x: pos.x as f64 - self.viewport_scroll.x,
1663 y: pos.y as f64 - self.viewport_scroll.y,
1664 width: node.unrounded_layout.size.width as f64,
1665 height: node.unrounded_layout.size.height as f64,
1666 })
1667 }
1668
1669 pub fn find_title_node(&self) -> Option<&Node> {
1670 TreeTraverser::new(self)
1671 .find(|node_id| {
1672 self.nodes[*node_id]
1673 .data
1674 .is_element_with_tag_name(&local_name!("title"))
1675 })
1676 .map(|node_id| &self.nodes[node_id])
1677 }
1678
1679 pub fn with_text_input(
1680 &mut self,
1681 node_id: usize,
1682 cb: impl FnOnce(PlainEditorDriver<TextBrush>),
1683 ) {
1684 let Some(node) = self.nodes.get_mut(node_id) else {
1685 return;
1686 };
1687
1688 if let Some(text_input) = node
1689 .element_data_mut()
1690 .and_then(|el| el.text_input_data_mut())
1691 {
1692 let mut font_ctx = self.font_ctx.lock().unwrap();
1693 let layout_ctx = &mut self.layout_ctx;
1694 let driver = text_input.editor.driver(&mut font_ctx, layout_ctx);
1695 cb(driver)
1696 }
1697 }
1698
1699 pub(crate) fn compute_has_canvas(&self) -> bool {
1700 TreeTraverser::new(self).any(|node_id| {
1701 let node = &self.nodes[node_id];
1702 let Some(element) = node.element_data() else {
1703 return false;
1704 };
1705 if element.name.local == local_name!("canvas") && element.has_attr(local_name!("src")) {
1706 return true;
1707 }
1708
1709 false
1710 })
1711 }
1712
1713 pub fn find_text_position(&self, x: f32, y: f32) -> Option<(usize, usize)> {
1719 let hit = self.hit(x, y)?;
1720 let hit_node = self.get_node(hit.node_id)?;
1721 let inline_root = hit_node.inline_root_ancestor()?;
1722 let byte_offset = inline_root.text_offset_at_point(hit.x, hit.y)?;
1723 Some((inline_root.id, byte_offset))
1724 }
1725
1726 pub fn set_text_selection(
1728 &mut self,
1729 anchor_node: usize,
1730 anchor_offset: usize,
1731 focus_node: usize,
1732 focus_offset: usize,
1733 ) {
1734 self.text_selection =
1735 TextSelection::new(anchor_node, anchor_offset, focus_node, focus_offset);
1736
1737 if let (Some(parent), Some(idx)) = self.anonymous_block_location(anchor_node) {
1739 self.text_selection
1740 .anchor
1741 .set_anonymous(parent, idx, anchor_offset);
1742 }
1743 if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1744 self.text_selection
1745 .focus
1746 .set_anonymous(parent, idx, focus_offset);
1747 }
1748 }
1749
1750 fn anonymous_block_location(&self, node_id: usize) -> (Option<usize>, Option<usize>) {
1753 let Some(node) = self.get_node(node_id) else {
1754 return (None, None);
1755 };
1756
1757 if !node.is_anonymous() {
1758 return (None, None);
1759 }
1760
1761 let Some(parent_id) = node.parent else {
1762 return (None, None);
1763 };
1764
1765 let Some(parent) = self.get_node(parent_id) else {
1766 return (Some(parent_id), None);
1767 };
1768
1769 let layout_children = parent.layout_children.borrow();
1770 let Some(children) = layout_children.as_ref() else {
1771 return (Some(parent_id), None);
1772 };
1773
1774 let mut anon_index = 0;
1776 for &child_id in children.iter() {
1777 if child_id == node_id {
1778 return (Some(parent_id), Some(anon_index));
1779 }
1780 if self.get_node(child_id).is_some_and(|n| n.is_anonymous()) {
1781 anon_index += 1;
1782 }
1783 }
1784
1785 (Some(parent_id), None)
1786 }
1787
1788 pub fn clear_text_selection(&mut self) {
1790 self.text_selection.clear();
1791 }
1792
1793 pub fn update_selection_focus(&mut self, focus_node: usize, focus_offset: usize) {
1795 if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1797 self.text_selection
1798 .focus
1799 .set_anonymous(parent, idx, focus_offset);
1800 } else {
1801 self.text_selection.set_focus(focus_node, focus_offset);
1802 }
1803 }
1804
1805 pub fn extend_text_selection_to_point(&mut self, x: f32, y: f32) -> bool {
1808 if !self.text_selection.anchor.is_some() {
1809 return false;
1810 }
1811
1812 if let Some((node, offset)) = self.find_text_position(x, y) {
1813 self.update_selection_focus(node, offset);
1814 self.shell_provider.request_redraw();
1815 true
1816 } else {
1817 false
1818 }
1819 }
1820
1821 fn find_anonymous_block_by_index(
1823 &self,
1824 parent_id: usize,
1825 target_index: usize,
1826 ) -> Option<usize> {
1827 let parent = self.get_node(parent_id)?;
1828 let layout_children = parent.layout_children.borrow();
1829 let children = layout_children.as_ref()?;
1830
1831 children
1832 .iter()
1833 .filter(|&&child_id| self.get_node(child_id).is_some_and(|n| n.is_anonymous()))
1834 .nth(target_index)
1835 .copied()
1836 }
1837
1838 pub fn has_text_selection(&self) -> bool {
1840 self.text_selection.is_active()
1841 }
1842
1843 pub fn get_selected_text(&self) -> Option<String> {
1845 let ranges = self.get_text_selection_ranges();
1846 if ranges.is_empty() {
1847 return None;
1848 }
1849
1850 let mut result = String::new();
1851 for (node_id, start, end) in &ranges {
1852 let node = self.get_node(*node_id)?;
1853 let element_data = node.element_data()?;
1854 let inline_layout = element_data.inline_layout_data.as_ref()?;
1855
1856 if *end > inline_layout.text.len() {
1857 continue;
1858 }
1859
1860 if !result.is_empty() {
1861 result.push(' ');
1862 }
1863 result.push_str(&inline_layout.text[*start..*end]);
1864 }
1865
1866 if result.is_empty() {
1867 None
1868 } else {
1869 Some(result)
1870 }
1871 }
1872
1873 pub fn get_text_selection_ranges(&self) -> Vec<(usize, usize, usize)> {
1876 let lookup = |parent_id, idx| self.find_anonymous_block_by_index(parent_id, idx);
1877
1878 let anchor_node = match self.text_selection.anchor.resolve_node_id(lookup) {
1879 Some(id) => id,
1880 None => return Vec::new(),
1881 };
1882 let focus_node = match self.text_selection.focus.resolve_node_id(lookup) {
1883 Some(id) => id,
1884 None => return Vec::new(),
1885 };
1886
1887 if anchor_node == focus_node {
1889 let start = self
1890 .text_selection
1891 .anchor
1892 .offset
1893 .min(self.text_selection.focus.offset);
1894 let end = self
1895 .text_selection
1896 .anchor
1897 .offset
1898 .max(self.text_selection.focus.offset);
1899
1900 if start == end {
1901 return Vec::new();
1902 }
1903 return vec![(anchor_node, start, end)];
1904 }
1905
1906 let inline_roots = self.collect_inline_roots_in_range(anchor_node, focus_node);
1908 if inline_roots.is_empty() {
1909 return Vec::new();
1910 }
1911
1912 let first_in_roots = inline_roots[0];
1915
1916 let (first_node, first_offset, last_node, last_offset) =
1917 if first_in_roots == anchor_node || (first_in_roots != focus_node) {
1918 (
1920 anchor_node,
1921 self.text_selection.anchor.offset,
1922 focus_node,
1923 self.text_selection.focus.offset,
1924 )
1925 } else {
1926 (
1928 focus_node,
1929 self.text_selection.focus.offset,
1930 anchor_node,
1931 self.text_selection.anchor.offset,
1932 )
1933 };
1934
1935 let mut ranges = Vec::with_capacity(inline_roots.len());
1936
1937 for &node_id in &inline_roots {
1938 let Some(node) = self.get_node(node_id) else {
1939 continue;
1940 };
1941 let Some(element_data) = node.element_data() else {
1942 continue;
1943 };
1944 let Some(inline_layout) = element_data.inline_layout_data.as_ref() else {
1945 continue;
1946 };
1947
1948 let text_len = inline_layout.text.len();
1949
1950 if node_id == first_node && node_id == last_node {
1951 let start = first_offset.min(last_offset);
1952 let end = first_offset.max(last_offset);
1953 if start < end && end <= text_len {
1954 ranges.push((node_id, start, end));
1955 }
1956 } else if node_id == first_node {
1957 if first_offset < text_len {
1958 ranges.push((node_id, first_offset, text_len));
1959 }
1960 } else if node_id == last_node {
1961 if last_offset > 0 && last_offset <= text_len {
1962 ranges.push((node_id, 0, last_offset));
1963 }
1964 } else if text_len > 0 {
1965 ranges.push((node_id, 0, text_len));
1966 }
1967 }
1968
1969 ranges
1970 }
1971}
1972
1973pub struct BoundingRect {
1974 pub x: f64,
1975 pub y: f64,
1976 pub width: f64,
1977 pub height: f64,
1978}
1979
1980impl AsRef<BaseDocument> for BaseDocument {
1981 fn as_ref(&self) -> &BaseDocument {
1982 self
1983 }
1984}
1985
1986impl AsMut<BaseDocument> for BaseDocument {
1987 fn as_mut(&mut self) -> &mut BaseDocument {
1988 self
1989 }
1990}
1991
1992#[cfg(test)]
1993mod font_face_override_tests {
1994 use super::*;
1995 use crate::net::{FontFaceOverrides, Resource, ResourceLoadResponse};
1996
1997 #[test]
2013 fn font_face_overrides_alias_family_name() {
2014 const ALIAS: &str = "AliasedFamily";
2015
2016 let mut document = BaseDocument::new(DocumentConfig::default());
2017
2018 {
2020 let mut ctx = document.font_ctx.lock().unwrap();
2021 assert!(
2022 ctx.collection.family_id(ALIAS).is_none(),
2023 "alias must not exist before registration",
2024 );
2025 }
2026
2027 let response = ResourceLoadResponse {
2032 request_id: 0,
2033 node_id: None,
2034 resolved_url: Some(String::from("test://aliased-family")),
2035 result: Ok(Resource::Font(
2036 blitz_traits::net::Bytes::from_static(crate::BULLET_FONT),
2037 FontFaceOverrides {
2038 family_name: Some(String::from(ALIAS)),
2039 weight: Some(800.0),
2040 style: Some(parley::fontique::FontStyle::Italic),
2041 },
2042 )),
2043 };
2044 document.load_resource(response);
2045
2046 let mut ctx = document.font_ctx.lock().unwrap();
2049 let family_id = ctx
2050 .collection
2051 .family_id(ALIAS)
2052 .expect("CSS-declared family name should be registered as a family alias");
2053 let resolved_name = ctx
2054 .collection
2055 .family_name(family_id)
2056 .expect("family id should resolve back to a name");
2057 assert_eq!(
2058 resolved_name, ALIAS,
2059 "registered family should report the CSS-declared name, \
2060 not the font file's internal `name` table entry",
2061 );
2062 }
2063}