1 use webapi::document_fragment::DocumentFragment;
2 use webapi::element::Element;
3 use webapi::event_target::{EventTarget, IEventTarget};
4 use webapi::node::{INode, Node};
5 use webapi::parent_node::IParentNode;
6 use webcore::try_from::TryInto;
7 use webcore::value::Reference;
8 
9 /// The mode associated to a shadow root.
10 /// Mainly used in [IElement::attach_shadow](trait.IElement.html#method.attach_shadow) and
11 /// [IShadowRoot::mode](trait.IShadowRoot.html#method.mode).
12 // https://dom.spec.whatwg.org/#shadowroot-mode
13 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
14 pub enum ShadowRootMode {
15     /// { mode: "open" }
16     Open,
17     /// { mode: "closed" }
18     Closed,
19 }
20 
21 impl ShadowRootMode {
as_str(&self) -> &'static str22     pub(crate) fn as_str(&self) -> &'static str {
23         match *self {
24             ShadowRootMode::Open => "open",
25             ShadowRootMode::Closed => "closed",
26         }
27     }
28 }
29 
30 /// The `ShadowRoot` interface of the Shadow DOM API is the root node of a DOM
31 /// subtree that is rendered separately from a document's main DOM tree.
32 ///
33 /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot)
34 // https://dom.spec.whatwg.org/#interface-shadowroot
35 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
36 #[reference(instance_of = "ShadowRoot")]
37 #[reference(subclass_of(EventTarget, Node, DocumentFragment))]
38 pub struct ShadowRoot(Reference);
39 
40 impl IEventTarget for ShadowRoot {}
41 impl INode for ShadowRoot {}
42 impl IParentNode for ShadowRoot {}
43 
44 impl ShadowRoot {
45     /// The mode property of the `ShadowRoot` specifies its mode.
46     ///
47     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode)
48     // https://dom.spec.whatwg.org/#ref-for-dom-shadowroot-mode
mode(&self) -> ShadowRootMode49     pub fn mode(&self) -> ShadowRootMode {
50         let mode_string: String = js!( return @{self.as_ref()}.mode; ).try_into().unwrap();
51 
52         match mode_string.as_str() {
53             "open" => ShadowRootMode::Open,
54             "closed" => ShadowRootMode::Closed,
55             _ => unreachable!("mode can only be `open` or `closed`"),
56         }
57     }
58 
59     /// The host read-only property of the `ShadowRoot` returns a reference to the DOM element
60     /// the ShadowRoot is attached to.
61     ///
62     /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/host)
63     // https://dom.spec.whatwg.org/#ref-for-dom-shadowroot-host
host(&self) -> Element64     pub fn host(&self) -> Element {
65         js!( return @{self.as_ref()}.host; ).try_into().unwrap()
66     }
67 }
68 
69 #[cfg(all(test, feature = "web_test"))]
70 mod tests {
71     use super::*;
72     use webapi::document::document;
73     use webapi::element::{Element, IElement};
74     use webapi::html_elements::{SlotContentKind, SlotElement, TemplateElement};
75     use webapi::node::{CloneKind, INode, Node};
76     use webapi::parent_node::IParentNode;
77 
78     #[test]
test_shadow_root_host()79     fn test_shadow_root_host() {
80         let element = document().create_element("div").unwrap();
81         let shadow_root = element.attach_shadow(ShadowRootMode::Open).unwrap();
82         assert_eq!(shadow_root.host(), element);
83     }
84 
85     #[test]
test_shadow_dom()86     fn test_shadow_dom() {
87         let div: Element = Node::from_html(r#"<div>
88   <span id="span1" slot="slot1"></span>
89 </div>"#)
90             .unwrap()
91             .try_into()
92             .unwrap();
93         let tpl: TemplateElement = Node::from_html(r#"<template>
94   <slot name="slot1" id="slot1"><span id="span2"></span></slot><br>
95   <slot name="slot2" id="slot2"><span id="span3"></span></slot><br>
96 </template>"#)
97             .unwrap()
98             .try_into()
99             .unwrap();
100 
101         let span1 = div.query_selector("#span1").unwrap().unwrap();
102 
103         let shadow_root = div.attach_shadow(ShadowRootMode::Open).unwrap();
104         let n = tpl.content().clone_node(CloneKind::Deep).unwrap();
105 
106         shadow_root.append_child(&n);
107 
108         let slot1: SlotElement = shadow_root
109             .query_selector("#slot1")
110             .unwrap()
111             .unwrap()
112             .try_into()
113             .unwrap();
114         let slot2: SlotElement = shadow_root
115             .query_selector("#slot2")
116             .unwrap()
117             .unwrap()
118             .try_into()
119             .unwrap();
120 
121         assert_eq!(
122             slot1
123                 .assigned_nodes(SlotContentKind::AssignedOnly)
124                 .iter()
125                 .map(|m| m.clone().try_into().unwrap())
126                 .collect::<Vec<Element>>(),
127             &[span1.clone()]
128         );
129         assert_eq!(slot2.assigned_nodes(SlotContentKind::AssignedOnly).len(), 0);
130 
131         assert_eq!(
132             slot1
133                 .assigned_nodes(SlotContentKind::WithFallback)
134                 .iter()
135                 .map(|m| m.clone().try_into().unwrap())
136                 .collect::<Vec<Element>>(),
137             &[span1.clone()]
138         );
139 
140         let slot2_nodes = slot2.assigned_nodes(SlotContentKind::WithFallback);
141         assert_eq!(slot2_nodes.len(), 1);
142         let fallback_span = slot2_nodes[0].clone();
143 
144         assert_eq!(js!( return @{fallback_span}.id; ), "span3");
145     }
146 }
147