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, 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.threads", -1);
371
372 let viewport = config.viewport.unwrap_or_default();
373 let media_type = config.media_type.unwrap_or_else(MediaType::screen);
374 let device = make_device(&viewport, media_type.clone(), font_ctx.clone());
375 let stylist = Stylist::new(device, QuirksMode::NoQuirks);
376 let snapshots = SnapshotMap::new();
377 let nodes = Box::new(Slab::new());
378 let guard = SharedRwLock::new();
379 let nodes_to_id = HashMap::new();
380
381 let base_url = config
382 .base_url
383 .and_then(|url| DocumentUrl::from_str(&url).ok())
384 .unwrap_or_default();
385
386 let net_provider = config
387 .net_provider
388 .unwrap_or_else(|| Arc::new(DummyNetProvider));
389 let navigation_provider = config
390 .navigation_provider
391 .unwrap_or_else(|| Arc::new(DummyNavigationProvider));
392 let shell_provider = config
393 .shell_provider
394 .unwrap_or_else(|| Arc::new(DummyShellProvider));
395 let html_parser_provider = config
396 .html_parser_provider
397 .unwrap_or_else(|| Arc::new(DummyHtmlParserProvider));
398
399 let (tx, rx) = channel();
400
401 let mut doc = Self {
402 id,
403 tx,
404 rx: Some(rx),
405
406 guard,
407 nodes,
408 stylist,
409 animations: DocumentAnimationSet::default(),
410 snapshots,
411 nodes_to_id,
412 viewport,
413 media_type,
414 style_threading: config.style_threading,
415 devtool_settings: DevtoolSettings::default(),
416 viewport_scroll: crate::Point::ZERO,
417 url: base_url,
418 ua_stylesheets: HashMap::new(),
419 nodes_to_stylesheet: BTreeMap::new(),
420 font_ctx,
421 #[cfg(feature = "parallel-construct")]
422 thread_font_contexts: ThreadLocal::new(),
423 layout_ctx: parley::LayoutContext::new(),
424
425 hover_node_id: None,
426 hover_node_is_text: false,
427 focus_node_id: None,
428 active_node_id: None,
429 mousedown_node_id: None,
430 has_active_animations: false,
431 subdoc_is_animating: false,
432 has_canvas: false,
433 sub_document_nodes: HashSet::new(),
434
435 #[cfg(feature = "custom-widget")]
436 custom_widget_nodes: HashSet::new(),
437 #[cfg(feature = "custom-widget")]
438 pending_resource_deallocations: Vec::new(),
439
440 changed_nodes: HashSet::new(),
441 deferred_construction_nodes: Vec::new(),
442 image_cache: HashMap::new(),
443 pending_images: HashMap::new(),
444 pending_critical_resources: HashSet::new(),
445 controls_to_form: HashMap::new(),
446 net_provider,
447 navigation_provider,
448 shell_provider,
449 html_parser_provider,
450 abort_signal: config.abort_signal,
451 last_mousedown_time: None,
452 mousedown_position: taffy::Point::ZERO,
453 click_count: 0,
454 drag_mode: DragMode::None,
455 scroll_animation: ScrollAnimationState::None,
456 text_selection: TextSelection::default(),
457 };
458
459 doc.create_node(NodeData::Document);
461 doc.root_node_mut().flags.insert(NodeFlags::IS_IN_DOCUMENT);
462
463 match config.ua_stylesheets {
464 Some(stylesheets) => {
465 for ss in &stylesheets {
466 doc.add_user_agent_stylesheet(ss);
467 }
468 }
469 None => doc.add_user_agent_stylesheet(DEFAULT_CSS),
470 }
471
472 let stylo_element_data = StyloElementData {
474 styles: ElementStyles {
475 primary: Some(
476 ComputedValues::initial_values_with_font_override(Font::initial_values())
477 .to_arc(),
478 ),
479 ..Default::default()
480 },
481 ..Default::default()
482 };
483 let stylo_data = &mut doc.root_node_mut().stylo_element_data;
484 *stylo_data.ensure_init_mut() = stylo_element_data;
485
486 doc
487 }
488
489 pub fn set_net_provider(&mut self, net_provider: Arc<dyn NetProvider>) {
491 self.net_provider = net_provider;
492 }
493
494 pub fn set_navigation_provider(&mut self, navigation_provider: Arc<dyn NavigationProvider>) {
496 self.navigation_provider = navigation_provider;
497 }
498
499 pub fn set_shell_provider(&mut self, shell_provider: Arc<dyn ShellProvider>) {
501 self.shell_provider = shell_provider;
502 }
503
504 pub fn set_html_parser_provider(&mut self, html_parser_provider: Arc<dyn HtmlParserProvider>) {
506 self.html_parser_provider = html_parser_provider;
507 }
508
509 pub fn set_base_url(&mut self, url: &str) {
511 self.url = DocumentUrl::from(Url::parse(url).unwrap());
512 }
513
514 pub fn guard(&self) -> &SharedRwLock {
515 &self.guard
516 }
517
518 pub fn tree(&self) -> &Slab<Node> {
519 &self.nodes
520 }
521
522 pub fn id(&self) -> usize {
523 self.id
524 }
525
526 pub(crate) fn build_request(&self, url: url::Url) -> Request {
529 crate::net::stamped_request(url, self.abort_signal.as_ref())
530 }
531
532 pub fn favicon_url(&self) -> Option<String> {
533 self.tree().iter().find_map(|(_, node)| {
534 let data = &node.data;
535 if !data.is_element_with_tag_name(&local_name!("link")) {
536 return None;
537 }
538 let rel = data.attr(local_name!("rel"))?;
539 if !rel
540 .split_ascii_whitespace()
541 .any(|v| v.eq_ignore_ascii_case("icon"))
542 {
543 return None;
544 }
545 data.attr(local_name!("href")).map(|s| s.to_string())
546 })
547 }
548
549 pub fn get_node(&self, node_id: usize) -> Option<&Node> {
550 self.nodes.get(node_id)
551 }
552
553 pub fn get_node_mut(&mut self, node_id: usize) -> Option<&mut Node> {
554 self.nodes.get_mut(node_id)
555 }
556
557 pub fn get_focussed_node_id(&self) -> Option<usize> {
558 self.focus_node_id
559 .or(self.try_root_element().map(|el| el.id))
560 }
561
562 pub fn mutate<'doc>(&'doc mut self) -> DocumentMutator<'doc> {
563 DocumentMutator::new(self)
564 }
565
566 pub fn handle_dom_event<F: FnMut(DomEvent)>(
567 &mut self,
568 event: &mut DomEvent,
569 dispatch_event: F,
570 ) {
571 handle_dom_event(self, event, dispatch_event)
572 }
573
574 pub fn as_any_mut(&mut self) -> &mut dyn Any {
575 self
576 }
577
578 pub fn label_bound_input_element(&self, label_node_id: usize) -> Option<&Node> {
585 let label_element = self.nodes[label_node_id].element_data()?;
586 if let Some(target_element_dom_id) = label_element.attr(local_name!("for")) {
587 TreeTraverser::new(self)
588 .filter_map(|id| {
589 let node = self.get_node(id)?;
590 let element_data = node.element_data()?;
591 if element_data.name.local != local_name!("input") {
592 return None;
593 }
594 let id = element_data.id.as_ref()?;
595 if *id == *target_element_dom_id {
596 Some(node)
597 } else {
598 None
599 }
600 })
601 .next()
602 } else {
603 TreeTraverser::new_with_root(self, label_node_id)
604 .filter_map(|child_id| {
605 let node = self.get_node(child_id)?;
606 let element_data = node.element_data()?;
607 if element_data.name.local == local_name!("input") {
608 Some(node)
609 } else {
610 None
611 }
612 })
613 .next()
614 }
615 }
616
617 pub fn toggle_checkbox(el: &mut ElementData) -> bool {
618 let Some(is_checked) = el.checkbox_input_checked_mut() else {
619 return false;
620 };
621 *is_checked = !*is_checked;
622
623 *is_checked
624 }
625
626 pub fn toggle_radio(&mut self, radio_set_name: String, target_radio_id: usize) {
627 for i in 0..self.nodes.len() {
628 let node = &mut self.nodes[i];
629 if let Some(node_data) = node.data.downcast_element_mut() {
630 if node_data.attr(local_name!("name")) == Some(&radio_set_name) {
631 let was_clicked = i == target_radio_id;
632 let Some(is_checked) = node_data.checkbox_input_checked_mut() else {
633 continue;
634 };
635 *is_checked = was_clicked;
636 }
637 }
638 }
639 }
640
641 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
642 let node = &mut self.nodes[node_id];
643 let did_change = node.element_data_mut().unwrap().set_style_property(
644 name,
645 value,
646 &self.guard,
647 self.url.url_extra_data(),
648 );
649 if did_change {
650 node.mark_style_attr_updated();
651 }
652 }
653
654 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
655 let node = &mut self.nodes[node_id];
656 let did_change = node.element_data_mut().unwrap().remove_style_property(
657 name,
658 &self.guard,
659 self.url.url_extra_data(),
660 );
661 if did_change {
662 node.mark_style_attr_updated();
663 }
664 }
665
666 pub fn sub_document_node_ids(&self) -> Vec<usize> {
667 self.sub_document_nodes.iter().copied().collect()
668 }
669
670 pub fn set_sub_document(&mut self, node_id: usize, sub_document: Box<dyn Document>) {
671 self.nodes[node_id]
672 .element_data_mut()
673 .unwrap()
674 .set_sub_document(sub_document);
675 self.sub_document_nodes.insert(node_id);
676 }
677
678 pub fn remove_sub_document(&mut self, node_id: usize) {
679 self.nodes[node_id]
680 .element_data_mut()
681 .unwrap()
682 .remove_sub_document();
683 self.sub_document_nodes.remove(&node_id);
684 }
685
686 #[cfg(feature = "custom-widget")]
687 pub fn custom_widget_node_ids(&self) -> Vec<usize> {
688 self.custom_widget_nodes.iter().copied().collect()
689 }
690
691 #[cfg(feature = "custom-widget")]
692 pub fn take_pending_resource_deallocations(&mut self) -> Vec<anyrender::ResourceId> {
693 std::mem::take(&mut self.pending_resource_deallocations)
694 }
695
696 #[cfg(feature = "custom-widget")]
697 pub fn set_custom_widget(&mut self, node_id: usize, widget: Box<dyn crate::Widget>) {
698 self.nodes[node_id]
699 .element_data_mut()
700 .unwrap()
701 .set_custom_widget(widget);
702 self.custom_widget_nodes.insert(node_id);
703 }
704
705 #[cfg(feature = "custom-widget")]
706 pub fn remove_custom_widget(&mut self, node_id: usize) {
707 let resources_to_deallocate = self.nodes[node_id]
708 .element_data_mut()
709 .unwrap()
710 .remove_custom_widget();
711 self.pending_resource_deallocations
712 .extend_from_slice(&resources_to_deallocate);
713 self.custom_widget_nodes.remove(&node_id);
714 }
715
716 pub fn root_node(&self) -> &Node {
717 &self.nodes[0]
718 }
719
720 pub fn root_node_mut(&mut self) -> &mut Node {
721 &mut self.nodes[0]
722 }
723
724 pub fn try_root_element(&self) -> Option<&Node> {
725 TDocument::as_node(&self.root_node()).first_element_child()
726 }
727
728 pub fn root_element(&self) -> &Node {
729 TDocument::as_node(&self.root_node())
730 .first_element_child()
731 .unwrap()
732 .as_element()
733 .unwrap()
734 }
735
736 pub fn create_node(&mut self, node_data: NodeData) -> usize {
737 let slab_ptr = self.nodes.as_mut() as *mut Slab<Node>;
738 let guard = self.guard.clone();
739
740 let entry = self.nodes.vacant_entry();
741 let id = entry.key();
742 entry.insert(Node::new(slab_ptr, id, guard, node_data));
743
744 self.changed_nodes.insert(id);
746 id
747 }
748
749 pub(crate) fn drop_node_ignoring_parent(&mut self, node_id: usize) -> Option<Node> {
750 let mut node = self.nodes.try_remove(node_id);
751 if let Some(node) = &mut node {
752 if let Some(before) = node.before {
753 self.drop_node_ignoring_parent(before);
754 }
755 if let Some(after) = node.after {
756 self.drop_node_ignoring_parent(after);
757 }
758
759 for &child in &node.children {
760 self.drop_node_ignoring_parent(child);
761 }
762 }
763 node
764 }
765
766 pub fn has_changes(&self) -> bool {
768 self.changed_nodes.is_empty()
769 }
770
771 pub fn create_text_node(&mut self, text: &str) -> usize {
772 let content = text.to_string();
773 let data = NodeData::Text(TextNodeData::new(content));
774 self.create_node(data)
775 }
776
777 pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
778 let node = &self.nodes[node_id];
780 let data = node.data.clone();
781 let children = node.children.clone();
782
783 let new_node_id = self.create_node(data);
785
786 let new_children: Vec<usize> = children
788 .into_iter()
789 .map(|child_id| self.deep_clone_node(child_id))
790 .collect();
791 for &child_id in &new_children {
792 self.nodes[child_id].parent = Some(new_node_id);
793 }
794 self.nodes[new_node_id].children = new_children;
795
796 new_node_id
797 }
798
799 pub(crate) fn remove_and_drop_pe(&mut self, node_id: usize) -> Option<Node> {
800 fn remove_pe_ignoring_parent(doc: &mut BaseDocument, node_id: usize) -> Option<Node> {
801 let mut node = doc.nodes.try_remove(node_id);
802 if let Some(node) = &mut node {
803 for &child in &node.children {
804 remove_pe_ignoring_parent(doc, child);
805 }
806 }
807 node
808 }
809
810 let node = remove_pe_ignoring_parent(self, node_id);
811
812 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
814 let parent = &mut self.nodes[parent_id];
815 parent.children.retain(|id| *id != node_id);
816 }
817
818 node
819 }
820
821 pub(crate) fn resolve_url(&self, raw: &str) -> url::Url {
822 self.url.resolve_relative(raw).unwrap_or_else(|| {
823 panic!(
824 "to be able to resolve {raw} with the base_url: {:?}",
825 *self.url
826 )
827 })
828 }
829
830 pub fn print_tree(&self) {
831 crate::util::walk_tree(0, self.root_node());
832 }
833
834 pub fn print_subtree(&self, node_id: usize) {
835 crate::util::walk_tree(0, &self.nodes[node_id]);
836 }
837
838 pub fn reload_resource_by_href(&mut self, href_to_reload: &str) {
839 for &node_id in self.nodes_to_stylesheet.keys() {
840 let node = &self.nodes[node_id];
841 let Some(element) = node.element_data() else {
842 continue;
843 };
844
845 if element.name.local == local_name!("link") {
846 if let Some(href) = element.attr(local_name!("href")) {
847 if href == href_to_reload {
849 let resolved_href = self.resolve_url(href);
850 self.net_provider.fetch(
851 self.id(),
852 self.build_request(resolved_href.clone()),
853 ResourceHandler::boxed(
854 self.tx.clone(),
855 self.id,
856 Some(node_id),
857 self.shell_provider.clone(),
858 StylesheetHandler {
859 source_url: resolved_href,
860 guard: self.guard.clone(),
861 net_provider: self.net_provider.clone(),
862 abort_signal: self.abort_signal.clone(),
863 },
864 ),
865 );
866 }
867 }
868 }
869 }
870 }
871
872 pub fn process_style_element(&mut self, target_id: usize) {
873 let css = self.nodes[target_id].text_content();
874 let css = html_escape::decode_html_entities(&css);
875 let sheet = self.make_stylesheet(&css, Origin::Author);
876 self.add_stylesheet_for_node(sheet, target_id);
877 }
878
879 pub fn remove_user_agent_stylesheet(&mut self, contents: &str) {
880 if let Some(sheet) = self.ua_stylesheets.remove(contents) {
881 self.stylist.remove_stylesheet(sheet, &self.guard.read());
882 }
883 }
884
885 pub fn add_user_agent_stylesheet(&mut self, css: &str) {
886 let sheet = self.make_stylesheet(css, Origin::UserAgent);
887 self.ua_stylesheets.insert(css.to_string(), sheet.clone());
888 self.stylist.append_stylesheet(sheet, &self.guard.read());
889 }
890
891 pub fn make_stylesheet(&self, css: impl AsRef<str>, origin: Origin) -> DocumentStyleSheet {
892 let data = Stylesheet::from_str(
893 css.as_ref(),
894 self.url.url_extra_data(),
895 origin,
896 ServoArc::new(self.guard.wrap(MediaList::empty())),
897 self.guard.clone(),
898 Some(&StylesheetLoader {
899 tx: self.tx.clone(),
900 doc_id: self.id,
901 net_provider: self.net_provider.clone(),
902 shell_provider: self.shell_provider.clone(),
903 abort_signal: self.abort_signal.clone(),
904 }),
905 None,
906 QuirksMode::NoQuirks,
907 AllowImportRules::Yes,
908 );
909
910 DocumentStyleSheet(ServoArc::new(data))
911 }
912
913 pub fn upsert_stylesheet_for_node(&mut self, node_id: usize) {
914 let raw_styles = self.nodes[node_id].text_content();
915 let sheet = self.make_stylesheet(raw_styles, Origin::Author);
916 self.add_stylesheet_for_node(sheet, node_id);
917 }
918
919 pub fn add_stylesheet_for_node(&mut self, stylesheet: DocumentStyleSheet, node_id: usize) {
920 let old = self.nodes_to_stylesheet.insert(node_id, stylesheet.clone());
921
922 if let Some(old) = old {
923 self.stylist.remove_stylesheet(old, &self.guard.read())
924 }
925
926 crate::net::fetch_font_face(
928 self.tx.clone(),
929 self.id,
930 Some(node_id),
931 &stylesheet.0,
932 &self.net_provider,
933 &self.shell_provider,
934 &self.guard.read(),
935 self.abort_signal.as_ref(),
936 );
937
938 let element = &mut self.nodes[node_id].element_data_mut().unwrap();
940 element.special_data = SpecialElementData::Stylesheet(stylesheet.clone());
941
942 let insertion_point = self
944 .nodes_to_stylesheet
945 .range((Bound::Excluded(node_id), Bound::Unbounded))
946 .next()
947 .map(|(_, sheet)| sheet);
948
949 if let Some(insertion_point) = insertion_point {
950 self.stylist.insert_stylesheet_before(
951 stylesheet,
952 insertion_point.clone(),
953 &self.guard.read(),
954 )
955 } else {
956 self.stylist
957 .append_stylesheet(stylesheet, &self.guard.read())
958 }
959 }
960
961 pub fn handle_messages(&mut self) {
962 let rx = self.rx.take().unwrap();
965
966 while let Ok(msg) = rx.try_recv() {
967 self.handle_message(msg);
968 }
969
970 self.rx = Some(rx);
972 }
973
974 pub fn handle_message(&mut self, msg: DocumentEvent) {
975 match msg {
976 DocumentEvent::ResourceLoad(resource) => self.load_resource(resource),
977 }
978 }
979
980 pub fn has_pending_critical_resources(&self) -> bool {
982 !self.pending_critical_resources.is_empty()
983 }
984
985 pub fn load_resource(&mut self, res: ResourceLoadResponse) {
986 self.pending_critical_resources.remove(&res.request_id);
987
988 let resource = match res.result {
989 Ok(resource) => resource,
990 Err(err) => {
991 if let Some(url) = res.resolved_url.as_ref() {
992 let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
993 #[cfg(feature = "tracing")]
994 tracing::warn!(
995 url = url.as_str(),
996 waiting_nodes = waiting_nodes.len(),
997 error = err.as_str(),
998 "Resource load failed"
999 );
1000 #[cfg(not(feature = "tracing"))]
1001 let _ = (waiting_nodes, err);
1002 } else {
1003 #[cfg(feature = "tracing")]
1004 tracing::warn!(error = err.as_str(), "Resource load failed (no url)");
1005 #[cfg(not(feature = "tracing"))]
1006 let _ = err;
1007 }
1008 return;
1009 }
1010 };
1011
1012 match resource {
1013 Resource::Css(css) => {
1014 let node_id = res.node_id.unwrap();
1015 self.add_stylesheet_for_node(css, node_id);
1016 }
1017 Resource::Image(_kind, width, height, image_data) => {
1018 let image = ImageData::Raster(RasterImageData::new(width, height, image_data));
1020
1021 let Some(url) = res.resolved_url.as_ref() else {
1022 return;
1023 };
1024
1025 let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
1027
1028 #[cfg(feature = "tracing")]
1029 tracing::info!(
1030 "Image {url} loaded, applying to {} nodes",
1031 waiting_nodes.len()
1032 );
1033
1034 self.image_cache.insert(url.clone(), image.clone());
1036
1037 for (node_id, image_type) in waiting_nodes {
1039 let Some(node) = self.get_node_mut(node_id) else {
1040 continue;
1041 };
1042
1043 match image_type {
1044 ImageType::Image => {
1045 node.element_data_mut().unwrap().special_data =
1046 SpecialElementData::Image(Box::new(image.clone()));
1047
1048 node.cache.clear();
1050 node.insert_damage(ALL_DAMAGE);
1051 }
1052 ImageType::Background(idx) => {
1053 if let Some(Some(bg_image)) = node
1054 .element_data_mut()
1055 .and_then(|el| el.background_images.get_mut(idx))
1056 {
1057 bg_image.status = Status::Ok;
1058 bg_image.image = image.clone();
1059 }
1060 }
1061 }
1062 }
1063 }
1064 #[cfg(feature = "svg")]
1065 Resource::Svg(_kind, tree) => {
1066 let image = ImageData::Svg(tree);
1068
1069 let Some(url) = res.resolved_url.as_ref() else {
1070 return;
1071 };
1072
1073 let waiting_nodes = self.pending_images.remove(url).unwrap_or_default();
1075
1076 #[cfg(feature = "tracing")]
1077 tracing::info!(
1078 "SVG {url} loaded, applying to {} nodes",
1079 waiting_nodes.len()
1080 );
1081
1082 self.image_cache.insert(url.clone(), image.clone());
1084
1085 for (node_id, image_type) in waiting_nodes {
1087 let Some(node) = self.get_node_mut(node_id) else {
1088 continue;
1089 };
1090
1091 match image_type {
1092 ImageType::Image => {
1093 node.element_data_mut().unwrap().special_data =
1094 SpecialElementData::Image(Box::new(image.clone()));
1095
1096 node.cache.clear();
1098 node.insert_damage(ALL_DAMAGE);
1099 }
1100 ImageType::Background(idx) => {
1101 if let Some(Some(bg_image)) = node
1102 .element_data_mut()
1103 .and_then(|el| el.background_images.get_mut(idx))
1104 {
1105 bg_image.status = Status::Ok;
1106 bg_image.image = image.clone();
1107 }
1108 }
1109 }
1110 }
1111 }
1112 Resource::Font(bytes, overrides) => {
1113 let font = Blob::new(Arc::new(bytes));
1114
1115 let weight_override = overrides.weight.map(parley::fontique::FontWeight::new);
1121 let info_override = parley::fontique::FontInfoOverride {
1122 family_name: overrides.family_name.as_deref(),
1123 weight: weight_override,
1124 style: overrides.style,
1125 ..Default::default()
1126 };
1127
1128 let mut global_font_ctx = self.font_ctx.lock().unwrap();
1130 global_font_ctx
1131 .collection
1132 .register_fonts(font.clone(), Some(info_override));
1133
1134 #[cfg(feature = "parallel-construct")]
1135 {
1136 rayon::broadcast(|_ctx| {
1137 let mut font_ctx = self
1138 .thread_font_contexts
1139 .get_or(|| RefCell::new(Box::new(global_font_ctx.clone())))
1140 .borrow_mut();
1141 font_ctx
1142 .collection
1143 .register_fonts(font.clone(), Some(info_override));
1144 });
1145 }
1146 drop(global_font_ctx);
1147
1148 self.invalidate_inline_contexts();
1150 }
1151 Resource::None => {
1152 }
1154 }
1155 }
1156
1157 pub fn snapshot_node(&mut self, node_id: usize) {
1158 let node = &mut self.nodes[node_id];
1159 let opaque_node_id = TNode::opaque(&&*node);
1160 node.has_snapshot = true;
1161 node.snapshot_handled
1162 .store(false, std::sync::atomic::Ordering::SeqCst);
1163
1164 if let Some(_existing_snapshot) = self.snapshots.get_mut(&opaque_node_id) {
1166 } else {
1169 let attrs: Option<Vec<_>> = node.attrs().map(|attrs| {
1170 attrs
1171 .iter()
1172 .map(|attr| {
1173 let ident = AttrIdentifier {
1174 local_name: GenericAtomIdent(attr.name.local.clone()),
1175 name: GenericAtomIdent(attr.name.local.clone()),
1176 namespace: GenericAtomIdent(attr.name.ns.clone()),
1177 prefix: None,
1178 };
1179
1180 let value = if attr.name.local == local_name!("id") {
1181 AttrValue::Atom(Atom::from(&*attr.value))
1182 } else if attr.name.local == local_name!("class") {
1183 let classes = attr
1184 .value
1185 .split_ascii_whitespace()
1186 .map(Atom::from)
1187 .collect();
1188 AttrValue::TokenList(attr.value.clone(), classes)
1189 } else {
1190 AttrValue::String(attr.value.clone())
1191 };
1192
1193 (ident, value)
1194 })
1195 .collect()
1196 });
1197
1198 let changed_attrs = attrs
1199 .as_ref()
1200 .map(|attrs| attrs.iter().map(|attr| attr.0.name.clone()).collect())
1201 .unwrap_or_default();
1202
1203 self.snapshots.insert(
1204 opaque_node_id,
1205 ServoElementSnapshot {
1206 state: Some(node.element_state),
1207 attrs,
1208 changed_attrs,
1209 class_changed: true,
1210 id_changed: true,
1211 other_attributes_changed: true,
1212 },
1213 );
1214 }
1215 }
1216
1217 pub fn snapshot_node_and(&mut self, node_id: usize, cb: impl FnOnce(&mut Node)) {
1218 self.snapshot_node(node_id);
1219 cb(&mut self.nodes[node_id]);
1220 }
1221
1222 pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
1224 if TDocument::as_node(&&self.nodes[0])
1225 .first_element_child()
1226 .is_none()
1227 {
1228 #[cfg(feature = "tracing")]
1229 tracing::warn!("No DOM - not resolving hit test");
1230 return None;
1231 }
1232
1233 self.root_element().hit(x, y)
1234 }
1235
1236 pub fn focus_next_node(&mut self) -> Option<usize> {
1237 let focussed_node_id = self.get_focussed_node_id()?;
1238 let id = self.next_node(&self.nodes[focussed_node_id], |node| node.is_focussable())?;
1239 self.set_focus_to(id);
1240 Some(id)
1241 }
1242
1243 pub fn clear_focus(&mut self) {
1245 if let Some(id) = self.focus_node_id {
1246 let shell_provider = self.shell_provider.clone();
1247 self.snapshot_node_and(id, |node| node.blur(shell_provider));
1248 self.focus_node_id = None;
1249 }
1250 }
1251
1252 pub fn set_mousedown_node_id(&mut self, node_id: Option<usize>) {
1253 self.mousedown_node_id = node_id;
1254 }
1255 pub fn set_focus_to(&mut self, focus_node_id: usize) -> bool {
1256 if Some(focus_node_id) == self.focus_node_id {
1257 return false;
1258 }
1259
1260 #[cfg(feature = "tracing")]
1261 tracing::info!("Focussed node {focus_node_id}");
1262
1263 let shell_provider = self.shell_provider.clone();
1264
1265 if let Some(id) = self.focus_node_id {
1267 self.snapshot_node_and(id, |node| node.blur(shell_provider.clone()));
1268 }
1269
1270 self.snapshot_node_and(focus_node_id, |node| node.focus(shell_provider));
1272
1273 self.focus_node_id = Some(focus_node_id);
1274
1275 true
1276 }
1277
1278 pub fn active_node(&mut self) -> bool {
1279 let Some(hover_node_id) = self.get_hover_node_id() else {
1280 return false;
1281 };
1282
1283 if let Some(active_node_id) = self.active_node_id {
1284 if active_node_id == hover_node_id {
1285 return true;
1286 }
1287 self.unactive_node();
1288 }
1289
1290 let active_node_id = Some(hover_node_id);
1291
1292 let node_path = self.maybe_node_layout_ancestors(active_node_id);
1293 for &id in node_path.iter() {
1294 self.snapshot_node_and(id, |node| node.active());
1295 }
1296
1297 self.active_node_id = active_node_id;
1298
1299 true
1300 }
1301
1302 pub fn unactive_node(&mut self) -> bool {
1303 let Some(active_node_id) = self.active_node_id.take() else {
1304 return false;
1305 };
1306
1307 let node_path = self.maybe_node_layout_ancestors(Some(active_node_id));
1308 for &id in node_path.iter() {
1309 self.snapshot_node_and(id, |node| node.unactive());
1310 }
1311
1312 true
1313 }
1314
1315 pub fn set_hover_to(&mut self, x: f32, y: f32) -> bool {
1316 let hit = self.hit(x, y);
1317 let hover_node_id = hit.map(|hit| hit.node_id);
1318 let new_is_text = hit.map(|hit| hit.is_text).unwrap_or(false);
1319
1320 if hover_node_id == self.hover_node_id {
1322 return false;
1323 }
1324
1325 let old_node_path = self.maybe_node_layout_ancestors(self.hover_node_id);
1326 let new_node_path = self.maybe_node_layout_ancestors(hover_node_id);
1327 let same_count = old_node_path
1328 .iter()
1329 .zip(&new_node_path)
1330 .take_while(|(o, n)| o == n)
1331 .count();
1332 for &id in old_node_path.iter().skip(same_count) {
1333 self.snapshot_node_and(id, |node| node.unhover());
1334 }
1335 for &id in new_node_path.iter().skip(same_count) {
1336 self.snapshot_node_and(id, |node| node.hover());
1337 }
1338
1339 self.hover_node_id = hover_node_id;
1340 self.hover_node_is_text = new_is_text;
1341
1342 let cursor = self.get_cursor().unwrap_or_default();
1344 self.shell_provider.set_cursor(cursor);
1345
1346 self.shell_provider.request_redraw();
1348
1349 true
1350 }
1351
1352 pub fn clear_hover(&mut self) -> bool {
1353 let Some(hover_node_id) = self.hover_node_id else {
1354 return false;
1355 };
1356
1357 let old_node_path = self.maybe_node_layout_ancestors(Some(hover_node_id));
1358 for &id in old_node_path.iter() {
1359 self.snapshot_node_and(id, |node| node.unhover());
1360 }
1361
1362 self.hover_node_id = None;
1363 self.hover_node_is_text = false;
1364
1365 let cursor = self.get_cursor().unwrap_or_default();
1367 self.shell_provider.set_cursor(cursor);
1368
1369 self.shell_provider.request_redraw();
1371
1372 true
1373 }
1374
1375 pub fn get_hover_node_id(&self) -> Option<usize> {
1376 self.hover_node_id
1377 }
1378
1379 pub fn set_viewport(&mut self, viewport: Viewport) {
1380 let scale_has_changed = viewport.scale_f64() != self.viewport.scale_f64();
1381 self.viewport = viewport;
1382 self.set_stylist_device(make_device(
1383 &self.viewport,
1384 self.media_type.clone(),
1385 self.font_ctx.clone(),
1386 ));
1387 self.scroll_viewport_by(0.0, 0.0); if scale_has_changed {
1390 self.invalidate_inline_contexts();
1391 self.shell_provider.request_redraw();
1392 }
1393 }
1394
1395 pub fn media_type(&self) -> &MediaType {
1397 &self.media_type
1398 }
1399
1400 pub fn set_media_type(&mut self, media_type: MediaType) {
1403 if self.media_type == media_type {
1404 return;
1405 }
1406 self.media_type = media_type;
1407 self.set_stylist_device(make_device(
1408 &self.viewport,
1409 self.media_type.clone(),
1410 self.font_ctx.clone(),
1411 ));
1412 }
1413
1414 pub fn viewport(&self) -> &Viewport {
1415 &self.viewport
1416 }
1417
1418 pub fn viewport_mut(&mut self) -> ViewportMut<'_> {
1419 ViewportMut::new(self)
1420 }
1421
1422 pub fn zoom_by(&mut self, increment: f32) {
1423 *self.viewport.zoom_mut() += increment;
1424 self.set_viewport(self.viewport.clone());
1425 }
1426
1427 pub fn zoom_to(&mut self, zoom: f32) {
1428 *self.viewport.zoom_mut() = zoom;
1429 self.set_viewport(self.viewport.clone());
1430 }
1431
1432 pub fn get_viewport(&self) -> Viewport {
1433 self.viewport.clone()
1434 }
1435
1436 pub fn devtools(&self) -> &DevtoolSettings {
1437 &self.devtool_settings
1438 }
1439
1440 pub fn devtools_mut(&mut self) -> &mut DevtoolSettings {
1441 &mut self.devtool_settings
1442 }
1443
1444 pub fn is_animating(&self) -> bool {
1445 #[cfg(feature = "custom-widget")]
1446 let has_custom_widgets = !self.custom_widget_nodes.is_empty();
1447 #[cfg(not(feature = "custom-widget"))]
1448 let has_custom_widgets = false;
1449
1450 self.has_canvas
1451 | self.has_active_animations
1452 | self.subdoc_is_animating
1453 | has_custom_widgets
1454 | (self.scroll_animation != ScrollAnimationState::None)
1455 }
1456
1457 pub fn set_stylist_device(&mut self, device: Device) {
1459 let origins = {
1460 let guard = &self.guard;
1461 let guards = StylesheetGuards {
1462 author: &guard.read(),
1463 ua_or_user: &guard.read(),
1464 };
1465 self.stylist.set_device(device, &guards)
1466 };
1467 self.stylist.force_stylesheet_origins_dirty(origins);
1468 }
1469
1470 pub fn stylist_device(&mut self) -> &Device {
1471 self.stylist.device()
1472 }
1473
1474 pub fn get_cursor(&self) -> Option<CursorIcon> {
1475 let node = &self.nodes[self.get_hover_node_id()?];
1476
1477 if let Some(subdoc) = node.subdoc().map(|doc| doc.inner()) {
1478 return subdoc.get_cursor();
1479 }
1480
1481 let style = node.primary_styles()?;
1482 let user_select = style.clone_user_select();
1483 let keyword = stylo_to_cursor_icon(style.clone_cursor().keyword);
1484
1485 if let Some(cursor) = keyword {
1487 return Some(cursor);
1488 }
1489
1490 if node
1492 .element_data()
1493 .is_some_and(|e| e.text_input_data().is_some())
1494 {
1495 return Some(CursorIcon::Text);
1496 }
1497
1498 let mut maybe_node = Some(node);
1500 while let Some(node) = maybe_node {
1501 if node.is_link() {
1502 return Some(CursorIcon::Pointer);
1503 }
1504
1505 maybe_node = node.layout_parent.get().map(|node_id| node.with(node_id));
1506 }
1507
1508 if self.hover_node_is_text {
1510 return Some(match user_select {
1511 UserSelect::Text => CursorIcon::Text,
1512 _ => CursorIcon::Default,
1513 });
1514 }
1515
1516 Some(CursorIcon::Default)
1518 }
1519
1520 pub fn scroll_node_by<F: FnMut(DomEvent)>(
1521 &mut self,
1522 node_id: usize,
1523 x: f64,
1524 y: f64,
1525 dispatch_event: F,
1526 ) {
1527 self.scroll_node_by_has_changed(node_id, x, y, dispatch_event);
1528 }
1529
1530 pub fn scroll_node_by_has_changed<F: FnMut(DomEvent)>(
1534 &mut self,
1535 node_id: usize,
1536 x: f64,
1537 y: f64,
1538 mut dispatch_event: F,
1539 ) -> bool {
1540 let Some(node) = self.nodes.get_mut(node_id) else {
1541 return false;
1542 };
1543
1544 let is_html_or_body = node.data.downcast_element().is_some_and(|e| {
1545 let tag = &e.name.local;
1546 tag == "html" || tag == "body"
1547 });
1548
1549 let (can_x_scroll, can_y_scroll) = node
1550 .primary_styles()
1551 .map(|styles| {
1552 (
1553 matches!(styles.clone_overflow_x(), Overflow::Scroll | Overflow::Auto),
1554 matches!(styles.clone_overflow_y(), Overflow::Scroll | Overflow::Auto)
1555 || (styles.clone_overflow_y() == Overflow::Visible && is_html_or_body),
1556 )
1557 })
1558 .unwrap_or((false, false));
1559
1560 let initial = node.scroll_offset;
1561 let new_x = node.scroll_offset.x - x;
1562 let new_y = node.scroll_offset.y - y;
1563
1564 let mut bubble_x = 0.0;
1565 let mut bubble_y = 0.0;
1566
1567 let scroll_width = node.final_layout.scroll_width() as f64;
1568 let scroll_height = node.final_layout.scroll_height() as f64;
1569
1570 if let Some(mut sub_doc) = node.subdoc_mut().map(|doc| doc.inner_mut()) {
1572 let has_changed = if let Some(hover_node_id) = sub_doc.get_hover_node_id() {
1573 sub_doc.scroll_node_by_has_changed(hover_node_id, x, y, dispatch_event)
1574 } else {
1575 sub_doc.scroll_viewport_by_has_changed(x, y)
1576 };
1577
1578 return has_changed;
1580 }
1581
1582 if !can_x_scroll {
1584 bubble_x = x
1585 } else if new_x < 0.0 {
1586 bubble_x = -new_x;
1587 node.scroll_offset.x = 0.0;
1588 } else if new_x > scroll_width {
1589 bubble_x = scroll_width - new_x;
1590 node.scroll_offset.x = scroll_width;
1591 } else {
1592 node.scroll_offset.x = new_x;
1593 }
1594
1595 if !can_y_scroll {
1596 bubble_y = y
1597 } else if new_y < 0.0 {
1598 bubble_y = -new_y;
1599 node.scroll_offset.y = 0.0;
1600 } else if new_y > scroll_height {
1601 bubble_y = scroll_height - new_y;
1602 node.scroll_offset.y = scroll_height;
1603 } else {
1604 node.scroll_offset.y = new_y;
1605 }
1606
1607 let has_changed = node.scroll_offset != initial;
1608
1609 if has_changed {
1610 let layout = node.final_layout;
1611 let event = BlitzScrollEvent {
1612 scroll_top: node.scroll_offset.y,
1613 scroll_left: node.scroll_offset.x,
1614 scroll_width: layout.scroll_width() as i32,
1615 scroll_height: layout.scroll_height() as i32,
1616 client_width: layout.size.width as i32,
1617 client_height: layout.size.height as i32,
1618 };
1619
1620 dispatch_event(DomEvent::new(node_id, DomEventData::Scroll(event)));
1621 }
1622
1623 if bubble_x != 0.0 || bubble_y != 0.0 {
1624 if let Some(parent) = node.parent {
1625 return self.scroll_node_by_has_changed(parent, bubble_x, bubble_y, dispatch_event)
1626 | has_changed;
1627 } else {
1628 return self.scroll_viewport_by_has_changed(bubble_x, bubble_y) | has_changed;
1629 }
1630 }
1631
1632 has_changed
1633 }
1634
1635 pub fn scroll_viewport_by(&mut self, x: f64, y: f64) {
1636 self.scroll_viewport_by_has_changed(x, y);
1637 }
1638
1639 pub fn scroll_viewport_by_has_changed(&mut self, x: f64, y: f64) -> bool {
1641 let content_size = self.root_element().final_layout.size;
1642 let new_scroll = (self.viewport_scroll.x - x, self.viewport_scroll.y - y);
1643 let window_width = self.viewport.window_size.0 as f64 / self.viewport.scale() as f64;
1644 let window_height = self.viewport.window_size.1 as f64 / self.viewport.scale() as f64;
1645
1646 let initial = self.viewport_scroll;
1647 self.viewport_scroll.x = f64::max(
1648 0.0,
1649 f64::min(new_scroll.0, content_size.width as f64 - window_width),
1650 );
1651 self.viewport_scroll.y = f64::max(
1652 0.0,
1653 f64::min(new_scroll.1, content_size.height as f64 - window_height),
1654 );
1655
1656 self.viewport_scroll != initial
1657 }
1658
1659 pub fn scroll_by(
1660 &mut self,
1661 anchor_node_id: Option<usize>,
1662 scroll_x: f64,
1663 scroll_y: f64,
1664 dispatch_event: &mut dyn FnMut(DomEvent),
1665 ) -> bool {
1666 if let Some(anchor_node_id) = anchor_node_id {
1667 self.scroll_node_by_has_changed(anchor_node_id, scroll_x, scroll_y, dispatch_event)
1668 } else {
1669 self.scroll_viewport_by_has_changed(scroll_x, scroll_y)
1670 }
1671 }
1672
1673 pub fn viewport_scroll(&self) -> crate::Point<f64> {
1674 self.viewport_scroll
1675 }
1676
1677 pub fn set_viewport_scroll(&mut self, scroll: crate::Point<f64>) {
1678 self.viewport_scroll = scroll;
1679 }
1680
1681 pub fn get_client_bounding_rect(&self, node_id: usize) -> Option<BoundingRect> {
1683 let node = self.get_node(node_id)?;
1684 let pos = node.absolute_position(0.0, 0.0);
1685
1686 Some(BoundingRect {
1687 x: pos.x as f64 - self.viewport_scroll.x,
1688 y: pos.y as f64 - self.viewport_scroll.y,
1689 width: node.unrounded_layout.size.width as f64,
1690 height: node.unrounded_layout.size.height as f64,
1691 })
1692 }
1693
1694 pub fn find_title_node(&self) -> Option<&Node> {
1695 TreeTraverser::new(self)
1696 .find(|node_id| {
1697 self.nodes[*node_id]
1698 .data
1699 .is_element_with_tag_name(&local_name!("title"))
1700 })
1701 .map(|node_id| &self.nodes[node_id])
1702 }
1703
1704 pub fn with_text_input(
1705 &mut self,
1706 node_id: usize,
1707 cb: impl FnOnce(PlainEditorDriver<TextBrush>),
1708 ) {
1709 let Some(node) = self.nodes.get_mut(node_id) else {
1710 return;
1711 };
1712
1713 if let Some(text_input) = node
1714 .element_data_mut()
1715 .and_then(|el| el.text_input_data_mut())
1716 {
1717 let mut font_ctx = self.font_ctx.lock().unwrap();
1718 let layout_ctx = &mut self.layout_ctx;
1719 let driver = text_input.editor.driver(&mut font_ctx, layout_ctx);
1720 cb(driver)
1721 }
1722 }
1723
1724 pub(crate) fn compute_has_canvas(&self) -> bool {
1725 TreeTraverser::new(self).any(|node_id| {
1726 let node = &self.nodes[node_id];
1727 let Some(element) = node.element_data() else {
1728 return false;
1729 };
1730 if element.name.local == local_name!("canvas") && element.has_attr(local_name!("src")) {
1731 return true;
1732 }
1733
1734 false
1735 })
1736 }
1737
1738 pub fn find_text_position(&self, x: f32, y: f32) -> Option<(usize, usize)> {
1744 let hit = self.hit(x, y)?;
1745 let hit_node = self.get_node(hit.node_id)?;
1746 let inline_root = hit_node.inline_root_ancestor()?;
1747 let byte_offset = inline_root.text_offset_at_point(hit.x, hit.y)?;
1748 Some((inline_root.id, byte_offset))
1749 }
1750
1751 pub fn set_text_selection(
1753 &mut self,
1754 anchor_node: usize,
1755 anchor_offset: usize,
1756 focus_node: usize,
1757 focus_offset: usize,
1758 ) {
1759 self.text_selection =
1760 TextSelection::new(anchor_node, anchor_offset, focus_node, focus_offset);
1761
1762 if let (Some(parent), Some(idx)) = self.anonymous_block_location(anchor_node) {
1764 self.text_selection
1765 .anchor
1766 .set_anonymous(parent, idx, anchor_offset);
1767 }
1768 if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1769 self.text_selection
1770 .focus
1771 .set_anonymous(parent, idx, focus_offset);
1772 }
1773 }
1774
1775 fn anonymous_block_location(&self, node_id: usize) -> (Option<usize>, Option<usize>) {
1778 let Some(node) = self.get_node(node_id) else {
1779 return (None, None);
1780 };
1781
1782 if !node.is_anonymous() {
1783 return (None, None);
1784 }
1785
1786 let Some(parent_id) = node.parent else {
1787 return (None, None);
1788 };
1789
1790 let Some(parent) = self.get_node(parent_id) else {
1791 return (Some(parent_id), None);
1792 };
1793
1794 let layout_children = parent.layout_children.borrow();
1795 let Some(children) = layout_children.as_ref() else {
1796 return (Some(parent_id), None);
1797 };
1798
1799 let mut anon_index = 0;
1801 for &child_id in children.iter() {
1802 if child_id == node_id {
1803 return (Some(parent_id), Some(anon_index));
1804 }
1805 if self.get_node(child_id).is_some_and(|n| n.is_anonymous()) {
1806 anon_index += 1;
1807 }
1808 }
1809
1810 (Some(parent_id), None)
1811 }
1812
1813 pub fn clear_text_selection(&mut self) {
1815 self.text_selection.clear();
1816 }
1817
1818 pub fn update_selection_focus(&mut self, focus_node: usize, focus_offset: usize) {
1820 if let (Some(parent), Some(idx)) = self.anonymous_block_location(focus_node) {
1822 self.text_selection
1823 .focus
1824 .set_anonymous(parent, idx, focus_offset);
1825 } else {
1826 self.text_selection.set_focus(focus_node, focus_offset);
1827 }
1828 }
1829
1830 pub fn extend_text_selection_to_point(&mut self, x: f32, y: f32) -> bool {
1833 if !self.text_selection.anchor.is_some() {
1834 return false;
1835 }
1836
1837 if let Some((node, offset)) = self.find_text_position(x, y) {
1838 self.update_selection_focus(node, offset);
1839 self.shell_provider.request_redraw();
1840 true
1841 } else {
1842 false
1843 }
1844 }
1845
1846 fn find_anonymous_block_by_index(
1848 &self,
1849 parent_id: usize,
1850 target_index: usize,
1851 ) -> Option<usize> {
1852 let parent = self.get_node(parent_id)?;
1853 let layout_children = parent.layout_children.borrow();
1854 let children = layout_children.as_ref()?;
1855
1856 children
1857 .iter()
1858 .filter(|&&child_id| self.get_node(child_id).is_some_and(|n| n.is_anonymous()))
1859 .nth(target_index)
1860 .copied()
1861 }
1862
1863 pub fn has_text_selection(&self) -> bool {
1865 self.text_selection.is_active()
1866 }
1867
1868 pub fn get_selected_text(&self) -> Option<String> {
1870 let ranges = self.get_text_selection_ranges();
1871 if ranges.is_empty() {
1872 return None;
1873 }
1874
1875 let mut result = String::new();
1876 for (node_id, start, end) in &ranges {
1877 let node = self.get_node(*node_id)?;
1878 let element_data = node.element_data()?;
1879 let inline_layout = element_data.inline_layout_data.as_ref()?;
1880
1881 if *end > inline_layout.text.len() {
1882 continue;
1883 }
1884
1885 if !result.is_empty() {
1886 result.push(' ');
1887 }
1888 result.push_str(&inline_layout.text[*start..*end]);
1889 }
1890
1891 if result.is_empty() {
1892 None
1893 } else {
1894 Some(result)
1895 }
1896 }
1897
1898 pub fn get_text_selection_ranges(&self) -> Vec<(usize, usize, usize)> {
1901 let lookup = |parent_id, idx| self.find_anonymous_block_by_index(parent_id, idx);
1902
1903 let anchor_node = match self.text_selection.anchor.resolve_node_id(lookup) {
1904 Some(id) => id,
1905 None => return Vec::new(),
1906 };
1907 let focus_node = match self.text_selection.focus.resolve_node_id(lookup) {
1908 Some(id) => id,
1909 None => return Vec::new(),
1910 };
1911
1912 if anchor_node == focus_node {
1914 let start = self
1915 .text_selection
1916 .anchor
1917 .offset
1918 .min(self.text_selection.focus.offset);
1919 let end = self
1920 .text_selection
1921 .anchor
1922 .offset
1923 .max(self.text_selection.focus.offset);
1924
1925 if start == end {
1926 return Vec::new();
1927 }
1928 return vec![(anchor_node, start, end)];
1929 }
1930
1931 let inline_roots = self.collect_inline_roots_in_range(anchor_node, focus_node);
1933 if inline_roots.is_empty() {
1934 return Vec::new();
1935 }
1936
1937 let first_in_roots = inline_roots[0];
1940
1941 let (first_node, first_offset, last_node, last_offset) =
1942 if first_in_roots == anchor_node || (first_in_roots != focus_node) {
1943 (
1945 anchor_node,
1946 self.text_selection.anchor.offset,
1947 focus_node,
1948 self.text_selection.focus.offset,
1949 )
1950 } else {
1951 (
1953 focus_node,
1954 self.text_selection.focus.offset,
1955 anchor_node,
1956 self.text_selection.anchor.offset,
1957 )
1958 };
1959
1960 let mut ranges = Vec::with_capacity(inline_roots.len());
1961
1962 for &node_id in &inline_roots {
1963 let Some(node) = self.get_node(node_id) else {
1964 continue;
1965 };
1966 let Some(element_data) = node.element_data() else {
1967 continue;
1968 };
1969 let Some(inline_layout) = element_data.inline_layout_data.as_ref() else {
1970 continue;
1971 };
1972
1973 let text_len = inline_layout.text.len();
1974
1975 if node_id == first_node && node_id == last_node {
1976 let start = first_offset.min(last_offset);
1977 let end = first_offset.max(last_offset);
1978 if start < end && end <= text_len {
1979 ranges.push((node_id, start, end));
1980 }
1981 } else if node_id == first_node {
1982 if first_offset < text_len {
1983 ranges.push((node_id, first_offset, text_len));
1984 }
1985 } else if node_id == last_node {
1986 if last_offset > 0 && last_offset <= text_len {
1987 ranges.push((node_id, 0, last_offset));
1988 }
1989 } else if text_len > 0 {
1990 ranges.push((node_id, 0, text_len));
1991 }
1992 }
1993
1994 ranges
1995 }
1996}
1997
1998pub struct BoundingRect {
1999 pub x: f64,
2000 pub y: f64,
2001 pub width: f64,
2002 pub height: f64,
2003}
2004
2005impl AsRef<BaseDocument> for BaseDocument {
2006 fn as_ref(&self) -> &BaseDocument {
2007 self
2008 }
2009}
2010
2011impl AsMut<BaseDocument> for BaseDocument {
2012 fn as_mut(&mut self) -> &mut BaseDocument {
2013 self
2014 }
2015}
2016
2017#[cfg(test)]
2018mod font_face_override_tests {
2019 use super::*;
2020 use crate::net::{FontFaceOverrides, Resource, ResourceLoadResponse};
2021
2022 #[test]
2038 fn font_face_overrides_alias_family_name() {
2039 const ALIAS: &str = "AliasedFamily";
2040
2041 let mut document = BaseDocument::new(DocumentConfig::default());
2042
2043 {
2045 let mut ctx = document.font_ctx.lock().unwrap();
2046 assert!(
2047 ctx.collection.family_id(ALIAS).is_none(),
2048 "alias must not exist before registration",
2049 );
2050 }
2051
2052 let response = ResourceLoadResponse {
2057 request_id: 0,
2058 node_id: None,
2059 resolved_url: Some(String::from("test://aliased-family")),
2060 result: Ok(Resource::Font(
2061 blitz_traits::net::Bytes::from_static(crate::BULLET_FONT),
2062 FontFaceOverrides {
2063 family_name: Some(String::from(ALIAS)),
2064 weight: Some(800.0),
2065 style: Some(parley::fontique::FontStyle::Italic),
2066 },
2067 )),
2068 };
2069 document.load_resource(response);
2070
2071 let mut ctx = document.font_ctx.lock().unwrap();
2074 let family_id = ctx
2075 .collection
2076 .family_id(ALIAS)
2077 .expect("CSS-declared family name should be registered as a family alias");
2078 let resolved_name = ctx
2079 .collection
2080 .family_name(family_id)
2081 .expect("family id should resolve back to a name");
2082 assert_eq!(
2083 resolved_name, ALIAS,
2084 "registered family should report the CSS-declared name, \
2085 not the font file's internal `name` table entry",
2086 );
2087 }
2088}