Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TreeWalker #37

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ node_modules
/target
*~
/.vim
/.vscode
12 changes: 9 additions & 3 deletions src/dom/document.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { setLock, getLock } from "../constructor-lock.ts";
import { Node, NodeType, Text, Comment } from "./node.ts";
import { NodeList, nodeListMutatorSym } from "./node-list.ts";
import { Filter } from "./node-filter.ts";
import { Element } from "./element.ts";
import { TreeWalker } from "./treewalker.ts"
import { DOM as NWAPI } from "./nwsapi-types.ts";

export class DOMImplementation {
Expand Down Expand Up @@ -165,6 +167,10 @@ export class Document extends Node {
return child;
}

createComment(data?: string): Comment {
return new Comment(data);
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this moved?

createElement(tagName: string, options?: ElementCreationOptions): Element {
tagName = tagName.toUpperCase();

Expand All @@ -191,8 +197,8 @@ export class Document extends Node {
return new Text(data);
}

createComment(data?: string): Comment {
return new Comment(data);
createTreeWalker(root: Node, whatToShow?: number, filter?: Filter): TreeWalker {
return new TreeWalker(root, whatToShow, filter);
}

querySelector(selectors: string): Element | null {
Expand Down Expand Up @@ -289,7 +295,7 @@ export class Document extends Node {

export class HTMLDocument extends Document {
constructor() {
let lock = getLock();
const lock = getLock();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this

super();

if (lock) {
Expand Down
26 changes: 26 additions & 0 deletions src/dom/node-filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Node } from "./node.ts";

export enum NodeFilter {
FILTER_ACCEPT = 1,
FILTER_REJECT,
FILTER_SKIP,

// staticants for whatToShow
SHOW_ALL = -1,
SHOW_ELEMENT = 0x1,
SHOW_ATTRIBUTE = 0x2,
SHOW_TEXT = 0x4,
SHOW_CDATA_SECTION = 0x8,
SHOW_ENTITY_REFERENCE = 0x10, // legacy
SHOW_ENTITY = 0x20, // legacy
SHOW_PROCESSING_INSTRUCTION = 0x40,
SHOW_COMMENT = 0x80,
SHOW_DOCUMENT = 0x100,
SHOW_DOCUMENT_TYPE = 0x200,
SHOW_DOCUMENT_FRAGMENT = 0x400,
SHOW_NOTATION = 0x800, // legacy
}

export interface Filter {
acceptNode(node: Node): number;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to treewalker.ts

10 changes: 5 additions & 5 deletions src/dom/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class Node extends EventTarget {
this._setOwnerDocument(newParent.#ownerDocument);

// Add parent chain to ancestors
let parent: Node | null = newParent;
const parent: Node | null = newParent;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this whole file, it is unrelated to the PR. If you want to submit this, please do it as a separate PR.

this._ancestors = new Set(newParent._ancestors);
this._ancestors.add(newParent);
} else {
Expand Down Expand Up @@ -287,7 +287,7 @@ export class Node extends EventTarget {
}

const index = parent._getChildNodesMutator().indexOf(this);
let next: Node | null = this.childNodes[index + 1] || null;
const next: Node | null = this.childNodes[index + 1] || null;

return next;
}
Expand All @@ -300,7 +300,7 @@ export class Node extends EventTarget {
}

const index = parent._getChildNodesMutator().indexOf(this);
let prev: Node | null = this.childNodes[index - 1] || null;
const prev: Node | null = this.childNodes[index - 1] || null;

return prev;
}
Expand Down Expand Up @@ -337,7 +337,7 @@ export class Text extends CharacterData {
constructor(
text: string = "",
) {
let oldLock = getLock();
const oldLock = getLock();
setLock(false);
super(
text,
Expand All @@ -359,7 +359,7 @@ export class Comment extends CharacterData {
constructor(
text: string = "",
) {
let oldLock = getLock();
const oldLock = getLock();
setLock(false);
super(
text,
Expand Down
41 changes: 41 additions & 0 deletions src/dom/traverser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NodeFilter, Filter } from "./node-filter.ts";
import { Node } from "./node.ts";

export abstract class Traverser {
public readonly root: Node;
public readonly whatToShow: number;
public readonly filter: Filter | undefined;
private activeFlag = false;

protected constructor(root: Node, whatToShow?: number, filter?: Filter) {
this.root = root;
this.whatToShow = whatToShow || -1;
this.filter = filter;
}

protected accept(node: Node): number {
if (this.activeFlag) {
throw new Error("DOMException: InvalidStateError");
}

if (!((1 << (node.nodeType-1)) & this.whatToShow)) {
return NodeFilter.FILTER_SKIP;
}

if (!this.filter) {
return NodeFilter.FILTER_ACCEPT;
}

this.activeFlag = true;
let result: number;
try {
result = this.filter.acceptNode(node);
} catch (error) {
this.activeFlag = false;
throw error;
}

this.activeFlag = false;
return result;
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to treewalker.ts

196 changes: 196 additions & 0 deletions src/dom/treewalker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { Node } from "./node.ts";
import { NodeFilter, Filter } from "./node-filter.ts";
import { Traverser } from "./traverser.ts";


export class TreeWalker extends Traverser {
public currentNode: Node;

constructor(root: Node, whatToShow?: number, filter?: Filter) {
super(root, whatToShow, filter);
this.currentNode = root;
}

parentNode(): Node | null {
let node: Node | null = this.currentNode;
while (node && node != this.root) {
node = node.parentNode;
if (node && this.accept(node) === NodeFilter.FILTER_ACCEPT) {
this.currentNode = node;
return node;
}
}

return null;
}

firstChild(): Node | null {
return this.traverseChildren(new Forwards());
}

lastChild(): Node | null {
return this.traverseChildren(new Backwards());
}

previousSibling(): Node | null {
return this.traverseSiblings(new Backwards());
}

nextSibling(): Node | null {
return this.traverseSiblings(new Forwards());
}

previousNode(): Node | null {
let node: Node = this.currentNode;
let tmp: Node | null;
while (node != this.root) {
while (tmp = node.previousSibling) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_REJECT) {
continue;
}
while (tmp = node.lastChild) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_REJECT) {
break;
}
}
if (result === NodeFilter.FILTER_ACCEPT) {
this.currentNode = node;
return node;
}
}
if (node === this.root) {
return null;
}
if (tmp = node.parentNode) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_ACCEPT) {
this.currentNode = node;
return node;
}
}
}

return null;
}

nextNode(): Node | null {
let tmp: Node | null;
let node: Node = this.currentNode;
loop: while (true) {
while (tmp = node.firstChild) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_ACCEPT) {
this.currentNode = node;
return node;
}
if (result === NodeFilter.FILTER_REJECT) {
break;
}
}
while (tmp = node.nextSibling) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_ACCEPT) {
this.currentNode = node;
return node;
}
if (result === NodeFilter.FILTER_SKIP) {
continue loop;
}
}
break;
}

return null;
}

private traverseChildren(strategy: Strategy): Node | null {
let node: Node;
let tmp: Node | null = strategy.first(this.currentNode);

while (tmp) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_ACCEPT) {
this.currentNode = node;
return node;
}
if (result === NodeFilter.FILTER_SKIP && (tmp = strategy.first(node))) {
continue;
}
do {
if (tmp = strategy.next(node)) {
break;
}
tmp = node.parentNode;
if (!tmp || tmp === this.root || tmp === this.currentNode) {
return null;
}
node = tmp;
} while (tmp);
}

return null;
}

private traverseSiblings(strategy: Strategy): Node | null {
let node: Node = this.currentNode;

if (node === this.root) {
return null;
}

let tmp: Node | null;
while (true) {
while (tmp = strategy.next(node)) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_ACCEPT) {
this.currentNode = node;
return node;
}
if (!(tmp = strategy.first(tmp)) || (result === NodeFilter.FILTER_REJECT)) {
tmp = strategy.next(node);
}
}
if ((tmp = node.parentNode) && (tmp != this.root)) {
node = tmp;
const result = this.accept(node);
if (result === NodeFilter.FILTER_ACCEPT) {
return null;
}
} else {
return null;
}
}
}
}

interface Strategy {
next(node: Node): Node | null;
first(node: Node): Node | null;
}

class Forwards implements Strategy {
next(node: Node): Node | null {
return node.nextSibling;
}
first(node: Node): Node | null {
return node.firstChild;
}
}

class Backwards implements Strategy {
next(node: Node): Node | null {
return node.previousSibling;
}
first(node: Node): Node | null {
return node.lastChild;
}
}
6 changes: 4 additions & 2 deletions test/units.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { dirname, join } from "https://deno.land/[email protected]/path/mod.ts";

const unitDir = join(dirname(new URL(import.meta.url).pathname), "units");
let unitDir = join(dirname(new URL(import.meta.url).pathname), "units");
if (Deno.build.os === "windows") {
unitDir = unitDir.slice(1);
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

const units = Array.from(Deno.readDirSync(unitDir))
.filter(file => file.isFile && file.name.endsWith(".ts"))
.map(file => join("units", file.name));
Expand All @@ -12,4 +15,3 @@ Deno.chdir(unitDir);
for (const file of units) {
await import("./" + file);
}

2 changes: 1 addition & 1 deletion test/wpt-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type Backend = "wasm" | "native";
export async function run(path: string, root: string, backend: Backend) {
const html = await Deno.readTextFile(path);
const doc = parser.parseFromString(html, "text/html")!;
let scripts = Array.from(doc.querySelectorAll("script")).map(scriptNode => {
const scripts = Array.from(doc.querySelectorAll("script")).map(scriptNode => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this file

const scriptElement = scriptNode as Element;
let script = scriptElement.getAttribute("src")!;
let scriptContents: string;
Expand Down
Loading