Skip to content

Commit 4267e3d

Browse files
committed
feat: InspectorDocument and expose .document
1 parent 7d102b3 commit 4267e3d

2 files changed

Lines changed: 151 additions & 55 deletions

File tree

src/Inspector.ts

Lines changed: 35 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { parseGetMatchedStylesForNodeResponse } from "@devtoolcss/parser";
22
import { CDPNodeType } from "./constants.js";
3-
import { InspectorElement, InspectorNode } from "./InspectorDOM.js";
3+
import {
4+
InspectorNode,
5+
InspectorElement,
6+
InspectorDocument,
7+
} from "./InspectorDOM.js";
48
import EventEmitter from "./EventEmitter.js";
59
import type { ParseOptions, ParsedCSS } from "@devtoolcss/parser";
610
import {
@@ -40,24 +44,20 @@ export type ComputedStyleOptions = {
4044
raw?: boolean;
4145
};
4246

43-
function nsResolver(prefix) {
44-
const ns = {
45-
svg: "http://www.w3.org/2000/svg",
46-
xhtml: "http://www.w3.org/1999/xhtml",
47-
};
48-
return ns[prefix] || null;
49-
}
50-
5147
// we need EventEmitter for warning events, which can happen
5248
// anytime event fired
5349
export class Inspector extends EventEmitter {
5450
readonly documentImpl: DOMImplementation;
5551
protected eventTimeout: number;
5652

57-
protected document: Document;
53+
protected RootDocument: Document;
5854

5955
protected selectedNode: InspectorNode | undefined;
6056

57+
get document(): InspectorDocument {
58+
return InspectorDocument.get(this.RootDocument);
59+
}
60+
6161
/**
6262
* @experimental
6363
*/
@@ -68,48 +68,22 @@ export class Inspector extends EventEmitter {
6868
return undefined;
6969
}
7070

71+
/* legacy forwardings */
72+
7173
querySelector(selector: string): InspectorElement | null {
72-
const el = this.document.querySelector(selector);
73-
return el ? InspectorElement.get(el) : null;
74+
return this.document.querySelector(selector);
7475
}
7576

7677
querySelectorAll(selector: string): InspectorElement[] {
77-
return Array.from(this.document.querySelectorAll(selector)).map(
78-
InspectorElement.get,
79-
);
78+
return this.document.querySelectorAll(selector);
8079
}
8180

8281
queryXPath(xpath: string): InspectorNode | null {
83-
const result = this.document.evaluate(
84-
xpath,
85-
this.document,
86-
nsResolver,
87-
9, //XPathResult.FIRST_ORDERED_NODE_TYPE
88-
null,
89-
);
90-
const node = result.singleNodeValue;
91-
return node ? InspectorNode.get(node) : null;
82+
return this.document.queryXPath(xpath);
9283
}
9384

9485
queryXPathAll(xpath: string): InspectorNode[] {
95-
const result = this.document.evaluate(
96-
xpath,
97-
this.document,
98-
nsResolver,
99-
7, //XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
100-
null,
101-
);
102-
const nodes: InspectorNode[] = [];
103-
for (let i = 0; i < result.snapshotLength; i++) {
104-
const node = result.snapshotItem(i);
105-
if (node) {
106-
const inspectorNode = InspectorNode.get(node);
107-
if (inspectorNode) {
108-
nodes.push(inspectorNode);
109-
}
110-
}
111-
}
112-
return nodes;
86+
return this.document.queryXPathAll(xpath);
11387
}
11488

11589
protected idToNode = new Map<number, InspectorNode>();
@@ -256,7 +230,7 @@ export class Inspector extends EventEmitter {
256230
switch (cdpNode.nodeType) {
257231
case CDPNodeType.ELEMENT_NODE:
258232
// iframe is safe because no children (not setting pierce)
259-
docNode = this.document.createElement(cdpNode.localName);
233+
docNode = this.RootDocument.createElement(cdpNode.localName);
260234

261235
if (Array.isArray(cdpNode.attributes)) {
262236
for (let i = 0; i < cdpNode.attributes.length; i += 2) {
@@ -269,17 +243,17 @@ export class Inspector extends EventEmitter {
269243
break;
270244

271245
case CDPNodeType.TEXT_NODE:
272-
docNode = this.document.createTextNode(cdpNode.nodeValue || "");
246+
docNode = this.RootDocument.createTextNode(cdpNode.nodeValue || "");
273247
break;
274248

275249
case CDPNodeType.COMMENT_NODE:
276-
docNode = this.document.createComment(cdpNode.nodeValue || "");
250+
docNode = this.RootDocument.createComment(cdpNode.nodeValue || "");
277251
break;
278252

279253
case CDPNodeType.DOCUMENT_NODE:
280-
this.document = this.documentImpl.createHTMLDocument();
281-
this.document.removeChild(this.document.documentElement);
282-
docNode = this.document;
254+
this.RootDocument = this.documentImpl.createHTMLDocument();
255+
this.RootDocument.removeChild(this.RootDocument.documentElement);
256+
docNode = this.RootDocument;
283257
break;
284258

285259
default:
@@ -294,12 +268,19 @@ export class Inspector extends EventEmitter {
294268
}
295269
}
296270

297-
// The only place to new InspectorNode/Element
298-
const node =
299-
docNode.nodeType === CDPNodeType.ELEMENT_NODE ||
300-
docNode.nodeType === CDPNodeType.DOCUMENT_NODE
301-
? new InspectorElement(docNode as Element, cdpNode, this)
302-
: new InspectorNode(docNode, cdpNode, this);
271+
// The only place to new InspectorNode/Element/Document
272+
let node: InspectorNode;
273+
switch (cdpNode.nodeType) {
274+
case CDPNodeType.DOCUMENT_NODE:
275+
node = new InspectorDocument(docNode as Document, cdpNode, this);
276+
(node as InspectorDocument).remove;
277+
break;
278+
case CDPNodeType.ELEMENT_NODE:
279+
node = new InspectorElement(docNode as Element, cdpNode, this);
280+
break;
281+
default:
282+
node = new InspectorNode(docNode, cdpNode, this);
283+
}
303284
this.setMap(cdpNode.nodeId, node);
304285

305286
return docNode;

src/InspectorDOM.ts

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export class InspectorElement extends InspectorNode {
163163
static get(element: Element): InspectorElement {
164164
const cached = InspectorElement.nodeMap.get(element);
165165
if (!cached) {
166-
throw Error("InspectorNode not found");
166+
throw Error("InspectorElement not found");
167167
}
168168
return cached as InspectorElement;
169169
}
@@ -341,3 +341,118 @@ export class InspectorElement extends InspectorNode {
341341
return await this.inspector.getComputedStyle(this, options);
342342
}
343343
}
344+
345+
export class InspectorDocument extends InspectorNode {
346+
private get document(): Document {
347+
return this._docNode as Document;
348+
}
349+
350+
static get(document: Document): InspectorDocument {
351+
const cached = InspectorDocument.nodeMap.get(document);
352+
if (!cached) {
353+
throw Error("InspectorDocument not found");
354+
}
355+
return cached as InspectorDocument;
356+
}
357+
358+
constructor(document: Document, cdpNode: CDPNode, inspector: Inspector) {
359+
super(document, cdpNode, inspector);
360+
}
361+
362+
protected nsResolver(prefix: string) {
363+
const ns = {
364+
svg: "http://www.w3.org/2000/svg",
365+
xhtml: "http://www.w3.org/1999/xhtml",
366+
};
367+
return ns[prefix] || null;
368+
}
369+
370+
get body(): InspectorElement | null {
371+
return this.document.body ? InspectorElement.get(this.document.body) : null;
372+
}
373+
374+
get head(): InspectorElement | null {
375+
return this.document.head ? InspectorElement.get(this.document.head) : null;
376+
}
377+
378+
get documentElement(): InspectorElement | null {
379+
return this.document.documentElement
380+
? InspectorElement.get(this.document.documentElement)
381+
: null;
382+
}
383+
384+
querySelector(selector: string): InspectorElement | null {
385+
const el = this.document.querySelector(selector);
386+
return el ? InspectorElement.get(el) : null;
387+
}
388+
389+
querySelectorAll(selector: string): InspectorElement[] {
390+
return Array.from(this.document.querySelectorAll(selector)).map(
391+
InspectorElement.get,
392+
);
393+
}
394+
395+
getElementById(id: string): InspectorElement | null {
396+
const el = this.document.getElementById(id);
397+
return el ? InspectorElement.get(el) : null;
398+
}
399+
400+
getElementsByClassName(className: string): InspectorElement[] {
401+
return Array.from(this.document.getElementsByClassName(className)).map(
402+
InspectorElement.get,
403+
);
404+
}
405+
406+
getElementsByTagName(tagName: string): InspectorElement[] {
407+
return Array.from(this.document.getElementsByTagName(tagName)).map(
408+
InspectorElement.get,
409+
);
410+
}
411+
412+
/**
413+
* @experimental
414+
*/
415+
queryXPath(xpath: string): InspectorNode | null {
416+
const result = this.document.evaluate(
417+
xpath,
418+
this.document,
419+
this.nsResolver,
420+
9, //XPathResult.FIRST_ORDERED_NODE_TYPE
421+
null,
422+
);
423+
const node = result.singleNodeValue;
424+
return node ? InspectorNode.get(node) : null;
425+
}
426+
427+
/**
428+
* @experimental
429+
*/
430+
queryXPathAll(xpath: string): InspectorNode[] {
431+
const result = this.document.evaluate(
432+
xpath,
433+
this.document,
434+
this.nsResolver,
435+
7, //XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
436+
null,
437+
);
438+
const nodes: InspectorNode[] = [];
439+
for (let i = 0; i < result.snapshotLength; i++) {
440+
const node = result.snapshotItem(i);
441+
if (node) {
442+
const inspectorNode = InspectorNode.get(node);
443+
if (inspectorNode) {
444+
nodes.push(inspectorNode);
445+
}
446+
}
447+
}
448+
return nodes;
449+
}
450+
451+
/**
452+
* @deprecated Use of remove() on the document node is not supported.
453+
* To fix, have to distinguish Node and ChildNode, maybe in future major release.
454+
*/
455+
async remove(): Promise<void> {
456+
throw new Error("Cannot remove the document node");
457+
}
458+
}

0 commit comments

Comments
 (0)