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