Merge pull request #476 from mnaza/fix/treewalker-parentnode-475
fix(js): implement DOM parentNode() traversal in TreeWalker (#475)
This commit is contained in:
2 files changed
+79
-4
No files matched your search
Vendored
+10
-4
@@ -2724,11 +2724,17 @@ class Document extends Node {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
// DOM 6.1 "parentNode" (issue #475). The old version looked only at the
|
||||||
|
// immediate parent, so it couldn't climb past a skipped ancestor; it also
|
||||||
|
// excluded `root` as a result yet stepped to root's own parent when
|
||||||
|
// currentNode was root — returning a node OUTSIDE the walker's subtree.
|
||||||
|
// The loop's `node !== this.root` guard is what keeps the walk inside
|
||||||
|
// root while still allowing root itself to be returned.
|
||||||
parentNode() {
|
parentNode() {
|
||||||
let parent = this.currentNode.parentNode;
|
let node = this.currentNode;
|
||||||
if (parent && parent !== this.root && this._accept(parent)) {
|
while (node && node !== this.root) {
|
||||||
this.currentNode = parent;
|
node = node.parentNode;
|
||||||
return parent;
|
if (node && this._accept(node)) { this.currentNode = node; return node; }
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1513,6 +1513,75 @@ mod tests {
|
|||||||
|
|
||||||
/// Issue #461: NodeIterator has no subtree pruning — DOM 6.2 says
|
/// Issue #461: NodeIterator has no subtree pruning — DOM 6.2 says
|
||||||
/// FILTER_REJECT behaves as FILTER_SKIP there. The shared walker must not
|
/// FILTER_REJECT behaves as FILTER_SKIP there. The shared walker must not
|
||||||
|
/// Issue #475: parentNode() must never surface a node above `root`. With
|
||||||
|
/// currentNode at root, the old guard stepped to root's own parent and
|
||||||
|
/// returned it — escaping the walker's subtree entirely.
|
||||||
|
#[test]
|
||||||
|
fn tree_walker_parent_node_does_not_escape_above_root() {
|
||||||
|
let mut rt = setup_runtime(r#"<div id="root"><a></a></div>"#);
|
||||||
|
let result = rt
|
||||||
|
.evaluate(
|
||||||
|
r#"
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
const w = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
||||||
|
const escaped = w.parentNode();
|
||||||
|
return [escaped, w.currentNode.id];
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// No parent within the subtree, and currentNode stays put at root.
|
||||||
|
assert_eq!(result, serde_json::json!([null, "root"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Issue #475: when the accepted ancestor is `root` itself, parentNode()
|
||||||
|
/// returns it and moves currentNode there — the old `parent !== root` guard
|
||||||
|
/// wrongly excluded it.
|
||||||
|
#[test]
|
||||||
|
fn tree_walker_parent_node_can_return_the_root() {
|
||||||
|
let mut rt = setup_runtime(r#"<div id="root"><a></a></div>"#);
|
||||||
|
let result = rt
|
||||||
|
.evaluate(
|
||||||
|
r#"
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
const w = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
||||||
|
w.currentNode = root.querySelector('a');
|
||||||
|
const p = w.parentNode();
|
||||||
|
return [p ? p.id : null, w.currentNode === root];
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(result, serde_json::json!(["root", true]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Issue #475: parentNode() climbs past a skipped ancestor to the first
|
||||||
|
/// accepted one, instead of stopping at the immediate parent.
|
||||||
|
#[test]
|
||||||
|
fn tree_walker_parent_node_climbs_past_skipped_ancestors() {
|
||||||
|
let mut rt = setup_runtime(
|
||||||
|
r#"<div id="root"><main id="m"><section><a></a></section></main></div>"#,
|
||||||
|
);
|
||||||
|
let result = rt
|
||||||
|
.evaluate(
|
||||||
|
r#"
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
const w = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
||||||
|
acceptNode(n) {
|
||||||
|
return n.tagName === 'SECTION'
|
||||||
|
? NodeFilter.FILTER_SKIP
|
||||||
|
: NodeFilter.FILTER_ACCEPT;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
w.currentNode = root.querySelector('a');
|
||||||
|
const p = w.parentNode();
|
||||||
|
return p ? p.id : null;
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// <a>'s parent <section> is skipped, so <main> is the first accepted
|
||||||
|
// ancestor — not null, and not the immediate <section>.
|
||||||
|
assert_eq!(result, serde_json::json!("m"));
|
||||||
|
}
|
||||||
|
|
||||||
/// leak TreeWalker's pruning into it.
|
/// leak TreeWalker's pruning into it.
|
||||||
#[test]
|
#[test]
|
||||||
fn node_iterator_treats_filter_reject_as_skip() {
|
fn node_iterator_treats_filter_reject_as_skip() {
|
||||||
|
|||||||
Reference in new issue
Block a user