Skip to content

Commit 6c5e0f9

Browse files
authored
fix: [#1605] Fixes issue related to the use of filtering in TreeWalker, which caused it not to work according to spec (#1737)
* fix: [#1605] Fixes issue when using filtering in TreeWalker, which caused it not to work according to spec * fix: [#1605] Fixes issue when using filtering in TreeWalker, which caused it not to work according to spec
1 parent c929243 commit 6c5e0f9

File tree

2 files changed

+316
-101
lines changed

2 files changed

+316
-101
lines changed

packages/happy-dom/src/tree-walker/TreeWalker.ts

+195-98
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,21 @@ import NodeFilterMask from './NodeFilterMask.js';
55
import DOMException from '../exception/DOMException.js';
66
import NodeFilter from './NodeFilter.js';
77

8+
enum TraverseChildrenTypeEnum {
9+
first = 'first',
10+
last = 'last'
11+
}
12+
13+
enum TraverseSiblingsTypeEnum {
14+
next = 'next',
15+
previous = 'previous'
16+
}
17+
818
/**
919
* The TreeWalker object represents the nodes of a document subtree and a position within them.
20+
*
21+
* @see https://developer.mozilla.org/en-US/docs/Web/API/TreeWalker
22+
* @see https://dom.spec.whatwg.org/#interface-treewalker
1023
*/
1124
export default class TreeWalker {
1225
public root: Node = null;
@@ -32,52 +45,20 @@ export default class TreeWalker {
3245
this.currentNode = root;
3346
}
3447

35-
/**
36-
* Moves the current Node to the next visible node in the document order.
37-
*
38-
* @returns Current node.
39-
*/
40-
public nextNode(): Node {
41-
if (!this.firstChild()) {
42-
while (!this.nextSibling() && this.parentNode()) {}
43-
this.currentNode = this.currentNode === this.root ? null : this.currentNode || null;
44-
}
45-
return this.currentNode;
46-
}
47-
48-
/**
49-
* Moves the current Node to the previous visible node in the document order, and returns the found node. It also moves the current node to this one. If no such node exists, or if it is before that the root node defined at the object construction, returns null and the current node is not changed.
50-
*
51-
* @returns Current node.
52-
*/
53-
public previousNode(): Node {
54-
while (!this.previousSibling() && this.parentNode()) {}
55-
this.currentNode = this.currentNode === this.root ? null : this.currentNode || null;
56-
return this.currentNode;
57-
}
58-
5948
/**
6049
* Moves the current Node to the first visible ancestor node in the document order, and returns the found node. It also moves the current node to this one. If no such node exists, or if it is before that the root node defined at the object construction, returns null and the current node is not changed.
6150
*
6251
* @returns Current node.
6352
*/
6453
public parentNode(): Node {
65-
if (
66-
this.currentNode !== this.root &&
67-
this.currentNode &&
68-
this.currentNode[PropertySymbol.parentNode]
69-
) {
70-
this.currentNode = this.currentNode[PropertySymbol.parentNode];
71-
72-
if (this[PropertySymbol.filterNode](this.currentNode) === NodeFilter.FILTER_ACCEPT) {
54+
let node = this.currentNode;
55+
while (node !== null && node !== this.root) {
56+
node = node.parentNode;
57+
if (node !== null && this[PropertySymbol.filterNode](node) === NodeFilter.FILTER_ACCEPT) {
58+
this.currentNode = node;
7359
return this.currentNode;
7460
}
75-
76-
this.parentNode();
7761
}
78-
79-
this.currentNode = null;
80-
8162
return null;
8263
}
8364

@@ -87,19 +68,7 @@ export default class TreeWalker {
8768
* @returns Current node.
8869
*/
8970
public firstChild(): Node {
90-
const childNodes = this.currentNode ? (<Node>this.currentNode)[PropertySymbol.nodeArray] : [];
91-
92-
if (childNodes.length > 0) {
93-
this.currentNode = childNodes[0];
94-
95-
if (this[PropertySymbol.filterNode](this.currentNode) === NodeFilter.FILTER_ACCEPT) {
96-
return this.currentNode;
97-
}
98-
99-
return this.nextSibling();
100-
}
101-
102-
return null;
71+
return this.#traverseChildren(TraverseChildrenTypeEnum.first);
10372
}
10473

10574
/**
@@ -108,19 +77,16 @@ export default class TreeWalker {
10877
* @returns Current node.
10978
*/
11079
public lastChild(): Node {
111-
const childNodes = this.currentNode ? (<Node>this.currentNode)[PropertySymbol.nodeArray] : [];
112-
113-
if (childNodes.length > 0) {
114-
this.currentNode = childNodes[childNodes.length - 1];
115-
116-
if (this[PropertySymbol.filterNode](this.currentNode) === NodeFilter.FILTER_ACCEPT) {
117-
return this.currentNode;
118-
}
119-
120-
return this.previousSibling();
121-
}
80+
return this.#traverseChildren(TraverseChildrenTypeEnum.last);
81+
}
12282

123-
return null;
83+
/**
84+
* Moves the current Node to its next sibling, if any, and returns the found sibling. If there is no such node, null is returned and the current node is not changed.
85+
*
86+
* @returns Current node.
87+
*/
88+
public nextSibling(): Node {
89+
return this.#traverseSiblings(TraverseSiblingsTypeEnum.next);
12490
}
12591

12692
/**
@@ -129,58 +95,98 @@ export default class TreeWalker {
12995
* @returns Current node.
13096
*/
13197
public previousSibling(): Node {
132-
if (
133-
this.currentNode !== this.root &&
134-
this.currentNode &&
135-
this.currentNode[PropertySymbol.parentNode]
136-
) {
137-
const siblings = (<Node>this.currentNode[PropertySymbol.parentNode])[
138-
PropertySymbol.nodeArray
139-
];
140-
const index = siblings.indexOf(this.currentNode);
141-
142-
if (index > 0) {
143-
this.currentNode = siblings[index - 1];
144-
145-
if (this[PropertySymbol.filterNode](this.currentNode) === NodeFilter.FILTER_ACCEPT) {
146-
return this.currentNode;
98+
return this.#traverseSiblings(TraverseSiblingsTypeEnum.previous);
99+
}
100+
101+
/**
102+
* Moves the current Node to the previous visible node in the document order, and returns the found node. It also moves the current node to this one. If no such node exists, or if it is before that the root node defined at the object construction, returns null and the current node is not changed.
103+
*
104+
* @returns Current node.
105+
*/
106+
public previousNode(): Node {
107+
let node = this.currentNode;
108+
109+
while (node !== this.root) {
110+
let sibling = node.previousSibling;
111+
112+
while (sibling !== null) {
113+
let node = sibling;
114+
let result = this[PropertySymbol.filterNode](node);
115+
116+
while (result !== NodeFilter.FILTER_REJECT && node[PropertySymbol.nodeArray].length) {
117+
node = node.lastChild;
118+
result = this[PropertySymbol.filterNode](node);
147119
}
148120

149-
return this.previousSibling();
121+
if (result === NodeFilter.FILTER_ACCEPT) {
122+
this.currentNode = node;
123+
return node;
124+
}
125+
126+
sibling = node.previousSibling;
127+
}
128+
129+
if (node === this.root || node.parentNode === null) {
130+
return null;
131+
}
132+
133+
node = node.parentNode;
134+
135+
if (this[PropertySymbol.filterNode](node) === NodeFilter.FILTER_ACCEPT) {
136+
this.currentNode = node;
137+
return node;
150138
}
151139
}
152140

153141
return null;
154142
}
155143

156144
/**
157-
* Moves the current Node to its next sibling, if any, and returns the found sibling. If there is no such node, null is returned and the current node is not changed.
145+
* Moves the current Node to the next visible node in the document order.
158146
*
159147
* @returns Current node.
160148
*/
161-
public nextSibling(): Node {
162-
if (
163-
this.currentNode !== this.root &&
164-
this.currentNode &&
165-
this.currentNode[PropertySymbol.parentNode]
166-
) {
167-
const siblings = (<Node>this.currentNode[PropertySymbol.parentNode])[
168-
PropertySymbol.nodeArray
169-
];
170-
const index = siblings.indexOf(this.currentNode);
171-
172-
if (index + 1 < siblings.length) {
173-
this.currentNode = siblings[index + 1];
174-
175-
if (this[PropertySymbol.filterNode](this.currentNode) === NodeFilter.FILTER_ACCEPT) {
176-
return this.currentNode;
149+
public nextNode(): Node | null {
150+
let node = this.currentNode;
151+
let result = NodeFilter.FILTER_ACCEPT;
152+
153+
while (true) {
154+
while (result !== NodeFilter.FILTER_REJECT && node[PropertySymbol.nodeArray].length) {
155+
node = node.firstChild;
156+
result = this[PropertySymbol.filterNode](node);
157+
158+
if (result === NodeFilter.FILTER_ACCEPT) {
159+
this.currentNode = node;
160+
return node;
177161
}
162+
}
178163

179-
return this.nextSibling();
164+
while (node !== null) {
165+
if (node === this.root) {
166+
return null;
167+
}
168+
169+
const sibling = node.nextSibling;
170+
171+
if (sibling !== null) {
172+
node = sibling;
173+
break;
174+
}
175+
176+
node = node.parentNode;
180177
}
181-
}
182178

183-
return null;
179+
if (node === null) {
180+
return null;
181+
}
182+
183+
result = this[PropertySymbol.filterNode](node);
184+
185+
if (result === NodeFilter.FILTER_ACCEPT) {
186+
this.currentNode = node;
187+
return node;
188+
}
189+
}
184190
}
185191

186192
/**
@@ -207,4 +213,95 @@ export default class TreeWalker {
207213

208214
return NodeFilter.FILTER_ACCEPT;
209215
}
216+
217+
/**
218+
* Traverses children.
219+
*
220+
* @param type Type.
221+
* @returns Node.
222+
*/
223+
#traverseChildren(type: TraverseChildrenTypeEnum): Node | null {
224+
let node: Node = this.currentNode;
225+
node = type === TraverseChildrenTypeEnum.first ? node.firstChild : node.lastChild;
226+
227+
while (node !== null) {
228+
const result = this[PropertySymbol.filterNode](node);
229+
230+
if (result === NodeFilter.FILTER_ACCEPT) {
231+
this.currentNode = node;
232+
return node;
233+
}
234+
235+
if (result === NodeFilter.FILTER_SKIP) {
236+
const child = type === TraverseChildrenTypeEnum.first ? node.firstChild : node.lastChild;
237+
238+
if (child !== null) {
239+
node = child;
240+
continue;
241+
}
242+
}
243+
244+
while (node !== null) {
245+
const sibling =
246+
type === TraverseChildrenTypeEnum.first ? node.nextSibling : node.previousSibling;
247+
if (sibling !== null) {
248+
node = sibling;
249+
break;
250+
}
251+
const parent = node.parentNode;
252+
if (parent === null || parent === this.root || parent === this.currentNode) {
253+
return null;
254+
}
255+
node = parent;
256+
}
257+
}
258+
259+
return null;
260+
}
261+
262+
/**
263+
* Traverses siblings.
264+
*
265+
* @param type Type.
266+
* @returns Node.
267+
*/
268+
#traverseSiblings(type: TraverseSiblingsTypeEnum): Node | null {
269+
let node: Node = this.currentNode;
270+
271+
if (node === this.root) {
272+
return null;
273+
}
274+
275+
while (true) {
276+
let sibling =
277+
type === TraverseSiblingsTypeEnum.next ? node.nextSibling : node.previousSibling;
278+
279+
while (sibling !== null) {
280+
const node = sibling;
281+
const result = this[PropertySymbol.filterNode](node);
282+
283+
if (result === NodeFilter.FILTER_ACCEPT) {
284+
this.currentNode = node;
285+
return node;
286+
}
287+
288+
sibling = type === TraverseSiblingsTypeEnum.next ? node.firstChild : node.lastChild;
289+
290+
if (result === NodeFilter.FILTER_REJECT || sibling === null) {
291+
sibling =
292+
type === TraverseSiblingsTypeEnum.next ? node.nextSibling : node.previousSibling;
293+
}
294+
}
295+
296+
node = node.parentNode;
297+
298+
if (node === null || node === this.root) {
299+
return null;
300+
}
301+
302+
if (this[PropertySymbol.filterNode](node) === NodeFilter.FILTER_ACCEPT) {
303+
return null;
304+
}
305+
}
306+
}
210307
}

0 commit comments

Comments
 (0)