1#[doc(hidden)]
4pub mod key;
5#[doc(hidden)]
6pub mod vcomp;
7#[doc(hidden)]
8pub mod vlist;
9#[doc(hidden)]
10pub mod vnode;
11#[doc(hidden)]
12pub mod vtag;
13#[doc(hidden)]
14pub mod vtext;
15
16use crate::html::{AnyScope, IntoOptPropValue, NodeRef};
17use cfg_if::cfg_if;
18use indexmap::IndexMap;
19use std::{borrow::Cow, collections::HashMap, fmt, hint::unreachable_unchecked, iter, mem, rc::Rc};
20cfg_if! {
21 if #[cfg(feature = "std_web")] {
22 use crate::html::EventListener;
23 use stdweb::web::{Element, INode, Node};
24 } else if #[cfg(feature = "web_sys")] {
25 use gloo::events::EventListener;
26 use web_sys::{Element, Node};
27 }
28}
29
30#[doc(inline)]
31pub use self::key::Key;
32#[doc(inline)]
33pub use self::vcomp::{VChild, VComp};
34#[doc(inline)]
35pub use self::vlist::VList;
36#[doc(inline)]
37pub use self::vnode::VNode;
38#[doc(inline)]
39pub use self::vtag::VTag;
40#[doc(inline)]
41pub use self::vtext::VText;
42
43pub trait Listener {
46 fn kind(&self) -> &'static str;
48 fn attach(&self, element: &Element) -> EventListener;
50}
51
52impl fmt::Debug for dyn Listener {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 write!(f, "Listener {{ kind: {} }}", self.kind())
55 }
56}
57
58type Listeners = Vec<Rc<dyn Listener>>;
60
61pub type AttrValue = Cow<'static, str>;
63
64#[derive(Clone, Debug, Eq, PartialEq)]
66pub struct PositionalAttr(pub &'static str, pub Option<AttrValue>);
67impl PositionalAttr {
68 pub fn new(key: &'static str, value: impl IntoOptPropValue<AttrValue>) -> Self {
70 Self(key, value.into_opt_prop_value())
71 }
72
73 pub fn new_boolean(key: &'static str, present: bool) -> Self {
76 let value = if present {
77 Some(Cow::Borrowed(key))
78 } else {
79 None
80 };
81 Self::new(key, value)
82 }
83
84 pub fn new_placeholder(key: &'static str) -> Self {
86 Self(key, None)
87 }
88
89 fn transpose(self) -> Option<(&'static str, AttrValue)> {
90 let Self(key, value) = self;
91 value.map(|v| (key, v))
92 }
93
94 fn transposed<'a>(&'a self) -> Option<(&'static str, &'a AttrValue)> {
95 let Self(key, value) = self;
96 value.as_ref().map(|v| (*key, v))
97 }
98}
99
100#[derive(PartialEq, Eq, Clone, Debug)]
102pub enum Attributes {
103 Vec(Vec<PositionalAttr>),
106
107 IndexMap(IndexMap<&'static str, AttrValue>),
110}
111impl Attributes {
112 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = (&'static str, &'a str)> + 'a> {
119 match self {
120 Self::Vec(v) => Box::new(
121 v.iter()
122 .filter_map(PositionalAttr::transposed)
123 .map(|(k, v)| (k, v.as_ref())),
124 ),
125 Self::IndexMap(m) => Box::new(m.iter().map(|(k, v)| (*k, v.as_ref()))),
126 }
127 }
128
129 pub fn get_mut_index_map(&mut self) -> &mut IndexMap<&'static str, AttrValue> {
132 match self {
133 Self::IndexMap(m) => m,
134 Self::Vec(v) => {
135 *self = Self::IndexMap(
136 mem::take(v)
137 .into_iter()
138 .filter_map(PositionalAttr::transpose)
139 .collect(),
140 );
141 match self {
142 Self::IndexMap(m) => m,
143 _ => unsafe { unreachable_unchecked() },
145 }
146 }
147 }
148 }
149
150 fn diff_vec<'a>(
151 new: &'a [PositionalAttr],
152 old: &[PositionalAttr],
153 ) -> Vec<Patch<&'static str, &'a str>> {
154 let mut out = Vec::new();
155 let mut new_iter = new.iter();
156 let mut old_iter = old.iter();
157
158 loop {
159 match (new_iter.next(), old_iter.next()) {
160 (
161 Some(PositionalAttr(key, new_value)),
162 Some(PositionalAttr(old_key, old_value)),
163 ) if key == old_key => match (new_value, old_value) {
164 (Some(new), Some(old)) => {
165 if new != old {
166 out.push(Patch::Replace(*key, new.as_ref()));
167 }
168 }
169 (Some(value), None) => out.push(Patch::Add(*key, value.as_ref())),
170 (None, Some(_)) => out.push(Patch::Remove(*key)),
171 (None, None) => {}
172 },
173 (Some(new_attr), Some(old_attr)) => {
175 let mut added = iter::once(new_attr)
177 .chain(new_iter)
178 .filter_map(PositionalAttr::transposed)
179 .map(|(key, value)| (key, value.as_ref()))
180 .collect::<HashMap<_, _>>();
181
182 for (key, old_value) in iter::once(old_attr)
184 .chain(old_iter)
185 .filter_map(PositionalAttr::transposed)
186 {
187 if let Some(new_value) = added.remove(key) {
188 if new_value != old_value.as_ref() {
190 out.push(Patch::Replace(key, new_value));
191 }
192 } else {
193 out.push(Patch::Remove(key));
195 }
196 }
197
198 out.extend(added.into_iter().map(|(k, v)| Patch::Add(k, v)));
200 break;
201 }
202 (Some(attr), None) => {
204 for PositionalAttr(key, value) in iter::once(attr).chain(new_iter) {
205 if let Some(value) = value {
207 out.push(Patch::Add(*key, value));
208 }
209 }
210 break;
211 }
212 (None, Some(attr)) => {
214 for PositionalAttr(key, value) in iter::once(attr).chain(old_iter) {
215 if value.is_some() {
217 out.push(Patch::Remove(*key));
218 }
219 }
220 break;
221 }
222 (None, None) => break,
223 }
224 }
225
226 out
227 }
228
229 fn diff_index_map<'a, A, B>(
230 mut new_iter: impl Iterator<Item = (&'static str, &'a str)>,
232 new: &IndexMap<&'static str, A>,
233 old: &IndexMap<&'static str, B>,
234 ) -> Vec<Patch<&'static str, &'a str>>
235 where
236 A: AsRef<str>,
237 B: AsRef<str>,
238 {
239 let mut out = Vec::new();
240 let mut old_iter = old.iter();
241 loop {
242 match (new_iter.next(), old_iter.next()) {
243 (Some((new_key, new_value)), Some((old_key, old_value))) => {
244 if new_key != *old_key {
245 break;
246 }
247 if new_value != old_value.as_ref() {
248 out.push(Patch::Replace(new_key, new_value));
249 }
250 }
251 (Some(attr), None) => {
253 for (key, value) in iter::once(attr).chain(new_iter) {
254 match old.get(key) {
255 Some(old_value) => {
256 if value != old_value.as_ref() {
257 out.push(Patch::Replace(key, value));
258 }
259 }
260 None => out.push(Patch::Add(key, value)),
261 }
262 }
263 break;
264 }
265 (None, Some(attr)) => {
267 for (key, _) in iter::once(attr).chain(old_iter) {
268 if !new.contains_key(key) {
269 out.push(Patch::Remove(*key));
270 }
271 }
272 break;
273 }
274 (None, None) => break,
275 }
276 }
277
278 out
279 }
280
281 fn diff<'a>(new: &'a Self, old: &'a Self) -> Vec<Patch<&'static str, &'a str>> {
282 match (new, old) {
283 (Self::Vec(new), Self::Vec(old)) => Self::diff_vec(new, old),
284 (Self::Vec(new), Self::IndexMap(old)) => {
285 let new_iter = new
288 .iter()
289 .filter_map(PositionalAttr::transposed)
290 .map(|(k, v)| (k, v.as_ref()));
291 let new = new.iter().filter_map(PositionalAttr::transposed).collect();
293 Self::diff_index_map(new_iter, &new, old)
294 }
295 (Self::IndexMap(new), Self::Vec(old)) => {
296 let new_iter = new.iter().map(|(k, v)| (*k, v.as_ref()));
297 Self::diff_index_map(
298 new_iter,
299 new,
300 &old.iter().filter_map(PositionalAttr::transposed).collect(),
301 )
302 }
303 (Self::IndexMap(new), Self::IndexMap(old)) => {
304 let new_iter = new.iter().map(|(k, v)| (*k, v.as_ref()));
305 Self::diff_index_map(new_iter, new, old)
306 }
307 }
308 }
309}
310
311impl From<Vec<PositionalAttr>> for Attributes {
312 fn from(v: Vec<PositionalAttr>) -> Self {
313 Self::Vec(v)
314 }
315}
316impl From<IndexMap<&'static str, AttrValue>> for Attributes {
317 fn from(v: IndexMap<&'static str, AttrValue>) -> Self {
318 Self::IndexMap(v)
319 }
320}
321
322impl Default for Attributes {
323 fn default() -> Self {
324 Self::Vec(Default::default())
325 }
326}
327
328#[derive(Debug, PartialEq)]
330enum Patch<ID, T> {
331 Add(ID, T),
332 Replace(ID, T),
333 Remove(ID),
334}
335
336pub(crate) trait VDiff {
342 fn detach(&mut self, parent: &Element);
344
345 fn apply(
371 &mut self,
372 parent_scope: &AnyScope,
373 parent: &Element,
374 next_sibling: NodeRef,
375 ancestor: Option<VNode>,
376 ) -> NodeRef;
377}
378
379#[cfg(feature = "web_sys")]
380pub(crate) fn insert_node(node: &Node, parent: &Element, next_sibling: Option<Node>) {
381 match next_sibling {
382 Some(next_sibling) => parent
383 .insert_before(&node, Some(&next_sibling))
384 .expect("failed to insert tag before next sibling"),
385 None => parent.append_child(node).expect("failed to append child"),
386 };
387}
388
389#[cfg(feature = "std_web")]
390pub(crate) fn insert_node(node: &impl INode, parent: &impl INode, next_sibling: Option<Node>) {
391 if let Some(next_sibling) = next_sibling {
392 parent
393 .insert_before(node, &next_sibling)
394 .expect("failed to insert tag before next sibling");
395 } else {
396 parent.append_child(node);
397 }
398}
399
400#[cfg(all(test, feature = "web_sys"))]
402mod layout_tests {
403 use super::*;
404 use crate::html::{AnyScope, Scope};
405 use crate::{Component, ComponentLink, Html, ShouldRender};
406
407 struct Comp;
408 impl Component for Comp {
409 type Message = ();
410 type Properties = ();
411
412 fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
413 unimplemented!()
414 }
415
416 fn update(&mut self, _: Self::Message) -> ShouldRender {
417 unimplemented!();
418 }
419
420 fn change(&mut self, _: Self::Properties) -> ShouldRender {
421 unimplemented!()
422 }
423
424 fn view(&self) -> Html {
425 unimplemented!()
426 }
427 }
428
429 pub(crate) struct TestLayout<'a> {
430 pub(crate) name: &'a str,
431 pub(crate) node: VNode,
432 pub(crate) expected: &'a str,
433 }
434
435 pub(crate) fn diff_layouts(layouts: Vec<TestLayout<'_>>) {
436 let document = crate::utils::document();
437 let parent_scope: AnyScope = Scope::<Comp>::new(None).into();
438 let parent_element = document.create_element("div").unwrap();
439 let parent_node: Node = parent_element.clone().into();
440 let end_node = document.create_text_node("END");
441 parent_node.append_child(&end_node).unwrap();
442 let mut empty_node: VNode = VText::new("").into();
443
444 let next_sibling = NodeRef::new(end_node.into());
446 for layout in layouts.iter() {
447 let mut node = layout.node.clone();
449 #[cfg(feature = "wasm_test")]
450 wasm_bindgen_test::console_log!("Independently apply layout '{}'", layout.name);
451 node.apply(&parent_scope, &parent_element, next_sibling.clone(), None);
452 assert_eq!(
453 parent_element.inner_html(),
454 format!("{}END", layout.expected),
455 "Independent apply failed for layout '{}'",
456 layout.name,
457 );
458
459 let mut node_clone = layout.node.clone();
461 #[cfg(feature = "wasm_test")]
462 wasm_bindgen_test::console_log!("Independently reapply layout '{}'", layout.name);
463 node_clone.apply(
464 &parent_scope,
465 &parent_element,
466 next_sibling.clone(),
467 Some(node),
468 );
469 assert_eq!(
470 parent_element.inner_html(),
471 format!("{}END", layout.expected),
472 "Independent reapply failed for layout '{}'",
473 layout.name,
474 );
475
476 empty_node.clone().apply(
478 &parent_scope,
479 &parent_element,
480 next_sibling.clone(),
481 Some(node_clone),
482 );
483 assert_eq!(
484 parent_element.inner_html(),
485 "END",
486 "Independent detach failed for layout '{}'",
487 layout.name,
488 );
489 }
490
491 let mut ancestor: Option<VNode> = None;
493 for layout in layouts.iter() {
494 let mut next_node = layout.node.clone();
495 #[cfg(feature = "wasm_test")]
496 wasm_bindgen_test::console_log!("Sequentially apply layout '{}'", layout.name);
497 next_node.apply(
498 &parent_scope,
499 &parent_element,
500 next_sibling.clone(),
501 ancestor,
502 );
503 assert_eq!(
504 parent_element.inner_html(),
505 format!("{}END", layout.expected),
506 "Sequential apply failed for layout '{}'",
507 layout.name,
508 );
509 ancestor = Some(next_node);
510 }
511
512 for layout in layouts.into_iter().rev() {
514 let mut next_node = layout.node.clone();
515 #[cfg(feature = "wasm_test")]
516 wasm_bindgen_test::console_log!("Sequentially detach layout '{}'", layout.name);
517 next_node.apply(
518 &parent_scope,
519 &parent_element,
520 next_sibling.clone(),
521 ancestor,
522 );
523 assert_eq!(
524 parent_element.inner_html(),
525 format!("{}END", layout.expected),
526 "Sequential detach failed for layout '{}'",
527 layout.name,
528 );
529 ancestor = Some(next_node);
530 }
531
532 empty_node.apply(&parent_scope, &parent_element, next_sibling, ancestor);
534 assert_eq!(
535 parent_element.inner_html(),
536 "END",
537 "Failed to detach last layout"
538 );
539 }
540}
541
542#[cfg(all(test, feature = "web_sys", feature = "wasm_bench"))]
543mod benchmarks {
544 use super::{Attributes, PositionalAttr};
545 use std::borrow::Cow;
546 use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
547
548 wasm_bindgen_test_configure!(run_in_browser);
549
550 fn create_pos_attrs() -> Vec<PositionalAttr> {
551 vec![
552 PositionalAttr::new("oh", Cow::Borrowed("danny")),
553 PositionalAttr::new("boy", Cow::Borrowed("the")),
554 PositionalAttr::new("pipes", Cow::Borrowed("the")),
555 PositionalAttr::new("are", Cow::Borrowed("calling")),
556 PositionalAttr::new("from", Cow::Borrowed("glen")),
557 PositionalAttr::new("to", Cow::Borrowed("glen")),
558 PositionalAttr::new("and", Cow::Borrowed("down")),
559 PositionalAttr::new("the", Cow::Borrowed("mountain")),
560 PositionalAttr::new("side", Cow::Borrowed("")),
561 ]
562 }
563
564 fn run_benchmarks(name: &str, new: Vec<PositionalAttr>, old: Vec<PositionalAttr>) {
565 let new_vec = Attributes::from(new);
566 let old_vec = Attributes::from(old);
567
568 let mut new_map = new_vec.clone();
569 let _ = new_map.get_mut_index_map();
570 let mut old_map = old_vec.clone();
571 let _ = old_map.get_mut_index_map();
572
573 const TIME_LIMIT: f64 = 2.0;
574
575 let vv = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_vec, &old_vec), |(new, old)| {
576 format!("{:?}", Attributes::diff(&new, &old))
577 });
578 let mm = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_map, &old_map), |(new, old)| {
579 format!("{:?}", Attributes::diff(&new, &old))
580 });
581
582 let vm = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_vec, &old_map), |(new, old)| {
583 format!("{:?}", Attributes::diff(&new, &old))
584 });
585 let mv = easybench_wasm::bench_env_limit(TIME_LIMIT, (&new_map, &old_vec), |(new, old)| {
586 format!("{:?}", Attributes::diff(&new, &old))
587 });
588
589 wasm_bindgen_test::console_log!(
590 "{}:\n\tvec-vec: {}\n\tmap-map: {}\n\tvec-map: {}\n\tmap-vec: {}",
591 name,
592 vv,
593 mm,
594 vm,
595 mv
596 );
597 }
598
599 #[wasm_bindgen_test]
600 fn bench_diff_attributes_equal() {
601 let old = create_pos_attrs();
602 let new = old.clone();
603
604 run_benchmarks("equal", new, old);
605 }
606
607 #[wasm_bindgen_test]
608 fn bench_diff_attributes_length_end() {
609 let old = create_pos_attrs();
610 let mut new = old.clone();
611 new.push(PositionalAttr::new("hidden", Cow::Borrowed("hidden")));
612
613 run_benchmarks("added to end", new.clone(), old.clone());
614 run_benchmarks("removed from end", old, new);
615 }
616 #[wasm_bindgen_test]
617 fn bench_diff_attributes_length_start() {
618 let old = create_pos_attrs();
619 let mut new = old.clone();
620 new.insert(0, PositionalAttr::new("hidden", Cow::Borrowed("hidden")));
621
622 run_benchmarks("added to start", new.clone(), old.clone());
623 run_benchmarks("removed from start", old, new);
624 }
625
626 #[wasm_bindgen_test]
627 fn bench_diff_attributes_reorder() {
628 let old = create_pos_attrs();
629 let new = old.clone().into_iter().rev().collect();
630
631 run_benchmarks("reordered", new, old);
632 }
633
634 #[wasm_bindgen_test]
635 fn bench_diff_attributes_change_first() {
636 let old = create_pos_attrs();
637 let mut new = old.clone();
638 new[0].1 = Some(Cow::Borrowed("changed"));
639
640 run_benchmarks("changed first", new, old);
641 }
642
643 #[wasm_bindgen_test]
644 fn bench_diff_attributes_change_middle() {
645 let old = create_pos_attrs();
646 let mut new = old.clone();
647 new[old.len() / 2].1 = Some(Cow::Borrowed("changed"));
648
649 run_benchmarks("changed middle", new, old);
650 }
651
652 #[wasm_bindgen_test]
653 fn bench_diff_attributes_change_last() {
654 let old = create_pos_attrs();
655 let mut new = old.clone();
656 new[old.len() - 1].1 = Some(Cow::Borrowed("changed"));
657
658 run_benchmarks("changed last", new, old);
659 }
660}