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