1use crate::diagnostics::{BuildDiagnostics, Spanned};
7use crate::expression_tree::{BindingExpression, Expression, NamedReference};
8use crate::langtype::{ElementType, Type};
9use crate::object_tree::*;
10use by_address::ByAddress;
11use smol_str::SmolStr;
12use std::cell::RefCell;
13use std::collections::{HashMap, HashSet};
14use std::rc::Rc;
15
16#[derive(Copy, Clone, Eq, PartialEq)]
17pub enum InlineSelection {
18 InlineAllComponents,
19 InlineOnlyRequiredComponents,
20}
21
22pub fn inline(doc: &Document, inline_selection: InlineSelection, diag: &mut BuildDiagnostics) {
23 fn inline_components_recursively(
24 component: &Rc<Component>,
25 roots: &HashSet<ByAddress<Rc<Component>>>,
26 inline_selection: InlineSelection,
27 diag: &mut BuildDiagnostics,
28 ) {
29 recurse_elem_no_borrow(&component.root_element, &(), &mut |elem, _| {
30 let base = elem.borrow().base_type.clone();
31 if let ElementType::Component(c) = base {
32 inline_components_recursively(&c, roots, inline_selection, diag);
34
35 if c.parent_element.upgrade().is_some() {
36 return;
38 }
39
40 if match inline_selection {
42 InlineSelection::InlineAllComponents => true,
43 InlineSelection::InlineOnlyRequiredComponents => {
44 component_requires_inlining(&c)
45 || element_require_inlining(elem)
46 || component.parent_element.upgrade().is_none() && Rc::ptr_eq(elem, &component.root_element)
49 || roots.contains(&ByAddress(c.clone()))
51 }
52 } {
53 inline_element(elem, &c, component, diag);
54 }
55 }
56 });
57 component.popup_windows.borrow().iter().for_each(|p| {
58 inline_components_recursively(&p.component, roots, inline_selection, diag)
59 })
60 }
61 let mut roots = HashSet::new();
62 if inline_selection == InlineSelection::InlineOnlyRequiredComponents {
63 for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
64 roots.insert(ByAddress(component.clone()));
65 }
66 }
67 for component in doc.exported_roots().chain(doc.popup_menu_impl.iter().cloned()) {
68 inline_components_recursively(&component, &roots, inline_selection, diag);
69 let mut init_code = component.init_code.borrow_mut();
70 let inlined_init_code = core::mem::take(&mut init_code.inlined_init_code);
71 init_code.constructor_code.splice(0..0, inlined_init_code.into_values());
72 }
73}
74
75fn element_key(e: ElementRc) -> ByAddress<ElementRc> {
76 ByAddress(e)
77}
78
79type Mapping = HashMap<ByAddress<ElementRc>, ElementRc>;
80
81fn inline_element(
82 elem: &ElementRc,
83 inlined_component: &Rc<Component>,
84 root_component: &Rc<Component>,
85 diag: &mut BuildDiagnostics,
86) {
87 debug_assert_eq!(elem.borrow().base_type, ElementType::Component(inlined_component.clone()));
89 debug_assert!(
90 inlined_component.root_element.borrow().repeated.is_none(),
91 "root element of a component cannot be repeated"
92 );
93 debug_assert!(inlined_component.parent_element.upgrade().is_none());
94
95 let mut elem_mut = elem.borrow_mut();
96 let priority_delta = 1 + elem_mut.inline_depth;
97 elem_mut.base_type = inlined_component.root_element.borrow().base_type.clone();
98 elem_mut.property_declarations.extend(
99 inlined_component.root_element.borrow().property_declarations.iter().map(|(name, decl)| {
100 let mut decl = decl.clone();
101 decl.expose_in_public_api = false;
102 (name.clone(), decl)
103 }),
104 );
105
106 for (p, a) in inlined_component.root_element.borrow().property_analysis.borrow().iter() {
107 elem_mut.property_analysis.borrow_mut().entry(p.clone()).or_default().merge_with_base(a);
108 }
109
110 debug_assert!(inlined_component.root_element.borrow().states.is_empty());
112 debug_assert!(inlined_component.root_element.borrow().transitions.is_empty());
113
114 let mut mapping = HashMap::new();
116 mapping.insert(element_key(inlined_component.root_element.clone()), elem.clone());
117
118 let mut new_children = Vec::with_capacity(
119 elem_mut.children.len() + inlined_component.root_element.borrow().children.len(),
120 );
121 new_children.extend(
122 inlined_component.root_element.borrow().children.iter().map(|x| {
123 duplicate_element_with_mapping(x, &mut mapping, root_component, priority_delta)
124 }),
125 );
126
127 let mut move_children_into_popup = None;
128
129 match inlined_component.child_insertion_point.borrow().as_ref() {
130 Some(inlined_cip) => {
131 let children = std::mem::take(&mut elem_mut.children);
132 let old_count = children.len();
133 if let Some(insertion_element) = mapping.get(&element_key(inlined_cip.parent.clone())) {
134 if old_count > 0 {
135 if !Rc::ptr_eq(elem, insertion_element) {
136 debug_assert!(std::rc::Weak::ptr_eq(
137 &insertion_element.borrow().enclosing_component,
138 &elem_mut.enclosing_component,
139 ));
140 insertion_element.borrow_mut().children.splice(
141 inlined_cip.insertion_index..inlined_cip.insertion_index,
142 children,
143 );
144 } else {
145 new_children.splice(
146 inlined_cip.insertion_index..inlined_cip.insertion_index,
147 children,
148 );
149 }
150 }
151 let mut cip = root_component.child_insertion_point.borrow_mut();
152 if let Some(cip) = cip.as_mut() {
153 if Rc::ptr_eq(&cip.parent, elem) {
154 *cip = ChildrenInsertionPoint {
155 parent: insertion_element.clone(),
156 insertion_index: inlined_cip.insertion_index + cip.insertion_index,
157 node: inlined_cip.node.clone(),
158 };
159 }
160 } else if Rc::ptr_eq(elem, &root_component.root_element) {
161 *cip = Some(ChildrenInsertionPoint {
162 parent: insertion_element.clone(),
163 insertion_index: inlined_cip.insertion_index + old_count,
164 node: inlined_cip.node.clone(),
165 });
166 };
167 } else if old_count > 0 {
168 debug_assert!(inlined_component.popup_windows.borrow().iter().any(|p| Rc::ptr_eq(
170 &p.component,
171 &inlined_cip.parent.borrow().enclosing_component.upgrade().unwrap()
172 )));
173 move_children_into_popup = Some(children);
174 };
175 }
176 _ => {
177 new_children.append(&mut elem_mut.children);
178 }
179 }
180
181 elem_mut.children = new_children;
182 elem_mut.debug.extend_from_slice(&inlined_component.root_element.borrow().debug);
183
184 if let ElementType::Component(c) = &mut elem_mut.base_type {
185 if c.parent_element.upgrade().is_some() {
186 debug_assert!(Rc::ptr_eq(elem, &c.parent_element.upgrade().unwrap()));
187 *c = duplicate_sub_component(c, elem, &mut mapping, priority_delta);
188 }
189 };
190
191 root_component.optimized_elements.borrow_mut().extend(
192 inlined_component.optimized_elements.borrow().iter().map(|x| {
193 duplicate_element_with_mapping(x, &mut mapping, root_component, priority_delta)
194 }),
195 );
196 root_component.popup_windows.borrow_mut().extend(
197 inlined_component
198 .popup_windows
199 .borrow()
200 .iter()
201 .map(|p| duplicate_popup(p, &mut mapping, priority_delta)),
202 );
203
204 root_component.menu_item_tree.borrow_mut().extend(
205 inlined_component
206 .menu_item_tree
207 .borrow()
208 .iter()
209 .map(|it| duplicate_sub_component(it, elem, &mut mapping, priority_delta)),
210 );
211
212 root_component.timers.borrow_mut().extend(inlined_component.timers.borrow().iter().map(|t| {
213 let inlined_element = mapping.get(&element_key(t.element.upgrade().unwrap())).unwrap();
214
215 Timer { element: Rc::downgrade(inlined_element), ..t.clone() }
216 }));
217
218 let mut moved_into_popup = HashSet::new();
219 if let Some(children) = move_children_into_popup {
220 let child_insertion_point = inlined_component.child_insertion_point.borrow();
221 let inlined_cip = child_insertion_point.as_ref().unwrap();
222
223 let insertion_element = mapping.get(&element_key(inlined_cip.parent.clone())).unwrap();
224 debug_assert!(!std::rc::Weak::ptr_eq(
225 &insertion_element.borrow().enclosing_component,
226 &elem_mut.enclosing_component,
227 ));
228 debug_assert!(root_component.popup_windows.borrow().iter().any(|p| Rc::ptr_eq(
229 &p.component,
230 &insertion_element.borrow().enclosing_component.upgrade().unwrap()
231 )));
232 for c in &children {
233 recurse_elem(c, &(), &mut |e, _| {
234 e.borrow_mut().enclosing_component =
235 insertion_element.borrow().enclosing_component.clone();
236 moved_into_popup.insert(element_key(e.clone()));
237 });
238 }
239 insertion_element
240 .borrow_mut()
241 .children
242 .splice(inlined_cip.insertion_index..inlined_cip.insertion_index, children);
243 let mut cip = root_component.child_insertion_point.borrow_mut();
244 if let Some(cip) = cip.as_mut() {
245 if Rc::ptr_eq(&cip.parent, elem) {
246 *cip = ChildrenInsertionPoint {
247 parent: insertion_element.clone(),
248 insertion_index: inlined_cip.insertion_index + cip.insertion_index,
249 node: inlined_cip.node.clone(),
250 };
251 }
252 } else {
253 *cip = Some(ChildrenInsertionPoint {
254 parent: insertion_element.clone(),
255 insertion_index: inlined_cip.insertion_index,
256 node: inlined_cip.node.clone(),
257 });
258 };
259 }
260
261 for (k, val) in inlined_component.root_element.borrow().bindings.iter() {
262 match elem_mut.bindings.entry(k.clone()) {
263 std::collections::btree_map::Entry::Vacant(entry) => {
264 let priority = &mut entry.insert(val.clone()).get_mut().priority;
265 *priority = priority.saturating_add(priority_delta);
266 }
267 std::collections::btree_map::Entry::Occupied(mut entry) => {
268 let entry = entry.get_mut().get_mut();
269 if entry.merge_with(&val.borrow()) {
270 entry.priority = entry.priority.saturating_add(priority_delta);
271 }
272 }
273 }
274 }
275 for (k, val) in inlined_component.root_element.borrow().change_callbacks.iter() {
276 match elem_mut.change_callbacks.entry(k.clone()) {
277 std::collections::btree_map::Entry::Vacant(entry) => {
278 entry.insert(val.clone());
279 }
280 std::collections::btree_map::Entry::Occupied(mut entry) => {
281 entry.get_mut().get_mut().splice(0..0, val.borrow().iter().cloned());
282 }
283 }
284 }
285
286 if let Some(orig) = &inlined_component.root_element.borrow().layout_info_prop {
287 if let Some(_new) = &mut elem_mut.layout_info_prop {
288 todo!("Merge layout infos");
289 } else {
290 elem_mut.layout_info_prop = Some(orig.clone());
291 }
292 }
293
294 core::mem::drop(elem_mut);
295
296 let fixup_init_expression = |mut init_code: Expression| {
297 visit_named_references_in_expression(&mut init_code, &mut |nr| {
299 fixup_reference(nr, &mapping)
300 });
301 fixup_element_references(&mut init_code, &mapping);
302 init_code
303 };
304 let inlined_init_code = inlined_component
305 .init_code
306 .borrow()
307 .inlined_init_code
308 .values()
309 .cloned()
310 .chain(inlined_component.init_code.borrow().constructor_code.iter().cloned())
311 .map(fixup_init_expression)
312 .collect();
313
314 root_component
315 .init_code
316 .borrow_mut()
317 .inlined_init_code
318 .insert(elem.borrow().span().offset, Expression::CodeBlock(inlined_init_code));
319
320 for e in mapping.values() {
322 visit_all_named_references_in_element(e, |nr| fixup_reference(nr, &mapping));
323 visit_element_expressions(e, |expr, _, _| fixup_element_references(expr, &mapping));
324 }
325 for p in root_component.popup_windows.borrow_mut().iter_mut() {
326 fixup_reference(&mut p.x, &mapping);
327 fixup_reference(&mut p.y, &mapping);
328 }
329 for t in root_component.timers.borrow_mut().iter_mut() {
330 fixup_reference(&mut t.interval, &mapping);
331 fixup_reference(&mut t.running, &mapping);
332 fixup_reference(&mut t.triggered, &mapping);
333 }
334 if !moved_into_popup.is_empty() {
336 recurse_elem_no_borrow(&root_component.root_element.clone(), &(), &mut |e, _| {
337 if !moved_into_popup.contains(&element_key(e.clone())) {
338 visit_all_named_references_in_element(e, |nr| {
339 if moved_into_popup.contains(&element_key(nr.element())) {
340 diag.push_error(format!("Access to property '{nr:?}' which is inlined into a PopupWindow via @children is forbidden"), &*e.borrow());
341 }
342 });
343 }
344 });
345 }
346}
347
348fn duplicate_element_with_mapping(
350 element: &ElementRc,
351 mapping: &mut Mapping,
352 root_component: &Rc<Component>,
353 priority_delta: i32,
354) -> ElementRc {
355 let elem = element.borrow();
356 let new = Rc::new(RefCell::new(Element {
357 base_type: elem.base_type.clone(),
358 id: elem.id.clone(),
359 property_declarations: elem.property_declarations.clone(),
360 bindings: elem
362 .bindings
363 .iter()
364 .map(|b| duplicate_binding(b, mapping, root_component, priority_delta))
365 .collect(),
366 change_callbacks: elem.change_callbacks.clone(),
367 property_analysis: elem.property_analysis.clone(),
368 children: elem
369 .children
370 .iter()
371 .map(|x| duplicate_element_with_mapping(x, mapping, root_component, priority_delta))
372 .collect(),
373 repeated: elem.repeated.clone(),
374 is_component_placeholder: elem.is_component_placeholder,
375 debug: elem.debug.clone(),
376 enclosing_component: Rc::downgrade(root_component),
377 states: elem.states.clone(),
378 transitions: elem
379 .transitions
380 .iter()
381 .map(|t| duplicate_transition(t, mapping, root_component, priority_delta))
382 .collect(),
383 child_of_layout: elem.child_of_layout,
384 layout_info_prop: elem.layout_info_prop.clone(),
385 default_fill_parent: elem.default_fill_parent,
386 accessibility_props: elem.accessibility_props.clone(),
387 geometry_props: elem.geometry_props.clone(),
388 named_references: Default::default(),
389 item_index: Default::default(), item_index_of_first_children: Default::default(),
391 is_flickable_viewport: elem.is_flickable_viewport,
392 has_popup_child: elem.has_popup_child,
393 is_legacy_syntax: elem.is_legacy_syntax,
394 inline_depth: elem.inline_depth + 1,
395 }));
396 mapping.insert(element_key(element.clone()), new.clone());
397 if let ElementType::Component(c) = &mut new.borrow_mut().base_type {
398 if c.parent_element.upgrade().is_some() {
399 debug_assert!(Rc::ptr_eq(element, &c.parent_element.upgrade().unwrap()));
400 *c = duplicate_sub_component(c, &new, mapping, priority_delta);
401 }
402 };
403
404 new
405}
406
407fn duplicate_sub_component(
409 component_to_duplicate: &Rc<Component>,
410 new_parent: &ElementRc,
411 mapping: &mut Mapping,
412 priority_delta: i32,
413) -> Rc<Component> {
414 debug_assert!(component_to_duplicate.parent_element.upgrade().is_some());
415 let new_component = Component {
416 node: component_to_duplicate.node.clone(),
417 id: component_to_duplicate.id.clone(),
418 root_element: duplicate_element_with_mapping(
419 &component_to_duplicate.root_element,
420 mapping,
421 component_to_duplicate, priority_delta,
423 ),
424 parent_element: Rc::downgrade(new_parent),
425 optimized_elements: RefCell::new(
426 component_to_duplicate
427 .optimized_elements
428 .borrow()
429 .iter()
430 .map(|e| {
431 duplicate_element_with_mapping(
432 e,
433 mapping,
434 component_to_duplicate,
435 priority_delta,
436 )
437 })
438 .collect(),
439 ),
440 root_constraints: component_to_duplicate.root_constraints.clone(),
441 child_insertion_point: component_to_duplicate.child_insertion_point.clone(),
442 init_code: component_to_duplicate.init_code.clone(),
443 popup_windows: Default::default(),
444 timers: component_to_duplicate.timers.clone(),
445 menu_item_tree: Default::default(),
446 exported_global_names: component_to_duplicate.exported_global_names.clone(),
447 used: component_to_duplicate.used.clone(),
448 private_properties: Default::default(),
449 inherits_popup_window: core::cell::Cell::new(false),
450 from_library: core::cell::Cell::new(false),
451 };
452
453 let new_component = Rc::new(new_component);
454 let weak = Rc::downgrade(&new_component);
455 recurse_elem(&new_component.root_element, &(), &mut |e, _| {
456 e.borrow_mut().enclosing_component = weak.clone()
457 });
458 for o in new_component.optimized_elements.borrow().iter() {
459 o.borrow_mut().enclosing_component = weak.clone()
460 }
461 *new_component.popup_windows.borrow_mut() = component_to_duplicate
462 .popup_windows
463 .borrow()
464 .iter()
465 .map(|p| duplicate_popup(p, mapping, priority_delta))
466 .collect();
467 for p in new_component.popup_windows.borrow_mut().iter_mut() {
468 fixup_reference(&mut p.x, mapping);
469 fixup_reference(&mut p.y, mapping);
470 }
471 for t in new_component.timers.borrow_mut().iter_mut() {
472 fixup_reference(&mut t.interval, mapping);
473 fixup_reference(&mut t.running, mapping);
474 fixup_reference(&mut t.triggered, mapping);
475 }
476 *new_component.menu_item_tree.borrow_mut() = component_to_duplicate
477 .menu_item_tree
478 .borrow()
479 .iter()
480 .map(|it| {
481 let new_parent =
482 mapping.get(&element_key(it.parent_element.upgrade().unwrap())).unwrap().clone();
483 duplicate_sub_component(it, &new_parent, mapping, priority_delta)
484 })
485 .collect();
486 new_component
487 .root_constraints
488 .borrow_mut()
489 .visit_named_references(&mut |nr| fixup_reference(nr, mapping));
490 new_component
491}
492
493fn duplicate_popup(p: &PopupWindow, mapping: &mut Mapping, priority_delta: i32) -> PopupWindow {
494 let parent = mapping
495 .get(&element_key(p.component.parent_element.upgrade().expect("must have a parent")))
496 .expect("Parent must be in the mapping")
497 .clone();
498 PopupWindow {
499 x: p.x.clone(),
500 y: p.y.clone(),
501 close_policy: p.close_policy.clone(),
502 component: duplicate_sub_component(&p.component, &parent, mapping, priority_delta),
503 parent_element: mapping
504 .get(&element_key(p.parent_element.clone()))
505 .expect("Parent element must be in the mapping")
506 .clone(),
507 }
508}
509
510fn duplicate_binding(
513 (k, b): (&SmolStr, &RefCell<BindingExpression>),
514 mapping: &mut Mapping,
515 root_component: &Rc<Component>,
516 priority_delta: i32,
517) -> (SmolStr, RefCell<BindingExpression>) {
518 let b = b.borrow();
519 let b = BindingExpression {
520 expression: b.expression.clone(),
521 span: b.span.clone(),
522 priority: b.priority.saturating_add(priority_delta),
523 animation: b
524 .animation
525 .as_ref()
526 .map(|pa| duplicate_property_animation(pa, mapping, root_component, priority_delta)),
527 analysis: b.analysis.clone(),
528 two_way_bindings: b.two_way_bindings.clone(),
529 };
530 (k.clone(), b.into())
531}
532
533fn duplicate_property_animation(
534 v: &PropertyAnimation,
535 mapping: &mut Mapping,
536 root_component: &Rc<Component>,
537 priority_delta: i32,
538) -> PropertyAnimation {
539 match v {
540 PropertyAnimation::Static(a) => PropertyAnimation::Static(duplicate_element_with_mapping(
541 a,
542 mapping,
543 root_component,
544 priority_delta,
545 )),
546 PropertyAnimation::Transition { state_ref, animations } => PropertyAnimation::Transition {
547 state_ref: state_ref.clone(),
548 animations: animations
549 .iter()
550 .map(|a| TransitionPropertyAnimation {
551 state_id: a.state_id,
552 direction: a.direction,
553 animation: duplicate_element_with_mapping(
554 &a.animation,
555 mapping,
556 root_component,
557 priority_delta,
558 ),
559 })
560 .collect(),
561 },
562 }
563}
564
565fn fixup_reference(nr: &mut NamedReference, mapping: &Mapping) {
566 if let Some(e) = mapping.get(&element_key(nr.element())) {
567 *nr = NamedReference::new(e, nr.name().clone());
568 }
569}
570
571fn fixup_element_references(expr: &mut Expression, mapping: &Mapping) {
572 let fx = |element: &mut std::rc::Weak<RefCell<Element>>| {
573 if let Some(e) = element.upgrade().and_then(|e| mapping.get(&element_key(e))) {
574 *element = Rc::downgrade(e);
575 }
576 };
577 let fxe = |element: &mut ElementRc| {
578 if let Some(e) = mapping.get(&element_key(element.clone())) {
579 *element = e.clone();
580 }
581 };
582 match expr {
583 Expression::ElementReference(element) => fx(element),
584 Expression::SolveLayout(l, _) | Expression::ComputeLayoutInfo(l, _) => match l {
585 crate::layout::Layout::GridLayout(l) => {
586 for e in &mut l.elems {
587 fxe(&mut e.item.element);
588 }
589 }
590 crate::layout::Layout::BoxLayout(l) => {
591 for e in &mut l.elems {
592 fxe(&mut e.element);
593 }
594 }
595 },
596 Expression::RepeaterModelReference { element }
597 | Expression::RepeaterIndexReference { element } => fx(element),
598 _ => expr.visit_mut(|e| fixup_element_references(e, mapping)),
599 }
600}
601
602fn duplicate_transition(
603 t: &Transition,
604 mapping: &mut HashMap<ByAddress<ElementRc>, Rc<RefCell<Element>>>,
605 root_component: &Rc<Component>,
606 priority_delta: i32,
607) -> Transition {
608 Transition {
609 direction: t.direction,
610 state_id: t.state_id.clone(),
611 property_animations: t
612 .property_animations
613 .iter()
614 .map(|(r, loc, anim)| {
615 (
616 r.clone(),
617 loc.clone(),
618 duplicate_element_with_mapping(anim, mapping, root_component, priority_delta),
619 )
620 })
621 .collect(),
622 node: t.node.clone(),
623 }
624}
625
626fn component_requires_inlining(component: &Rc<Component>) -> bool {
629 let root_element = &component.root_element;
630 if super::flickable::is_flickable_element(root_element) {
631 return true;
632 }
633
634 for (prop, binding) in &root_element.borrow().bindings {
635 let binding = binding.borrow();
636 if prop.starts_with("drop-shadow-")
639 || prop == "opacity"
640 || prop == "cache-rendering-hint"
641 || prop == "visible"
642 {
643 return true;
644 }
645 if (prop == "height" || prop == "width") && binding.expression.ty() == Type::Percent {
646 return true;
648 }
649 if binding.animation.is_some() {
650 let lookup_result = root_element.borrow().lookup_property(prop);
651 if !lookup_result.is_valid()
652 || !lookup_result.is_local_to_component
653 || !matches!(
654 lookup_result.property_visibility,
655 PropertyVisibility::Private | PropertyVisibility::Output
656 )
657 {
658 return true;
661 }
662 }
663 }
664
665 false
666}
667
668fn element_require_inlining(elem: &ElementRc) -> bool {
669 if !elem.borrow().children.is_empty() {
670 return true;
672 }
673
674 if super::lower_popups::is_popup_window(elem) {
676 return true;
677 }
678
679 for (prop, binding) in &elem.borrow().bindings {
680 if prop == "clip" {
681 return true;
683 }
684
685 if prop == "padding"
686 || prop == "spacing"
687 || prop.starts_with("padding-")
688 || prop.starts_with("spacing-")
689 || prop == "alignment"
690 {
691 if let ElementType::Component(base) = &elem.borrow().base_type {
692 if crate::layout::is_layout(&base.root_element.borrow().base_type) {
693 if !base.root_element.borrow().is_binding_set(prop, false) {
694 return true;
696 }
697 }
698 }
699 }
700
701 let binding = binding.borrow();
702 if binding.animation.is_some() && matches!(binding.expression, Expression::Invalid) {
703 return true;
705 }
706 }
707
708 false
709}