import { Buffer } from 'buffer';
import { webcrypto } from 'crypto';
import { TextEncoder, TextDecoder } from 'util';
import Stream from 'stream';
import { ReadableStream } from 'stream/web';
import { URLSearchParams } from 'url';
import VM from 'vm';
import * as PropertySymbol from '../PropertySymbol.js';
import Base64 from '../base64/Base64.js';
import BrowserErrorCaptureEnum from '../browser/enums/BrowserErrorCaptureEnum.js';
import type IConsole from '../console/IConsole.js';
import type IBrowserFrame from '../browser/types/IBrowserFrame.js';
import Clipboard from '../clipboard/Clipboard.js';
import ClipboardItem from '../clipboard/ClipboardItem.js';
import CSS from '../css/CSS.js';
import CSSRule from '../css/CSSRule.js';
import type CSSStyleSheet from '../css/CSSStyleSheet.js';
import CSSUnitValue from '../css/CSSUnitValue.js';
import CSSStyleDeclaration from '../css/declaration/CSSStyleDeclaration.js';
import CSSContainerRule from '../css/rules/CSSContainerRule.js';
import CSSFontFaceRule from '../css/rules/CSSFontFaceRule.js';
import CSSKeyframeRule from '../css/rules/CSSKeyframeRule.js';
import CSSKeyframesRule from '../css/rules/CSSKeyframesRule.js';
import CSSMediaRule from '../css/rules/CSSMediaRule.js';
import CSSStyleRule from '../css/rules/CSSStyleRule.js';
import CSSSupportsRule from '../css/rules/CSSSupportsRule.js';
import CustomElementRegistry from '../custom-element/CustomElementRegistry.js';
import type DOMParser from '../dom-parser/DOMParser.js';
import DataTransfer from '../event/DataTransfer.js';
import DataTransferItem from '../event/DataTransferItem.js';
import DataTransferItemList from '../event/DataTransferItemList.js';
import Event from '../event/Event.js';
import EventTarget from '../event/EventTarget.js';
import type MessagePort from '../event/MessagePort.js';
import Touch from '../event/Touch.js';
import UIEvent from '../event/UIEvent.js';
import AnimationEvent from '../event/events/AnimationEvent.js';
import ClipboardEvent from '../event/events/ClipboardEvent.js';
import CustomEvent from '../event/events/CustomEvent.js';
import ErrorEvent from '../event/events/ErrorEvent.js';
import FocusEvent from '../event/events/FocusEvent.js';
import HashChangeEvent from '../event/events/HashChangeEvent.js';
import InputEvent from '../event/events/InputEvent.js';
import KeyboardEvent from '../event/events/KeyboardEvent.js';
import MediaQueryListEvent from '../event/events/MediaQueryListEvent.js';
import MessageEvent from '../event/events/MessageEvent.js';
import MouseEvent from '../event/events/MouseEvent.js';
import PointerEvent from '../event/events/PointerEvent.js';
import ProgressEvent from '../event/events/ProgressEvent.js';
import StorageEvent from '../event/events/StorageEvent.js';
import SubmitEvent from '../event/events/SubmitEvent.js';
import TouchEvent from '../event/events/TouchEvent.js';
import WheelEvent from '../event/events/WheelEvent.js';
import type DOMException from '../exception/DOMException.js';
import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum.js';
import type AbortController from '../fetch/AbortController.js';
import type AbortSignal from '../fetch/AbortSignal.js';
import Fetch from '../fetch/Fetch.js';
import type Headers from '../fetch/Headers.js';
import type Request from '../fetch/Request.js';
import type Response from '../fetch/Response.js';
import type { TRequestInfo } from '../fetch/types/TRequestInfo.js';
import type IRequestInit from '../fetch/types/IRequestInit.js';
import Blob from '../file/Blob.js';
import File from '../file/File.js';
import type FileReader from '../file/FileReader.js';
import type FormData from '../form-data/FormData.js';
import History from '../history/History.js';
import IntersectionObserver from '../intersection-observer/IntersectionObserver.js';
import IntersectionObserverEntry from '../intersection-observer/IntersectionObserverEntry.js';
import Location from '../location/Location.js';
import MediaQueryList from '../match-media/MediaQueryList.js';
import type MutationObserver from '../mutation-observer/MutationObserver.js';
import MutationRecord from '../mutation-observer/MutationRecord.js';
import MimeType from '../navigator/MimeType.js';
import MimeTypeArray from '../navigator/MimeTypeArray.js';
import Navigator from '../navigator/Navigator.js';
import Plugin from '../navigator/Plugin.js';
import PluginArray from '../navigator/PluginArray.js';
import Attr from '../nodes/attr/Attr.js';
import CharacterData from '../nodes/character-data/CharacterData.js';
import type Comment from '../nodes/comment/Comment.js';
import type DocumentFragment from '../nodes/document-fragment/DocumentFragment.js';
import DocumentType from '../nodes/document-type/DocumentType.js';
import type Document from '../nodes/document/Document.js';
import DocumentReadyStateEnum from '../nodes/document/DocumentReadyStateEnum.js';
import DocumentReadyStateManager from '../nodes/document/DocumentReadyStateManager.js';
import DOMRect from '../dom/DOMRect.js';
import DOMRectReadOnly from '../dom/DOMRectReadOnly.js';
import Element from '../nodes/element/Element.js';
import HTMLCollection from '../nodes/element/HTMLCollection.js';
import NamedNodeMap from '../nodes/element/NamedNodeMap.js';
import HTMLAnchorElement from '../nodes/html-anchor-element/HTMLAnchorElement.js';
import HTMLAreaElement from '../nodes/html-area-element/HTMLAreaElement.js';
import type Audio from '../nodes/html-audio-element/Audio.js';
import HTMLAudioElement from '../nodes/html-audio-element/HTMLAudioElement.js';
import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement.js';
import HTMLBodyElement from '../nodes/html-body-element/HTMLBodyElement.js';
import HTMLBRElement from '../nodes/html-br-element/HTMLBRElement.js';
import HTMLButtonElement from '../nodes/html-button-element/HTMLButtonElement.js';
import HTMLCanvasElement from '../nodes/html-canvas-element/HTMLCanvasElement.js';
import HTMLDListElement from '../nodes/html-d-list-element/HTMLDListElement.js';
import HTMLDataElement from '../nodes/html-data-element/HTMLDataElement.js';
import HTMLDataListElement from '../nodes/html-data-list-element/HTMLDataListElement.js';
import HTMLDetailsElement from '../nodes/html-details-element/HTMLDetailsElement.js';
import HTMLDialogElement from '../nodes/html-dialog-element/HTMLDialogElement.js';
import HTMLDivElement from '../nodes/html-div-element/HTMLDivElement.js';
import type HTMLDocument from '../nodes/html-document/HTMLDocument.js';
import HTMLElement from '../nodes/html-element/HTMLElement.js';
import HTMLEmbedElement from '../nodes/html-embed-element/HTMLEmbedElement.js';
import HTMLFieldSetElement from '../nodes/html-field-set-element/HTMLFieldSetElement.js';
import HTMLFormControlsCollection from '../nodes/html-form-element/HTMLFormControlsCollection.js';
import HTMLFormElement from '../nodes/html-form-element/HTMLFormElement.js';
import RadioNodeList from '../nodes/html-form-element/RadioNodeList.js';
import HTMLHeadElement from '../nodes/html-head-element/HTMLHeadElement.js';
import HTMLHeadingElement from '../nodes/html-heading-element/HTMLHeadingElement.js';
import HTMLHRElement from '../nodes/html-hr-element/HTMLHRElement.js';
import HTMLHtmlElement from '../nodes/html-html-element/HTMLHtmlElement.js';
import HTMLIFrameElement from '../nodes/html-iframe-element/HTMLIFrameElement.js';
import HTMLImageElement from '../nodes/html-image-element/HTMLImageElement.js';
import type Image from '../nodes/html-image-element/Image.js';
import FileList from '../nodes/html-input-element/FileList.js';
import HTMLInputElement from '../nodes/html-input-element/HTMLInputElement.js';
import HTMLLabelElement from '../nodes/html-label-element/HTMLLabelElement.js';
import HTMLLegendElement from '../nodes/html-legend-element/HTMLLegendElement.js';
import HTMLLIElement from '../nodes/html-li-element/HTMLLIElement.js';
import HTMLLinkElement from '../nodes/html-link-element/HTMLLinkElement.js';
import HTMLMapElement from '../nodes/html-map-element/HTMLMapElement.js';
import HTMLMediaElement from '../nodes/html-media-element/HTMLMediaElement.js';
import type MediaStream from '../nodes/html-media-element/MediaStream.js';
import type MediaStreamTrack from '../nodes/html-media-element/MediaStreamTrack.js';
import type RemotePlayback from '../nodes/html-media-element/RemotePlayback.js';
import type TextTrack from '../nodes/html-media-element/TextTrack.js';
import type TextTrackCue from '../nodes/html-media-element/TextTrackCue.js';
import TextTrackCueList from '../nodes/html-media-element/TextTrackCueList.js';
import type TextTrackList from '../nodes/html-media-element/TextTrackList.js';
import TimeRanges from '../nodes/html-media-element/TimeRanges.js';
import type VTTCue from '../nodes/html-media-element/VTTCue.js';
import HTMLMenuElement from '../nodes/html-menu-element/HTMLMenuElement.js';
import HTMLMetaElement from '../nodes/html-meta-element/HTMLMetaElement.js';
import HTMLMeterElement from '../nodes/html-meter-element/HTMLMeterElement.js';
import HTMLModElement from '../nodes/html-mod-element/HTMLModElement.js';
import HTMLOListElement from '../nodes/html-o-list-element/HTMLOListElement.js';
import HTMLObjectElement from '../nodes/html-object-element/HTMLObjectElement.js';
import HTMLOptGroupElement from '../nodes/html-opt-group-element/HTMLOptGroupElement.js';
import HTMLOptionElement from '../nodes/html-option-element/HTMLOptionElement.js';
import HTMLOutputElement from '../nodes/html-output-element/HTMLOutputElement.js';
import HTMLParagraphElement from '../nodes/html-paragraph-element/HTMLParagraphElement.js';
import HTMLParamElement from '../nodes/html-param-element/HTMLParamElement.js';
import HTMLPictureElement from '../nodes/html-picture-element/HTMLPictureElement.js';
import HTMLPreElement from '../nodes/html-pre-element/HTMLPreElement.js';
import HTMLProgressElement from '../nodes/html-progress-element/HTMLProgressElement.js';
import HTMLQuoteElement from '../nodes/html-quote-element/HTMLQuoteElement.js';
import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement.js';
import HTMLSelectElement from '../nodes/html-select-element/HTMLSelectElement.js';
import HTMLSlotElement from '../nodes/html-slot-element/HTMLSlotElement.js';
import HTMLSourceElement from '../nodes/html-source-element/HTMLSourceElement.js';
import HTMLSpanElement from '../nodes/html-span-element/HTMLSpanElement.js';
import HTMLStyleElement from '../nodes/html-style-element/HTMLStyleElement.js';
import HTMLTableCaptionElement from '../nodes/html-table-caption-element/HTMLTableCaptionElement.js';
import HTMLTableCellElement from '../nodes/html-table-cell-element/HTMLTableCellElement.js';
import HTMLTableColElement from '../nodes/html-table-col-element/HTMLTableColElement.js';
import HTMLTableElement from '../nodes/html-table-element/HTMLTableElement.js';
import HTMLTableRowElement from '../nodes/html-table-row-element/HTMLTableRowElement.js';
import HTMLTableSectionElement from '../nodes/html-table-section-element/HTMLTableSectionElement.js';
import HTMLTemplateElement from '../nodes/html-template-element/HTMLTemplateElement.js';
import HTMLTextAreaElement from '../nodes/html-text-area-element/HTMLTextAreaElement.js';
import HTMLTimeElement from '../nodes/html-time-element/HTMLTimeElement.js';
import HTMLTitleElement from '../nodes/html-title-element/HTMLTitleElement.js';
import HTMLTrackElement from '../nodes/html-track-element/HTMLTrackElement.js';
import HTMLUListElement from '../nodes/html-u-list-element/HTMLUListElement.js';
import HTMLUnknownElement from '../nodes/html-unknown-element/HTMLUnknownElement.js';
import HTMLVideoElement from '../nodes/html-video-element/HTMLVideoElement.js';
import Node from '../nodes/node/Node.js';
import NodeList from '../nodes/node/NodeList.js';
import ProcessingInstruction from '../nodes/processing-instruction/ProcessingInstruction.js';
import ShadowRoot from '../nodes/shadow-root/ShadowRoot.js';
import SVGElement from '../nodes/svg-element/SVGElement.js';
import type Text from '../nodes/text/Text.js';
import type XMLDocument from '../nodes/xml-document/XMLDocument.js';
import type PermissionStatus from '../permissions/PermissionStatus.js';
import Permissions from '../permissions/Permissions.js';
import type Range from '../range/Range.js';
import ResizeObserver from '../resize-observer/ResizeObserver.js';
import Screen from '../screen/Screen.js';
import ScreenDetails from '../screen/ScreenDetails.js';
import ScreenDetailed from '../screen/ScreenDetailed.js';
import Selection from '../selection/Selection.js';
import Storage from '../storage/Storage.js';
import NodeFilter from '../tree-walker/NodeFilter.js';
import NodeIterator from '../tree-walker/NodeIterator.js';
import TreeWalker from '../tree-walker/TreeWalker.js';
import type URL from '../url/URL.js';
import ValidityState from '../validity-state/ValidityState.js';
import type XMLHttpRequest from '../xml-http-request/XMLHttpRequest.js';
import type XMLHttpRequestEventTarget from '../xml-http-request/XMLHttpRequestEventTarget.js';
import type XMLHttpRequestUpload from '../xml-http-request/XMLHttpRequestUpload.js';
import XMLSerializer from '../xml-serializer/XMLSerializer.js';
import type CrossOriginBrowserWindow from './CrossOriginBrowserWindow.js';
import type INodeJSGlobal from './INodeJSGlobal.js';
import VMGlobalPropertyScript from './VMGlobalPropertyScript.js';
import WindowPageOpenUtility from './WindowPageOpenUtility.js';
import type { PerformanceObserverEntryList as IPerformanceObserverEntryList } from 'node:perf_hooks';
import { PerformanceObserver, PerformanceEntry } from 'node:perf_hooks';
import EventPhaseEnum from '../event/EventPhaseEnum.js';
import HTMLOptionsCollection from '../nodes/html-select-element/HTMLOptionsCollection.js';
import WindowContextClassExtender from './WindowContextClassExtender.js';
import WindowBrowserContext from './WindowBrowserContext.js';
import type CanvasCaptureMediaStreamTrack from '../nodes/html-canvas-element/CanvasCaptureMediaStreamTrack.js';
import SVGSVGElement from '../nodes/svg-svg-element/SVGSVGElement.js';
import SVGGraphicsElement from '../nodes/svg-graphics-element/SVGGraphicsElement.js';
import SVGAnimateElement from '../nodes/svg-animate-element/SVGAnimateElement.js';
import SVGAnimateMotionElement from '../nodes/svg-animate-motion-element/SVGAnimateMotionElement.js';
import SVGAnimateTransformElement from '../nodes/svg-animate-transform-element/SVGAnimateTransformElement.js';
import SVGCircleElement from '../nodes/svg-circle-element/SVGCircleElement.js';
import SVGClipPathElement from '../nodes/svg-clip-path-element/SVGClipPathElement.js';
import SVGDefsElement from '../nodes/svg-defs-element/SVGDefsElement.js';
import SVGDescElement from '../nodes/svg-desc-element/SVGDescElement.js';
import SVGEllipseElement from '../nodes/svg-ellipse-element/SVGEllipseElement.js';
import SVGFEBlendElement from '../nodes/svg-fe-blend-element/SVGFEBlendElement.js';
import SVGFEColorMatrixElement from '../nodes/svg-fe-color-matrix-element/SVGFEColorMatrixElement.js';
import SVGFEComponentTransferElement from '../nodes/svg-fe-component-transfer-element/SVGFEComponentTransferElement.js';
import SVGFECompositeElement from '../nodes/svg-fe-composite-element/SVGFECompositeElement.js';
import SVGFEConvolveMatrixElement from '../nodes/svg-fe-convolve-matrix-element/SVGFEConvolveMatrixElement.js';
import SVGFEDiffuseLightingElement from '../nodes/svg-fe-diffuse-lighting-element/SVGFEDiffuseLightingElement.js';
import SVGFEDisplacementMapElement from '../nodes/svg-fe-displacement-map-element/SVGFEDisplacementMapElement.js';
import SVGFEDistantLightElement from '../nodes/svg-fe-distant-light-element/SVGFEDistantLightElement.js';
import SVGFEDropShadowElement from '../nodes/svg-fe-drop-shadow-element/SVGFEDropShadowElement.js';
import SVGFEFloodElement from '../nodes/svg-fe-flood-element/SVGFEFloodElement.js';
import SVGFEFuncAElement from '../nodes/svg-fe-func-a-element/SVGFEFuncAElement.js';
import SVGFEFuncBElement from '../nodes/svg-fe-func-b-element/SVGFEFuncBElement.js';
import SVGFEFuncGElement from '../nodes/svg-fe-func-g-element/SVGFEFuncGElement.js';
import SVGFEFuncRElement from '../nodes/svg-fe-func-r-element/SVGFEFuncRElement.js';
import SVGFEGaussianBlurElement from '../nodes/svg-fe-gaussian-blur-element/SVGFEGaussianBlurElement.js';
import SVGFEImageElement from '../nodes/svg-fe-image-element/SVGFEImageElement.js';
import SVGFEMergeElement from '../nodes/svg-fe-merge-element/SVGFEMergeElement.js';
import SVGFEMergeNodeElement from '../nodes/svg-fe-merge-node-element/SVGFEMergeNodeElement.js';
import SVGFEMorphologyElement from '../nodes/svg-fe-morphology-element/SVGFEMorphologyElement.js';
import SVGFEOffsetElement from '../nodes/svg-fe-offset-element/SVGFEOffsetElement.js';
import SVGFEPointLightElement from '../nodes/svg-fe-point-light-element/SVGFEPointLightElement.js';
import SVGFESpecularLightingElement from '../nodes/svg-fe-specular-lighting-element/SVGFESpecularLightingElement.js';
import SVGFESpotLightElement from '../nodes/svg-fe-spot-light-element/SVGFESpotLightElement.js';
import SVGFETileElement from '../nodes/svg-fe-tile-element/SVGFETileElement.js';
import SVGFETurbulenceElement from '../nodes/svg-fe-turbulence-element/SVGFETurbulenceElement.js';
import SVGFilterElement from '../nodes/svg-filter-element/SVGFilterElement.js';
import SVGForeignObjectElement from '../nodes/svg-foreign-object-element/SVGForeignObjectElement.js';
import SVGGElement from '../nodes/svg-g-element/SVGGElement.js';
import SVGImageElement from '../nodes/svg-image-element/SVGImageElement.js';
import SVGLineElement from '../nodes/svg-line-element/SVGLineElement.js';
import SVGLinearGradientElement from '../nodes/svg-linear-gradient-element/SVGLinearGradientElement.js';
import SVGMarkerElement from '../nodes/svg-marker-element/SVGMarkerElement.js';
import SVGMaskElement from '../nodes/svg-mask-element/SVGMaskElement.js';
import SVGMetadataElement from '../nodes/svg-metadata-element/SVGMetadataElement.js';
import SVGMPathElement from '../nodes/svg-m-path-element/SVGMPathElement.js';
import SVGPathElement from '../nodes/svg-path-element/SVGPathElement.js';
import SVGPatternElement from '../nodes/svg-pattern-element/SVGPatternElement.js';
import SVGPolygonElement from '../nodes/svg-polygon-element/SVGPolygonElement.js';
import SVGPolylineElement from '../nodes/svg-polyline-element/SVGPolylineElement.js';
import SVGRadialGradientElement from '../nodes/svg-radial-gradient-element/SVGRadialGradientElement.js';
import SVGRectElement from '../nodes/svg-rect-element/SVGRectElement.js';
import SVGScriptElement from '../nodes/svg-script-element/SVGScriptElement.js';
import SVGSetElement from '../nodes/svg-set-element/SVGSetElement.js';
import SVGStopElement from '../nodes/svg-stop-element/SVGStopElement.js';
import SVGStyleElement from '../nodes/svg-style-element/SVGStyleElement.js';
import SVGSwitchElement from '../nodes/svg-switch-element/SVGSwitchElement.js';
import SVGSymbolElement from '../nodes/svg-symbol-element/SVGSymbolElement.js';
import SVGTextElement from '../nodes/svg-text-element/SVGTextElement.js';
import SVGTextPathElement from '../nodes/svg-text-path-element/SVGTextPathElement.js';
import SVGTitleElement from '../nodes/svg-title-element/SVGTitleElement.js';
import SVGTSpanElement from '../nodes/svg-t-span-element/SVGTSpanElement.js';
import SVGUseElement from '../nodes/svg-use-element/SVGUseElement.js';
import SVGViewElement from '../nodes/svg-view-element/SVGViewElement.js';
import SVGAnimationElement from '../nodes/svg-animation-element/SVGAnimationElement.js';
import SVGComponentTransferFunctionElement from '../nodes/svg-component-transfer-function-element/SVGComponentTransferFunctionElement.js';
import SVGGeometryElement from '../nodes/svg-geometry-element/SVGGeometryElement.js';
import SVGGradientElement from '../nodes/svg-gradient-element/SVGGradientElement.js';
import SVGTextPositioningElement from '../nodes/svg-text-positioning-element/SVGTextPositioningElement.js';
import type DOMMatrixReadOnly from '../dom/dom-matrix/DOMMatrixReadOnly.js';
import type DOMMatrix from '../dom/dom-matrix/DOMMatrix.js';
import type DOMPointReadOnly from '../dom/DOMPointReadOnly.js';
import type DOMPoint from '../dom/DOMPoint.js';
import SVGAngle from '../svg/SVGAngle.js';
import SVGAnimatedAngle from '../svg/SVGAnimatedAngle.js';
import SVGAnimatedBoolean from '../svg/SVGAnimatedBoolean.js';
import SVGAnimatedEnumeration from '../svg/SVGAnimatedEnumeration.js';
import SVGAnimatedInteger from '../svg/SVGAnimatedInteger.js';
import SVGAnimatedLength from '../svg/SVGAnimatedLength.js';
import SVGLength from '../svg/SVGLength.js';
import SVGAnimatedNumber from '../svg/SVGAnimatedNumber.js';
import SVGAnimatedNumberList from '../svg/SVGAnimatedNumberList.js';
import SVGAnimatedPreserveAspectRatio from '../svg/SVGAnimatedPreserveAspectRatio.js';
import SVGAnimatedRect from '../svg/SVGAnimatedRect.js';
import SVGAnimatedString from '../svg/SVGAnimatedString.js';
import SVGAnimatedTransformList from '../svg/SVGAnimatedTransformList.js';
import SVGLengthList from '../svg/SVGLengthList.js';
import SVGMatrix from '../svg/SVGMatrix.js';
import SVGNumber from '../svg/SVGNumber.js';
import SVGNumberList from '../svg/SVGNumberList.js';
import SVGPoint from '../svg/SVGPoint.js';
import SVGPointList from '../svg/SVGPointList.js';
import SVGPreserveAspectRatio from '../svg/SVGPreserveAspectRatio.js';
import SVGRect from '../svg/SVGRect.js';
import SVGStringList from '../svg/SVGStringList.js';
import SVGTransform from '../svg/SVGTransform.js';
import SVGTransformList from '../svg/SVGTransformList.js';
import SVGUnitTypes from '../svg/SVGUnitTypes.js';
import SVGAnimatedLengthList from '../svg/SVGAnimatedLengthList.js';
import CustomElementReactionStack from '../custom-element/CustomElementReactionStack.js';
import type IScrollToOptions from './IScrollToOptions.js';
import type IModule from '../module/types/IModule.js';
import type IModuleImportMap from '../module/types/IModuleImportMap.js';
import StylePropertyMapReadOnly from '../css/style-property-map/StylePropertyMapReadOnly.js';
import StylePropertyMap from '../css/style-property-map/StylePropertyMap.js';
import MediaList from '../css/MediaList.js';
import CSSKeywordValue from '../css/style-property-map/CSSKeywordValue.js';
import CSSStyleValue from '../css/style-property-map/CSSStyleValue.js';
import CSSConditionRule from '../css/rules/CSSConditionRule.js';
import CSSGroupingRule from '../css/rules/CSSGroupingRule.js';
import CSSScopeRule from '../css/rules/CSSScopeRule.js';
import PopStateEvent from '../event/events/PopStateEvent.js';
import type ITimerLoopsLimit from './ITimerLoopsLimit.js';
import CloseEvent from '../event/events/CloseEvent.js';
import type WebSocket from '../web-socket/WebSocket.js';
import type { TImageBitmapSource } from '../canvas/TImageBitmapSource.js';
import type IImageBitmapOptions from '../canvas/IImageBitmapOptions.js';
import ImageBitmap from '../canvas/ImageBitmap.js';
import type ImageData from '../canvas/ImageData.js';
import type OffscreenCanvas from '../canvas/OffscreenCanvas.js';

const TIMER = {
	setTimeout: globalThis.setTimeout.bind(globalThis),
	clearTimeout: globalThis.clearTimeout.bind(globalThis),
	setInterval: globalThis.setInterval.bind(globalThis),
	clearInterval: globalThis.clearInterval.bind(globalThis),
	queueMicrotask: globalThis.queueMicrotask.bind(globalThis),
	setImmediate: globalThis.setImmediate.bind(globalThis),
	clearImmediate: globalThis.clearImmediate.bind(globalThis)
};

const IS_NODE_JS_TIMEOUT_ENVIRONMENT = setTimeout.toString().includes('new Timeout');

const IS_CODE_GENERATION_FROM_STRINGS_ENVIRONMENT = (() => {
	try {
		// eslint-disable-next-line no-new-func
		new Function('return true;')();
		return true;
	} catch {
		return false;
	}
})();

const IS_UNFROZEN_INTRINSICS_ENVIRONMENT = (() => {
	try {
		(<any>globalThis.Function)['$UNFROZEN$'] = true;
		delete (<any>globalThis.Function)['$UNFROZEN$'];
		return true;
	} catch {
		return false;
	}
})();

/**
 * Class for PerformanceObserverEntryList as it is only available as an interface from Node.js.
 */
class PerformanceObserverEntryList {
	/**
	 * Constructor.
	 */
	constructor() {
		throw new TypeError('Illegal constructor');
	}
}
/**
 * Zero Timeout.
 */
class Timeout {
	public callback: () => void;
	/**
	 * Constructor.
	 * @param callback Callback.
	 */
	constructor(callback: () => void) {
		this.callback = callback;
	}
}

/**
 * Browser window.
 *
 * Reference:
 * https://developer.mozilla.org/en-US/docs/Web/API/Window.
 */
export default class BrowserWindow extends EventTarget implements INodeJSGlobal {
	// Nodes
	public readonly Node = Node;
	public readonly Attr = Attr;
	public readonly ShadowRoot = ShadowRoot;
	public readonly ProcessingInstruction = ProcessingInstruction;
	public readonly Element = Element;
	public readonly CharacterData = CharacterData;
	public readonly DocumentType = DocumentType;

	// Nodes that can be created using "new" keyword (populated by WindowContextClassExtender)
	public declare readonly Document: typeof Document;
	public declare readonly HTMLDocument: typeof HTMLDocument;
	public declare readonly XMLDocument: typeof XMLDocument;
	public declare readonly DocumentFragment: typeof DocumentFragment;
	public declare readonly Text: typeof Text;
	public declare readonly Comment: typeof Comment;
	public declare readonly Image: typeof Image;
	public declare readonly Audio: typeof Audio;
	public declare readonly WebSocket: typeof WebSocket;

	// HTML Element classes
	public readonly HTMLAnchorElement = HTMLAnchorElement;
	public readonly HTMLButtonElement = HTMLButtonElement;
	public readonly HTMLOptGroupElement = HTMLOptGroupElement;
	public readonly HTMLOptionElement = HTMLOptionElement;
	public readonly HTMLElement = HTMLElement;
	public readonly HTMLUnknownElement = HTMLUnknownElement;
	public readonly HTMLTemplateElement = HTMLTemplateElement;
	public readonly HTMLInputElement = HTMLInputElement;
	public readonly HTMLSelectElement = HTMLSelectElement;
	public readonly HTMLTextAreaElement = HTMLTextAreaElement;
	public readonly HTMLImageElement = HTMLImageElement;
	public readonly HTMLStyleElement = HTMLStyleElement;
	public readonly HTMLLabelElement = HTMLLabelElement;
	public readonly HTMLSlotElement = HTMLSlotElement;
	public readonly HTMLMetaElement = HTMLMetaElement;
	public readonly HTMLMediaElement = HTMLMediaElement;
	public readonly HTMLAudioElement = HTMLAudioElement;
	public readonly HTMLVideoElement = HTMLVideoElement;
	public readonly HTMLBaseElement = HTMLBaseElement;
	public readonly HTMLDialogElement = HTMLDialogElement;
	public readonly HTMLScriptElement = HTMLScriptElement;
	public readonly HTMLLinkElement = HTMLLinkElement;
	public readonly HTMLIFrameElement = HTMLIFrameElement;
	public readonly HTMLFormElement = HTMLFormElement;
	public readonly HTMLUListElement = HTMLUListElement;
	public readonly HTMLTrackElement = HTMLTrackElement;
	public readonly HTMLTableRowElement = HTMLTableRowElement;
	public readonly HTMLTitleElement = HTMLTitleElement;
	public readonly HTMLTimeElement = HTMLTimeElement;
	public readonly HTMLTableSectionElement = HTMLTableSectionElement;
	public readonly HTMLTableCellElement = HTMLTableCellElement;
	public readonly HTMLTableElement = HTMLTableElement;
	public readonly HTMLSpanElement = HTMLSpanElement;
	public readonly HTMLSourceElement = HTMLSourceElement;
	public readonly HTMLQuoteElement = HTMLQuoteElement;
	public readonly HTMLProgressElement = HTMLProgressElement;
	public readonly HTMLPreElement = HTMLPreElement;
	public readonly HTMLPictureElement = HTMLPictureElement;
	public readonly HTMLParamElement = HTMLParamElement;
	public readonly HTMLParagraphElement = HTMLParagraphElement;
	public readonly HTMLOutputElement = HTMLOutputElement;
	public readonly HTMLOListElement = HTMLOListElement;
	public readonly HTMLObjectElement = HTMLObjectElement;
	public readonly HTMLMeterElement = HTMLMeterElement;
	public readonly HTMLMenuElement = HTMLMenuElement;
	public readonly HTMLMapElement = HTMLMapElement;
	public readonly HTMLLIElement = HTMLLIElement;
	public readonly HTMLLegendElement = HTMLLegendElement;
	public readonly HTMLModElement = HTMLModElement;
	public readonly HTMLHtmlElement = HTMLHtmlElement;
	public readonly HTMLHRElement = HTMLHRElement;
	public readonly HTMLHeadElement = HTMLHeadElement;
	public readonly HTMLHeadingElement = HTMLHeadingElement;
	public readonly HTMLFieldSetElement = HTMLFieldSetElement;
	public readonly HTMLEmbedElement = HTMLEmbedElement;
	public readonly HTMLDListElement = HTMLDListElement;
	public readonly HTMLDivElement = HTMLDivElement;
	public readonly HTMLDetailsElement = HTMLDetailsElement;
	public readonly HTMLDataListElement = HTMLDataListElement;
	public readonly HTMLDataElement = HTMLDataElement;
	public readonly HTMLTableColElement = HTMLTableColElement;
	public readonly HTMLTableCaptionElement = HTMLTableCaptionElement;
	public readonly HTMLCanvasElement = HTMLCanvasElement;
	public readonly HTMLBRElement = HTMLBRElement;
	public readonly HTMLBodyElement = HTMLBodyElement;
	public readonly HTMLAreaElement = HTMLAreaElement;

	// SVG Element classes
	public readonly SVGSVGElement = SVGSVGElement;
	public readonly SVGAnimateElement = SVGAnimateElement;
	public readonly SVGAnimateMotionElement = SVGAnimateMotionElement;
	public readonly SVGAnimateTransformElement = SVGAnimateTransformElement;
	public readonly SVGCircleElement = SVGCircleElement;
	public readonly SVGClipPathElement = SVGClipPathElement;
	public readonly SVGDefsElement = SVGDefsElement;
	public readonly SVGDescElement = SVGDescElement;
	public readonly SVGEllipseElement = SVGEllipseElement;
	public readonly SVGFEBlendElement = SVGFEBlendElement;
	public readonly SVGFEColorMatrixElement = SVGFEColorMatrixElement;
	public readonly SVGFEComponentTransferElement = SVGFEComponentTransferElement;
	public readonly SVGFECompositeElement = SVGFECompositeElement;
	public readonly SVGFEConvolveMatrixElement = SVGFEConvolveMatrixElement;
	public readonly SVGFEDiffuseLightingElement = SVGFEDiffuseLightingElement;
	public readonly SVGFEDisplacementMapElement = SVGFEDisplacementMapElement;
	public readonly SVGFEDistantLightElement = SVGFEDistantLightElement;
	public readonly SVGFEDropShadowElement = SVGFEDropShadowElement;
	public readonly SVGFEFloodElement = SVGFEFloodElement;
	public readonly SVGFEFuncAElement = SVGFEFuncAElement;
	public readonly SVGFEFuncBElement = SVGFEFuncBElement;
	public readonly SVGFEFuncGElement = SVGFEFuncGElement;
	public readonly SVGFEFuncRElement = SVGFEFuncRElement;
	public readonly SVGFEGaussianBlurElement = SVGFEGaussianBlurElement;
	public readonly SVGFEImageElement = SVGFEImageElement;
	public readonly SVGFEMergeElement = SVGFEMergeElement;
	public readonly SVGFEMergeNodeElement = SVGFEMergeNodeElement;
	public readonly SVGFEMorphologyElement = SVGFEMorphologyElement;
	public readonly SVGFEOffsetElement = SVGFEOffsetElement;
	public readonly SVGFEPointLightElement = SVGFEPointLightElement;
	public readonly SVGFESpecularLightingElement = SVGFESpecularLightingElement;
	public readonly SVGFESpotLightElement = SVGFESpotLightElement;
	public readonly SVGFETileElement = SVGFETileElement;
	public readonly SVGFETurbulenceElement = SVGFETurbulenceElement;
	public readonly SVGFilterElement = SVGFilterElement;
	public readonly SVGForeignObjectElement = SVGForeignObjectElement;
	public readonly SVGGElement = SVGGElement;
	public readonly SVGImageElement = SVGImageElement;
	public readonly SVGLineElement = SVGLineElement;
	public readonly SVGLinearGradientElement = SVGLinearGradientElement;
	public readonly SVGMarkerElement = SVGMarkerElement;
	public readonly SVGMaskElement = SVGMaskElement;
	public readonly SVGMetadataElement = SVGMetadataElement;
	public readonly SVGMPathElement = SVGMPathElement;
	public readonly SVGPathElement = SVGPathElement;
	public readonly SVGPatternElement = SVGPatternElement;
	public readonly SVGPolygonElement = SVGPolygonElement;
	public readonly SVGPolylineElement = SVGPolylineElement;
	public readonly SVGRadialGradientElement = SVGRadialGradientElement;
	public readonly SVGRectElement = SVGRectElement;
	public readonly SVGScriptElement = SVGScriptElement;
	public readonly SVGSetElement = SVGSetElement;
	public readonly SVGStopElement = SVGStopElement;
	public readonly SVGStyleElement = SVGStyleElement;
	public readonly SVGSwitchElement = SVGSwitchElement;
	public readonly SVGSymbolElement = SVGSymbolElement;
	public readonly SVGTextElement = SVGTextElement;
	public readonly SVGTextPathElement = SVGTextPathElement;
	public readonly SVGTitleElement = SVGTitleElement;
	public readonly SVGTSpanElement = SVGTSpanElement;
	public readonly SVGUseElement = SVGUseElement;
	public readonly SVGViewElement = SVGViewElement;

	// Abstract SVG Element classes
	public readonly SVGElement = SVGElement;
	public readonly SVGAnimationElement = SVGAnimationElement;
	public readonly SVGComponentTransferFunctionElement = SVGComponentTransferFunctionElement;
	public readonly SVGGeometryElement = SVGGeometryElement;
	public readonly SVGGradientElement = SVGGradientElement;
	public readonly SVGTextPositioningElement = SVGTextPositioningElement;
	public readonly SVGGraphicsElement = SVGGraphicsElement;

	// Event classes
	public readonly Event = Event;
	public readonly UIEvent = UIEvent;
	public readonly CustomEvent = CustomEvent;
	public readonly CloseEvent = CloseEvent;
	public readonly AnimationEvent = AnimationEvent;
	public readonly KeyboardEvent = KeyboardEvent;
	public readonly MessageEvent = MessageEvent;
	public readonly MouseEvent = MouseEvent;
	public readonly PointerEvent = PointerEvent;
	public readonly FocusEvent = FocusEvent;
	public readonly WheelEvent = WheelEvent;
	public readonly InputEvent = InputEvent;
	public readonly ErrorEvent = ErrorEvent;
	public readonly StorageEvent = StorageEvent;
	public readonly SubmitEvent = SubmitEvent;
	public readonly ProgressEvent = ProgressEvent;
	public readonly MediaQueryListEvent = MediaQueryListEvent;
	public readonly HashChangeEvent = HashChangeEvent;
	public readonly ClipboardEvent = ClipboardEvent;
	public readonly TouchEvent = TouchEvent;
	public readonly PopStateEvent = PopStateEvent;
	public readonly Touch = Touch;

	// Non-implemented event classes
	public readonly AudioProcessingEvent = Event;
	public readonly BeforeInputEvent = Event;
	public readonly BeforeUnloadEvent = Event;
	public readonly BlobEvent = Event;
	public readonly CompositionEvent = Event;
	public readonly CSSFontFaceLoadEvent = Event;
	public readonly DeviceLightEvent = Event;
	public readonly DeviceMotionEvent = Event;
	public readonly DeviceOrientationEvent = Event;
	public readonly DeviceProximityEvent = Event;
	public readonly DOMTransactionEvent = Event;
	public readonly DragEvent = Event;
	public readonly EditingBeforeInputEvent = Event;
	public readonly FetchEvent = Event;
	public readonly GamepadEvent = Event;
	public readonly IDBVersionChangeEvent = Event;
	public readonly MediaStreamEvent = Event;
	public readonly MutationEvent = Event;
	public readonly OfflineAudioCompletionEvent = Event;
	public readonly OverconstrainedError = Event;
	public readonly PageTransitionEvent = Event;
	public readonly PaymentRequestUpdateEvent = Event;
	public readonly RelatedEvent = Event;
	public readonly RTCDataChannelEvent = Event;
	public readonly RTCIdentityErrorEvent = Event;
	public readonly RTCIdentityEvent = Event;
	public readonly RTCPeerConnectionIceEvent = Event;
	public readonly SensorEvent = Event;
	public readonly SVGEvent = Event;
	public readonly SVGZoomEvent = Event;
	public readonly TimeEvent = Event;
	public readonly TrackEvent = Event;
	public readonly TransitionEvent = Event;
	public readonly UserProximityEvent = Event;
	public readonly WebGLContextEvent = Event;
	public readonly TextEvent = Event;

	// Other classes that has to be bound to the Window context (populated by WindowContextClassExtender)
	public declare readonly MutationObserver: typeof MutationObserver;
	public declare readonly MessagePort: typeof MessagePort;
	public declare readonly CSSStyleSheet: typeof CSSStyleSheet;
	public declare readonly DOMException: typeof DOMException;
	public declare readonly Headers: typeof Headers;
	public declare readonly Request: typeof Request;
	public declare readonly Response: typeof Response;
	public declare readonly EventTarget: typeof EventTarget;
	public declare readonly XMLHttpRequestUpload: typeof XMLHttpRequestUpload;
	public declare readonly XMLHttpRequestEventTarget: typeof XMLHttpRequestEventTarget;
	public declare readonly AbortController: typeof AbortController;
	public declare readonly AbortSignal: typeof AbortSignal;
	public declare readonly FormData: typeof FormData;
	public declare readonly PermissionStatus: typeof PermissionStatus;
	public declare readonly XMLHttpRequest: typeof XMLHttpRequest;
	public declare readonly DOMParser: typeof DOMParser;
	public declare readonly Range: typeof Range;
	public declare readonly VTTCue: typeof VTTCue;
	public declare readonly FileReader: typeof FileReader;
	public declare readonly MediaStream: typeof MediaStream;
	public declare readonly MediaStreamTrack: typeof MediaStreamTrack;
	public declare readonly CanvasCaptureMediaStreamTrack: typeof CanvasCaptureMediaStreamTrack;
	public declare readonly TextTrack: typeof TextTrack;
	public declare readonly TextTrackList: typeof TextTrackList;
	public declare readonly TextTrackCue: typeof TextTrackCue;
	public declare readonly RemotePlayback: typeof RemotePlayback;
	public declare readonly URL: typeof URL;
	public declare readonly ImageData: typeof ImageData;
	public declare readonly OffscreenCanvas: typeof OffscreenCanvas;
	public declare readonly DOMMatrixReadOnly: typeof DOMMatrixReadOnly;
	public declare readonly DOMMatrix: typeof DOMMatrix;
	public declare readonly DOMPointReadOnly: typeof DOMPointReadOnly;
	public declare readonly DOMPoint: typeof DOMPoint;

	// Other classes that don't have to be bound to the Window context
	public readonly Permissions = Permissions;
	public readonly History = History;
	public readonly Navigator = Navigator;
	public readonly Clipboard = Clipboard;
	public readonly TimeRanges = TimeRanges;
	public readonly TextTrackCueList = TextTrackCueList;
	public readonly ValidityState = ValidityState;
	public readonly MutationRecord = MutationRecord;
	public readonly IntersectionObserver = IntersectionObserver;
	public readonly IntersectionObserverEntry = IntersectionObserverEntry;
	public readonly CSSStyleDeclaration = CSSStyleDeclaration;
	public readonly CSSRule = CSSRule;
	public readonly CSSContainerRule = CSSContainerRule;
	public readonly CSSFontFaceRule = CSSFontFaceRule;
	public readonly CSSKeyframeRule = CSSKeyframeRule;
	public readonly CSSKeyframesRule = CSSKeyframesRule;
	public readonly CSSMediaRule = CSSMediaRule;
	public readonly CSSStyleRule = CSSStyleRule;
	public readonly CSSSupportsRule = CSSSupportsRule;
	public readonly CSSConditionRule = CSSConditionRule;
	public readonly CSSGroupingRule = CSSGroupingRule;
	public readonly CSSScopeRule = CSSScopeRule;
	public readonly DOMRect = DOMRect;
	public readonly DOMRectReadOnly = DOMRectReadOnly;
	public readonly Plugin = Plugin;
	public readonly PluginArray = PluginArray;
	public readonly Location = Location;
	public readonly CustomElementRegistry = CustomElementRegistry;
	public readonly ResizeObserver = ResizeObserver;
	public readonly Blob = Blob;
	public readonly File = File;
	public readonly Storage = Storage;
	public readonly MimeType = MimeType;
	public readonly MimeTypeArray = MimeTypeArray;
	public readonly NodeFilter = NodeFilter;
	public readonly ImageBitmap = ImageBitmap;
	public readonly HTMLCollection = HTMLCollection;
	public readonly HTMLFormControlCollection = HTMLFormControlsCollection;
	public readonly HTMLOptionsCollection = HTMLOptionsCollection;
	public readonly NodeList = NodeList;
	public readonly RadioNodeList = RadioNodeList;
	public readonly FileList = FileList;
	public readonly Screen = Screen;
	public readonly ScreenDetails = ScreenDetails;
	public readonly ScreenDetailed = ScreenDetailed;
	public readonly NamedNodeMap = NamedNodeMap;
	public readonly TreeWalker = TreeWalker;
	public readonly NodeIterator = NodeIterator;
	public readonly DataTransfer = DataTransfer;
	public readonly DataTransferItem = DataTransferItem;
	public readonly DataTransferItemList = DataTransferItemList;
	public readonly XMLSerializer = XMLSerializer;
	public readonly ClipboardItem = ClipboardItem;
	public readonly Selection = Selection;
	public readonly CSSUnitValue = CSSUnitValue;
	public readonly SVGAngle = SVGAngle;
	public readonly SVGAnimatedAngle = SVGAnimatedAngle;
	public readonly SVGAnimatedBoolean = SVGAnimatedBoolean;
	public readonly SVGAnimatedEnumeration = SVGAnimatedEnumeration;
	public readonly SVGAnimatedInteger = SVGAnimatedInteger;
	public readonly SVGAnimatedLength = SVGAnimatedLength;
	public readonly SVGAnimatedNumber = SVGAnimatedNumber;
	public readonly SVGAnimatedNumberList = SVGAnimatedNumberList;
	public readonly SVGAnimatedPreserveAspectRatio = SVGAnimatedPreserveAspectRatio;
	public readonly SVGAnimatedRect = SVGAnimatedRect;
	public readonly SVGAnimatedString = SVGAnimatedString;
	public readonly SVGAnimatedTransformList = SVGAnimatedTransformList;
	public readonly SVGLength = SVGLength;
	public readonly SVGLengthList = SVGLengthList;
	public readonly SVGMatrix = SVGMatrix;
	public readonly SVGNumber = SVGNumber;
	public readonly SVGNumberList = SVGNumberList;
	public readonly SVGPoint = SVGPoint;
	public readonly SVGPointList = SVGPointList;
	public readonly SVGPreserveAspectRatio = SVGPreserveAspectRatio;
	public readonly SVGRect = SVGRect;
	public readonly SVGStringList = SVGStringList;
	public readonly SVGTransform = SVGTransform;
	public readonly SVGTransformList = SVGTransformList;
	public readonly SVGAnimatedLengthList = SVGAnimatedLengthList;
	public readonly SVGUnitTypes = SVGUnitTypes;
	public readonly StylePropertyMap = StylePropertyMap;
	public readonly StylePropertyMapReadOnly = StylePropertyMapReadOnly;
	public readonly MediaList = MediaList;
	public readonly CSSKeywordValue = CSSKeywordValue;
	public readonly CSSStyleValue = CSSStyleValue;
	public readonly Window = <typeof BrowserWindow>this.constructor;

	// Node.js Classes
	public readonly URLSearchParams = URLSearchParams;
	public readonly WritableStream = Stream.Writable;
	public readonly ReadableStream = ReadableStream;
	public readonly TransformStream = Stream.Transform;
	public readonly PerformanceObserver = PerformanceObserver;
	public readonly PerformanceEntry = PerformanceEntry;
	public readonly PerformanceObserverEntryList = <new () => IPerformanceObserverEntryList>(
		PerformanceObserverEntryList
	);

	// Public properties.
	public readonly document: Document;
	public readonly customElements: CustomElementRegistry = new CustomElementRegistry(this);
	public readonly window: BrowserWindow = this;
	public readonly globalThis: BrowserWindow = this;
	public readonly performance: typeof performance = performance;
	public readonly screenLeft: number = 0;
	public readonly screenTop: number = 0;
	public readonly screenX: number = 0;
	public readonly screenY: number = 0;
	public readonly crypto: typeof webcrypto = webcrypto;
	public readonly TextEncoder: typeof TextEncoder = TextEncoder;
	public readonly TextDecoder: typeof TextDecoder = TextDecoder;
	public readonly closed = false;
	public console: IConsole;
	public name = '';

	// Node.js Globals (populated by VMGlobalPropertyScript)
	public declare Array: typeof Array;
	public declare ArrayBuffer: typeof ArrayBuffer;
	public declare Boolean: typeof Boolean;
	public Buffer: typeof Buffer = Buffer;
	public declare DataView: typeof DataView;
	public declare Date: typeof Date;
	public declare Error: typeof Error;
	public declare EvalError: typeof EvalError;
	public declare Float32Array: typeof Float32Array;
	public declare Float64Array: typeof Float64Array;
	public declare Function: typeof Function;
	public declare Infinity: typeof Infinity;
	public declare Int16Array: typeof Int16Array;
	public declare Int32Array: typeof Int32Array;
	public declare Int8Array: typeof Int8Array;
	public declare Intl: typeof Intl;
	public declare JSON: typeof JSON;
	public declare Map: MapConstructor;
	public declare Math: typeof Math;
	public declare NaN: typeof NaN;
	public declare Number: typeof Number;
	public declare Object: typeof Object;
	public declare Promise: typeof Promise;
	public declare RangeError: typeof RangeError;
	public declare ReferenceError: typeof ReferenceError;
	public declare RegExp: typeof RegExp;
	public declare Set: SetConstructor;
	public declare String: typeof String;
	public declare Symbol: Function;
	public declare SyntaxError: typeof SyntaxError;
	public declare TypeError: typeof TypeError;
	public declare URIError: typeof URIError;
	public declare Uint16Array: typeof Uint16Array;
	public declare Uint32Array: typeof Uint32Array;
	public declare Uint8Array: typeof Uint8Array;
	public declare Uint8ClampedArray: typeof Uint8ClampedArray;
	public declare WeakMap: WeakMapConstructor;
	public declare WeakSet: WeakSetConstructor;
	public declare decodeURI: typeof decodeURI;
	public declare decodeURIComponent: typeof decodeURIComponent;
	public declare encodeURI: typeof encodeURI;
	public declare encodeURIComponent: typeof encodeURIComponent;
	public declare eval: typeof eval;

	/**
	 * @deprecated
	 */
	public declare escape: (str: string) => string;
	public declare global: typeof globalThis;
	public declare isFinite: typeof isFinite;
	public declare isNaN: typeof isNaN;
	public declare parseFloat: typeof parseFloat;
	public declare parseInt: typeof parseInt;
	public declare undefined: typeof undefined;
	/**
	 * @deprecated
	 */
	public declare unescape: (str: string) => string;
	public declare gc: () => void;
	public declare v8debug?: unknown;

	// Public internal properties

	// Used for tracking capture event listeners to improve performance when they are not used.
	// See EventTarget class.
	public [PropertySymbol.mutationObservers]: MutationObserver[] = [];
	public readonly [PropertySymbol.readyStateManager]: DocumentReadyStateManager;
	public [PropertySymbol.location]: Location;
	public [PropertySymbol.history]: History;
	public [PropertySymbol.navigator]: Navigator;
	public [PropertySymbol.screen]: Screen;
	public [PropertySymbol.sessionStorage]: Storage;
	public [PropertySymbol.localStorage]: Storage;
	public [PropertySymbol.self]: BrowserWindow | null = this;
	public [PropertySymbol.top]: BrowserWindow | null = this;
	public [PropertySymbol.parent]: BrowserWindow | null = this;
	public [PropertySymbol.window]: BrowserWindow = this;
	public [PropertySymbol.frames]: BrowserWindow = this;
	public [PropertySymbol.internalId]: number = -1;
	public [PropertySymbol.customElementReactionStack] = new CustomElementReactionStack(this);
	public [PropertySymbol.modules]: {
		json: Map<string, IModule>;
		css: Map<string, IModule>;
		esm: Map<string, IModule>;
	} = {
		json: new Map(),
		css: new Map(),
		esm: new Map()
	};
	public [PropertySymbol.moduleImportMap]: IModuleImportMap | null = null;
	public [PropertySymbol.openWebSockets]: WebSocket[] = [];
	public [PropertySymbol.propertyEventListeners]: Map<string, ((event: Event) => void) | null> =
		new Map();

	/* eslint-enable jsdoc/require-jsdoc */

	// Private properties
	#browserFrame: IBrowserFrame;
	#innerWidth: number | null = null;
	#innerHeight: number | null = null;
	#outerWidth: number | null = null;
	#outerHeight: number | null = null;
	#devicePixelRatio: number | null = null;
	#zeroDelayTimeout: { timeouts: Array<Timeout> | null } = { timeouts: null };
	#timerLoopStacks: string[] = [];
	#timerLoopLimits: ITimerLoopsLimit[] = [];

	/**
	 * Constructor.
	 *
	 * @param browserFrame Browser frame.
	 * @param [options] Options.
	 * @param [options.url] URL.
	 */
	constructor(browserFrame: IBrowserFrame, options?: { url?: string }) {
		super();

		this.#browserFrame = browserFrame;

		this[PropertySymbol.validateJavaScriptExecutionEnvironment]();

		this.console = browserFrame.page.console;

		this[PropertySymbol.readyStateManager] = new DocumentReadyStateManager(browserFrame);
		this[PropertySymbol.navigator] = new Navigator(this);
		this[PropertySymbol.screen] = new Screen();
		this[PropertySymbol.sessionStorage] = new Storage();
		this[PropertySymbol.localStorage] = new Storage();
		this[PropertySymbol.location] = new Location(this.#browserFrame, options?.url ?? 'about:blank');
		this[PropertySymbol.history] = new History(this.#browserFrame, this);

		browserFrame[PropertySymbol.history].currentItem.href = options?.url ?? 'about:blank';

		WindowBrowserContext.setWindowBrowserFrameRelation(this, this.#browserFrame);

		this[PropertySymbol.setupVMContext]();

		WindowContextClassExtender.extendClasses(this);

		// Document
		this.document = new this.HTMLDocument();
		this.document[PropertySymbol.defaultView] = this;

		// Ready state manager
		const taskID = browserFrame[PropertySymbol.asyncTaskManager].startTask(() =>
			this[PropertySymbol.readyStateManager].destroy()
		);
		this[PropertySymbol.readyStateManager].waitUntilComplete().then(() => {
			browserFrame[PropertySymbol.asyncTaskManager].endTask(taskID);

			this.document[PropertySymbol.readyState] = DocumentReadyStateEnum.complete;
			this.document.dispatchEvent(new Event('readystatechange'));

			const loadEvent = new Event('load');

			// Not sure why target is set to document here, but this is how it seem to work in the browser
			loadEvent[PropertySymbol.currentTarget] = this;
			loadEvent[PropertySymbol.target] = this.document;
			loadEvent[PropertySymbol.eventPhase] = EventPhaseEnum.atTarget;

			this.dispatchEvent(loadEvent);

			loadEvent[PropertySymbol.currentTarget] = null;
			loadEvent[PropertySymbol.eventPhase] = EventPhaseEnum.none;
			loadEvent[PropertySymbol.dispatching] = false;
		});

		this[PropertySymbol.bindMethods]();
	}

	// Events

	/* eslint-disable jsdoc/require-jsdoc */

	public get onsearch(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onsearch') ?? null;
	}

	public set onsearch(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onsearch', value);
	}

	public get onappinstalled(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onappinstalled') ?? null;
	}

	public set onappinstalled(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onappinstalled', value);
	}

	public get onbeforeinstallprompt(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onbeforeinstallprompt') ?? null;
	}

	public set onbeforeinstallprompt(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onbeforeinstallprompt', value);
	}

	public get onabort(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onabort') ?? null;
	}

	public set onabort(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onabort', value);
	}

	public get onbeforeinput(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onbeforeinput') ?? null;
	}

	public set onbeforeinput(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onbeforeinput', value);
	}

	public get onbeforematch(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onbeforematch') ?? null;
	}

	public set onbeforematch(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onbeforematch', value);
	}

	public get onbeforetoggle(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onbeforetoggle') ?? null;
	}

	public set onbeforetoggle(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onbeforetoggle', value);
	}

	public get onblur(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onblur') ?? null;
	}

	public set onblur(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onblur', value);
	}

	public get oncancel(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncancel') ?? null;
	}

	public set oncancel(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncancel', value);
	}

	public get oncanplay(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncanplay') ?? null;
	}

	public set oncanplay(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncanplay', value);
	}

	public get oncanplaythrough(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncanplaythrough') ?? null;
	}

	public set oncanplaythrough(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncanplaythrough', value);
	}

	public get onchange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onchange') ?? null;
	}

	public set onchange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onchange', value);
	}

	public get onclick(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onclick') ?? null;
	}

	public set onclick(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onclick', value);
	}

	public get onclose(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onclose') ?? null;
	}

	public set onclose(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onclose', value);
	}

	public get oncommand(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncommand') ?? null;
	}

	public set oncommand(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncommand', value);
	}

	public get oncontentvisibilityautostatechange(): ((event: Event) => void) | null {
		return (
			this[PropertySymbol.propertyEventListeners].get('oncontentvisibilityautostatechange') ?? null
		);
	}

	public set oncontentvisibilityautostatechange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncontentvisibilityautostatechange', value);
	}

	public get oncontextlost(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncontextlost') ?? null;
	}

	public set oncontextlost(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncontextlost', value);
	}

	public get oncontextmenu(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncontextmenu') ?? null;
	}

	public set oncontextmenu(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncontextmenu', value);
	}

	public get oncontextrestored(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncontextrestored') ?? null;
	}

	public set oncontextrestored(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncontextrestored', value);
	}

	public get oncuechange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oncuechange') ?? null;
	}

	public set oncuechange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oncuechange', value);
	}

	public get ondblclick(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondblclick') ?? null;
	}

	public set ondblclick(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondblclick', value);
	}

	public get ondrag(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondrag') ?? null;
	}

	public set ondrag(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondrag', value);
	}

	public get ondragend(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondragend') ?? null;
	}

	public set ondragend(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondragend', value);
	}

	public get ondragenter(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondragenter') ?? null;
	}

	public set ondragenter(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondragenter', value);
	}

	public get ondragleave(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondragleave') ?? null;
	}

	public set ondragleave(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondragleave', value);
	}

	public get ondragover(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondragover') ?? null;
	}

	public set ondragover(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondragover', value);
	}

	public get ondragstart(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondragstart') ?? null;
	}

	public set ondragstart(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondragstart', value);
	}

	public get ondrop(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondrop') ?? null;
	}

	public set ondrop(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondrop', value);
	}

	public get ondurationchange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondurationchange') ?? null;
	}

	public set ondurationchange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondurationchange', value);
	}

	public get onemptied(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onemptied') ?? null;
	}

	public set onemptied(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onemptied', value);
	}

	public get onended(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onended') ?? null;
	}

	public set onended(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onended', value);
	}

	public get onerror(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onerror') ?? null;
	}

	public set onerror(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onerror', value);
	}

	public get onfocus(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onfocus') ?? null;
	}

	public set onfocus(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onfocus', value);
	}

	public get onformdata(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onformdata') ?? null;
	}

	public set onformdata(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onformdata', value);
	}

	public get oninput(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oninput') ?? null;
	}

	public set oninput(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oninput', value);
	}

	public get oninvalid(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('oninvalid') ?? null;
	}

	public set oninvalid(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('oninvalid', value);
	}

	public get onkeydown(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onkeydown') ?? null;
	}

	public set onkeydown(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onkeydown', value);
	}

	public get onkeypress(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onkeypress') ?? null;
	}

	public set onkeypress(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onkeypress', value);
	}

	public get onkeyup(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onkeyup') ?? null;
	}

	public set onkeyup(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onkeyup', value);
	}

	public get onload(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onload') ?? null;
	}

	public set onload(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onload', value);
	}

	public get onloadeddata(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onloadeddata') ?? null;
	}

	public set onloadeddata(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onloadeddata', value);
	}

	public get onloadedmetadata(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onloadedmetadata') ?? null;
	}

	public set onloadedmetadata(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onloadedmetadata', value);
	}

	public get onloadstart(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onloadstart') ?? null;
	}

	public set onloadstart(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onloadstart', value);
	}

	public get onmousedown(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmousedown') ?? null;
	}

	public set onmousedown(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmousedown', value);
	}

	public get onmouseenter(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmouseenter') ?? null;
	}

	public set onmouseenter(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmouseenter', value);
	}

	public get onmouseleave(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmouseleave') ?? null;
	}

	public set onmouseleave(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmouseleave', value);
	}

	public get onmousemove(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmousemove') ?? null;
	}

	public set onmousemove(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmousemove', value);
	}

	public get onmouseout(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmouseout') ?? null;
	}

	public set onmouseout(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmouseout', value);
	}

	public get onmouseover(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmouseover') ?? null;
	}

	public set onmouseover(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmouseover', value);
	}

	public get onmouseup(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmouseup') ?? null;
	}

	public set onmouseup(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmouseup', value);
	}

	public get onmousewheel(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmousewheel') ?? null;
	}

	public set onmousewheel(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmousewheel', value);
	}

	public get onpause(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpause') ?? null;
	}

	public set onpause(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpause', value);
	}

	public get onplay(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onplay') ?? null;
	}

	public set onplay(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onplay', value);
	}

	public get onplaying(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onplaying') ?? null;
	}

	public set onplaying(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onplaying', value);
	}

	public get onprogress(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onprogress') ?? null;
	}

	public set onprogress(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onprogress', value);
	}

	public get onratechange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onratechange') ?? null;
	}

	public set onratechange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onratechange', value);
	}

	public get onreset(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onreset') ?? null;
	}

	public set onreset(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onreset', value);
	}

	public get onresize(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onresize') ?? null;
	}

	public set onresize(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onresize', value);
	}

	public get onscroll(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onscroll') ?? null;
	}

	public set onscroll(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onscroll', value);
	}

	public get onscrollend(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onscrollend') ?? null;
	}

	public set onscrollend(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onscrollend', value);
	}

	public get onsecuritypolicyviolation(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onsecuritypolicyviolation') ?? null;
	}

	public set onsecuritypolicyviolation(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onsecuritypolicyviolation', value);
	}

	public get onseeked(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onseeked') ?? null;
	}

	public set onseeked(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onseeked', value);
	}

	public get onseeking(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onseeking') ?? null;
	}

	public set onseeking(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onseeking', value);
	}

	public get onselect(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onselect') ?? null;
	}

	public set onselect(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onselect', value);
	}

	public get onslotchange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onslotchange') ?? null;
	}

	public set onslotchange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onslotchange', value);
	}

	public get onstalled(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onstalled') ?? null;
	}

	public set onstalled(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onstalled', value);
	}

	public get onsubmit(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onsubmit') ?? null;
	}

	public set onsubmit(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onsubmit', value);
	}

	public get onsuspend(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onsuspend') ?? null;
	}

	public set onsuspend(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onsuspend', value);
	}

	public get ontimeupdate(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ontimeupdate') ?? null;
	}

	public set ontimeupdate(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ontimeupdate', value);
	}

	public get ontoggle(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ontoggle') ?? null;
	}

	public set ontoggle(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ontoggle', value);
	}

	public get onvolumechange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onvolumechange') ?? null;
	}

	public set onvolumechange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onvolumechange', value);
	}

	public get onwaiting(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onwaiting') ?? null;
	}

	public set onwaiting(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onwaiting', value);
	}

	public get onwebkitanimationend(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onwebkitanimationend') ?? null;
	}

	public set onwebkitanimationend(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onwebkitanimationend', value);
	}

	public get onwebkitanimationiteration(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onwebkitanimationiteration') ?? null;
	}

	public set onwebkitanimationiteration(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onwebkitanimationiteration', value);
	}

	public get onwebkitanimationstart(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onwebkitanimationstart') ?? null;
	}

	public set onwebkitanimationstart(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onwebkitanimationstart', value);
	}

	public get onwebkittransitionend(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onwebkittransitionend') ?? null;
	}

	public set onwebkittransitionend(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onwebkittransitionend', value);
	}

	public get onwheel(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onwheel') ?? null;
	}

	public set onwheel(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onwheel', value);
	}

	public get onauxclick(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onauxclick') ?? null;
	}

	public set onauxclick(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onauxclick', value);
	}

	public get ongotpointercapture(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ongotpointercapture') ?? null;
	}

	public set ongotpointercapture(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ongotpointercapture', value);
	}

	public get onlostpointercapture(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onlostpointercapture') ?? null;
	}

	public set onlostpointercapture(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onlostpointercapture', value);
	}

	public get onpointerdown(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointerdown') ?? null;
	}

	public set onpointerdown(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointerdown', value);
	}

	public get onpointermove(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointermove') ?? null;
	}

	public set onpointermove(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointermove', value);
	}

	public get onpointerup(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointerup') ?? null;
	}

	public set onpointerup(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointerup', value);
	}

	public get onpointercancel(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointercancel') ?? null;
	}

	public set onpointercancel(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointercancel', value);
	}

	public get onpointerover(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointerover') ?? null;
	}

	public set onpointerover(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointerover', value);
	}

	public get onpointerout(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointerout') ?? null;
	}

	public set onpointerout(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointerout', value);
	}

	public get onpointerenter(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointerenter') ?? null;
	}

	public set onpointerenter(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointerenter', value);
	}

	public get onpointerleave(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointerleave') ?? null;
	}

	public set onpointerleave(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointerleave', value);
	}

	public get onselectstart(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onselectstart') ?? null;
	}

	public set onselectstart(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onselectstart', value);
	}

	public get onselectionchange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onselectionchange') ?? null;
	}

	public set onselectionchange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onselectionchange', value);
	}

	public get onanimationcancel(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onanimationcancel') ?? null;
	}

	public set onanimationcancel(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onanimationcancel', value);
	}

	public get onanimationend(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onanimationend') ?? null;
	}

	public set onanimationend(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onanimationend', value);
	}

	public get onanimationiteration(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onanimationiteration') ?? null;
	}

	public set onanimationiteration(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onanimationiteration', value);
	}

	public get onanimationstart(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onanimationstart') ?? null;
	}

	public set onanimationstart(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onanimationstart', value);
	}

	public get ontransitionrun(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ontransitionrun') ?? null;
	}

	public set ontransitionrun(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ontransitionrun', value);
	}

	public get ontransitionstart(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ontransitionstart') ?? null;
	}

	public set ontransitionstart(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ontransitionstart', value);
	}

	public get ontransitionend(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ontransitionend') ?? null;
	}

	public set ontransitionend(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ontransitionend', value);
	}

	public get ontransitioncancel(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ontransitioncancel') ?? null;
	}

	public set ontransitioncancel(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ontransitioncancel', value);
	}

	public get onbeforexrselect(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onbeforexrselect') ?? null;
	}

	public set onbeforexrselect(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onbeforexrselect', value);
	}

	public get onafterprint(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onafterprint') ?? null;
	}

	public set onafterprint(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onafterprint', value);
	}

	public get onbeforeprint(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onbeforeprint') ?? null;
	}

	public set onbeforeprint(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onbeforeprint', value);
	}

	public get onbeforeunload(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onbeforeunload') ?? null;
	}

	public set onbeforeunload(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onbeforeunload', value);
	}

	public get onhashchange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onhashchange') ?? null;
	}

	public set onhashchange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onhashchange', value);
	}

	public get onlanguagechange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onlanguagechange') ?? null;
	}

	public set onlanguagechange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onlanguagechange', value);
	}

	public get onmessage(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmessage') ?? null;
	}

	public set onmessage(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmessage', value);
	}

	public get onmessageerror(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onmessageerror') ?? null;
	}

	public set onmessageerror(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onmessageerror', value);
	}

	public get onoffline(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onoffline') ?? null;
	}

	public set onoffline(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onoffline', value);
	}

	public get ononline(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ononline') ?? null;
	}

	public set ononline(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ononline', value);
	}

	public get onpagehide(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpagehide') ?? null;
	}

	public set onpagehide(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpagehide', value);
	}

	public get onpageshow(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpageshow') ?? null;
	}

	public set onpageshow(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpageshow', value);
	}

	public get onpopstate(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpopstate') ?? null;
	}

	public set onpopstate(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpopstate', value);
	}

	public get onrejectionhandled(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onrejectionhandled') ?? null;
	}

	public set onrejectionhandled(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onrejectionhandled', value);
	}

	public get onstorage(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onstorage') ?? null;
	}

	public set onstorage(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onstorage', value);
	}

	public get onunhandledrejection(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onunhandledrejection') ?? null;
	}

	public set onunhandledrejection(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onunhandledrejection', value);
	}

	public get onunload(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onunload') ?? null;
	}

	public set onunload(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onunload', value);
	}

	public get ondevicemotion(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondevicemotion') ?? null;
	}

	public set ondevicemotion(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondevicemotion', value);
	}

	public get ondeviceorientation(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondeviceorientation') ?? null;
	}

	public set ondeviceorientation(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondeviceorientation', value);
	}

	public get ondeviceorientationabsolute(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ondeviceorientationabsolute') ?? null;
	}

	public set ondeviceorientationabsolute(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ondeviceorientationabsolute', value);
	}

	public get onpointerrawupdate(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpointerrawupdate') ?? null;
	}

	public set onpointerrawupdate(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpointerrawupdate', value);
	}

	public get onpageswap(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpageswap') ?? null;
	}

	public set onpageswap(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpageswap', value);
	}

	public get onpagereveal(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onpagereveal') ?? null;
	}

	public set onpagereveal(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onpagereveal', value);
	}

	public get onscrollsnapchange(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onscrollsnapchange') ?? null;
	}

	public set onscrollsnapchange(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onscrollsnapchange', value);
	}

	public get onscrollsnapchanging(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('onscrollsnapchanging') ?? null;
	}

	public set onscrollsnapchanging(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('onscrollsnapchanging', value);
	}

	public get ongamepadconnected(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ongamepadconnected') ?? null;
	}

	public set ongamepadconnected(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ongamepadconnected', value);
	}

	public get ongamepaddisconnected(): ((event: Event) => void) | null {
		return this[PropertySymbol.propertyEventListeners].get('ongamepaddisconnected') ?? null;
	}

	public set ongamepaddisconnected(value: ((event: Event) => void) | null) {
		this[PropertySymbol.propertyEventListeners].set('ongamepaddisconnected', value);
	}

	/* eslint-enable jsdoc/require-jsdoc */

	/**
	 * Returns self.
	 *
	 * @returns Self.
	 */
	public get self(): BrowserWindow {
		return this[PropertySymbol.self]!;
	}

	/**
	 * Sets self.
	 *
	 * @param self Self.
	 */
	public set self(self: BrowserWindow | null) {
		this[PropertySymbol.self] = self;
	}

	/**
	 * Returns top.
	 *
	 * @returns Top.
	 */
	public get top(): BrowserWindow {
		return this[PropertySymbol.top]!;
	}

	/**
	 * Returns parent.
	 *
	 * @returns Parent.
	 */
	public get parent(): BrowserWindow {
		return this[PropertySymbol.parent]!;
	}

	/**
	 * Sets parent.
	 *
	 * @param parent Parent.
	 */
	public set parent(parent: BrowserWindow | null) {
		this[PropertySymbol.parent] = parent;
	}

	/**
	 * Returns frames.
	 *
	 * @returns Frames.
	 */
	public get frames(): BrowserWindow {
		return this[PropertySymbol.frames];
	}

	/**
	 * Sets frames.
	 *
	 * @param frames Frames.
	 */
	public set frames(frames: BrowserWindow | null) {
		this[PropertySymbol.parent] = frames;
	}

	/**
	 * Returns location.
	 */
	public get location(): Location {
		return this[PropertySymbol.location];
	}

	/**
	 * Returns location.
	 *
	 * @param href Href.
	 */
	public set location(href: string) {
		this[PropertySymbol.location].href = href;
	}

	/**
	 * Returns history.
	 */
	public get history(): History {
		return this[PropertySymbol.history];
	}

	/**
	 * Returns navigator.
	 */
	public get navigator(): Navigator {
		return this[PropertySymbol.navigator];
	}

	/**
	 * Returns screen.
	 */
	public get screen(): Screen {
		return this[PropertySymbol.screen];
	}

	/**
	 * Returns session storage.
	 */
	public get sessionStorage(): Storage {
		return this[PropertySymbol.sessionStorage];
	}

	/**
	 * Returns local storage.
	 */
	public get localStorage(): Storage {
		return this[PropertySymbol.localStorage];
	}

	/**
	 * Returns opener.
	 *
	 * @returns Opener.
	 */
	public get opener(): BrowserWindow | CrossOriginBrowserWindow | null {
		return this.#browserFrame[PropertySymbol.openerWindow];
	}

	/**
	 * The number of pixels that the document is currently scrolled horizontally.
	 *
	 * @returns Scroll X.
	 */
	public get scrollX(): number {
		return this.document?.documentElement?.scrollLeft ?? 0;
	}

	/**
	 * The read-only Window property pageXOffset is an alias for scrollX.
	 *
	 * @returns Scroll X.
	 */
	public get pageXOffset(): number {
		return this.scrollX;
	}

	/**
	 * The number of pixels that the document is currently scrolled vertically.
	 *
	 * @returns Scroll Y.
	 */
	public get scrollY(): number {
		return this.document?.documentElement?.scrollTop ?? 0;
	}

	/**
	 * The read-only Window property pageYOffset is an alias for scrollY.
	 *
	 * @returns Scroll Y.
	 */
	public get pageYOffset(): number {
		return this.scrollY;
	}

	/**
	 * The CSS interface holds useful CSS-related methods.
	 *
	 * @returns CSS interface.
	 */
	public get CSS(): CSS {
		return new CSS();
	}

	/**
	 * Returns inner width.
	 *
	 * @returns Inner width.
	 */
	public get innerWidth(): number {
		if (this.#innerWidth === null) {
			return this.#browserFrame.page.viewport.width;
		}
		return this.#innerWidth;
	}

	/**
	 * Sets inner width.
	 *
	 * @param value Inner width.
	 */
	public set innerWidth(value: number) {
		this.#innerWidth = value;
	}

	/**
	 * Returns inner height.
	 *
	 * @returns Inner height.
	 */
	public get innerHeight(): number {
		// It seems like this value can be defined according to spec, but changing it has no effect on the actual viewport.
		if (this.#innerHeight === null) {
			return this.#browserFrame.page.viewport.height;
		}
		return this.#innerHeight;
	}

	/**
	 * Sets inner height.
	 *
	 * @param value Inner height.
	 */
	public set innerHeight(value: number) {
		this.#innerHeight = value;
	}

	/**
	 * Returns outer width.
	 *
	 * @returns Outer width.
	 */
	public get outerWidth(): number {
		// It seems like this value can be defined according to spec, but changing it has no effect on the actual viewport.
		if (this.#outerWidth === null) {
			return this.#browserFrame.page.viewport.width;
		}
		return this.#outerWidth;
	}

	/**
	 * Sets outer width.
	 *
	 * @param value Outer width.
	 */
	public set outerWidth(value: number) {
		this.#outerWidth = value;
	}

	/**
	 * Returns outer height.
	 *
	 * @returns Outer height.
	 */
	public get outerHeight(): number {
		if (this.#outerHeight === null) {
			return this.#browserFrame.page.viewport.height;
		}
		return this.#outerHeight;
	}

	/**
	 * Sets outer height.
	 *
	 * @param value Outer height.
	 */
	public set outerHeight(value: number) {
		this.#outerHeight = value;
	}

	/**
	 * Returns device pixel ratio.
	 *
	 * @returns Device pixel ratio.
	 */
	public get devicePixelRatio(): number {
		// It seems like this value can be defined according to spec, but changing it has no effect on the actual viewport.
		if (this.#devicePixelRatio === null) {
			return this.#browserFrame.page.viewport.devicePixelRatio;
		}
		return this.#devicePixelRatio;
	}

	/**
	 * Sets device pixel ratio.
	 *
	 * @param value Device pixel ratio.
	 */
	public set devicePixelRatio(value: number) {
		this.#devicePixelRatio = value;
	}

	/**
	 * Returns an object containing the values of all CSS properties of an element.
	 *
	 * @param element Element.
	 * @returns CSS style declaration.
	 */
	public getComputedStyle(element: Element): CSSStyleDeclaration {
		element[PropertySymbol.computedStyle] =
			element[PropertySymbol.computedStyle] ||
			new CSSStyleDeclaration(PropertySymbol.illegalConstructor, this, { element, computed: true });
		return element[PropertySymbol.computedStyle];
	}

	/**
	 * Returns selection.
	 *
	 * @returns Selection.
	 */
	public getSelection(): Selection {
		return this.document.getSelection();
	}

	/**
	 * Scrolls to a particular set of coordinates.
	 *
	 * @param x X position or options object.
	 * @param y Y position.
	 */
	public scroll(x: IScrollToOptions | number, y?: number): void {
		if (typeof x !== 'object' && arguments.length === 1) {
			throw new this.TypeError(
				"Failed to execute 'scroll' on 'Window': The provided value is not of type 'ScrollToOptions'."
			);
		}

		const options = typeof x === 'object' ? x : { left: x, top: y };

		if (options.behavior === 'smooth') {
			this.setTimeout(() => {
				if (options.top !== undefined) {
					const top = Number(options.top);
					this.document.documentElement.scrollTop = isNaN(top) ? 0 : top;
				}
				if (options.left !== undefined) {
					const left = Number(options.left);
					this.document.documentElement.scrollLeft = isNaN(left) ? 0 : left;
				}
			});
		} else {
			if (options.top !== undefined) {
				const top = Number(options.top);
				this.document.documentElement.scrollTop = isNaN(top) ? 0 : top;
			}
			if (options.left !== undefined) {
				const left = Number(options.left);
				this.document.documentElement.scrollLeft = isNaN(left) ? 0 : left;
			}
		}
	}

	/**
	 * Scrolls to a particular set of coordinates.
	 *
	 * @param x X position or options object.
	 * @param y Y position.
	 */
	public scrollTo(x: IScrollToOptions | number, y?: number): void {
		if (typeof x !== 'object' && arguments.length === 1) {
			throw new this.TypeError(
				"Failed to execute 'scrollTo' on 'Window': The provided value is not of type 'ScrollToOptions'."
			);
		}
		this.scroll(x, y);
	}

	/**
	 * Scrolls by a relative amount from the current position.
	 *
	 * @param x Pixels to scroll by from top or scroll options object.
	 * @param y Pixels to scroll by from left.
	 */
	public scrollBy(x: IScrollToOptions | number, y?: number): void {
		if (typeof x !== 'object' && arguments.length === 1) {
			throw new this.TypeError(
				"Failed to execute 'scrollBy' on 'Window': The provided value is not of type 'ScrollToOptions'."
			);
		}
		const options = typeof x === 'object' ? x : { left: x, top: y };
		this.scroll({
			left: this.document.documentElement.scrollLeft + (options.left ?? 0),
			top: this.document.documentElement.scrollTop + (options.top ?? 0),
			behavior: options.behavior
		});
	}

	/**
	 * Shifts focus away from the window.
	 */
	public blur(): void {
		// TODO: Implement.
	}

	/**
	 * Gives focus to the window.
	 */
	public focus(): void {
		// TODO: Implement.
	}

	/**
	 * Loads a specified resource into a new or existing browsing context (that is, a tab, a window, or an iframe) under a specified name.
	 *
	 * @param [url] URL.
	 * @param [target] Target.
	 * @param [features] Window features.
	 * @returns Window.
	 */
	public open(
		url?: string,
		target?: string,
		features?: string
	): BrowserWindow | CrossOriginBrowserWindow | null {
		return WindowPageOpenUtility.openPage(this.#browserFrame, {
			url,
			target,
			features
		});
	}

	/**
	 * Closes the window.
	 */
	public close(): void {
		// Users can only close the main frame of a page (not child frames such as iframes).
		if (
			this.#browserFrame.page.mainFrame === this.#browserFrame &&
			// Scripts may close only the windows that were opened by them.
			// In this case, this means windows that have been opened by another window
			this.#browserFrame[PropertySymbol.openerWindow]
		) {
			this[PropertySymbol.destroy]();
			this.#browserFrame.page.close();
		}
	}

	/**
	 * Returns a new MediaQueryList object that can then be used to determine if the document matches the media query string.
	 *
	 * @param mediaQueryString A string specifying the media query to parse into a MediaQueryList.
	 * @returns A new MediaQueryList.
	 */
	public matchMedia(mediaQueryString: string): MediaQueryList {
		return new MediaQueryList({ window: this, media: mediaQueryString });
	}

	/**
	 * Returns a promise that fulfills with a ScreenDetails object instance.
	 *
	 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/getScreenDetails
	 * @returns A Promise that fulfills with a ScreenDetails object instance.
	 */
	public getScreenDetails(): Promise<ScreenDetails> {
		return Promise.resolve(new ScreenDetails());
	}

	/**
	 * Sets a timer which executes a function once the timer expires.
	 *
	 * @param callback Function to be executed.
	 * @param [delay=0] Delay in ms.
	 * @param args Arguments passed to the callback function.
	 * @returns Timeout ID.
	 */
	public setTimeout(callback: Function, delay = 0, ...args: unknown[]): NodeJS.Timeout {
		if (this.closed) {
			return <NodeJS.Timeout>{};
		}

		const settings = this.#browserFrame.page.context.browser.settings;

		if (settings.timer.preventTimerLoops) {
			const stack = new Error().stack!;
			const timerLoopStacks = this.#timerLoopStacks;
			const timerLoopLimits = this.#timerLoopLimits;
			const index = timerLoopStacks.indexOf(stack);
			if (index !== -1) {
				timerLoopLimits[index].timeout++;
				if (
					timerLoopLimits[index].timeout >=
					(settings.timer.preventTimerLoops === true
						? 1
						: (settings.timer.preventTimerLoops.timeout ?? 1))
				) {
					return <NodeJS.Timeout>{};
				}
			} else {
				timerLoopStacks.push(stack);
				timerLoopLimits.push({
					timeout: 0,
					requestAnimationFrame: 0
				});
			}
		}

		// We can group timeouts with a delay of 0 into one timeout to improve performance.
		// Grouping timeouts will also improve the performance of the async task manager.
		// It also makes the async task manager more stable as many timeouts may cause waitUntilComplete() to be resolved too early.
		if (!delay) {
			const zeroDelayTimeout = this.#zeroDelayTimeout;

			if (!zeroDelayTimeout.timeouts) {
				const useTryCatch =
					!settings ||
					(!settings.disableErrorCapturing &&
						settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch);

				const id = TIMER.setTimeout(() => {
					// We need to call endTimer() before the callback as the callback might throw an error.
					this.#browserFrame[PropertySymbol.asyncTaskManager].endTimer(id);
					const timeouts = zeroDelayTimeout.timeouts!;
					zeroDelayTimeout.timeouts = null;
					for (const timeout of timeouts) {
						if (useTryCatch) {
							let result: any;
							try {
								result = timeout.callback();
							} catch (error) {
								this[PropertySymbol.dispatchError](<Error>error);
							}
							if (result instanceof Promise) {
								result.catch((error: Error) => this[PropertySymbol.dispatchError](error));
							}
						} else {
							timeout.callback();
						}
					}
				}, 0);

				zeroDelayTimeout.timeouts = [];
				this.#browserFrame[PropertySymbol.asyncTaskManager].startTimer(id);
			}

			const timeout = new Timeout(() => callback(...args));

			zeroDelayTimeout.timeouts.push(timeout);

			return <NodeJS.Timeout>(<unknown>timeout);
		}

		const useTryCatch =
			!settings ||
			(!settings.disableErrorCapturing &&
				settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch);

		const id = TIMER.setTimeout(
			() => {
				// We need to call endTimer() before the callback as the callback might throw an error.
				this.#browserFrame[PropertySymbol.asyncTaskManager].endTimer(id);
				if (useTryCatch) {
					let result: any;
					try {
						result = callback(...args);
					} catch (error) {
						this[PropertySymbol.dispatchError](<Error>error);
					}
					if (result instanceof Promise) {
						result.catch((error: Error) => this[PropertySymbol.dispatchError](error));
					}
				} else {
					callback(...args);
				}
			},
			settings?.timer.maxTimeout !== -1 && delay && delay > settings?.timer.maxTimeout
				? settings?.timer.maxTimeout
				: delay
		);
		this.#browserFrame[PropertySymbol.asyncTaskManager].startTimer(id);
		return id;
	}

	/**
	 * Cancels a timeout previously established by calling setTimeout().
	 *
	 * @param id ID of the timeout.
	 */
	public clearTimeout(id: NodeJS.Timeout): void {
		if (id && id instanceof Timeout) {
			const zeroDelayTimeout = this.#zeroDelayTimeout;
			if (!zeroDelayTimeout.timeouts) {
				return;
			}
			const index = zeroDelayTimeout.timeouts.indexOf(<Timeout>(<unknown>id));
			if (index !== -1) {
				zeroDelayTimeout.timeouts.splice(index, 1);
			}
			return;
		}
		// We need to make sure that the ID is a Timeout object, otherwise Node.js might throw an error.
		// This is only necessary if we are in a Node.js environment.
		if (IS_NODE_JS_TIMEOUT_ENVIRONMENT && (!id || id.constructor.name !== 'Timeout')) {
			return;
		}
		TIMER.clearTimeout(id);
		this.#browserFrame[PropertySymbol.asyncTaskManager].endTimer(id);
	}

	/**
	 * Calls a function with a fixed time delay between each call.
	 *
	 * @param callback Function to be executed.
	 * @param [delay=0] Delay in ms.
	 * @param args Arguments passed to the callback function.
	 * @returns Interval ID.
	 */
	public setInterval(callback: Function, delay = 0, ...args: unknown[]): NodeJS.Timeout {
		if (this.closed) {
			return <NodeJS.Timeout>{};
		}
		const settings = this.#browserFrame.page.context.browser.settings;
		const useTryCatch =
			!settings ||
			(!settings.disableErrorCapturing &&
				settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch);
		let iterations = 0;
		const id = TIMER.setInterval(
			() => {
				if (useTryCatch) {
					let result: any;
					try {
						result = callback(...args);
					} catch (error) {
						this.clearInterval(id);
						this[PropertySymbol.dispatchError](<Error>error);
					}
					if (result instanceof Promise) {
						result.catch((error: Error) => {
							this.clearInterval(id);
							this[PropertySymbol.dispatchError](error);
						});
					}
				} else {
					callback(...args);
				}
				if (settings?.timer.maxIntervalIterations !== -1) {
					if (iterations >= settings?.timer.maxIntervalIterations) {
						this.clearInterval(id);
					}
					iterations++;
				}
			},
			settings?.timer.maxIntervalTime !== -1 && delay && delay > settings?.timer.maxIntervalTime
				? settings?.timer.maxIntervalTime
				: delay
		);
		this.#browserFrame[PropertySymbol.asyncTaskManager].startTimer(id);
		return id;
	}

	/**
	 * Cancels a timed repeating action which was previously established by a call to setInterval().
	 *
	 * @param id ID of the interval.
	 */
	public clearInterval(id: NodeJS.Timeout): void {
		// We need to make sure that the ID is a Timeout object, otherwise Node.js might throw an error.
		// This is only necessary if we are in a Node.js environment.
		if (IS_NODE_JS_TIMEOUT_ENVIRONMENT && (!id || id.constructor.name !== 'Timeout')) {
			return;
		}
		TIMER.clearInterval(id);
		this.#browserFrame[PropertySymbol.asyncTaskManager].endTimer(id);
	}

	/**
	 * Mock animation frames with timeouts.
	 *
	 * @param callback Callback.
	 * @returns ID.
	 */
	public requestAnimationFrame(callback: (timestamp: number) => void): NodeJS.Immediate {
		if (this.closed) {
			return <NodeJS.Immediate>{};
		}
		const settings = this.#browserFrame.page.context.browser.settings;

		if (settings.timer.preventTimerLoops) {
			const stack = new Error().stack!;
			const timerLoopStacks = this.#timerLoopStacks;
			const timerLoopLimits = this.#timerLoopLimits;
			const index = timerLoopStacks.indexOf(stack);
			if (index !== -1) {
				timerLoopLimits[index].requestAnimationFrame++;
				if (
					timerLoopLimits[index].requestAnimationFrame >=
					(settings.timer.preventTimerLoops === true
						? 1
						: (settings.timer.preventTimerLoops.requestAnimationFrame ?? 1))
				) {
					return <NodeJS.Immediate>{};
				}
			} else {
				timerLoopStacks.push(stack);
				timerLoopLimits.push({
					timeout: 0,
					requestAnimationFrame: 0
				});
			}
		}

		const useTryCatch =
			!settings ||
			(!settings.disableErrorCapturing &&
				settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch);
		const id = TIMER.setImmediate(() => {
			// We need to call endImmediate() before the callback as the callback might throw an error.
			this.#browserFrame[PropertySymbol.asyncTaskManager].endImmediate(id);
			if (useTryCatch) {
				let result: any;
				try {
					result = callback(this.performance.now());
				} catch (error) {
					this[PropertySymbol.dispatchError](<Error>error);
				}
				if (result instanceof Promise) {
					result.catch((error: Error) => this[PropertySymbol.dispatchError](error));
				}
			} else {
				callback(this.performance.now());
			}
		});
		this.#browserFrame[PropertySymbol.asyncTaskManager].startImmediate(id);
		return id;
	}

	/**
	 * Mock animation frames with timeouts.
	 *
	 * @param id ID.
	 */
	public cancelAnimationFrame(id: NodeJS.Immediate): void {
		// We need to make sure that the ID is an Immediate object, otherwise Node.js might throw an error.
		// This is only necessary if we are in a Node.js environment.
		if (IS_NODE_JS_TIMEOUT_ENVIRONMENT && (!id || id.constructor.name !== 'Immediate')) {
			return;
		}
		TIMER.clearImmediate(id);
		this.#browserFrame[PropertySymbol.asyncTaskManager].endImmediate(id);
	}

	/**
	 * Queues a microtask to be executed at a safe time prior to control returning to the browser's event loop.
	 *
	 * @param callback Function to be executed.
	 */
	public queueMicrotask(callback: Function): void {
		if (this.closed) {
			return;
		}
		let isAborted = false;
		const taskId = this.#browserFrame[PropertySymbol.asyncTaskManager].startTask(
			() => (isAborted = true)
		);
		const settings = this.#browserFrame.page.context.browser.settings;
		const useTryCatch =
			!settings ||
			(!settings.disableErrorCapturing &&
				settings.errorCapture === BrowserErrorCaptureEnum.tryAndCatch);
		TIMER.queueMicrotask(() => {
			if (!isAborted) {
				// We need to call endTask() before the callback as the callback might throw an error.
				this.#browserFrame[PropertySymbol.asyncTaskManager].endTask(taskId);
				if (useTryCatch) {
					let result: any;
					try {
						result = callback();
					} catch (error) {
						this[PropertySymbol.dispatchError](<Error>error);
					}
					if (result instanceof Promise) {
						result.catch((error: Error) => this[PropertySymbol.dispatchError](error));
					}
				} else {
					callback();
				}
			}
		});
	}

	/**
	 * This method provides an easy, logical way to fetch resources asynchronously across the network.
	 *
	 * @param url URL.
	 * @param [init] Init.
	 * @returns Promise.
	 */
	public async fetch(url: TRequestInfo, init?: IRequestInit): Promise<Response> {
		if (this.closed) {
			return Promise.reject(
				new this.DOMException(
					"Failed to execute 'fetch' on 'Window': The window is closed.",
					DOMExceptionNameEnum.invalidStateError
				)
			);
		}
		return await new Fetch({
			browserFrame: this.#browserFrame,
			window: this,
			url,
			init
		}).send();
	}

	/**
	 * Creates a Base64-encoded ASCII string from a binary string (i.e., a string in which each character in the string is treated as a byte of binary data).
	 *
	 * @see https://developer.mozilla.org/en-US/docs/Web/API/btoa
	 * @param data Binay data.
	 * @returns Base64-encoded string.
	 */
	public btoa(data: unknown): string {
		return Base64.btoa(data);
	}

	/**
	 * Decodes a string of data which has been encoded using Base64 encoding.
	 *
	 * @see https://developer.mozilla.org/en-US/docs/Web/API/atob
	 * @see https://infra.spec.whatwg.org/#forgiving-base64-encode.
	 * @see Https://html.spec.whatwg.org/multipage/webappapis.html#btoa.
	 * @param data Binay string.
	 * @returns An ASCII string containing decoded data from encodedData.
	 */
	public atob(data: unknown): string {
		return Base64.atob(data);
	}

	/**
	 * Safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
	 *
	 * @param message Message.
	 * @param [targetOrigin=*] Target origin.
	 * @param _transfer Transfer. Not implemented.
	 */
	public postMessage(message: unknown, targetOrigin = '*', _transfer?: unknown[]): void {
		// TODO: Implement transfer.

		if (this.closed) {
			return;
		}

		if (targetOrigin && targetOrigin !== '*' && this.location.origin !== targetOrigin) {
			throw new this.DOMException(
				`Failed to execute 'postMessage' on 'Window': The target origin provided ('${targetOrigin}') does not match the recipient window\'s origin ('${this.location.origin}').`,
				DOMExceptionNameEnum.securityError
			);
		}

		try {
			JSON.stringify(message);
		} catch (error) {
			throw new this.DOMException(
				`Failed to execute 'postMessage' on 'Window': The provided message cannot be serialized.`,
				DOMExceptionNameEnum.invalidStateError
			);
		}

		this.setTimeout(() =>
			this.dispatchEvent(
				new MessageEvent('message', {
					data: message,
					origin: this.#browserFrame.parentFrame
						? this.#browserFrame.parentFrame.window.location.origin
						: this.#browserFrame.window.location.origin,
					source: this.#browserFrame.parentFrame
						? this.#browserFrame.parentFrame.window
						: this.#browserFrame.window,
					lastEventId: ''
				})
			)
		);
	}

	/**
	 * Resizes the window.
	 *
	 * @param width Width.
	 * @param height Height.
	 */
	public resizeTo(width: number, height: number): void {
		if (this.closed) {
			return;
		}

		if (!width || !height) {
			throw new this.DOMException(
				`Failed to execute 'resizeTo' on 'Window': 2 arguments required, but only ${arguments.length} present.`
			);
		}

		// We can only resize the window if it is a popup.
		if (this.#browserFrame[PropertySymbol.popup]) {
			this.#browserFrame.page.setViewport({ width, height });
		}
	}

	/**
	 * Resizes the current window by a specified amount.
	 *
	 * @param width Width.
	 * @param height Height.
	 */
	public resizeBy(width: number, height: number): void {
		if (this.closed) {
			return;
		}

		if (!width || !height) {
			throw new this.DOMException(
				`Failed to execute 'resizeBy' on 'Window': 2 arguments required, but only ${arguments.length} present.`
			);
		}

		// We can only resize the window if it is a popup.
		if (this.#browserFrame[PropertySymbol.popup]) {
			const viewport = this.#browserFrame.page.viewport;
			this.#browserFrame.page.setViewport({
				width: viewport.width + width,
				height: viewport.height + height
			});
		}
	}

	/**
	 * Creates a bitmap from a given source, optionally cropped to contain only a portion of that source.
	 *
	 * @param source Source.
	 * @param [sx] X.
	 * @param [sy] X.
	 * @param [sw] Width.
	 * @param [sh] Height.
	 * @param [options] Options.
	 */
	public async createImageBitmap(
		source: TImageBitmapSource,
		sx?: number | IImageBitmapOptions,
		sy?: number,
		sw?: number | IImageBitmapOptions,
		sh?: number,
		options?: IImageBitmapOptions
	): Promise<ImageBitmap> {
		return new ImageBitmap(
			PropertySymbol.illegalConstructor,
			this,
			source,
			sx,
			sy,
			sw,
			sh,
			options
		);
	}

	/**
	 * Dispatches an error event and outputs the error to the console.
	 *
	 * @param error Error.
	 */
	public [PropertySymbol.dispatchError](error: Error): void {
		this.#browserFrame.page.console.error(error);
		this.dispatchEvent(new ErrorEvent('error', { message: error.message, error }));
	}

	/**
	 * Evaluates code in a VM context.
	 *
	 * @param code Code.
	 * @param [options] Options.
	 * @param [options.filename] Filename.
	 * @returns any.
	 */
	public [PropertySymbol.evaluateScript](code: string, options?: { filename?: string }): any {
		return new VM.Script(code, options).runInContext(this);
	}

	/**
	 * Setup of VM context.
	 */
	protected [PropertySymbol.setupVMContext](): void {
		if (!VM.isContext(this)) {
			VM.createContext(this);

			// Sets global properties from the VM to the Window object.
			// Otherwise "this.Array" will be undefined for example.
			VMGlobalPropertyScript.runInContext(this);
		}
	}

	/**
	 * Checks if the JavaScript execution environment is secure and outputs a warning if not.
	 */
	protected [PropertySymbol.validateJavaScriptExecutionEnvironment](): void {
		const browserFrame = this.#browserFrame;
		if (
			(IS_CODE_GENERATION_FROM_STRINGS_ENVIRONMENT || IS_UNFROZEN_INTRINSICS_ENVIRONMENT) &&
			browserFrame.page.context.browser.settings.enableJavaScriptEvaluation &&
			!browserFrame.page.context.browser.settings.suppressInsecureJavaScriptEnvironmentWarning &&
			!browserFrame.page.context.browser.settings.suppressCodeGenerationFromStringsWarning
		) {
			// eslint-disable-next-line no-console
			console.warn(
				'\nWarning! Happy DOM has JavaScript evaluation enabled and is running in an insecure environment.' +
					'\n\nA VM Context is not an isolated environment, and if you run untrusted code you are at risk of RCE (Remote Code Execution) attacks. The attacker can escape the VM and run code at process level.' +
					'\n\nIt is recommended to disable code generation and freeze all builtins at process level by running node with the flags "--disallow-code-generation-from-strings" and "--frozen-intrinsics".' +
					' You can suppress this warning by setting "suppressInsecureJavaScriptEnvironmentWarning" to "true" at your own risk.' +
					'\n\nFor more information, see https://github.com/capricorn86/happy-dom/wiki/JavaScript-Evaluation-Warning\n\n'
			);
		}
	}

	/**
	 * @override
	 */
	public override [PropertySymbol.destroy](): void {
		if (this.closed) {
			return;
		}

		super[PropertySymbol.destroy]();

		(<boolean>this.closed) = true;

		const mutationObservers = this[PropertySymbol.mutationObservers];

		for (const mutationObserver of mutationObservers) {
			if (mutationObserver[PropertySymbol.destroy]) {
				mutationObserver[PropertySymbol.destroy]();
			}
		}

		this[PropertySymbol.mutationObservers] = [];

		for (const webSocket of this[PropertySymbol.openWebSockets]) {
			webSocket[PropertySymbol.destroy]();
		}

		this[PropertySymbol.openWebSockets] = [];

		this.document[PropertySymbol.destroy]();

		// Create some empty elements for scripts that are still running.
		const htmlElement = this.document.createElement('html');
		const headElement = this.document.createElement('head');
		const bodyElement = this.document.createElement('body');

		htmlElement.appendChild(headElement);
		htmlElement.appendChild(bodyElement);

		this.document[PropertySymbol.appendChild](htmlElement);

		if (this.location[PropertySymbol.destroy]) {
			this.location[PropertySymbol.destroy]();
		}

		if (this.customElements[PropertySymbol.destroy]) {
			this.customElements[PropertySymbol.destroy]();
		}

		if (this.history[PropertySymbol.destroy]) {
			this.history[PropertySymbol.destroy]();
		}

		this[PropertySymbol.modules].json.clear();
		this[PropertySymbol.modules].css.clear();
		this[PropertySymbol.modules].esm.clear();

		this.document[PropertySymbol.preloads].clear();

		this.document[PropertySymbol.activeElement] = null;
		this.document[PropertySymbol.nextActiveElement] = null;
		this.document[PropertySymbol.currentScript] = null;
		this.document[PropertySymbol.selection] = null;

		// Clear parent/top references to break circular references
		this[PropertySymbol.parent] = null;
		this[PropertySymbol.top] = null;

		WindowBrowserContext.removeWindowBrowserFrameRelation(this);
	}

	/**
	 * Binds methods to a window as scope.
	 */
	public [PropertySymbol.bindMethods](): void {
		for (const _class of [BrowserWindow, EventTarget]) {
			const propertyDescriptors = Object.getOwnPropertyDescriptors(_class.prototype);
			const keys: Array<string | symbol> = Object.keys(propertyDescriptors);

			for (const key of keys) {
				const descriptor = propertyDescriptors[<string>key];
				if (descriptor.get || descriptor.set) {
					Object.defineProperty(this, key, {
						...descriptor,
						get: descriptor.get?.bind(this),
						set: descriptor.set?.bind(this)
					});
				} else if (
					key !== 'constructor' &&
					typeof descriptor.value === 'function' &&
					!descriptor.value.toString().startsWith('class ')
				) {
					Object.defineProperty(this, key, {
						...descriptor,
						value: descriptor.value.bind(this)
					});
				}
			}
		}
	}
}
