From 3d9a227fb964d4986a73c4a1610a0dc977a93dad Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 29 Jun 2022 17:59:33 +0200 Subject: [PATCH] #450@trivial: Continue on Range implementation. --- .../dom-implementation/DOMImplementation.ts | 2 + .../happy-dom/src/dom-parser/DOMParser.ts | 3 + .../happy-dom/src/nodes/document/Document.ts | 50 +++++----- packages/happy-dom/src/window/Window.ts | 99 +++++++++++-------- .../DOMImplementation.test.ts | 3 +- packages/happy-dom/test/window/Window.test.ts | 10 +- 6 files changed, 92 insertions(+), 75 deletions(-) diff --git a/packages/happy-dom/src/dom-implementation/DOMImplementation.ts b/packages/happy-dom/src/dom-implementation/DOMImplementation.ts index 8134e1602..51e943e22 100644 --- a/packages/happy-dom/src/dom-implementation/DOMImplementation.ts +++ b/packages/happy-dom/src/dom-implementation/DOMImplementation.ts @@ -24,6 +24,8 @@ export default class DOMImplementation { public createDocument(): IDocument { const documentClass = this._ownerDocument.constructor; // @ts-ignore + documentClass._defaultView = this._ownerDocument.defaultView; + // @ts-ignore return new documentClass(); } diff --git a/packages/happy-dom/src/dom-parser/DOMParser.ts b/packages/happy-dom/src/dom-parser/DOMParser.ts index 807f0211b..8656835ca 100644 --- a/packages/happy-dom/src/dom-parser/DOMParser.ts +++ b/packages/happy-dom/src/dom-parser/DOMParser.ts @@ -96,12 +96,15 @@ export default class DOMParser { private _createDocument(mimeType: string): IDocument { switch (mimeType) { case 'text/html': + HTMLDocument._defaultView = this._ownerDocument.defaultView; return new HTMLDocument(); case 'image/svg+xml': + SVGDocument._defaultView = this._ownerDocument.defaultView; return new SVGDocument(); case 'text/xml': case 'application/xml': case 'application/xhtml+xml': + XMLDocument._defaultView = this._ownerDocument.defaultView; return new XMLDocument(); default: throw new DOMException(`Unknown mime type "${mimeType}".`); diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts index e7f5b83d2..43235f9ac 100644 --- a/packages/happy-dom/src/nodes/document/Document.ts +++ b/packages/happy-dom/src/nodes/document/Document.ts @@ -45,6 +45,7 @@ import IHTMLBaseElement from '../html-base-element/IHTMLBaseElement'; * Document. */ export default class Document extends Node implements IDocument { + public static _defaultView: IWindow = null; public onreadystatechange: (event: Event) => void = null; public nodeType = Node.DOCUMENT_NODE; public adoptedStyleSheets: CSSStyleSheet[] = []; @@ -52,22 +53,27 @@ export default class Document extends Node implements IDocument { public readonly children: IHTMLCollection = HTMLCollectionFactory.create(); public readonly readyState = DocumentReadyStateEnum.interactive; public readonly isConnected: boolean = true; - public _readyStateManager: DocumentReadyStateManager = null; + public readonly defaultView: IWindow; + public readonly _readyStateManager: DocumentReadyStateManager; public _activeElement: IHTMLElement = null; protected _isFirstWrite = true; protected _isFirstWriteAfterOpen = false; - private _defaultView: IWindow = null; private _cookie = ''; private _selection: Selection = null; /** * Creates an instance of Document. + * + * @param defaultView Default view. */ constructor() { super(); + this.defaultView = (this.constructor)._defaultView; this.implementation = new DOMImplementation(this); + this._readyStateManager = new DocumentReadyStateManager(this.defaultView); + const doctype = this.implementation.createDocumentType('html', '', ''); const documentElement = this.createElement('html'); const bodyElement = this.createElement('body'); @@ -100,29 +106,6 @@ export default class Document extends Node implements IDocument { return charset ? charset : 'UTF-8'; } - /** - * Returns default view. - * - * @returns Default view. - */ - public get defaultView(): IWindow { - return this._defaultView; - } - - /** - * Sets a default view. - * - * @param defaultView Default view. - */ - public set defaultView(defaultView: IWindow) { - this._defaultView = defaultView; - this._readyStateManager = new DocumentReadyStateManager(defaultView); - this._readyStateManager.whenComplete().then(() => { - (this.readyState) = DocumentReadyStateEnum.complete; - this.dispatchEvent(new Event('readystatechange')); - }); - } - /** * Last element child. * @@ -272,7 +255,7 @@ export default class Document extends Node implements IDocument { * @returns Location. */ public get location(): Location { - return this._defaultView.location; + return this.defaultView.location; } /** @@ -419,6 +402,8 @@ export default class Document extends Node implements IDocument { * @returns Cloned node. */ public cloneNode(deep = false): IDocument { + (this.constructor)._defaultView = this.defaultView; + const clone = super.cloneNode(deep); if (deep) { @@ -429,8 +414,6 @@ export default class Document extends Node implements IDocument { } } - clone.defaultView = this.defaultView; - return clone; } @@ -832,4 +815,15 @@ export default class Document extends Node implements IDocument { return returnValue; } + + /** + * Triggered by window when it is ready. + */ + public _onWindowReady(): void { + this._readyStateManager.whenComplete().then(() => { + (this.readyState) = DocumentReadyStateEnum.complete; + this.dispatchEvent(new Event('readystatechange')); + this.dispatchEvent(new Event('load', { bubbles: true })); + }); + } } diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index 5411c93ed..369dcf1cc 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -96,6 +96,11 @@ import { Buffer } from 'buffer'; import Base64 from '../base64/Base64'; import IDocument from '../nodes/document/IDocument'; +const ORIGINAL_SET_TIMEOUT = setTimeout; +const ORIGINAL_CLEAR_TIMEOUT = clearTimeout; +const ORIGINAL_SET_INTERVAL = setInterval; +const ORIGINAL_CLEAR_INTERVAL = clearInterval; + /** * Browser window. * @@ -287,10 +292,10 @@ export default class Window extends EventTarget implements IWindow { public Function; // Private properties - private _setTimeout = setTimeout; - private _clearTimeout = clearTimeout; - private _setInterval = setInterval; - private _clearInterval = clearInterval; + private _setTimeout; + private _clearTimeout; + private _setInterval; + private _clearInterval; /** * Constructor. @@ -298,41 +303,10 @@ export default class Window extends EventTarget implements IWindow { constructor() { super(); - const document = new HTMLDocument(); - - this.document = document; - this.document.defaultView = this; - this.document._readyStateManager.whenComplete().then(() => { - this.dispatchEvent(new Event('load')); - }); - - // We need to set the correct owner document when the class is constructed. - // To achieve this we will extend the original implementation with a class that sets the owner document. - - ResponseImplementation._ownerDocument = document; - RequestImplementation._ownerDocument = document; - ImageImplementation._ownerDocument = document; - FileReaderImplementation._ownerDocument = document; - DOMParserImplementation._ownerDocument = document; - RangeImplementation._ownerDocument = document; - this.Response = class Response extends ResponseImplementation { - public static _ownerDocument: IDocument = document; - }; - this.Request = class Request extends RequestImplementation { - public static _ownerDocument: IDocument = document; - }; - this.Image = class Image extends ImageImplementation { - public static _ownerDocument: IDocument = document; - }; - this.FileReader = class FileReader extends FileReaderImplementation { - public static _ownerDocument: IDocument = document; - }; - this.DOMParser = class DOMParser extends DOMParserImplementation { - public static _ownerDocument: IDocument = document; - }; - this.Range = class Range extends RangeImplementation { - public static _ownerDocument: IDocument = document; - }; + this._setTimeout = ORIGINAL_SET_TIMEOUT; + this._clearTimeout = ORIGINAL_CLEAR_TIMEOUT; + this._setInterval = ORIGINAL_SET_INTERVAL; + this._clearInterval = ORIGINAL_CLEAR_INTERVAL; // Non-implemented event types for (const eventType of NonImplementedEventTypes) { @@ -362,7 +336,54 @@ export default class Window extends EventTarget implements IWindow { } } + HTMLDocument._defaultView = this; + + const document = new HTMLDocument(); + + this.document = document; + + // We need to set the correct owner document when the class is constructed. + // To achieve this we will extend the original implementation with a class that sets the owner document. + + ResponseImplementation._ownerDocument = document; + RequestImplementation._ownerDocument = document; + ImageImplementation._ownerDocument = document; + FileReaderImplementation._ownerDocument = document; + DOMParserImplementation._ownerDocument = document; + RangeImplementation._ownerDocument = document; + + /* eslint-disable jsdoc/require-jsdoc */ + class Response extends ResponseImplementation { + public static _ownerDocument: IDocument = document; + } + class Request extends RequestImplementation { + public static _ownerDocument: IDocument = document; + } + class Image extends ImageImplementation { + public static _ownerDocument: IDocument = document; + } + class FileReader extends FileReaderImplementation { + public static _ownerDocument: IDocument = document; + } + class DOMParser extends DOMParserImplementation { + public static _ownerDocument: IDocument = document; + } + class Range extends RangeImplementation { + public static _ownerDocument: IDocument = document; + } + + /* eslint-enable jsdoc/require-jsdoc */ + + this.Response = Response; + this.Request = Request; + this.Image = Image; + this.FileReader = FileReader; + this.DOMParser = DOMParser; + this.Range = Range; + this._setupVMContext(); + + this.document._onWindowReady(); } /** diff --git a/packages/happy-dom/test/dom-implementation/DOMImplementation.test.ts b/packages/happy-dom/test/dom-implementation/DOMImplementation.test.ts index cc2fcecfc..2e560f822 100644 --- a/packages/happy-dom/test/dom-implementation/DOMImplementation.test.ts +++ b/packages/happy-dom/test/dom-implementation/DOMImplementation.test.ts @@ -7,8 +7,7 @@ describe('DOMImplementation', () => { beforeEach(() => { ownerDocument = new Document(); - domImplementation = new DOMImplementation(); - domImplementation._ownerDocument = ownerDocument; + domImplementation = new DOMImplementation(ownerDocument); }); describe('createHTMLDocument()', () => { diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts index 3f5ce31f1..4713efa4d 100644 --- a/packages/happy-dom/test/window/Window.test.ts +++ b/packages/happy-dom/test/window/Window.test.ts @@ -8,8 +8,6 @@ import Window from '../../src/window/Window'; import IWindow from '../../src/window/IWindow'; import Navigator from '../../src/navigator/Navigator'; import Headers from '../../src/fetch/Headers'; -import Response from '../../src/fetch/Response'; -import Request from '../../src/fetch/Request'; import Selection from '../../src/selection/Selection'; import DOMException from '../../src/exception/DOMException'; import DOMExceptionNameEnum from '../../src/exception/DOMExceptionNameEnum'; @@ -86,7 +84,7 @@ describe('Window', () => { describe('get Response()', () => { it('Returns Response class.', () => { expect(window.Response['_ownerDocument']).toBe(document); - expect(window.Response).toBe(Response); + expect(window.Response.name).toBe('Response'); }); for (const method of ['arrayBuffer', 'blob', 'buffer', 'json', 'text', 'textConverted']) { @@ -101,7 +99,7 @@ describe('Window', () => { describe('get Request()', () => { it('Returns Request class.', () => { expect(window.Request['_ownerDocument']).toBe(document); - expect(window.Request).toBe(Request); + expect(window.Request.name).toBe('Request'); }); for (const method of ['arrayBuffer', 'blob', 'buffer', 'json', 'text', 'textConverted']) { @@ -440,7 +438,7 @@ describe('Window', () => { }); setTimeout(() => { - expect(loadEvent.target).toBe(window); + expect(loadEvent.target).toBe(document); done(); }, 1); }); @@ -490,7 +488,7 @@ describe('Window', () => { expect(resourceFetchCSSURL).toBe(cssURL); expect(resourceFetchJSDocument).toBe(document); expect(resourceFetchJSURL).toBe(jsURL); - expect(loadEvent.target).toBe(window); + expect(loadEvent.target).toBe(document); expect(document.styleSheets.length).toBe(1); expect(document.styleSheets[0].cssRules[0].cssText).toBe(cssResponse);