stdweb/webapi/
shadow_root.rs

1use webapi::document_fragment::DocumentFragment;
2use webapi::element::Element;
3use webapi::event_target::{EventTarget, IEventTarget};
4use webapi::node::{INode, Node};
5use webapi::parent_node::IParentNode;
6use webcore::try_from::TryInto;
7use 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)]
14pub enum ShadowRootMode {
15    /// { mode: "open" }
16    Open,
17    /// { mode: "closed" }
18    Closed,
19}
20
21impl ShadowRootMode {
22    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))]
38pub struct ShadowRoot(Reference);
39
40impl IEventTarget for ShadowRoot {}
41impl INode for ShadowRoot {}
42impl IParentNode for ShadowRoot {}
43
44impl 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
49    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
64    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"))]
70mod 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]
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]
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}