1#![deny(clippy::all, missing_docs)]
14
15static_assertions::assert_cfg!(
16 any(feature = "webdom", feature = "rsdom"),
17 "At least one DOM implementation's feature must be enabled (`webdom`, `rsdom`)"
18);
19
20#[cfg(feature = "webdom")]
21pub use {wasm_bindgen::JsCast, web_sys as sys};
22
23#[cfg(feature = "rsdom")]
24use {rsdom::VirtNode, std::rc::Rc};
25
26use {
27 quick_xml::Writer as XmlWriter,
28 std::{
29 fmt::{Debug, Display, Formatter, Result as FmtResult},
30 io::{prelude::*, Cursor},
31 },
32};
33
34#[cfg(feature = "rsdom")]
35pub mod rsdom;
36#[cfg(feature = "webdom")]
37pub mod webdom;
38
39pub mod event;
40
41#[cfg(feature = "webdom")]
43pub fn window() -> sys::Window {
44 sys::window().expect("must run from within a `window`")
45}
46
47#[cfg(feature = "webdom")]
49pub fn document() -> sys::Document {
50 window()
51 .document()
52 .expect("must run from within a `window` with a valid `document`")
53}
54
55pub trait Dom: Sized {
57 fn write_xml<W: Write>(&self, writer: &mut XmlWriter<W>);
61
62 fn outer_html(&self) -> String {
64 let mut buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());
65 {
66 let mut writer = XmlWriter::new(&mut buf);
67 self.write_xml(&mut writer);
68 }
69 String::from_utf8(buf.into_inner()).unwrap()
70 }
71
72 fn pretty_outer_html(&self, indent: usize) -> String {
74 let mut buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());
75 {
76 let mut writer = XmlWriter::new_with_indent(&mut buf, b' ', indent);
77 self.write_xml(&mut writer);
78 }
79 String::from_utf8(buf.into_inner()).unwrap()
80 }
81
82 fn create_element(&self, ty: &str) -> Self;
84
85 fn create_text_node(&self, contents: &str) -> Self;
87
88 fn set_attribute(&self, name: &str, value: &str);
90
91 fn remove_attribute(&self, name: &str);
93
94 fn next_sibling(&self) -> Option<Self>;
96
97 fn first_child(&self) -> Option<Self>;
99
100 fn append_child(&self, child: &Self);
102
103 fn replace_child(&self, new_child: &Self, existing: &Self);
105
106 fn remove_child(&self, to_remove: &Self) -> Option<Self>;
108}
109
110#[derive(Clone)]
112pub enum Node {
113 #[cfg(feature = "webdom")]
115 Concrete(sys::Node),
116
117 #[cfg(feature = "rsdom")]
120 Virtual(Rc<VirtNode>),
121}
122
123impl Debug for Node {
124 fn fmt(&self, f: &mut Formatter) -> FmtResult {
125 let s = if f.alternate() {
126 self.pretty_outer_html(4)
127 } else {
128 self.outer_html()
129 };
130 f.write_str(&s)
131 }
132}
133
134impl Display for Node {
135 fn fmt(&self, f: &mut Formatter) -> FmtResult {
136 f.write_str(&self.pretty_outer_html(2))
137 }
138}
139
140impl PartialEq for Node {
141 fn eq(&self, other: &Self) -> bool {
142 match (self, other) {
143 #[cfg(feature = "webdom")]
144 (Node::Concrete(s), Node::Concrete(o)) => s.is_same_node(Some(o)),
145
146 #[cfg(feature = "rsdom")]
147 (Node::Virtual(s), Node::Virtual(o)) => Rc::ptr_eq(s, o),
148
149 #[cfg(all(feature = "webdom", feature = "rsdom"))]
150 _ => unreachable!("if moxie-dom is comparing two different types of nodes...uh-oh."),
151 }
152 }
153}
154
155impl Dom for Node {
156 fn write_xml<W: Write>(&self, writer: &mut XmlWriter<W>) {
157 match self {
158 #[cfg(feature = "webdom")]
159 Node::Concrete(n) => {
160 n.write_xml(writer);
161 }
162
163 #[cfg(feature = "rsdom")]
164 Node::Virtual(n) => {
165 n.write_xml(writer);
166 }
167 }
168 }
169
170 fn create_element(&self, ty: &str) -> Self {
171 match self {
172 #[cfg(feature = "webdom")]
173 Node::Concrete(n) => Node::Concrete(n.create_element(ty)),
174
175 #[cfg(feature = "rsdom")]
176 Node::Virtual(n) => Node::Virtual(n.create_element(ty)),
177 }
178 }
179
180 fn create_text_node(&self, contents: &str) -> Self {
181 match self {
182 #[cfg(feature = "webdom")]
183 Node::Concrete(n) => Node::Concrete(n.create_text_node(contents)),
184
185 #[cfg(feature = "rsdom")]
186 Node::Virtual(n) => Node::Virtual(n.create_text_node(contents)),
187 }
188 }
189
190 fn first_child(&self) -> Option<Self> {
191 match self {
192 #[cfg(feature = "webdom")]
193 Node::Concrete(n) => <sys::Node as Dom>::first_child(n).map(Node::Concrete),
194
195 #[cfg(feature = "rsdom")]
196 Node::Virtual(n) => n.first_child().map(Node::Virtual),
197 }
198 }
199
200 fn append_child(&self, child: &Self) {
201 match self {
202 #[cfg(feature = "webdom")]
203 Node::Concrete(n) => {
204 <sys::Node as Dom>::append_child(n, child.expect_concrete());
205 }
206
207 #[cfg(feature = "rsdom")]
208 Node::Virtual(n) => {
209 n.append_child(child.expect_virtual());
210 }
211 }
212 }
213
214 fn next_sibling(&self) -> Option<Self> {
215 match self {
216 #[cfg(feature = "webdom")]
217 Node::Concrete(n) => <sys::Node as Dom>::next_sibling(n).map(Node::Concrete),
218
219 #[cfg(feature = "rsdom")]
220 Node::Virtual(n) => n.next_sibling().map(Node::Virtual),
221 }
222 }
223
224 fn remove_child(&self, to_remove: &Self) -> Option<Self> {
225 match self {
226 #[cfg(feature = "webdom")]
227 Node::Concrete(n) => {
228 <sys::Node as Dom>::remove_child(n, to_remove.expect_concrete()).map(Node::Concrete)
229 }
230
231 #[cfg(feature = "rsdom")]
232 Node::Virtual(n) => n
233 .remove_child(to_remove.expect_virtual())
234 .map(Node::Virtual),
235 }
236 }
237
238 fn replace_child(&self, new_child: &Node, existing: &Node) {
239 match self {
240 #[cfg(feature = "webdom")]
241 Node::Concrete(n) => {
242 <sys::Node as Dom>::replace_child(
243 n,
244 new_child.expect_concrete(),
245 existing.expect_concrete(),
246 );
247 }
248
249 #[cfg(feature = "rsdom")]
250 Node::Virtual(n) => {
251 n.replace_child(new_child.expect_virtual(), existing.expect_virtual());
252 }
253 }
254 }
255
256 fn set_attribute(&self, name: &str, value: &str) {
257 match self {
258 #[cfg(feature = "webdom")]
259 Node::Concrete(n) => <sys::Node as Dom>::set_attribute(n, name, value),
260 #[cfg(feature = "rsdom")]
261 Node::Virtual(n) => n.set_attribute(name, value),
262 }
263 }
264
265 fn remove_attribute(&self, name: &str) {
266 match self {
267 #[cfg(feature = "webdom")]
268 Node::Concrete(n) => <sys::Node as Dom>::remove_attribute(n, name),
269 #[cfg(feature = "rsdom")]
270 Node::Virtual(n) => n.remove_attribute(name),
271 }
272 }
273}