script/dom/node/treewalker.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::Cell;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use script_bindings::callback::OwnerWindow;
11use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
12use script_bindings::script_runtime::temp_cx;
13
14use crate::dom::bindings::callback::ExceptionHandling::Rethrow;
15use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
16use crate::dom::bindings::codegen::Bindings::NodeFilterBinding::{NodeFilter, NodeFilterConstants};
17use crate::dom::bindings::codegen::Bindings::TreeWalkerBinding::TreeWalkerMethods;
18use crate::dom::bindings::error::{Error, Fallible};
19use crate::dom::bindings::root::{Dom, DomRoot, MutDom};
20use crate::dom::document::Document;
21use crate::dom::node::Node;
22
23// https://dom.spec.whatwg.org/#interface-treewalker
24#[dom_struct]
25pub(crate) struct TreeWalker {
26 reflector_: Reflector,
27 root_node: Dom<Node>,
28 current_node: MutDom<Node>,
29 what_to_show: u32,
30 #[ignore_malloc_size_of = "function pointers and Rc<T> are hard"]
31 filter: Filter,
32 active: Cell<bool>,
33}
34
35impl TreeWalker {
36 fn new_inherited(root_node: &Node, what_to_show: u32, filter: Filter) -> TreeWalker {
37 TreeWalker {
38 reflector_: Reflector::new(),
39 root_node: Dom::from_ref(root_node),
40 current_node: MutDom::new(root_node),
41 what_to_show,
42 filter,
43 active: Cell::new(false),
44 }
45 }
46
47 pub(crate) fn new_with_filter(
48 cx: &mut JSContext,
49 document: &Document,
50 root_node: &Node,
51 what_to_show: u32,
52 filter: Filter,
53 ) -> DomRoot<TreeWalker> {
54 reflect_dom_object_with_cx(
55 Box::new(TreeWalker::new_inherited(root_node, what_to_show, filter)),
56 document.window(),
57 cx,
58 )
59 }
60
61 pub(crate) fn new(
62 cx: &mut JSContext,
63 document: &Document,
64 root_node: &Node,
65 what_to_show: u32,
66 node_filter: Option<Rc<NodeFilter>>,
67 ) -> DomRoot<TreeWalker> {
68 let filter = match node_filter {
69 None => Filter::None,
70 Some(jsfilter) => Filter::Dom(jsfilter),
71 };
72 TreeWalker::new_with_filter(cx, document, root_node, what_to_show, filter)
73 }
74}
75
76impl TreeWalkerMethods<crate::DomTypeHolder> for TreeWalker {
77 /// <https://dom.spec.whatwg.org/#dom-treewalker-root>
78 fn Root(&self) -> DomRoot<Node> {
79 DomRoot::from_ref(&*self.root_node)
80 }
81
82 /// <https://dom.spec.whatwg.org/#dom-treewalker-whattoshow>
83 fn WhatToShow(&self) -> u32 {
84 self.what_to_show
85 }
86
87 /// <https://dom.spec.whatwg.org/#dom-treewalker-filter>
88 fn GetFilter(&self) -> Option<Rc<NodeFilter>> {
89 match self.filter {
90 Filter::None => None,
91 Filter::Dom(ref nf) => Some(nf.clone()),
92 }
93 }
94
95 /// <https://dom.spec.whatwg.org/#dom-treewalker-currentnode>
96 fn CurrentNode(&self) -> DomRoot<Node> {
97 self.current_node.get()
98 }
99
100 /// <https://dom.spec.whatwg.org/#dom-treewalker-currentnode>
101 fn SetCurrentNode(&self, node: &Node) {
102 self.current_node.set(node);
103 }
104
105 /// <https://dom.spec.whatwg.org/#dom-treewalker-parentnode>
106 fn ParentNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
107 // "1. Let node be the value of the currentNode attribute."
108 let mut node = self.current_node.get();
109 // "2. While node is not null and is not root, run these substeps:"
110 while !self.is_root_node(&node) {
111 // "1. Let node be node's parent."
112 match node.GetParentNode() {
113 Some(n) => {
114 node = n;
115 // "2. If node is not null and filtering node returns FILTER_ACCEPT,
116 // then set the currentNode attribute to node, return node."
117 if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(cx, &node)? {
118 self.current_node.set(&node);
119 return Ok(Some(node));
120 }
121 },
122 None => break,
123 }
124 }
125 // "3. Return null."
126 Ok(None)
127 }
128
129 /// <https://dom.spec.whatwg.org/#dom-treewalker-firstchild>
130 fn FirstChild(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
131 // "The firstChild() method must traverse children of type first."
132 self.traverse_children(
133 cx,
134 |node| node.GetFirstChild(),
135 |node| node.GetNextSibling(),
136 )
137 }
138
139 /// <https://dom.spec.whatwg.org/#dom-treewalker-lastchild>
140 fn LastChild(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
141 // "The lastChild() method must traverse children of type last."
142 self.traverse_children(
143 cx,
144 |node| node.GetLastChild(),
145 |node| node.GetPreviousSibling(),
146 )
147 }
148
149 /// <https://dom.spec.whatwg.org/#dom-treewalker-previoussibling>
150 fn PreviousSibling(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
151 // "The nextSibling() method must traverse siblings of type next."
152 self.traverse_siblings(
153 cx,
154 |node| node.GetLastChild(),
155 |node| node.GetPreviousSibling(),
156 )
157 }
158
159 /// <https://dom.spec.whatwg.org/#dom-treewalker-nextsibling>
160 fn NextSibling(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
161 // "The previousSibling() method must traverse siblings of type previous."
162 self.traverse_siblings(
163 cx,
164 |node| node.GetFirstChild(),
165 |node| node.GetNextSibling(),
166 )
167 }
168
169 /// <https://dom.spec.whatwg.org/#dom-treewalker-previousnode>
170 fn PreviousNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
171 // "1. Let node be the value of the currentNode attribute."
172 let mut node = self.current_node.get();
173 // "2. While node is not root, run these substeps:"
174 while !self.is_root_node(&node) {
175 // "1. Let sibling be the previous sibling of node."
176 let mut sibling_op = node.GetPreviousSibling();
177 // "2. While sibling is not null, run these subsubsteps:"
178 while sibling_op.is_some() {
179 // "1. Set node to sibling."
180 node = sibling_op.unwrap();
181 // "2. Filter node and let result be the return value."
182 // "3. While result is not FILTER_REJECT and node has a child,
183 // set node to its last child and then filter node and
184 // set result to the return value."
185 // "4. If result is FILTER_ACCEPT, then
186 // set the currentNode attribute to node and return node."
187 loop {
188 let result = self.accept_node(cx, &node)?;
189 match result {
190 NodeFilterConstants::FILTER_REJECT => break,
191 _ if node.GetFirstChild().is_some() => node = node.GetLastChild().unwrap(),
192 NodeFilterConstants::FILTER_ACCEPT => {
193 self.current_node.set(&node);
194 return Ok(Some(node));
195 },
196 _ => break,
197 }
198 }
199 // "5. Set sibling to the previous sibling of node."
200 sibling_op = node.GetPreviousSibling()
201 }
202 // "3. If node is root or node's parent is null, return null."
203 if self.is_root_node(&node) || node.GetParentNode().is_none() {
204 return Ok(None);
205 }
206 // "4. Set node to its parent."
207 match node.GetParentNode() {
208 None =>
209 // This can happen if the user set the current node to somewhere
210 // outside of the tree rooted at the original root.
211 {
212 return Ok(None);
213 },
214 Some(n) => node = n,
215 }
216 // "5. Filter node and if the return value is FILTER_ACCEPT, then
217 // set the currentNode attribute to node and return node."
218 if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(cx, &node)? {
219 self.current_node.set(&node);
220 return Ok(Some(node));
221 }
222 }
223 // "6. Return null."
224 Ok(None)
225 }
226
227 /// <https://dom.spec.whatwg.org/#dom-treewalker-nextnode>
228 fn NextNode(&self, cx: &mut JSContext) -> Fallible<Option<DomRoot<Node>>> {
229 // "1. Let node be the value of the currentNode attribute."
230 let mut node = self.current_node.get();
231 // "2. Let result be FILTER_ACCEPT."
232 let mut result = NodeFilterConstants::FILTER_ACCEPT;
233 // "3. Run these substeps:"
234 loop {
235 // "1. While result is not FILTER_REJECT and node has a child, run these subsubsteps:"
236 loop {
237 if NodeFilterConstants::FILTER_REJECT == result {
238 break;
239 }
240 match node.GetFirstChild() {
241 None => break,
242 Some(child) => {
243 // "1. Set node to its first child."
244 node = child;
245 // "2. Filter node and set result to the return value."
246 result = self.accept_node(cx, &node)?;
247 // "3. If result is FILTER_ACCEPT, then
248 // set the currentNode attribute to node and return node."
249 if NodeFilterConstants::FILTER_ACCEPT == result {
250 self.current_node.set(&node);
251 return Ok(Some(node));
252 }
253 },
254 }
255 }
256 // "2. If a node is following node and is not following root,
257 // set node to the first such node."
258 // "Otherwise, return null."
259 match self.first_following_node_not_following_root(&node) {
260 None => return Ok(None),
261 Some(n) => {
262 node = n;
263 // "3. Filter node and set result to the return value."
264 result = self.accept_node(cx, &node)?;
265 // "4. If result is FILTER_ACCEPT, then
266 // set the currentNode attribute to node and return node."
267 if NodeFilterConstants::FILTER_ACCEPT == result {
268 self.current_node.set(&node);
269 return Ok(Some(node));
270 }
271 },
272 }
273 // "5. Run these substeps again."
274 }
275 }
276}
277
278impl TreeWalker {
279 /// <https://dom.spec.whatwg.org/#concept-traverse-children>
280 fn traverse_children<F, G>(
281 &self,
282 cx: &mut JSContext,
283 next_child: F,
284 next_sibling: G,
285 ) -> Fallible<Option<DomRoot<Node>>>
286 where
287 F: Fn(&Node) -> Option<DomRoot<Node>>,
288 G: Fn(&Node) -> Option<DomRoot<Node>>,
289 {
290 // "To **traverse children** of type *type*, run these steps:"
291 // "1. Let node be the value of the currentNode attribute."
292 let cur = self.current_node.get();
293
294 // "2. Set node to node's first child if type is first, and node's last child if type is last."
295 // "3. If node is null, return null."
296 let mut node = match next_child(&cur) {
297 Some(node) => node,
298 None => return Ok(None),
299 };
300
301 // 4. Main: Repeat these substeps:
302 'main: loop {
303 // "1. Filter node and let result be the return value."
304 let result = self.accept_node(cx, &node)?;
305 match result {
306 // "2. If result is FILTER_ACCEPT, then set the currentNode
307 // attribute to node and return node."
308 NodeFilterConstants::FILTER_ACCEPT => {
309 self.current_node.set(&node);
310 return Ok(Some(DomRoot::from_ref(&node)));
311 },
312 // "3. If result is FILTER_SKIP, run these subsubsteps:"
313 NodeFilterConstants::FILTER_SKIP => {
314 // "1. Let child be node's first child if type is first,
315 // and node's last child if type is last."
316 if let Some(child) = next_child(&node) {
317 // "2. If child is not null, set node to child and goto Main."
318 node = child;
319 continue 'main;
320 }
321 },
322 _ => {},
323 }
324 // "4. Repeat these subsubsteps:"
325 loop {
326 // "1. Let sibling be node's next sibling if type is next,
327 // and node's previous sibling if type is previous."
328 match next_sibling(&node) {
329 // "2. If sibling is not null,
330 // set node to sibling and goto Main."
331 Some(sibling) => {
332 node = sibling;
333 continue 'main;
334 },
335 None => {
336 // "3. Let parent be node's parent."
337 match node.GetParentNode() {
338 // "4. If parent is null, parent is root,
339 // or parent is currentNode attribute's value,
340 // return null."
341 None => return Ok(None),
342 Some(ref parent)
343 if self.is_root_node(parent) || self.is_current_node(parent) =>
344 {
345 return Ok(None);
346 },
347 // "5. Otherwise, set node to parent."
348 Some(parent) => node = parent,
349 }
350 },
351 }
352 }
353 }
354 }
355
356 /// <https://dom.spec.whatwg.org/#concept-traverse-siblings>
357 fn traverse_siblings<F, G>(
358 &self,
359 cx: &mut JSContext,
360 next_child: F,
361 next_sibling: G,
362 ) -> Fallible<Option<DomRoot<Node>>>
363 where
364 F: Fn(&Node) -> Option<DomRoot<Node>>,
365 G: Fn(&Node) -> Option<DomRoot<Node>>,
366 {
367 // "To **traverse siblings** of type *type* run these steps:"
368 // "1. Let node be the value of the currentNode attribute."
369 let mut node = self.current_node.get();
370 // "2. If node is root, return null."
371 if self.is_root_node(&node) {
372 return Ok(None);
373 }
374 // "3. Run these substeps:"
375 loop {
376 // "1. Let sibling be node's next sibling if type is next,
377 // and node's previous sibling if type is previous."
378 let mut sibling_op = next_sibling(&node);
379 // "2. While sibling is not null, run these subsubsteps:"
380 while sibling_op.is_some() {
381 // "1. Set node to sibling."
382 node = sibling_op.unwrap();
383 // "2. Filter node and let result be the return value."
384 let result = self.accept_node(cx, &node)?;
385 // "3. If result is FILTER_ACCEPT, then set the currentNode
386 // attribute to node and return node."
387 if NodeFilterConstants::FILTER_ACCEPT == result {
388 self.current_node.set(&node);
389 return Ok(Some(node));
390 }
391
392 // "4. Set sibling to node's first child if type is next,
393 // and node's last child if type is previous."
394 sibling_op = next_child(&node);
395 // "5. If result is FILTER_REJECT or sibling is null,
396 // then set sibling to node's next sibling if type is next,
397 // and node's previous sibling if type is previous."
398 match (result, &sibling_op) {
399 (NodeFilterConstants::FILTER_REJECT, _) | (_, &None) => {
400 sibling_op = next_sibling(&node)
401 },
402 _ => {},
403 }
404 }
405 // "3. Set node to its parent."
406 match node.GetParentNode() {
407 // "4. If node is null or is root, return null."
408 None => return Ok(None),
409 Some(ref n) if self.is_root_node(n) => return Ok(None),
410 // "5. Filter node and if the return value is FILTER_ACCEPT, then return null."
411 Some(n) => {
412 node = n;
413 if NodeFilterConstants::FILTER_ACCEPT == self.accept_node(cx, &node)? {
414 return Ok(None);
415 }
416 },
417 }
418 // "6. Run these substeps again."
419 }
420 }
421
422 /// <https://dom.spec.whatwg.org/#concept-tree-following>
423 fn first_following_node_not_following_root(&self, node: &Node) -> Option<DomRoot<Node>> {
424 // "An object A is following an object B if A and B are in the same tree
425 // and A comes after B in tree order."
426 match node.GetNextSibling() {
427 None => {
428 let mut candidate = DomRoot::from_ref(node);
429 while !self.is_root_node(&candidate) && candidate.GetNextSibling().is_none() {
430 // This can return None if the user set the current node to somewhere
431 // outside of the tree rooted at the original root.
432 candidate = candidate.GetParentNode()?;
433 }
434 if self.is_root_node(&candidate) {
435 None
436 } else {
437 candidate.GetNextSibling()
438 }
439 },
440 it => it,
441 }
442 }
443
444 /// <https://dom.spec.whatwg.org/#concept-node-filter>
445 fn accept_node(&self, cx: &mut JSContext, node: &Node) -> Fallible<u16> {
446 // Step 1.
447 if self.active.get() {
448 return Err(Error::InvalidState(None));
449 }
450 // Step 2.
451 let n = node.NodeType() - 1;
452 // Step 3.
453 if (self.what_to_show & (1 << n)) == 0 {
454 return Ok(NodeFilterConstants::FILTER_SKIP);
455 }
456 match self.filter {
457 // Step 4.
458 Filter::None => Ok(NodeFilterConstants::FILTER_ACCEPT),
459 Filter::Dom(ref callback) => {
460 // Step 5.
461 self.active.set(true);
462 // Step 6.
463 let result = callback.AcceptNode_(cx, self, node, Rethrow);
464 // Step 7.
465 self.active.set(false);
466 // Step 8.
467 result
468 },
469 }
470 }
471
472 fn is_root_node(&self, node: &Node) -> bool {
473 Dom::from_ref(node) == self.root_node
474 }
475
476 fn is_current_node(&self, node: &Node) -> bool {
477 node == &*self.current_node.get()
478 }
479}
480
481impl Iterator for &TreeWalker {
482 type Item = DomRoot<Node>;
483
484 #[expect(unsafe_code)]
485 fn next(&mut self) -> Option<DomRoot<Node>> {
486 // TODO: https://github.com/servo/servo/issues/43311
487 let mut cx = unsafe { temp_cx() };
488 match self.NextNode(&mut cx) {
489 Ok(node) => node,
490 Err(_) =>
491 // The Err path happens only when a JavaScript
492 // NodeFilter throws an exception. This iterator
493 // is meant for internal use from Rust code, which
494 // will probably be using a native Rust filter,
495 // which cannot produce an Err result.
496 {
497 unreachable!()
498 },
499 }
500 }
501}
502
503#[derive(JSTraceable)]
504pub(crate) enum Filter {
505 None,
506 Dom(Rc<NodeFilter>),
507}
508
509impl OwnerWindow<crate::DomTypeHolder> for TreeWalker {}