1use std::collections::HashSet;
2use std::mem;
3use std::ops::{Deref, DerefMut};
4
5use crate::document::make_device;
6use crate::net::{CssHandler, ImageHandler};
7use crate::node::{CanvasData, NodeFlags, SpecialElementData};
8use crate::util::ImageType;
9use crate::{
10 Attribute, BaseDocument, ElementData, Node, NodeData, QualName, local_name, qual_name,
11};
12use blitz_traits::net::Request;
13use blitz_traits::shell::Viewport;
14use style::Atom;
15use style::invalidation::element::restyle_hints::RestyleHint;
16use style::stylesheets::OriginSet;
17
18macro_rules! tag_and_attr {
19 ($tag:tt, $attr:tt) => {
20 (&local_name!($tag), &local_name!($attr))
21 };
22}
23
24#[derive(Debug, Clone)]
25pub enum AppendTextErr {
26 NotTextNode,
28}
29
30enum SpecialOp {
33 LoadImage(usize),
34 LoadStylesheet(usize),
35 UnloadStylesheet(usize),
36 LoadCustomPaintSource(usize),
37 ProcessButtonInput(usize),
38}
39
40pub struct DocumentMutator<'doc> {
41 pub doc: &'doc mut BaseDocument,
44
45 eager_op_queue: Vec<SpecialOp>,
46
47 title_node: Option<usize>,
49 style_nodes: HashSet<usize>,
50 form_nodes: HashSet<usize>,
51
52 recompute_is_animating: bool,
54
55 #[cfg(feature = "autofocus")]
57 node_to_autofocus: Option<usize>,
58}
59
60impl Drop for DocumentMutator<'_> {
61 fn drop(&mut self) {
62 self.flush(); }
64}
65
66impl DocumentMutator<'_> {
67 pub fn new<'doc>(doc: &'doc mut BaseDocument) -> DocumentMutator<'doc> {
68 DocumentMutator {
69 doc,
70 eager_op_queue: Vec::new(),
71 title_node: None,
72 style_nodes: HashSet::new(),
73 form_nodes: HashSet::new(),
74 recompute_is_animating: false,
75 #[cfg(feature = "autofocus")]
76 node_to_autofocus: None,
77 }
78 }
79
80 pub fn node_has_parent(&self, node_id: usize) -> bool {
83 self.doc.nodes[node_id].parent.is_some()
84 }
85
86 pub fn previous_sibling_id(&self, node_id: usize) -> Option<usize> {
87 self.doc.nodes[node_id].backward(1).map(|node| node.id)
88 }
89
90 pub fn next_sibling_id(&self, node_id: usize) -> Option<usize> {
91 self.doc.nodes[node_id].forward(1).map(|node| node.id)
92 }
93
94 pub fn parent_id(&self, node_id: usize) -> Option<usize> {
95 self.doc.nodes[node_id].parent
96 }
97
98 pub fn last_child_id(&self, node_id: usize) -> Option<usize> {
99 self.doc.nodes[node_id].children.last().copied()
100 }
101
102 pub fn child_ids(&self, node_id: usize) -> Vec<usize> {
103 self.doc.nodes[node_id].children.clone()
104 }
105
106 pub fn element_name(&self, node_id: usize) -> Option<&QualName> {
107 self.doc.nodes[node_id].element_data().map(|el| &el.name)
108 }
109
110 pub fn node_at_path(&self, start_node_id: usize, path: &[u8]) -> usize {
111 let mut current = &self.doc.nodes[start_node_id];
112 for i in path {
113 let new_id = current.children[*i as usize];
114 current = &self.doc.nodes[new_id];
115 }
116 current.id
117 }
118
119 pub fn create_comment_node(&mut self) -> usize {
122 self.doc.create_node(NodeData::Comment)
123 }
124
125 pub fn create_text_node(&mut self, text: &str) -> usize {
126 self.doc.create_text_node(text)
127 }
128
129 pub fn create_element(&mut self, name: QualName, attrs: Vec<Attribute>) -> usize {
130 let mut data = ElementData::new(name, attrs);
131 data.flush_style_attribute(self.doc.guard(), &self.doc.url.url_extra_data());
132
133 let id = self.doc.create_node(NodeData::Element(data));
134 let node = self.doc.get_node(id).unwrap();
135
136 *node.stylo_element_data.borrow_mut() = Some(Default::default());
138
139 id
140 }
141
142 pub fn deep_clone_node(&mut self, node_id: usize) -> usize {
143 self.doc.deep_clone_node(node_id)
144 }
145
146 pub fn set_node_text(&mut self, node_id: usize, value: &str) {
149 let node = self.doc.get_node_mut(node_id).unwrap();
150
151 let text = match node.data {
152 NodeData::Text(ref mut text) => text,
153 _ => return,
155 };
156
157 let changed = text.content != value;
158 if changed {
159 text.content.clear();
160 text.content.push_str(value);
161 let parent = node.parent;
162 self.maybe_record_node(parent);
163 }
164 }
165
166 pub fn append_text_to_node(&mut self, node_id: usize, text: &str) -> Result<(), AppendTextErr> {
167 match self.doc.nodes[node_id].text_data_mut() {
168 Some(data) => {
169 data.content += text;
170 Ok(())
171 }
172 None => Err(AppendTextErr::NotTextNode),
173 }
174 }
175
176 pub fn add_attrs_if_missing(&mut self, node_id: usize, attrs: Vec<Attribute>) {
177 let node = &mut self.doc.nodes[node_id];
178 let element_data = node.element_data_mut().expect("Not an element");
179
180 let existing_names = element_data
181 .attrs
182 .iter()
183 .map(|e| e.name.clone())
184 .collect::<HashSet<_>>();
185
186 for attr in attrs
187 .into_iter()
188 .filter(|attr| !existing_names.contains(&attr.name))
189 {
190 self.set_attribute(node_id, attr.name, &attr.value);
191 }
192 }
193
194 pub fn set_attribute(&mut self, node_id: usize, name: QualName, value: &str) {
195 self.doc.snapshot_node(node_id);
196
197 let node = &mut self.doc.nodes[node_id];
198 if let Some(data) = &mut *node.stylo_element_data.borrow_mut() {
199 data.hint |= RestyleHint::restyle_subtree();
200 }
201
202 let NodeData::Element(ref mut element) = node.data else {
203 return;
204 };
205
206 element.attrs.set(name.clone(), value);
207
208 let tag = &element.name.local;
209 let attr = &name.local;
210
211 if *attr == local_name!("id") {
212 element.id = Some(Atom::from(value))
213 }
214
215 if *attr == local_name!("value") {
216 if let Some(input_data) = element.text_input_data_mut() {
217 input_data.set_text(
219 &mut self.doc.font_ctx.lock().unwrap(),
220 &mut self.doc.layout_ctx,
221 value,
222 );
223 }
224 return;
225 }
226
227 if *attr == local_name!("style") {
228 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
229 return;
230 }
231
232 if !node.flags.is_in_document() {
235 return;
236 }
237
238 if (tag, attr) == tag_and_attr!("input", "checked") {
239 set_input_checked_state(element, value.to_string());
240 } else if (tag, attr) == tag_and_attr!("img", "src") {
241 self.load_image(node_id);
242 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
243 self.load_custom_paint_src(node_id);
244 }
245 }
246
247 pub fn clear_attribute(&mut self, node_id: usize, name: QualName) {
248 self.doc.snapshot_node(node_id);
249
250 let node = &mut self.doc.nodes[node_id];
251
252 let mut stylo_element_data = node.stylo_element_data.borrow_mut();
253 if let Some(data) = &mut *stylo_element_data {
254 data.hint |= RestyleHint::restyle_subtree();
255 }
256 drop(stylo_element_data);
257
258 let Some(element) = node.element_data_mut() else {
259 return;
260 };
261
262 let removed_attr = element.attrs.remove(&name);
263 let had_attr = removed_attr.is_some();
264 if !had_attr {
265 return;
266 }
267
268 if name.local == local_name!("id") {
269 element.id = None;
270 }
271
272 if name.local == local_name!("value") {
274 if let Some(input_data) = element.text_input_data_mut() {
275 input_data.set_text(
276 &mut self.doc.font_ctx.lock().unwrap(),
277 &mut self.doc.layout_ctx,
278 "",
279 );
280 }
281 }
282
283 let tag = &element.name.local;
284 let attr = &name.local;
285 if *attr == local_name!("style") {
286 element.flush_style_attribute(&self.doc.guard, &self.doc.url.url_extra_data());
287 } else if (tag, attr) == tag_and_attr!("canvas", "src") {
288 self.recompute_is_animating = true;
289 } else if (tag, attr) == tag_and_attr!("link", "href") {
290 self.unload_stylesheet(node_id);
291 }
292 }
293
294 pub fn set_style_property(&mut self, node_id: usize, name: &str, value: &str) {
295 self.doc.set_style_property(node_id, name, value)
296 }
297
298 pub fn remove_style_property(&mut self, node_id: usize, name: &str) {
299 self.doc.remove_style_property(node_id, name)
300 }
301
302 pub fn remove_node(&mut self, node_id: usize) {
304 let node = &mut self.doc.nodes[node_id];
305
306 if let Some(parent_id) = node.parent.take() {
308 let parent = &mut self.doc.nodes[parent_id];
309 parent.children.retain(|id| *id != node_id);
310 self.maybe_record_node(parent_id);
311 }
312
313 self.process_removed_subtree(node_id);
314 }
315
316 fn remove_node_ignoring_parent(&mut self, node_id: usize) -> Option<Node> {
317 let mut node = self.doc.nodes.try_remove(node_id);
318 if let Some(node) = &mut node {
319 for &child in &node.children {
320 self.remove_node_ignoring_parent(child);
321 }
322 }
323 node
324 }
325
326 pub fn remove_and_drop_node(&mut self, node_id: usize) -> Option<Node> {
327 self.process_removed_subtree(node_id);
328
329 let node = self.remove_node_ignoring_parent(node_id);
330
331 if let Some(parent_id) = node.as_ref().and_then(|node| node.parent) {
333 let parent = &mut self.doc.nodes[parent_id];
334 parent.children.retain(|id| *id != node_id);
335 self.maybe_record_node(parent_id);
336 }
337
338 node
339 }
340
341 pub fn remove_and_drop_all_children(&mut self, node_id: usize) {
342 let children = mem::take(&mut self.doc.nodes[node_id].children);
343 for child_id in children {
344 self.process_removed_subtree(child_id);
345 let _ = self.remove_node_ignoring_parent(child_id);
346 }
347 self.maybe_record_node(node_id);
348 }
349
350 pub fn remove_node_if_unparented(&mut self, node_id: usize) {
352 if let Some(node) = self.doc.get_node(node_id) {
353 if node.parent.is_none() {
354 self.remove_and_drop_node(node_id);
355 }
356 }
357 }
358
359 pub fn append_children(&mut self, parent_id: usize, child_ids: &[usize]) {
361 self.add_children_to_parent(parent_id, child_ids, &|parent, child_ids| {
362 parent.children.extend_from_slice(child_ids);
363 });
364 }
365
366 pub fn insert_nodes_before(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
367 let parent_id = self.doc.nodes[anchor_node_id].parent.unwrap();
368 self.add_children_to_parent(parent_id, new_node_ids, &|parent, child_ids| {
369 let node_child_idx = parent.index_of_child(anchor_node_id).unwrap();
370 parent
371 .children
372 .splice(node_child_idx..node_child_idx, child_ids.iter().copied());
373 });
374 }
375
376 fn add_children_to_parent(
377 &mut self,
378 parent_id: usize,
379 child_ids: &[usize],
380 insert_children_fn: &dyn Fn(&mut Node, &[usize]),
381 ) {
382 let new_parent = &mut self.doc.nodes[parent_id];
383 let new_parent_is_in_doc = new_parent.flags.is_in_document();
384
385 insert_children_fn(new_parent, child_ids);
386
387 for child_id in child_ids.iter().copied() {
388 let child = &mut self.doc.nodes[child_id];
389 let old_parent_id = child.parent.replace(parent_id);
390
391 let child_was_in_doc = child.flags.is_in_document();
392 if new_parent_is_in_doc != child_was_in_doc {
393 self.process_added_subtree(child_id);
394 }
395
396 if let Some(old_parent_id) = old_parent_id {
397 let old_parent = &mut self.doc.nodes[old_parent_id];
398
399 old_parent.children.retain(|id| *id != child_id);
400 self.maybe_record_node(old_parent_id);
401 }
402 }
403
404 self.maybe_record_node(parent_id);
405 }
406
407 pub fn insert_nodes_after(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
409 match self.next_sibling_id(anchor_node_id) {
410 Some(id) => self.insert_nodes_before(id, new_node_ids),
411 None => {
412 let parent_id = self.parent_id(anchor_node_id).unwrap();
413 self.append_children(parent_id, new_node_ids)
414 }
415 }
416 }
417
418 pub fn reparent_children(&mut self, old_parent_id: usize, new_parent_id: usize) {
419 let child_ids = std::mem::take(&mut self.doc.nodes[old_parent_id].children);
420 self.maybe_record_node(old_parent_id);
421 self.append_children(new_parent_id, &child_ids);
422 }
423
424 pub fn replace_node_with(&mut self, anchor_node_id: usize, new_node_ids: &[usize]) {
425 self.insert_nodes_before(anchor_node_id, new_node_ids);
426 self.remove_node(anchor_node_id);
427 }
428}
429
430impl<'doc> DocumentMutator<'doc> {
431 pub fn flush(&mut self) {
432 if self.recompute_is_animating {
433 self.doc.is_animating = self.doc.compute_is_animating();
434 }
435
436 if let Some(id) = self.title_node {
437 let title = self.doc.nodes[id].text_content();
438 self.doc.shell_provider.set_window_title(title);
439 }
440
441 for id in self.style_nodes.drain() {
443 self.doc.process_style_element(id);
444 }
445
446 for id in self.form_nodes.drain() {
447 self.doc.reset_form_owner(id);
448 }
449
450 #[cfg(feature = "autofocus")]
451 if let Some(node_id) = self.node_to_autofocus.take() {
452 if self.doc.get_node(node_id).is_some() {
453 self.doc.set_focus_to(node_id);
454 }
455 }
456 }
457
458 pub fn set_inner_html(&mut self, node_id: usize, html: &str) {
459 self.remove_and_drop_all_children(node_id);
460 self.doc
461 .html_parser_provider
462 .clone()
463 .parse_inner_html(self, node_id, html);
464 }
465
466 fn flush_eager_ops(&mut self) {
467 let mut ops = mem::take(&mut self.eager_op_queue);
468 for op in ops.drain(0..) {
469 match op {
470 SpecialOp::LoadImage(node_id) => self.load_image(node_id),
471 SpecialOp::LoadStylesheet(node_id) => self.load_linked_stylesheet(node_id),
472 SpecialOp::UnloadStylesheet(node_id) => self.unload_stylesheet(node_id),
473 SpecialOp::LoadCustomPaintSource(node_id) => self.load_custom_paint_src(node_id),
474 SpecialOp::ProcessButtonInput(node_id) => self.process_button_input(node_id),
475 }
476 }
477
478 self.eager_op_queue = ops;
480 }
481
482 fn process_added_subtree(&mut self, node_id: usize) {
483 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
484 let node = &mut doc.nodes[node_id];
485 node.flags.set(NodeFlags::IS_IN_DOCUMENT, true);
486
487 if let Some(id_attr) = node.attr(local_name!("id")) {
489 doc.nodes_to_id.insert(id_attr.to_string(), node_id);
490 }
491
492 let NodeData::Element(ref mut element) = node.data else {
493 return;
494 };
495
496 let tag = element.name.local.as_ref();
498 match tag {
499 "title" => self.title_node = Some(node_id),
500 "link" => self.eager_op_queue.push(SpecialOp::LoadStylesheet(node_id)),
501 "img" => self.eager_op_queue.push(SpecialOp::LoadImage(node_id)),
502 "canvas" => self
503 .eager_op_queue
504 .push(SpecialOp::LoadCustomPaintSource(node_id)),
505 "style" => {
506 self.style_nodes.insert(node_id);
507 }
508 "button" | "fieldset" | "input" | "select" | "textarea" | "object" | "output" => {
509 self.eager_op_queue
510 .push(SpecialOp::ProcessButtonInput(node_id));
511 self.form_nodes.insert(node_id);
512 }
513 _ => {}
514 }
515
516 #[cfg(feature = "autofocus")]
517 if node.is_focussable() {
518 if let NodeData::Element(ref element) = node.data {
519 if let Some(value) = element.attr(local_name!("autofocus")) {
520 if value == "true" {
521 self.node_to_autofocus = Some(node_id);
522 }
523 }
524 }
525 }
526 });
527
528 self.flush_eager_ops();
529 }
530
531 fn process_removed_subtree(&mut self, node_id: usize) {
532 self.doc.iter_subtree_mut(node_id, |node_id, doc| {
533 let node = &mut doc.nodes[node_id];
534 node.flags.set(NodeFlags::IS_IN_DOCUMENT, false);
535
536 if let Some(id_attr) = node.attr(local_name!("id")) {
538 doc.nodes_to_id.remove(id_attr);
539 }
540
541 let NodeData::Element(ref mut element) = node.data else {
542 return;
543 };
544
545 match &element.special_data {
546 SpecialElementData::Stylesheet(_) => self
547 .eager_op_queue
548 .push(SpecialOp::UnloadStylesheet(node_id)),
549 SpecialElementData::Image(_) => {}
550 SpecialElementData::Canvas(_) => {
551 self.recompute_is_animating = true;
552 }
553 SpecialElementData::TableRoot(_) => {}
554 SpecialElementData::TextInput(_) => {}
555 SpecialElementData::CheckboxInput(_) => {}
556 #[cfg(feature = "file_input")]
557 SpecialElementData::FileInput(_) => {}
558 SpecialElementData::None => {}
559 }
560 });
561
562 self.flush_eager_ops();
563 }
564
565 fn maybe_record_node(&mut self, node_id: impl Into<Option<usize>>) {
566 let Some(node_id) = node_id.into() else {
567 return;
568 };
569
570 let Some(tag_name) = self.doc.nodes[node_id]
571 .data
572 .downcast_element()
573 .map(|elem| &elem.name.local)
574 else {
575 return;
576 };
577
578 match tag_name.as_ref() {
579 "title" => self.title_node = Some(node_id),
580 "style" => {
581 self.style_nodes.insert(node_id);
582 }
583 _ => {}
584 }
585 }
586
587 fn load_linked_stylesheet(&mut self, target_id: usize) {
588 let node = &self.doc.nodes[target_id];
589
590 let rel_attr = node.attr(local_name!("rel"));
591 let href_attr = node.attr(local_name!("href"));
592
593 let (Some(rels), Some(href)) = (rel_attr, href_attr) else {
594 return;
595 };
596 if !rels.split_ascii_whitespace().any(|rel| rel == "stylesheet") {
597 return;
598 }
599
600 let url = self.doc.resolve_url(href);
601 self.doc.net_provider.fetch(
602 self.doc.id(),
603 Request::get(url.clone()),
604 Box::new(CssHandler {
605 node: target_id,
606 source_url: url,
607 guard: self.doc.guard.clone(),
608 provider: self.doc.net_provider.clone(),
609 }),
610 );
611 }
612
613 fn unload_stylesheet(&mut self, node_id: usize) {
614 let node = &mut self.doc.nodes[node_id];
615 let Some(element) = node.element_data_mut() else {
616 unreachable!();
617 };
618 let SpecialElementData::Stylesheet(stylesheet) = element.special_data.take() else {
619 unreachable!();
620 };
621
622 let guard = self.doc.guard.read();
623 self.doc.stylist.remove_stylesheet(stylesheet, &guard);
624 self.doc
625 .stylist
626 .force_stylesheet_origins_dirty(OriginSet::all());
627
628 self.doc.nodes_to_stylesheet.remove(&node_id);
629 }
630
631 fn load_image(&mut self, target_id: usize) {
632 let node = &self.doc.nodes[target_id];
633 if let Some(raw_src) = node.attr(local_name!("src")) {
634 if !raw_src.is_empty() {
635 let src = self.doc.resolve_url(raw_src);
636 self.doc.net_provider.fetch(
637 self.doc.id(),
638 Request::get(src),
639 Box::new(ImageHandler::new(target_id, ImageType::Image)),
640 );
641 }
642 }
643 }
644
645 fn load_custom_paint_src(&mut self, target_id: usize) {
646 let node = &mut self.doc.nodes[target_id];
647 if let Some(raw_src) = node.attr(local_name!("src")) {
648 if let Ok(custom_paint_source_id) = raw_src.parse::<u64>() {
649 self.recompute_is_animating = true;
650 let canvas_data = SpecialElementData::Canvas(CanvasData {
651 custom_paint_source_id,
652 });
653 node.element_data_mut().unwrap().special_data = canvas_data;
654 }
655 }
656 }
657
658 fn process_button_input(&mut self, target_id: usize) {
659 let node = &self.doc.nodes[target_id];
660 let Some(data) = node.element_data() else {
661 return;
662 };
663
664 let tagname = data.name.local.as_ref();
665 let type_attr = data.attr(local_name!("type"));
666 let value = data.attr(local_name!("value"));
667
668 if let ("input", Some("button" | "submit" | "reset"), Some(value)) =
671 (tagname, type_attr, value)
672 {
673 let value = value.to_string();
674 let id = self.create_text_node(&value);
675 self.append_children(target_id, &[id]);
676 return;
677 }
678 #[cfg(feature = "file_input")]
679 if let ("input", Some("file")) = (tagname, type_attr) {
680 let button_id = self.create_element(
681 qual_name!("button", html),
682 vec![
683 Attribute {
684 name: qual_name!("type", html),
685 value: "button".to_string(),
686 },
687 Attribute {
688 name: qual_name!("tabindex", html),
689 value: "-1".to_string(),
690 },
691 ],
692 );
693 let label_id = self.create_element(qual_name!("label", html), vec![]);
694 let text_id = self.create_text_node("No File Selected");
695 let button_text_id = self.create_text_node("Browse");
696 self.append_children(target_id, &[button_id, label_id]);
697 self.append_children(label_id, &[text_id]);
698 self.append_children(button_id, &[button_text_id]);
699 }
700 }
701}
702
703fn set_input_checked_state(element: &mut ElementData, value: String) {
705 let Ok(checked) = value.parse() else {
706 return;
707 };
708 match element.special_data {
709 SpecialElementData::CheckboxInput(ref mut checked_mut) => *checked_mut = checked,
710 SpecialElementData::None => element.attrs.push(Attribute {
715 name: qual_name!("checked", html),
716 value: checked.to_string(),
717 }),
718 _ => {}
719 }
720}
721
722pub struct ViewportMut<'doc> {
725 doc: &'doc mut BaseDocument,
726 initial_scale: f64,
727}
728impl ViewportMut<'_> {
729 pub fn new(doc: &mut BaseDocument) -> ViewportMut<'_> {
730 let initial_scale = doc.viewport.scale_f64();
731 ViewportMut { doc, initial_scale }
732 }
733}
734impl Deref for ViewportMut<'_> {
735 type Target = Viewport;
736
737 fn deref(&self) -> &Self::Target {
738 &self.doc.viewport
739 }
740}
741impl DerefMut for ViewportMut<'_> {
742 fn deref_mut(&mut self) -> &mut Self::Target {
743 &mut self.doc.viewport
744 }
745}
746impl Drop for ViewportMut<'_> {
747 fn drop(&mut self) {
748 self.doc
749 .set_stylist_device(make_device(&self.doc.viewport, self.doc.font_ctx.clone()));
750 self.doc.scroll_viewport_by(0.0, 0.0); let scale_has_changed = self.doc.viewport().scale_f64() != self.initial_scale;
753 if scale_has_changed {
754 self.doc.invalidate_inline_contexts();
755 }
756 }
757}