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