fix(js): walk reverse document order in TreeWalker.previousNode() (#462)
previousNode() considered exactly one candidate — the previous sibling's deepest last child — and returned null as soon as the filter rejected it, so a backward walk died mid-tree. This is the backward mirror of #432, which was fixed forward-only in #447. <div id=root><a><b></b></a><c></c></div>, SHOW_ELEMENT, B -> FILTER_SKIP forward: ["A","C"] backward: [] (Chrome: ["A"]) Replaced with the DOM 6.1 algorithm verbatim: scan previous siblings, descend to the deepest last descendant of each, then step up to the parent, continuing until a node is accepted or root is reached. The descent stops on FILTER_REJECT, which is what makes rejection prune subtrees in the backward direction too. Unlike nextNode this stays in JS rather than adding a DOM traversal op: the descent has to consult the filter at every step to honour REJECT, so there is no run of JS/native crossings for a native helper to collapse. Depends on the three-valued `_filter` from #461.
This commit is contained in:
1 parent
1a5c27a592
commit
a8c0a19f67
2 files changed
+130
-10
No files matched your search
Vendored
+30
-10
@@ -2646,18 +2646,38 @@ class Document extends Node {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// DOM 6.1 "previousNode", implemented as specified (issue #462). The old
|
||||
// version looked at exactly one candidate — the previous sibling's
|
||||
// deepest last child — and returned null the moment it was filtered out,
|
||||
// so a backward walk died mid-tree the way nextNode used to before #432.
|
||||
//
|
||||
// Unlike nextNode this stays in JS rather than using a DOM traversal op:
|
||||
// the descent into last children has to stop on FILTER_REJECT, so the
|
||||
// filter is consulted at every step anyway and there is no run of
|
||||
// crossings for a native helper to collapse.
|
||||
previousNode() {
|
||||
let node = this.currentNode;
|
||||
if (node === this.root) return null;
|
||||
let sibling = node.previousSibling;
|
||||
if (sibling) {
|
||||
while (sibling.lastChild) sibling = sibling.lastChild;
|
||||
if (this._accept(sibling)) { this.currentNode = sibling; return sibling; }
|
||||
}
|
||||
let parent = node.parentNode;
|
||||
if (parent && parent !== this.root && this._accept(parent)) {
|
||||
this.currentNode = parent;
|
||||
return parent;
|
||||
while (node !== this.root) {
|
||||
let sibling = node.previousSibling;
|
||||
while (sibling) {
|
||||
node = sibling;
|
||||
let verdict = this._filter(node);
|
||||
// Descend to the deepest last descendant, but never into a rejected
|
||||
// subtree — that is what makes REJECT prune backwards as well.
|
||||
while (verdict !== 2 && node.lastChild) {
|
||||
node = node.lastChild;
|
||||
verdict = this._filter(node);
|
||||
}
|
||||
if (verdict === 1) { this.currentNode = node; return node; }
|
||||
sibling = node.previousSibling;
|
||||
}
|
||||
const parent = node.parentNode;
|
||||
// Reaching root (or a detached node) ends the walk: root is never
|
||||
// returned by a backward traversal.
|
||||
if (!parent || node === this.root) return null;
|
||||
node = parent;
|
||||
if (node === this.root) return null;
|
||||
if (this._filter(node) === 1) { this.currentNode = node; return node; }
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user