Merge pull request #471 from mnaza/fix/treewalker-movers-469
fix(js): descend on FILTER_SKIP in TreeWalker child/sibling movers (#469)
This commit is contained in:
3 files changed
+425
-28
No files matched your search
@@ -0,0 +1,283 @@
|
||||
Compiling proc-macro2 v1.0.106
|
||||
Compiling quote v1.0.45
|
||||
Compiling unicode-ident v1.0.24
|
||||
Compiling libc v0.2.186
|
||||
Compiling cfg-if v1.0.4
|
||||
Compiling parking_lot_core v0.9.12
|
||||
Compiling serde_core v1.0.228
|
||||
Compiling serde v1.0.228
|
||||
Compiling smallvec v1.15.2
|
||||
Compiling icu_properties_data v2.2.0
|
||||
Compiling icu_normalizer_data v2.2.0
|
||||
Compiling stable_deref_trait v1.2.1
|
||||
Compiling pin-project-lite v0.2.17
|
||||
Compiling version_check v0.9.5
|
||||
Compiling scopeguard v1.2.0
|
||||
Compiling itoa v1.0.18
|
||||
Compiling memchr v2.8.2
|
||||
Compiling typenum v1.20.1
|
||||
Compiling lock_api v0.4.14
|
||||
Compiling bytes v1.11.1
|
||||
Compiling crc32fast v1.5.0
|
||||
Compiling zmij v1.0.21
|
||||
Compiling generic-array v0.14.7
|
||||
Compiling rand_core v0.6.4
|
||||
Compiling thiserror v2.0.18
|
||||
Compiling rand v0.8.6
|
||||
Compiling serde_json v1.0.150
|
||||
Compiling subtle v2.6.1
|
||||
Compiling futures-core v0.3.32
|
||||
Compiling syn v2.0.117
|
||||
Compiling glob v0.3.3
|
||||
Compiling litemap v0.8.2
|
||||
Compiling writeable v0.6.3
|
||||
Compiling utf8_iter v1.0.4
|
||||
Compiling prettyplease v0.2.37
|
||||
Compiling clang-sys v1.8.1
|
||||
Compiling siphasher v1.0.3
|
||||
Compiling getrandom v0.2.17
|
||||
Compiling errno v0.3.14
|
||||
Compiling mio v1.2.1
|
||||
Compiling socket2 v0.6.4
|
||||
Compiling signal-hook-registry v1.4.8
|
||||
Compiling autocfg v1.5.1
|
||||
Compiling parking_lot v0.12.5
|
||||
Compiling futures-sink v0.3.32
|
||||
Compiling phf_shared v0.11.3
|
||||
Compiling crypto-common v0.1.7
|
||||
Compiling rustix v0.38.44
|
||||
Compiling regex-syntax v0.8.11
|
||||
Compiling percent-encoding v2.3.2
|
||||
Compiling num-traits v0.2.19
|
||||
Compiling either v1.16.0
|
||||
Compiling bitflags v2.13.0
|
||||
Compiling minimal-lexical v0.2.1
|
||||
Compiling phf_generator v0.11.3
|
||||
Compiling futures-channel v0.3.32
|
||||
Compiling nom v7.1.3
|
||||
Compiling libloading v0.8.9
|
||||
Compiling linux-raw-sys v0.4.15
|
||||
Compiling bindgen v0.71.1
|
||||
Compiling rustc-hash v2.1.2
|
||||
Compiling itertools v0.13.0
|
||||
Compiling log v0.4.32
|
||||
Compiling futures-io v0.3.32
|
||||
Compiling shlex v1.3.0
|
||||
Compiling adler2 v2.0.1
|
||||
Compiling home v0.5.12
|
||||
Compiling once_cell v1.21.4
|
||||
Compiling hashbrown v0.17.1
|
||||
Compiling regex-automata v0.4.14
|
||||
Compiling equivalent v1.0.2
|
||||
Compiling radium v0.7.0
|
||||
Compiling slab v0.4.12
|
||||
Compiling futures-task v0.3.32
|
||||
Compiling indexmap v2.14.0
|
||||
Compiling cexpr v0.6.0
|
||||
Compiling miniz_oxide v0.8.9
|
||||
Compiling gzip-header v1.1.0
|
||||
Compiling form_urlencoded v1.2.2
|
||||
Compiling fslock v0.2.1
|
||||
Compiling find-msvc-tools v0.1.9
|
||||
Compiling shlex v2.0.1
|
||||
Compiling cc v1.2.64
|
||||
Compiling which v6.0.3
|
||||
Compiling http v1.4.2
|
||||
Compiling paste v1.0.15
|
||||
Compiling synstructure v0.13.2
|
||||
Compiling regex v1.12.4
|
||||
Compiling heck v0.5.0
|
||||
Compiling httparse v1.10.1
|
||||
Compiling ring v0.17.14
|
||||
Compiling tracing-core v0.1.36
|
||||
Compiling phf_codegen v0.11.3
|
||||
Compiling block-buffer v0.10.4
|
||||
Compiling alloc-no-stdlib v2.0.4
|
||||
Compiling zerofrom-derive v0.1.7
|
||||
Compiling yoke-derive v0.8.2
|
||||
Compiling zerovec-derive v0.11.3
|
||||
Compiling displaydoc v0.2.6
|
||||
Compiling serde_derive v1.0.228
|
||||
Compiling tokio-macros v2.7.0
|
||||
Compiling futures-macro v0.3.32
|
||||
Compiling thiserror-impl v2.0.18
|
||||
Compiling deno_error_macro v0.6.1
|
||||
Compiling sys_traits_macros v0.1.0
|
||||
Compiling zerofrom v0.1.8
|
||||
Compiling yoke v0.8.3
|
||||
Compiling tokio v1.52.3
|
||||
Compiling zerotrie v0.2.4
|
||||
Compiling zerovec v0.11.6
|
||||
Compiling futures-util v0.3.32
|
||||
Compiling tinystr v0.8.3
|
||||
Compiling potential_utf v0.1.5
|
||||
Compiling icu_locale_core v2.2.0
|
||||
Compiling icu_collections v2.2.0
|
||||
Compiling cpufeatures v0.2.17
|
||||
Compiling zeroize v1.9.0
|
||||
Compiling anyhow v1.0.102
|
||||
Compiling icu_provider v2.2.0
|
||||
Compiling getrandom v0.4.2
|
||||
Compiling icu_properties v2.2.0
|
||||
Compiling icu_normalizer v2.2.0
|
||||
Compiling idna_adapter v1.2.2
|
||||
Compiling idna v1.1.0
|
||||
Compiling rustls-pki-types v1.14.1
|
||||
Compiling url v2.5.8
|
||||
Compiling v8 v137.3.0
|
||||
Compiling alloc-stdlib v0.2.2
|
||||
Compiling tracing-attributes v0.1.31
|
||||
Compiling digest v0.10.7
|
||||
Compiling http-body v1.0.1
|
||||
Compiling new_debug_unreachable v1.0.6
|
||||
Compiling simd-adler32 v0.3.9
|
||||
Compiling untrusted v0.9.0
|
||||
Compiling proc-macro-rules-macros v0.4.0
|
||||
Compiling tracing v0.1.44
|
||||
Compiling deno_error v0.6.1
|
||||
Compiling brotli-decompressor v5.0.1
|
||||
Compiling phf_macros v0.11.3
|
||||
Compiling strum_macros v0.27.2
|
||||
Compiling capacity_builder_macros v0.3.0
|
||||
Compiling pin-project-internal v1.1.13
|
||||
Compiling block-padding v0.3.3
|
||||
Compiling tap v1.0.1
|
||||
Compiling rustls v0.23.40
|
||||
Compiling tower-service v0.3.3
|
||||
Compiling try-lock v0.2.5
|
||||
Compiling utf-8 v0.7.6
|
||||
Compiling wyz v0.5.1
|
||||
Compiling want v0.3.1
|
||||
Compiling brotli v8.0.3
|
||||
Compiling inout v0.1.4
|
||||
Compiling phf v0.11.3
|
||||
Compiling num-integer v0.1.46
|
||||
Compiling flate2 v1.1.9
|
||||
Compiling rustls-webpki v0.103.13
|
||||
Compiling proc-macro-rules v0.4.0
|
||||
Compiling string_cache_codegen v0.5.4
|
||||
Compiling stringcase v0.4.0
|
||||
Compiling funty v2.0.0
|
||||
Compiling compression-core v0.4.32
|
||||
Compiling uuid v1.23.3
|
||||
Compiling atomic-waker v1.1.2
|
||||
Compiling mac v0.1.1
|
||||
Compiling outref v0.5.2
|
||||
Compiling precomputed-hash v0.1.1
|
||||
Compiling vsimd v0.8.0
|
||||
Compiling futf v0.1.5
|
||||
Compiling hyper v1.10.1
|
||||
Compiling debugid v0.8.0
|
||||
Compiling bitvec v1.0.1
|
||||
Compiling markup5ever v0.14.1
|
||||
Compiling strum v0.27.2
|
||||
Compiling base64-simd v0.8.0
|
||||
Compiling deno_ops v0.226.0
|
||||
Compiling futures-executor v0.3.32
|
||||
Compiling compression-codecs v0.4.38
|
||||
Compiling num-bigint v0.4.6
|
||||
Compiling cipher v0.4.4
|
||||
Compiling sys_traits v0.1.28
|
||||
Compiling sync_wrapper v1.0.2
|
||||
Compiling tower-layer v0.3.3
|
||||
Compiling ipnet v2.12.0
|
||||
Compiling dtoa v1.0.11
|
||||
Compiling unicode-id-start v1.4.0
|
||||
Compiling data-encoding v2.11.0
|
||||
Compiling bit-vec v0.8.0
|
||||
Compiling base64 v0.22.1
|
||||
Compiling if_chain v1.0.3
|
||||
Compiling zerocopy v0.8.52
|
||||
Compiling rustix v1.1.4
|
||||
Compiling getrandom v0.3.4
|
||||
Compiling serde_v8 v0.259.0
|
||||
Compiling hyper-util v0.1.20
|
||||
Compiling bit-set v0.8.0
|
||||
Compiling sourcemap v9.3.2
|
||||
Compiling dtoa-short v0.3.5
|
||||
Compiling tower v0.5.3
|
||||
Compiling deno_path_util v0.4.0
|
||||
Compiling tokio-rustls v0.26.4
|
||||
Compiling async-compression v0.4.42
|
||||
Compiling futures v0.3.32
|
||||
Compiling tendril v0.4.3
|
||||
Compiling string_cache v0.8.9
|
||||
Compiling deno_unsync v0.4.4
|
||||
Compiling pin-project v1.1.13
|
||||
Compiling capacity_builder v0.5.0
|
||||
Compiling wasm_dep_analyzer v0.3.0
|
||||
Compiling tokio-util v0.7.18
|
||||
Compiling http-body-util v0.1.3
|
||||
Compiling webpki-roots v1.0.7
|
||||
Compiling bincode v1.3.3
|
||||
Compiling cssparser-macros v0.6.1
|
||||
Compiling selectors v0.26.0
|
||||
Compiling universal-hash v0.5.1
|
||||
Compiling byteorder v1.5.0
|
||||
Compiling deno_core_icudata v0.74.0
|
||||
Compiling ryu v1.0.23
|
||||
Compiling cooked-waker v5.0.0
|
||||
Compiling opaque-debug v0.3.1
|
||||
Compiling az v1.3.0
|
||||
Compiling static_assertions v1.1.0
|
||||
Compiling linux-raw-sys v0.12.1
|
||||
Compiling serde_urlencoded v0.7.1
|
||||
Compiling polyval v0.6.2
|
||||
Compiling fxhash v0.2.1
|
||||
Compiling cssparser v0.34.0
|
||||
Compiling tower-http v0.6.11
|
||||
Compiling hyper-rustls v0.27.9
|
||||
Compiling hmac v0.12.1
|
||||
Compiling sha1 v0.10.6
|
||||
Compiling deno_core v0.350.0
|
||||
Compiling derive_more v0.99.20
|
||||
Compiling match_token v0.1.0
|
||||
Compiling servo_arc v0.4.3
|
||||
Compiling fastrand v2.4.1
|
||||
Compiling tempfile v3.27.0
|
||||
Compiling html5ever v0.29.1
|
||||
Compiling rand_core v0.9.5
|
||||
Compiling reqwest v0.12.28
|
||||
Compiling ghash v0.5.1
|
||||
Compiling ctr v0.9.2
|
||||
Compiling aes v0.8.4
|
||||
Compiling async-trait v0.1.89
|
||||
Compiling aead v0.5.2
|
||||
Compiling encoding_rs v0.8.35
|
||||
Compiling aes-gcm v0.10.3
|
||||
Compiling ppv-lite86 v0.2.21
|
||||
Compiling obscura-dom v0.1.0 (/root/obscura2/merge471/crates/obscura-dom)
|
||||
Compiling hkdf v0.12.4
|
||||
Compiling pbkdf2 v0.12.2
|
||||
Compiling cbc v0.1.2
|
||||
Compiling sha2 v0.10.9
|
||||
Compiling rand_chacha v0.9.0
|
||||
Compiling utf8parse v0.2.2
|
||||
Compiling anstyle-parse v1.0.0
|
||||
Compiling rand v0.9.4
|
||||
Compiling obscura-net v0.1.0 (/root/obscura2/merge471/crates/obscura-net)
|
||||
Compiling colorchoice v1.0.5
|
||||
Compiling anstyle v1.0.14
|
||||
Compiling anstyle-query v1.1.5
|
||||
Compiling is_terminal_polyfill v1.70.2
|
||||
Compiling anstream v1.0.0
|
||||
Compiling tungstenite v0.26.2
|
||||
Compiling strsim v0.11.1
|
||||
Compiling lazy_static v1.5.0
|
||||
Compiling clap_lex v1.1.0
|
||||
Compiling obscura-js v0.1.0 (/root/obscura2/merge471/crates/obscura-js)
|
||||
Compiling clap_builder v4.6.0
|
||||
Compiling sharded-slab v0.1.7
|
||||
Compiling tokio-tungstenite v0.26.2
|
||||
Compiling tracing-log v0.2.0
|
||||
Compiling clap_derive v4.6.1
|
||||
Compiling thread_local v1.1.9
|
||||
Compiling nu-ansi-term v0.50.3
|
||||
Compiling obscura-cli v0.1.0 (/root/obscura2/merge471/crates/obscura-cli)
|
||||
Compiling matchers v0.2.0
|
||||
Compiling tracing-subscriber v0.3.23
|
||||
Compiling clap v4.6.1
|
||||
Compiling obscura-browser v0.1.0 (/root/obscura2/merge471/crates/obscura-browser)
|
||||
Compiling obscura-cdp v0.1.0 (/root/obscura2/merge471/crates/obscura-cdp)
|
||||
Compiling obscura-mcp v0.1.0 (/root/obscura2/merge471/crates/obscura-mcp)
|
||||
Finished `release` profile [optimized] target(s) in 1m 52s
|
||||
Vendored
+49
-28
@@ -2710,42 +2710,63 @@ class Document extends Node {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
firstChild() {
|
||||
let child = this.currentNode.firstChild;
|
||||
while (child) {
|
||||
if (this._accept(child)) { this.currentNode = child; return child; }
|
||||
child = child.nextSibling;
|
||||
// DOM 6.1 "traverse children" (issue #469). The movers used to step
|
||||
// straight to the next sibling when a node was not accepted, so a
|
||||
// FILTER_SKIP node hid its children instead of exposing them. `edge` and
|
||||
// `step` pick the direction: first/next for forward, last/previous for
|
||||
// backward.
|
||||
_traverseChildren(edge, step) {
|
||||
let node = this.currentNode[edge];
|
||||
while (node) {
|
||||
const verdict = this._filter(node);
|
||||
if (verdict === 1) { this.currentNode = node; return node; }
|
||||
// Only SKIP leaves the children eligible; REJECT prunes the subtree.
|
||||
if (verdict === 3) {
|
||||
const child = node[edge];
|
||||
if (child) { node = child; continue; }
|
||||
}
|
||||
// Subtree exhausted: step sideways, climbing out without passing
|
||||
// root or the node the walk started from.
|
||||
while (node) {
|
||||
const sibling = node[step];
|
||||
if (sibling) { node = sibling; break; }
|
||||
const parent = node.parentNode;
|
||||
if (!parent || parent === this.root || parent === this.currentNode) return null;
|
||||
node = parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
lastChild() {
|
||||
let child = this.currentNode.lastChild;
|
||||
while (child) {
|
||||
if (this._accept(child)) { this.currentNode = child; return child; }
|
||||
child = child.previousSibling;
|
||||
// DOM 6.1 "traverse siblings" (issue #469).
|
||||
_traverseSiblings(edge, step) {
|
||||
let node = this.currentNode;
|
||||
if (node === this.root) return null;
|
||||
for (;;) {
|
||||
let sibling = node[step];
|
||||
while (sibling) {
|
||||
node = sibling;
|
||||
const verdict = this._filter(node);
|
||||
if (verdict === 1) { this.currentNode = node; return node; }
|
||||
// Descend into a skipped sibling's subtree; a rejected one is
|
||||
// off-limits, and a childless one has nothing to descend into.
|
||||
sibling = node[edge];
|
||||
if (verdict === 2 || !sibling) sibling = node[step];
|
||||
}
|
||||
node = node.parentNode;
|
||||
if (!node || node === this.root) return null;
|
||||
// An accepted parent is where the walk would go next, so there is no
|
||||
// sibling to return.
|
||||
if (this._filter(node) === 1) return null;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
nextSibling() {
|
||||
let sibling = this.currentNode.nextSibling;
|
||||
while (sibling) {
|
||||
if (this._accept(sibling)) { this.currentNode = sibling; return sibling; }
|
||||
sibling = sibling.nextSibling;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
previousSibling() {
|
||||
let sibling = this.currentNode.previousSibling;
|
||||
while (sibling) {
|
||||
if (this._accept(sibling)) { this.currentNode = sibling; return sibling; }
|
||||
sibling = sibling.previousSibling;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
firstChild() { return this._traverseChildren('firstChild', 'nextSibling'); },
|
||||
lastChild() { return this._traverseChildren('lastChild', 'previousSibling'); },
|
||||
nextSibling() { return this._traverseSiblings('firstChild', 'nextSibling'); },
|
||||
previousSibling() { return this._traverseSiblings('lastChild', 'previousSibling'); },
|
||||
// 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.
|
||||
// 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() {
|
||||
|
||||
@@ -1920,6 +1920,99 @@ mod tests {
|
||||
assert_eq!(result.value.unwrap(), serde_json::json!([1, 1, 400]));
|
||||
}
|
||||
|
||||
/// Issue #469: FILTER_SKIP leaves a skipped node's children eligible, so
|
||||
/// firstChild()/lastChild() must descend into them. FILTER_REJECT must not.
|
||||
#[test]
|
||||
fn tree_walker_child_movers_descend_on_skip_but_not_on_reject() {
|
||||
let mut rt = setup_runtime(
|
||||
r#"<div id="root"><section><a></a><b></b></section></div>"#,
|
||||
);
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const root = document.getElementById('root');
|
||||
function mover(verdict, method) {
|
||||
const w = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode(node) {
|
||||
return node.tagName === 'SECTION' ? verdict : NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
});
|
||||
const found = w[method]();
|
||||
return found ? found.tagName : null;
|
||||
}
|
||||
return [
|
||||
mover(NodeFilter.FILTER_SKIP, 'firstChild'),
|
||||
mover(NodeFilter.FILTER_SKIP, 'lastChild'),
|
||||
mover(NodeFilter.FILTER_REJECT, 'firstChild'),
|
||||
mover(NodeFilter.FILTER_REJECT, 'lastChild'),
|
||||
];
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
// SKIP descends into <section>; REJECT prunes it and finds nothing else.
|
||||
assert_eq!(result, serde_json::json!(["A", "B", null, null]));
|
||||
}
|
||||
|
||||
/// Issue #469: nextSibling()/previousSibling() must descend into a skipped
|
||||
/// sibling's subtree rather than stepping straight over it.
|
||||
#[test]
|
||||
fn tree_walker_sibling_movers_descend_into_skipped_siblings() {
|
||||
let mut rt = setup_runtime(
|
||||
r#"<div id="root"><p id="start"></p><section><a></a></section><q></q></div>"#,
|
||||
);
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const root = document.getElementById('root');
|
||||
function mover(verdict, method, from) {
|
||||
const w = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode(node) {
|
||||
return node.tagName === 'SECTION' ? verdict : NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
});
|
||||
w.currentNode = document.getElementById(from);
|
||||
const found = w[method]();
|
||||
return found ? found.tagName : null;
|
||||
}
|
||||
return [
|
||||
// <section> is skipped, so its child <a> is the next sibling.
|
||||
mover(NodeFilter.FILTER_SKIP, 'nextSibling', 'start'),
|
||||
// Rejected: the subtree is off-limits, so skip past to <q>.
|
||||
mover(NodeFilter.FILTER_REJECT, 'nextSibling', 'start'),
|
||||
];
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(result, serde_json::json!(["A", "Q"]));
|
||||
}
|
||||
|
||||
/// Issue #469: the backward sibling mover descends to *last* children.
|
||||
#[test]
|
||||
fn tree_walker_previous_sibling_descends_to_last_child() {
|
||||
let mut rt = setup_runtime(
|
||||
r#"<div id="root"><section><a></a><b></b></section><p id="start"></p></div>"#,
|
||||
);
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const root = document.getElementById('root');
|
||||
const w = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode(node) {
|
||||
return node.tagName === 'SECTION'
|
||||
? NodeFilter.FILTER_SKIP
|
||||
: NodeFilter.FILTER_ACCEPT;
|
||||
}
|
||||
});
|
||||
w.currentNode = document.getElementById('start');
|
||||
const found = w.previousSibling();
|
||||
return found ? found.tagName : null;
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
// Reverse order descends to <section>'s last child, not its first.
|
||||
assert_eq!(result, serde_json::json!("B"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn append_child_flattens_document_fragment() {
|
||||
let mut rt = setup_runtime(r#"<main id="host"></main>"#);
|
||||
|
||||
Reference in new issue
Block a user