summaryrefslogtreecommitdiff
path: root/packages/webapi/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/webapi/src')
-rw-r--r--packages/webapi/src/exclusions.ts17
-rw-r--r--packages/webapi/src/inheritence.ts27
-rw-r--r--packages/webapi/src/lib/Alert.ts3
-rw-r--r--packages/webapi/src/lib/AnimationFrame.ts35
-rw-r--r--packages/webapi/src/lib/Base64.ts7
-rw-r--r--packages/webapi/src/lib/CanvasRenderingContext2D.ts182
-rw-r--r--packages/webapi/src/lib/CharacterData.ts36
-rw-r--r--packages/webapi/src/lib/ContextEvent.ts52
-rw-r--r--packages/webapi/src/lib/CustomElementRegistry.ts58
-rw-r--r--packages/webapi/src/lib/CustomEvent.ts24
-rw-r--r--packages/webapi/src/lib/DOMException.ts40
-rw-r--r--packages/webapi/src/lib/Document.ts159
-rw-r--r--packages/webapi/src/lib/Element.ts118
-rw-r--r--packages/webapi/src/lib/HTMLCanvasElement.ts57
-rw-r--r--packages/webapi/src/lib/HTMLImageElement.ts16
-rw-r--r--packages/webapi/src/lib/IdleCallback.ts35
-rw-r--r--packages/webapi/src/lib/Image.ts15
-rw-r--r--packages/webapi/src/lib/ImageData.ts75
-rw-r--r--packages/webapi/src/lib/MediaQueryList.ts37
-rw-r--r--packages/webapi/src/lib/Node.ts166
-rw-r--r--packages/webapi/src/lib/Object.ts20
-rw-r--r--packages/webapi/src/lib/Observer.ts41
-rw-r--r--packages/webapi/src/lib/OffscreenCanvas.ts80
-rw-r--r--packages/webapi/src/lib/Promise.ts29
-rw-r--r--packages/webapi/src/lib/RelativeIndexingMethod.ts32
-rw-r--r--packages/webapi/src/lib/Storage.ts51
-rw-r--r--packages/webapi/src/lib/String.ts22
-rw-r--r--packages/webapi/src/lib/StyleSheet.ts24
-rw-r--r--packages/webapi/src/lib/Timeout.ts24
-rw-r--r--packages/webapi/src/lib/TreeWalker.ts55
-rw-r--r--packages/webapi/src/lib/Window.ts49
-rw-r--r--packages/webapi/src/lib/fetch.ts75
-rw-r--r--packages/webapi/src/lib/structuredClone.ts4
-rw-r--r--packages/webapi/src/lib/utils.ts61
-rw-r--r--packages/webapi/src/polyfill.ts338
-rw-r--r--packages/webapi/src/ponyfill.ts127
-rw-r--r--packages/webapi/src/types.d.ts6
37 files changed, 2197 insertions, 0 deletions
diff --git a/packages/webapi/src/exclusions.ts b/packages/webapi/src/exclusions.ts
new file mode 100644
index 000000000..d5a00f84f
--- /dev/null
+++ b/packages/webapi/src/exclusions.ts
@@ -0,0 +1,17 @@
+const exclusionsForHTMLElement = [ 'CustomElementsRegistry', 'HTMLElement', 'HTMLBodyElement', 'HTMLCanvasElement', 'HTMLDivElement', 'HTMLHeadElement', 'HTMLHtmlElement', 'HTMLImageElement', 'HTMLStyleElement', 'HTMLTemplateElement', 'HTMLUnknownElement', 'Image' ]
+const exclusionsForElement = [ 'Element', ...exclusionsForHTMLElement ] as const
+const exclusionsForDocument = [ 'CustomElementsRegistry', 'Document', 'HTMLDocument', 'document', 'customElements' ] as const
+const exclusionsForNode = [ 'Node', 'DocumentFragment', 'ShadowRoot', ...exclusionsForDocument, ...exclusionsForElement ] as const
+const exclusionsForEventTarget = [ 'AbortSignal', 'Event', 'CustomEvent', 'EventTarget', 'OffscreenCanvas', 'MediaQueryList', 'Window', ...exclusionsForNode ] as const
+const exclusionsForEvent = [ 'AbortSignal', 'Event', 'CustomEvent', 'EventTarget', 'MediaQueryList', 'OffscreenCanvas', 'Window', ...exclusionsForNode ] as const
+
+export const exclusions = {
+ 'Blob+': [ 'Blob', 'File' ],
+ 'Document+': exclusionsForDocument,
+ 'Element+': exclusionsForElement,
+ 'Event+': exclusionsForEvent,
+ 'EventTarget+': exclusionsForEventTarget,
+ 'HTMLElement+': exclusionsForHTMLElement,
+ 'Node+': exclusionsForNode,
+ 'StyleSheet+': [ 'StyleSheet', 'CSSStyleSheet' ],
+}
diff --git a/packages/webapi/src/inheritence.ts b/packages/webapi/src/inheritence.ts
new file mode 100644
index 000000000..f3b1476ae
--- /dev/null
+++ b/packages/webapi/src/inheritence.ts
@@ -0,0 +1,27 @@
+export const inheritence = {
+ CSSStyleSheet: 'StyleSheet',
+ CustomEvent: 'Event',
+ DOMException: 'Error',
+ Document: 'Node',
+ DocumentFragment: 'Node',
+ Element: 'Node',
+ File: 'Blob',
+ HTMLDocument: 'Document',
+ HTMLElement: 'Element',
+ HTMLBodyElement: 'HTMLElement',
+ HTMLCanvasElement: 'HTMLElement',
+ HTMLDivElement: 'HTMLElement',
+ HTMLHeadElement: 'HTMLElement',
+ HTMLHtmlElement: 'HTMLElement',
+ HTMLImageElement: 'HTMLElement',
+ HTMLSpanElement: 'HTMLElement',
+ HTMLStyleElement: 'HTMLElement',
+ HTMLTemplateElement: 'HTMLElement',
+ HTMLUnknownElement: 'HTMLElement',
+ Image: 'HTMLElement',
+ MediaQueryList: 'EventTarget',
+ Node: 'EventTarget',
+ OffscreenCanvas: 'EventTarget',
+ ShadowRoot: 'DocumentFragment',
+ Window: 'EventTarget',
+} as const
diff --git a/packages/webapi/src/lib/Alert.ts b/packages/webapi/src/lib/Alert.ts
new file mode 100644
index 000000000..0aacd645f
--- /dev/null
+++ b/packages/webapi/src/lib/Alert.ts
@@ -0,0 +1,3 @@
+export function alert(...messages: any[]) {
+ console.log(...messages)
+}
diff --git a/packages/webapi/src/lib/AnimationFrame.ts b/packages/webapi/src/lib/AnimationFrame.ts
new file mode 100644
index 000000000..744c445bf
--- /dev/null
+++ b/packages/webapi/src/lib/AnimationFrame.ts
@@ -0,0 +1,35 @@
+import { setTimeout as nodeSetTimeout, clearTimeout as nodeClearTimeout } from 'node:timers'
+import * as _ from './utils.js'
+
+const INTERNAL = { tick: 0, pool: new Map }
+
+export function requestAnimationFrame<TArgs extends any[], TFunc extends (...args: TArgs) => any>(callback: TFunc): number {
+ if (!INTERNAL.pool.size) {
+ nodeSetTimeout(() => {
+ const next = _.__performance_now()
+
+ for (const func of INTERNAL.pool.values()) {
+ func(next)
+ }
+
+ INTERNAL.pool.clear()
+ }, 1000 / 16)
+ }
+
+ const func = _.__function_bind(callback, undefined)
+ const tick = ++INTERNAL.tick
+
+ INTERNAL.pool.set(tick, func)
+
+ return tick
+}
+
+export function cancelAnimationFrame(requestId: number): void {
+ const timeout = INTERNAL.pool.get(requestId)
+
+ if (timeout) {
+ nodeClearTimeout(timeout)
+
+ INTERNAL.pool.delete(requestId)
+ }
+}
diff --git a/packages/webapi/src/lib/Base64.ts b/packages/webapi/src/lib/Base64.ts
new file mode 100644
index 000000000..593c56ae0
--- /dev/null
+++ b/packages/webapi/src/lib/Base64.ts
@@ -0,0 +1,7 @@
+export function atob(data: string): string {
+ return Buffer.from(data, 'base64').toString('binary')
+}
+
+export function btoa(data: string): string {
+ return Buffer.from(data, 'binary').toString('base64')
+}
diff --git a/packages/webapi/src/lib/CanvasRenderingContext2D.ts b/packages/webapi/src/lib/CanvasRenderingContext2D.ts
new file mode 100644
index 000000000..d83a227aa
--- /dev/null
+++ b/packages/webapi/src/lib/CanvasRenderingContext2D.ts
@@ -0,0 +1,182 @@
+import type { HTMLCanvasElement } from './HTMLCanvasElement'
+import type { OffscreenCanvas } from './OffscreenCanvas'
+
+import * as _ from './utils'
+import { ImageData } from './ImageData'
+
+export class CanvasRenderingContext2D {
+ get canvas(): HTMLCanvasElement | OffscreenCanvas | null {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'canvas').canvas
+ }
+
+ get direction(): 'ltr' | 'rtl' | 'inherit' {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'direction').direction
+ }
+
+ get fillStyle(): string {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'fillStyle').fillStyle
+ }
+
+ get filter(): string {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'filter').filter
+ }
+
+ get globalAlpha(): number {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'globalAlpha').globalAlpha
+ }
+
+ get globalCompositeOperation(): string {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'globalCompositeOperation').globalCompositeOperation
+ }
+
+ get font(): string {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'font').font
+ }
+
+ get imageSmoothingEnabled(): boolean {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'imageSmoothingEnabled').imageSmoothingEnabled
+ }
+
+ get imageSmoothingQuality(): 'low' | 'medium' | 'high' {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'imageSmoothingQuality').imageSmoothingQuality
+ }
+
+ get lineCap(): 'butt' | 'round' | 'square' {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'lineCap').lineCap
+ }
+
+ get lineDashOffset(): number {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'lineDashOffset').lineDashOffset
+ }
+
+ get lineJoin(): 'bevel' | 'round' | 'miter' {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'lineJoin').lineJoin
+ }
+
+ get lineWidth(): number {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'lineWidth').lineWidth
+ }
+
+ get miterLimit(): number {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'miterLimit').miterLimit
+ }
+
+ get strokeStyle(): string {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'strokeStyle').strokeStyle
+ }
+
+ get shadowOffsetX(): number {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'shadowOffsetX').shadowOffsetX
+ }
+
+ get shadowOffsetY(): number {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'shadowOffsetY').shadowOffsetY
+ }
+
+ get shadowBlur(): number {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'shadowBlur').shadowBlur
+ }
+
+ get shadowColor(): string {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'shadowColor').shadowColor
+ }
+
+ get textAlign(): 'left' | 'right' | 'center' | 'start' | 'end' {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'textAlign').textAlign
+ }
+
+ get textBaseline(): 'top' | 'hanging' | 'middle' | 'alphabetic' | 'ideographic' | 'bottom' {
+ return _.internalsOf(this, 'CanvasRenderingContext2D', 'textBaseline').textBaseline
+ }
+
+ arc() {}
+ arcTo() {}
+ beginPath() {}
+ bezierCurveTo() {}
+ clearRect() {}
+ clip() {}
+ closePath() {}
+
+ createImageData(width: number, height: number): void
+ createImageData(imagedata: ImageData): void
+
+ createImageData(arg0: number | ImageData, arg1?: void | number) {
+ /** Whether ImageData is provided. */
+ const hasData = _.__object_isPrototypeOf(ImageData.prototype, arg0)
+
+ const w = hasData ? (arg0 as ImageData).width : arg0 as number
+ const h = hasData ? (arg0 as ImageData).height : arg1 as number
+ const d = hasData ? (arg0 as ImageData).data : new Uint8ClampedArray(w * h * 4)
+
+ return new ImageData(d, w, h)
+ }
+
+ createLinearGradient() {}
+ createPattern() {}
+ createRadialGradient() {}
+ drawFocusIfNeeded() {}
+ drawImage() {}
+ ellipse() {}
+ fill() {}
+ fillRect() {}
+ fillText() {}
+ getContextAttributes() {}
+ getImageData() {}
+ getLineDash() {}
+ getTransform() {}
+ isPointInPath() {}
+ isPointInStroke() {}
+ lineTo() {}
+ measureText() {}
+ moveTo() {}
+ putImageData() {}
+ quadraticCurveTo() {}
+ rect() {}
+ resetTransform() {}
+ restore() {}
+ rotate() {}
+ save() {}
+ scale() {}
+ setLineDash() {}
+ setTransform() {}
+ stroke() {}
+ strokeRect() {}
+ strokeText() {}
+ transform() {}
+ translate() {}
+}
+
+_.allowStringTag(CanvasRenderingContext2D)
+
+export const __createCanvasRenderingContext2D = (canvas: EventTarget): CanvasRenderingContext2D => {
+ const renderingContext2D = Object.create(CanvasRenderingContext2D.prototype) as CanvasRenderingContext2D
+
+ _.INTERNALS.set(renderingContext2D, {
+ canvas,
+ direction: 'inherit',
+ fillStyle: '#000',
+ filter: 'none',
+ font: '10px sans-serif',
+ globalAlpha: 0,
+ globalCompositeOperation: 'source-over',
+ imageSmoothingEnabled: false,
+ imageSmoothingQuality: 'high',
+ lineCap: 'butt',
+ lineDashOffset: 0.0,
+ lineJoin: 'miter',
+ lineWidth: 1.0,
+ miterLimit: 10.0,
+ shadowBlur: 0,
+ shadowColor: '#000',
+ shadowOffsetX: 0,
+ shadowOffsetY: 0,
+ strokeStyle: '#000',
+ textAlign: 'start',
+ textBaseline: 'alphabetic',
+ })
+
+ return renderingContext2D
+}
+
+/** Returns whether the value is an instance of ImageData. */
+const isImageData = <T>(value: T) => (Object(value).data instanceof Uint8ClampedArray) as T extends ImageData ? true : false
diff --git a/packages/webapi/src/lib/CharacterData.ts b/packages/webapi/src/lib/CharacterData.ts
new file mode 100644
index 000000000..8bbcce12d
--- /dev/null
+++ b/packages/webapi/src/lib/CharacterData.ts
@@ -0,0 +1,36 @@
+import * as _ from './utils'
+
+export class CharacterData extends Node {
+ constructor(data: string) {
+ _.INTERNALS.set(super(), {
+ data: String(data),
+ } as CharacterDataInternals)
+ }
+ get data(): string {
+ return _.internalsOf<CharacterDataInternals>(this, 'CharacterData', 'data').data
+ }
+
+ get textContent(): string {
+ return _.internalsOf<CharacterDataInternals>(this, 'CharacterData', 'textContent').data
+ }
+}
+
+export class Comment extends CharacterData {}
+
+export class Text extends CharacterData {
+ get assignedSlot(): HTMLSlotElement | null {
+ return null
+ }
+
+ get wholeText(): string {
+ return _.internalsOf<CharacterDataInternals>(this, 'CharacterData', 'textContent').data
+ }
+}
+
+_.allowStringTag(CharacterData)
+_.allowStringTag(Text)
+_.allowStringTag(Comment)
+
+interface CharacterDataInternals {
+ data: string
+}
diff --git a/packages/webapi/src/lib/ContextEvent.ts b/packages/webapi/src/lib/ContextEvent.ts
new file mode 100644
index 000000000..98cd72de5
--- /dev/null
+++ b/packages/webapi/src/lib/ContextEvent.ts
@@ -0,0 +1,52 @@
+import { Event } from 'event-target-shim'
+
+/** An event fired by a context requester to signal it desires a named context. */
+export class ContextEvent<T = unknown> extends Event<'context-request'> {
+ constructor(init: ContextEventInit<T>) {
+ super('context-request', { bubbles: true, composed: true })
+
+ init = Object(init) as Required<ContextEventInit<T>>
+
+ this.context = init.context
+ }
+
+ context!: Context<T>
+ multiple!: boolean
+ callback!: ContextCallback<Context<T>>
+}
+
+interface ContextEventInit<T = unknown> {
+ context: Context<T>
+ multiple?: boolean
+ callback: ContextCallback<Context<T>>
+}
+
+/** A Context object defines an optional initial value for a Context, as well as a name identifier for debugging purposes. */
+export type Context<T = unknown> = {
+ name: string
+ initialValue?: T
+}
+
+/** A helper type which can extract a Context value type from a Context type. */
+export type ContextType<T extends Context> = T extends Context<infer Y> ? Y : never
+
+/** A function which creates a Context value object */
+export function createContext<T>(name: string, initialValue?: T): Readonly<Context<T>> {
+ return {
+ name,
+ initialValue,
+ }
+}
+
+/** A callback which is provided by a context requester and is called with the value satisfying the request. */
+export type ContextCallback<ValueType> = (
+ value: ValueType,
+ dispose?: () => void
+) => void
+
+declare global {
+ interface HTMLElementEventMap {
+ /** A 'context-request' event can be emitted by any element which desires a context value to be injected by an external provider. */
+ 'context-request': ContextEvent
+ }
+}
diff --git a/packages/webapi/src/lib/CustomElementRegistry.ts b/packages/webapi/src/lib/CustomElementRegistry.ts
new file mode 100644
index 000000000..650435c17
--- /dev/null
+++ b/packages/webapi/src/lib/CustomElementRegistry.ts
@@ -0,0 +1,58 @@
+import * as _ from './utils'
+
+export class CustomElementRegistry {
+ /** Defines a new custom element using the given tag name and HTMLElement constructor. */
+ define(name: string, constructor: Function, options?: ElementDefinitionOptions) {
+ const internals = _.internalsOf<CustomElementRegistryInternals>(this, 'CustomElementRegistry', 'define')
+
+ name = String(name)
+
+ if (/[A-Z]/.test(name)) throw new SyntaxError('Custom element name cannot contain an uppercase ASCII letter')
+ if (!/^[a-z]/.test(name)) throw new SyntaxError('Custom element name must have a lowercase ASCII letter as its first character')
+ if (!/-/.test(name)) throw new SyntaxError('Custom element name must contain a hyphen')
+
+ internals.constructorByName.set(name, constructor)
+ internals.nameByConstructor.set(constructor, name)
+
+ void options
+ }
+
+ /** Returns the constructor associated with the given tag name. */
+ get(name: string) {
+ const internals = _.internalsOf<CustomElementRegistryInternals>(this, 'CustomElementRegistry', 'get')
+
+ name = String(name).toLowerCase()
+
+ return internals.constructorByName.get(name)
+ }
+
+ getName(constructor: Function) {
+ const internals = _.internalsOf<CustomElementRegistryInternals>(this, 'CustomElementRegistry', 'getName')
+
+ return internals.nameByConstructor.get(constructor)
+ }
+}
+
+_.allowStringTag(CustomElementRegistry)
+
+interface CustomElementRegistryInternals {
+ constructorByName: Map<string, Function>;
+ nameByConstructor: Map<Function, string>;
+}
+
+interface ElementDefinitionOptions {
+ extends?: string | undefined;
+}
+
+export const initCustomElementRegistry = (target: Record<any, any>, exclude: Set<string>) => {
+ if (exclude.has('customElements')) return
+
+ const CustomElementRegistry = target.CustomElementRegistry || globalThis.CustomElementRegistry
+
+ const customElements: CustomElementRegistry = target.customElements = Object.create(CustomElementRegistry.prototype)
+
+ _.INTERNALS.set(customElements, {
+ constructorByName: new Map,
+ nameByConstructor: new Map,
+ } as CustomElementRegistryInternals)
+}
diff --git a/packages/webapi/src/lib/CustomEvent.ts b/packages/webapi/src/lib/CustomEvent.ts
new file mode 100644
index 000000000..951ca8e81
--- /dev/null
+++ b/packages/webapi/src/lib/CustomEvent.ts
@@ -0,0 +1,24 @@
+import * as _ from './utils'
+import { Event } from 'event-target-shim'
+
+class CustomEvent<TEventType extends string = string> extends Event<TEventType> {
+ constructor(type: TEventType, params?: CustomEventInit) {
+ params = Object(params) as Required<CustomEventInit>
+
+ super(type, params)
+
+ if ('detail' in params) this.detail = params.detail
+ }
+
+ detail!: any
+}
+
+_.allowStringTag(CustomEvent)
+
+export { CustomEvent }
+
+interface CustomEventInit {
+ bubbles?: boolean
+ cancelable?: false
+ detail?: any
+}
diff --git a/packages/webapi/src/lib/DOMException.ts b/packages/webapi/src/lib/DOMException.ts
new file mode 100644
index 000000000..539871c6b
--- /dev/null
+++ b/packages/webapi/src/lib/DOMException.ts
@@ -0,0 +1,40 @@
+import * as _ from './utils'
+
+export class DOMException extends Error {
+ constructor(message = '', name = 'Error') {
+ super(message)
+
+ this.code = 0
+ this.name = name
+ }
+
+ code!: number
+
+ static INDEX_SIZE_ERR = 1
+ static DOMSTRING_SIZE_ERR = 2
+ static HIERARCHY_REQUEST_ERR = 3
+ static WRONG_DOCUMENT_ERR = 4
+ static INVALID_CHARACTER_ERR = 5
+ static NO_DATA_ALLOWED_ERR = 6
+ static NO_MODIFICATION_ALLOWED_ERR = 7
+ static NOT_FOUND_ERR = 8
+ static NOT_SUPPORTED_ERR = 9
+ static INUSE_ATTRIBUTE_ERR = 10
+ static INVALID_STATE_ERR = 11
+ static SYNTAX_ERR = 12
+ static INVALID_MODIFICATION_ERR = 13
+ static NAMESPACE_ERR = 14
+ static INVALID_ACCESS_ERR = 15
+ static VALIDATION_ERR = 16
+ static TYPE_MISMATCH_ERR = 17
+ static SECURITY_ERR = 18
+ static NETWORK_ERR = 19
+ static ABORT_ERR = 20
+ static URL_MISMATCH_ERR = 21
+ static QUOTA_EXCEEDED_ERR = 22
+ static TIMEOUT_ERR = 23
+ static INVALID_NODE_TYPE_ERR = 24
+ static DATA_CLONE_ERR = 25
+}
+
+_.allowStringTag(DOMException)
diff --git a/packages/webapi/src/lib/Document.ts b/packages/webapi/src/lib/Document.ts
new file mode 100644
index 000000000..867cc4448
--- /dev/null
+++ b/packages/webapi/src/lib/Document.ts
@@ -0,0 +1,159 @@
+import * as _ from './utils'
+import { Text } from './CharacterData'
+import { TreeWalker } from './TreeWalker'
+
+export class Document extends Node {
+ createElement(name: string) {
+ const internals = _.internalsOf<DocumentInternals>(this, 'Document', 'createElement')
+
+ const customElementInternals: CustomElementRegistryInternals = _.INTERNALS.get(internals.target.customElements)
+
+ name = String(name).toLowerCase()
+
+ const TypeOfHTMLElement = internals.constructorByName.get(name) || (customElementInternals && customElementInternals.constructorByName.get(name)) || HTMLUnknownElement
+
+ const element = Object.setPrototypeOf(new EventTarget(), TypeOfHTMLElement.prototype) as HTMLElement
+
+ _.INTERNALS.set(element, {
+ attributes: {},
+ localName: name,
+ ownerDocument: this,
+ shadowInit: null as unknown as ShadowRootInit,
+ shadowRoot: null as unknown as ShadowRoot,
+ } as ElementInternals)
+
+ return element
+ }
+
+ createNodeIterator(root: Node, whatToShow: number = NodeFilter.SHOW_ALL, filter?: NodeIteratorInternals['filter']) {
+ const target = Object.create(NodeIterator.prototype)
+
+ _.INTERNALS.set(target, { filter, pointerBeforeReferenceNode: false, referenceNode: root, root, whatToShow } as NodeIteratorInternals)
+
+ return target
+ }
+
+ createTextNode(data: string) {
+ return new Text(data)
+ }
+
+ createTreeWalker(root: Node, whatToShow: number = NodeFilter.SHOW_ALL, filter?: NodeFilter, expandEntityReferences?: boolean) {
+ const target = Object.create(TreeWalker.prototype)
+
+ _.INTERNALS.set(target, { filter, currentNode: root, root, whatToShow } as TreeWalkerInternals)
+
+ return target
+ }
+
+ get adoptedStyleSheets(): StyleSheet[] {
+ return []
+ }
+
+ get styleSheets(): StyleSheet[] {
+ return []
+ }
+
+ body!: HTMLBodyElement
+ documentElement!: HTMLHtmlElement
+ head!: HTMLHeadElement
+}
+
+export class HTMLDocument extends Document {}
+
+_.allowStringTag(Document)
+_.allowStringTag(HTMLDocument)
+
+export const initDocument = (target: Target, exclude: Set<string>) => {
+ if (exclude.has('document')) return
+
+ const EventTarget = target.EventTarget || globalThis.EventTarget
+ const HTMLDocument = target.HTMLDocument || globalThis.HTMLDocument
+
+ const document: HTMLDocument = target.document = Object.setPrototypeOf(new EventTarget(), HTMLDocument.prototype)
+
+ _.INTERNALS.set(document, {
+ target,
+ constructorByName: new Map<string, Function>([
+ ['body', target.HTMLBodyElement],
+ ['canvas', target.HTMLCanvasElement],
+ ['div', target.HTMLDivElement],
+ ['head', target.HTMLHeadElement],
+ ['html', target.HTMLHtmlElement],
+ ['img', target.HTMLImageElement],
+ ['span', target.HTMLSpanElement],
+ ['style', target.HTMLStyleElement],
+ ]),
+ nameByConstructor: new Map,
+ } as DocumentInternals)
+
+ const initElement = (name: string, Class: Function) => {
+ const target = Object.setPrototypeOf(new EventTarget(), Class.prototype)
+
+ _.INTERNALS.set(target, {
+ attributes: {},
+ localName: name,
+ ownerDocument: document,
+ shadowRoot: null as unknown as ShadowRoot,
+ shadowInit: null as unknown as ShadowRootInit,
+ } as ElementInternals)
+
+ return target
+ }
+
+ document.body = initElement('body', target.HTMLBodyElement) as HTMLBodyElement
+ document.head = initElement('head', target.HTMLHeadElement) as HTMLHeadElement
+ document.documentElement = initElement('html', target.HTMLHtmlElement) as HTMLHtmlElement
+}
+
+interface DocumentInternals {
+ body: HTMLBodyElement
+ documentElement: HTMLHtmlElement
+ head: HTMLHeadElement
+ constructorByName: Map<string, Function>
+ nameByConstructor: Map<Function, string>
+ target: Target
+}
+
+interface CustomElementRegistryInternals {
+ constructorByName: Map<string, Function>
+ nameByConstructor: Map<Function, string>
+}
+
+interface ElementInternals {
+ attributes: { [name: string]: string },
+ localName: string
+ ownerDocument: Document
+ shadowRoot: ShadowRoot
+ shadowInit: ShadowRootInit
+}
+
+interface ShadowRootInit extends Record<any, any> {
+ mode?: string
+}
+
+interface Target extends Record<any, any> {
+ HTMLBodyElement: typeof HTMLBodyElement
+ HTMLDivElement: typeof HTMLDivElement
+ HTMLElement: typeof HTMLElement
+ HTMLHeadElement: typeof HTMLHeadElement
+ HTMLHtmlElement: typeof HTMLHtmlElement
+ HTMLSpanElement: typeof HTMLSpanElement
+ HTMLStyleElement: typeof HTMLStyleElement
+ customElements: CustomElementRegistry
+ document: DocumentInternals
+}
+
+interface NodeIteratorInternals {
+ filter: NodeFilter
+ pointerBeforeReferenceNode: boolean
+ referenceNode: Node
+ root: Node
+ whatToShow: number
+}
+
+interface TreeWalkerInternals {
+ filter: NodeFilter
+ currentNode: Node
+ root: Node
+ whatToShow: number
+}
diff --git a/packages/webapi/src/lib/Element.ts b/packages/webapi/src/lib/Element.ts
new file mode 100644
index 000000000..ec096fb6d
--- /dev/null
+++ b/packages/webapi/src/lib/Element.ts
@@ -0,0 +1,118 @@
+import * as _ from './utils'
+
+export class Element extends Node {
+ hasAttribute(name: string): boolean {
+ void name
+
+ return false
+ }
+
+ getAttribute(name: string): string | null {
+ return null
+ }
+
+ setAttribute(name: string, value: string): void {
+ void name
+ void value
+ }
+
+ removeAttribute(name: string): void {
+ void name
+ }
+
+ attachShadow(init: Partial<ShadowRootInit>) {
+ if (arguments.length < 1) throw new TypeError(`Failed to execute 'attachShadow' on 'Element': 1 argument required, but only 0 present.`)
+
+ if (init !== Object(init)) throw new TypeError(`Failed to execute 'attachShadow' on 'Element': The provided value is not of type 'ShadowRootInit'.`)
+
+ if (init.mode !== 'open' && init.mode !== 'closed') throw new TypeError(`Failed to execute 'attachShadow' on 'Element': Failed to read the 'mode' property from 'ShadowRootInit': The provided value '${init.mode}' is not a valid enum value of type ShadowRootMode.`)
+
+ const internals = _.internalsOf<ElementInternals>(this, 'Element', 'attachShadow')
+
+ if (internals.shadowRoot) throw new Error('The operation is not supported.')
+
+ internals.shadowInit = internals.shadowInit || {
+ mode: init.mode,
+ delegatesFocus: Boolean(init.delegatesFocus),
+ }
+
+ internals.shadowRoot = internals.shadowRoot || (/^open$/.test(internals.shadowInit.mode as string) ? Object.setPrototypeOf(new EventTarget(), ShadowRoot.prototype) as ShadowRoot : null)
+
+ return internals.shadowRoot
+ }
+
+ get assignedSlot(): HTMLSlotElement | null {
+ return null
+ }
+
+ get innerHTML(): string {
+ _.internalsOf<ElementInternals>(this, 'Element', 'innerHTML')
+
+ return ''
+ }
+
+ set innerHTML(value) {
+ _.internalsOf<ElementInternals>(this, 'Element', 'innerHTML')
+
+ void value
+ }
+
+ get shadowRoot(): ShadowRoot | null {
+ const internals = _.internalsOf<ElementInternals>(this, 'Element', 'shadowRoot')
+
+ return Object(internals.shadowInit).mode === 'open' ? internals.shadowRoot : null
+ }
+
+ get localName(): string {
+ return _.internalsOf<ElementInternals>(this, 'Element', 'localName').localName as string
+ }
+
+ get nodeName(): string {
+ return (_.internalsOf<ElementInternals>(this, 'Element', 'nodeName').localName as string).toUpperCase()
+ }
+
+ get tagName(): string {
+ return (_.internalsOf<ElementInternals>(this, 'Element', 'tagName').localName as string).toUpperCase()
+ }
+}
+
+export class HTMLElement extends Element {}
+
+export class HTMLBodyElement extends HTMLElement {}
+
+export class HTMLDivElement extends HTMLElement {}
+
+export class HTMLHeadElement extends HTMLElement {}
+
+export class HTMLHtmlElement extends HTMLElement {}
+
+export class HTMLSpanElement extends HTMLElement {}
+
+export class HTMLStyleElement extends HTMLElement {}
+
+export class HTMLTemplateElement extends HTMLElement {}
+
+export class HTMLUnknownElement extends HTMLElement {}
+
+_.allowStringTag(Element)
+_.allowStringTag(HTMLElement)
+_.allowStringTag(HTMLBodyElement)
+_.allowStringTag(HTMLDivElement)
+_.allowStringTag(HTMLHeadElement)
+_.allowStringTag(HTMLHtmlElement)
+_.allowStringTag(HTMLSpanElement)
+_.allowStringTag(HTMLStyleElement)
+_.allowStringTag(HTMLTemplateElement)
+_.allowStringTag(HTMLUnknownElement)
+
+export interface ElementInternals {
+ attributes: { [name: string]: string },
+ localName?: string
+ shadowRoot: ShadowRoot | null
+ shadowInit: ShadowRootInit | void
+}
+
+export interface ShadowRootInit {
+ mode: 'open' | 'closed'
+ delegatesFocus: boolean
+} \ No newline at end of file
diff --git a/packages/webapi/src/lib/HTMLCanvasElement.ts b/packages/webapi/src/lib/HTMLCanvasElement.ts
new file mode 100644
index 000000000..2558490a4
--- /dev/null
+++ b/packages/webapi/src/lib/HTMLCanvasElement.ts
@@ -0,0 +1,57 @@
+import type { CanvasRenderingContext2D } from './CanvasRenderingContext2D'
+
+import * as _ from './utils'
+import { __createCanvasRenderingContext2D } from './CanvasRenderingContext2D'
+
+export class HTMLCanvasElement extends HTMLElement {
+ get height(): number {
+ return _.internalsOf(this, 'HTMLCanvasElement', 'height').height
+ }
+
+ set height(value) {
+ _.internalsOf(this, 'HTMLCanvasElement', 'height').height = Number(value) || 0
+ }
+
+ get width(): number {
+ return _.internalsOf(this, 'HTMLCanvasElement', 'width').width
+ }
+
+ set width(value) {
+ _.internalsOf(this, 'HTMLCanvasElement', 'width').width = Number(value) || 0
+ }
+
+ captureStream(): null {
+ return null
+ }
+
+ getContext(contextType: PredefinedContextId): CanvasRenderingContext2D | null {
+ const internals = _.internalsOf<HTMLCanvasElementInternals>(this, 'HTMLCanvasElement', 'getContext')
+
+ switch (contextType) {
+ case '2d':
+ if (internals.renderingContext2D) return internals.renderingContext2D
+
+ internals.renderingContext2D = __createCanvasRenderingContext2D(this)
+
+ return internals.renderingContext2D
+ default:
+ return null
+ }
+ }
+
+ toBlob() {}
+
+ toDataURL() {}
+
+ transferControlToOffscreen() {}
+}
+
+_.allowStringTag(HTMLCanvasElement)
+
+interface HTMLCanvasElementInternals {
+ width: number
+ height: number
+ renderingContext2D: CanvasRenderingContext2D
+}
+
+type PredefinedContextId = '2d' | 'bitmaprenderer' | 'webgl' | 'webgl2' | 'webgpu'
diff --git a/packages/webapi/src/lib/HTMLImageElement.ts b/packages/webapi/src/lib/HTMLImageElement.ts
new file mode 100644
index 000000000..9ddcb0ef5
--- /dev/null
+++ b/packages/webapi/src/lib/HTMLImageElement.ts
@@ -0,0 +1,16 @@
+import * as _ from './utils'
+import { HTMLElement } from './Element'
+
+export class HTMLImageElement extends HTMLElement {
+ get src(): string {
+ return _.internalsOf(this, 'HTMLImageElement', 'src').src
+ }
+
+ set src(value) {
+ const internals = _.internalsOf(this, 'HTMLImageElement', 'src')
+
+ internals.src = String(value)
+ }
+}
+
+_.allowStringTag(HTMLImageElement)
diff --git a/packages/webapi/src/lib/IdleCallback.ts b/packages/webapi/src/lib/IdleCallback.ts
new file mode 100644
index 000000000..0d0f25bdf
--- /dev/null
+++ b/packages/webapi/src/lib/IdleCallback.ts
@@ -0,0 +1,35 @@
+import { setTimeout as nodeSetTimeout, clearTimeout as nodeClearTimeout } from 'node:timers'
+import * as _ from './utils.js'
+
+const INTERNAL = { tick: 0, pool: new Map }
+
+export function requestIdleCallback<TArgs extends any[], TFunc extends (...args: TArgs) => any>(callback: TFunc): number {
+ if (!INTERNAL.pool.size) {
+ nodeSetTimeout(() => {
+ const next = _.__performance_now()
+
+ for (const func of INTERNAL.pool.values()) {
+ func(next)
+ }
+
+ INTERNAL.pool.clear()
+ }, 1000 / 16)
+ }
+
+ const func = _.__function_bind(callback, undefined)
+ const tick = ++INTERNAL.tick
+
+ INTERNAL.pool.set(tick, func)
+
+ return tick
+}
+
+export function cancelIdleCallback(requestId: number): void {
+ const timeout = INTERNAL.pool.get(requestId)
+
+ if (timeout) {
+ nodeClearTimeout(timeout)
+
+ INTERNAL.pool.delete(requestId)
+ }
+}
diff --git a/packages/webapi/src/lib/Image.ts b/packages/webapi/src/lib/Image.ts
new file mode 100644
index 000000000..d72c33159
--- /dev/null
+++ b/packages/webapi/src/lib/Image.ts
@@ -0,0 +1,15 @@
+import * as _ from './utils'
+import { HTMLImageElement } from './HTMLImageElement'
+
+export function Image() {
+ // @ts-ignore
+ _.INTERNALS.set(this, {
+ attributes: {},
+ localName: 'img',
+ innerHTML: '',
+ shadowRoot: null,
+ shadowInit: null,
+ })
+}
+
+Image.prototype = HTMLImageElement.prototype
diff --git a/packages/webapi/src/lib/ImageData.ts b/packages/webapi/src/lib/ImageData.ts
new file mode 100644
index 000000000..776213880
--- /dev/null
+++ b/packages/webapi/src/lib/ImageData.ts
@@ -0,0 +1,75 @@
+import * as _ from './utils'
+
+export class ImageData {
+ constructor(width: number, height: number);
+ constructor(width: number, height: number, settings: ImageDataSettings);
+ constructor(data: Uint8ClampedArray, width: number);
+ constructor(data: Uint8ClampedArray, width: number, height: number);
+ constructor(data: Uint8ClampedArray, width: number, height: number, settings: ImageDataSettings);
+
+ constructor(arg0: number | Uint8ClampedArray, arg1: number, ...args: [] | [number] | [ImageDataSettings] | [number, ImageDataSettings]) {
+ if (arguments.length < 2) throw new TypeError(`Failed to construct 'ImageData': 2 arguments required.`)
+
+ /** Whether Uint8ClampedArray data is provided. */
+ const hasData = _.__object_isPrototypeOf(Uint8ClampedArray.prototype, arg0)
+
+ /** Image data, either provided or calculated. */
+ const d = hasData ? arg0 as Uint8ClampedArray : new Uint8ClampedArray(asNumber(arg0, 'width') * asNumber(arg1, 'height') * 4)
+
+ /** Image width. */
+ const w = asNumber(hasData ? arg1 : arg0, 'width')
+
+ /** Image height. */
+ const h = d.length / w / 4
+
+ /** Image color space. */
+ const c = String(Object(hasData ? args[1] : args[0]).colorSpace || 'srgb') as PredefinedColorSpace
+
+ // throw if a provided height does not match the calculated height
+ if (args.length && asNumber(args[0], 'height') !== h) throw new DOMException('height is not equal to (4 * width * height)', 'IndexSizeError')
+
+ // throw if a provided colorspace does not match a known colorspace
+ if (c !== 'srgb' && c !== 'rec2020' && c !== 'display-p3') throw new TypeError('colorSpace is not known value')
+
+ Object.defineProperty(this, 'data', { configurable: true, enumerable: true, value: d })
+
+ _.INTERNALS.set(this, { width: w, height: h, colorSpace: c } as ImageDataInternals)
+ }
+
+ get data(): Uint8ClampedArray {
+ _.internalsOf<ImageDataInternals>(this, 'ImageData', 'data')
+
+ return (Object.getOwnPropertyDescriptor(this, 'data') as { value: Uint8ClampedArray }).value
+ }
+
+ get width(): ImageDataInternals['width'] {
+ return _.internalsOf<ImageDataInternals>(this, 'ImageData', 'width').width
+ }
+
+ get height(): ImageDataInternals['height'] {
+ return _.internalsOf<ImageDataInternals>(this, 'ImageData', 'height').height
+ }
+}
+
+_.allowStringTag(ImageData)
+
+/** Returns a coerced number, optionally throwing if the number is zero-ish. */
+const asNumber = (value: any, axis: string): number => {
+ value = Number(value) || 0
+
+ if (value === 0) throw new TypeError(`The source ${axis} is zero or not a number.`)
+
+ return value
+}
+
+interface ImageDataInternals {
+ colorSpace: PredefinedColorSpace
+ height: number
+ width: number
+}
+
+interface ImageDataSettings {
+ colorSpace?: PredefinedColorSpace
+}
+
+type PredefinedColorSpace = 'srgb' | 'rec2020' | 'display-p3'
diff --git a/packages/webapi/src/lib/MediaQueryList.ts b/packages/webapi/src/lib/MediaQueryList.ts
new file mode 100644
index 000000000..44edd89be
--- /dev/null
+++ b/packages/webapi/src/lib/MediaQueryList.ts
@@ -0,0 +1,37 @@
+import * as _ from './utils'
+
+export class MediaQueryList extends EventTarget {
+ get matches(): boolean {
+ return _.internalsOf(this, 'MediaQueryList', 'matches').matches
+ }
+
+ get media(): string {
+ return _.internalsOf(this, 'MediaQueryList', 'media').media
+ }
+}
+
+_.allowStringTag(MediaQueryList)
+
+export const initMediaQueryList = (target: Target, exclude: Set<string>) => {
+ if (exclude.has('MediaQueryList') || exclude.has('matchMedia')) return
+
+ const EventTarget = target.EventTarget || globalThis.EventTarget
+ const MediaQueryList = target.MediaQueryList || globalThis.MediaQueryList
+
+ target.matchMedia = function matchMedia(media: string) {
+ const mql = Object.setPrototypeOf(new EventTarget(), MediaQueryList.prototype) as MediaQueryList
+
+ _.INTERNALS.set(mql, {
+ matches: false,
+ media,
+ })
+
+ return mql
+ }
+}
+
+interface Target extends Record<any, any> {
+ matchMedia: {
+ (media: string): MediaQueryList
+ }
+}
diff --git a/packages/webapi/src/lib/Node.ts b/packages/webapi/src/lib/Node.ts
new file mode 100644
index 000000000..c2f621950
--- /dev/null
+++ b/packages/webapi/src/lib/Node.ts
@@ -0,0 +1,166 @@
+import * as _ from './utils'
+
+export class Node extends EventTarget {
+ append(...nodesOrDOMStrings: NodeOrString[]): void {
+ void nodesOrDOMStrings
+ }
+
+ appendChild(childNode: Node): Node {
+ return childNode
+ }
+
+ after(...nodesOrDOMStrings: NodeOrString[]): void {
+ void nodesOrDOMStrings
+ }
+
+ before(...nodesOrDOMStrings: NodeOrString[]): void {
+ void nodesOrDOMStrings
+ }
+
+ prepend(...nodesOrDOMStrings: NodeOrString[]): void {
+ void nodesOrDOMStrings
+ }
+
+ replaceChild(newChild: Node, oldChild: Node): Node {
+ void newChild
+
+ return oldChild
+ }
+
+ removeChild(childNode: Node): Node {
+ return childNode
+ }
+
+ get attributes(): object {
+ return {}
+ }
+
+ get childNodes(): Node[] {
+ return []
+ }
+
+ get children(): Element[] {
+ return []
+ }
+
+ get ownerDocument(): Node | null {
+ return null
+ }
+
+ get nodeValue(): string {
+ return ''
+ }
+
+ set nodeValue(value: string) {
+ void value
+ }
+
+ get textContent(): string {
+ return ''
+ }
+
+ set textContent(value: string) {
+ void value
+ }
+
+ get previousElementSibling(): Node | null {
+ return null
+ }
+
+ get nextElementSibling(): Node | null {
+ return null
+ }
+
+ [Symbol.for('nodejs.util.inspect.custom')](depth: number, options: Record<string, any>) {
+ return `${this.constructor.name}`;
+ }
+}
+
+export class DocumentFragment extends Node {}
+
+export class ShadowRoot extends DocumentFragment {
+ get innerHTML() {
+ return ''
+ }
+
+ set innerHTML(value: string) {
+ void value
+ }
+}
+
+export const NodeFilter = Object.assign({
+ NodeFilter() {
+ throw new TypeError('Illegal constructor')
+ }
+}.NodeFilter, {
+ FILTER_ACCEPT: 1,
+ FILTER_REJECT: 2,
+ FILTER_SKIP: 3,
+ SHOW_ALL: 4294967295,
+ SHOW_ELEMENT: 1,
+ SHOW_ATTRIBUTE: 2,
+ SHOW_TEXT: 4,
+ SHOW_CDATA_SECTION: 8,
+ SHOW_ENTITY_REFERENCE: 16,
+ SHOW_ENTITY: 32,
+ SHOW_PROCESSING_INSTRUCTION: 64,
+ SHOW_COMMENT: 128,
+ SHOW_DOCUMENT: 256,
+ SHOW_DOCUMENT_TYPE: 512,
+ SHOW_DOCUMENT_FRAGMENT: 1024,
+ SHOW_NOTATION: 2048,
+})
+
+export class NodeIterator {
+ nextNode(): Node | null {
+ return null
+ }
+
+ previousNode(): Node | null {
+ return null
+ }
+
+ get filter(): NodeFilter {
+ const internals = _.internalsOf<NodeIteratorInternals>(this, 'NodeIterator', 'filter')
+ return internals.filter
+ }
+
+ get pointerBeforeReferenceNode(): boolean {
+ const internals = _.internalsOf<NodeIteratorInternals>(this, 'NodeIterator', 'pointerBeforeReferenceNode')
+ return internals.pointerBeforeReferenceNode
+ }
+
+ get referenceNode(): Node {
+ const internals = _.internalsOf<NodeIteratorInternals>(this, 'NodeIterator', 'referenceNode')
+ return internals.referenceNode
+ }
+
+ get root(): Node {
+ const internals = _.internalsOf<NodeIteratorInternals>(this, 'NodeIterator', 'root')
+ return internals.root
+ }
+
+ get whatToShow(): number {
+ const internals = _.internalsOf<NodeIteratorInternals>(this, 'NodeIterator', 'whatToShow')
+ return internals.whatToShow
+ }
+}
+
+_.allowStringTag(Node)
+_.allowStringTag(NodeIterator)
+_.allowStringTag(DocumentFragment)
+_.allowStringTag(ShadowRoot)
+
+type NodeOrString = string | Node
+
+export interface NodeFilter {
+ acceptNode(node: Node): number
+}
+
+export interface NodeIteratorInternals {
+ filter: NodeFilter
+ pointerBeforeReferenceNode: boolean
+ referenceNode: Node
+ root: Node
+ whatToShow: number
+}
diff --git a/packages/webapi/src/lib/Object.ts b/packages/webapi/src/lib/Object.ts
new file mode 100644
index 000000000..5ad5b1388
--- /dev/null
+++ b/packages/webapi/src/lib/Object.ts
@@ -0,0 +1,20 @@
+import * as _ from './utils'
+
+export const hasOwn = {
+ hasOwn(instance: object, property: any) {
+ return _.__object_hasOwnProperty(instance, property)
+ }
+}.hasOwn
+
+export const initObject = (target: any, exclude: Set<string>) => {
+ if (exclude.has('Object') || exclude.has('object') || exclude.has('hasOwn')) return
+
+ const Class = target.Object || globalThis.Object
+
+ Object.defineProperty(Class, 'hasOwn', {
+ value: hasOwn,
+ writable: true,
+ enumerable: false,
+ configurable: true
+ })
+}
diff --git a/packages/webapi/src/lib/Observer.ts b/packages/webapi/src/lib/Observer.ts
new file mode 100644
index 000000000..845de1312
--- /dev/null
+++ b/packages/webapi/src/lib/Observer.ts
@@ -0,0 +1,41 @@
+import * as _ from './utils'
+
+export class IntersectionObserver {
+ disconnect() {}
+
+ observe() {}
+
+ takeRecords() {
+ return []
+ }
+
+ unobserve() {}
+}
+
+export class MutationObserver {
+ disconnect() {}
+
+ observe() {}
+
+ takeRecords() {
+ return []
+ }
+
+ unobserve() {}
+}
+
+export class ResizeObserver {
+ disconnect() {}
+
+ observe() {}
+
+ takeRecords() {
+ return []
+ }
+
+ unobserve() {}
+}
+
+_.allowStringTag(MutationObserver)
+_.allowStringTag(IntersectionObserver)
+_.allowStringTag(ResizeObserver)
diff --git a/packages/webapi/src/lib/OffscreenCanvas.ts b/packages/webapi/src/lib/OffscreenCanvas.ts
new file mode 100644
index 000000000..01ee5d34a
--- /dev/null
+++ b/packages/webapi/src/lib/OffscreenCanvas.ts
@@ -0,0 +1,80 @@
+import type { CanvasRenderingContext2D } from './CanvasRenderingContext2D'
+
+import * as _ from './utils'
+import { __createCanvasRenderingContext2D } from './CanvasRenderingContext2D'
+
+export class OffscreenCanvas extends EventTarget {
+ constructor(width: number, height: number) {
+ super()
+
+ if (arguments.length < 2) throw new TypeError(`Failed to construct 'OffscreenCanvas': 2 arguments required.`)
+
+ width = Number(width) || 0
+ height = Number(height) || 0
+
+ _.INTERNALS.set(this, { width, height } as OffscreenCanvasInternals)
+ }
+
+ get height(): number {
+ return _.internalsOf(this, 'OffscreenCanvas', 'height').height
+ }
+
+ set height(value) {
+ _.internalsOf(this, 'OffscreenCanvas', 'height').height = Number(value) || 0
+ }
+
+ get width(): number {
+ return _.internalsOf(this, 'OffscreenCanvas', 'width').width
+ }
+
+ set width(value) {
+ _.internalsOf(this, 'OffscreenCanvas', 'width').width = Number(value) || 0
+ }
+
+ getContext(contextType: PredefinedContextId): CanvasRenderingContext2D | null {
+ const internals = _.internalsOf<OffscreenCanvasInternals>(this, 'HTMLCanvasElement', 'getContext')
+
+ switch (contextType) {
+ case '2d':
+ if (internals.renderingContext2D) return internals.renderingContext2D
+
+ internals.renderingContext2D = __createCanvasRenderingContext2D(this)
+
+ return internals.renderingContext2D
+ default:
+ return null
+ }
+ }
+
+ convertToBlob(options: Partial<ConvertToBlobOptions>) {
+ options = Object(options)
+
+ const quality = Number(options.quality) || 0
+ const type = getImageType(String(options.type).trim().toLowerCase())
+
+ void quality
+
+ return Promise.resolve(
+ new Blob([], { type })
+ )
+ }
+}
+
+_.allowStringTag(OffscreenCanvas)
+
+const getImageType = (type: string): PredefinedImageType => type === 'image/avif' || type === 'image/jpeg' || type === 'image/png' || type === 'image/webp' ? type : 'image/png'
+
+interface OffscreenCanvasInternals {
+ height: number
+ renderingContext2D: CanvasRenderingContext2D
+ width: number
+}
+
+interface ConvertToBlobOptions {
+ quality: number
+ type: PredefinedImageType
+}
+
+type PredefinedContextId = '2d' | 'bitmaprenderer' | 'webgl' | 'webgl2' | 'webgpu'
+
+type PredefinedImageType = 'image/avif' | 'image/jpeg' | 'image/png' | 'image/webp'
diff --git a/packages/webapi/src/lib/Promise.ts b/packages/webapi/src/lib/Promise.ts
new file mode 100644
index 000000000..ea2c7c419
--- /dev/null
+++ b/packages/webapi/src/lib/Promise.ts
@@ -0,0 +1,29 @@
+export const any = {
+ async any <T>(
+ iterable: Iterable<T | PromiseLike<T>>
+ ): Promise<T> {
+ return Promise.all(
+ [...iterable].map(promise => {
+ return new Promise((resolve, reject) =>
+ Promise.resolve(promise).then(reject, resolve)
+ )
+ })
+ ).then(
+ errors => Promise.reject(errors),
+ value => Promise.resolve<T>(value)
+ )
+ }
+}.any
+
+export const initPromise = (target: any, exclude: Set<string>) => {
+ if (exclude.has('Promise') || exclude.has('any')) return
+
+ const Class = target.Promise || globalThis.Promise
+
+ if (!Class.any) Object.defineProperty(Class, 'any', {
+ value: any,
+ writable: true,
+ enumerable: false,
+ configurable: true
+ })
+}
diff --git a/packages/webapi/src/lib/RelativeIndexingMethod.ts b/packages/webapi/src/lib/RelativeIndexingMethod.ts
new file mode 100644
index 000000000..56627dfc3
--- /dev/null
+++ b/packages/webapi/src/lib/RelativeIndexingMethod.ts
@@ -0,0 +1,32 @@
+type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array
+
+export const at = {
+ at<T extends Array<any> | string | TypedArray>(this: T, index: number) {
+ index = Math.trunc(index) || 0
+
+ if (index < 0) index += this.length;
+
+ if (index < 0 || index >= this.length) return undefined;
+
+ return this[index];
+ }
+}.at
+
+export const initRelativeIndexingMethod = (target: any, exclude: Set<string>) => {
+ if (exclude.has('at')) return
+
+ const Classes = []
+
+ if (!exclude.has('TypedArray')) Classes.push(Object.getPrototypeOf(target.Int8Array || globalThis.Int8Array))
+ if (!exclude.has('Array')) Classes.push(target.Array || globalThis.Array)
+ if (!exclude.has('String')) Classes.push(target.String || globalThis.String)
+
+ for (const Class of Classes) {
+ if (!Class.prototype.at) Object.defineProperty(Class.prototype, 'at', {
+ value: at,
+ writable: true,
+ enumerable: false,
+ configurable: true
+ })
+ }
+}
diff --git a/packages/webapi/src/lib/Storage.ts b/packages/webapi/src/lib/Storage.ts
new file mode 100644
index 000000000..672c537c8
--- /dev/null
+++ b/packages/webapi/src/lib/Storage.ts
@@ -0,0 +1,51 @@
+import * as _ from './utils'
+
+export class Storage {
+ clear(): void {
+ _.internalsOf<StorageInternals>(this, 'Storage', 'clear').storage.clear()
+ }
+
+ getItem(key: string): string | null {
+ return getStringOrNull(
+ _.internalsOf<StorageInternals>(this, 'Storage', 'getItem').storage.get(String(key))
+ )
+ }
+
+ key(index: number): string | null {
+ return getStringOrNull([ ..._.internalsOf<StorageInternals>(this, 'Storage', 'key').storage.keys() ][Number(index) || 0])
+ }
+
+ removeItem(key: string): void {
+ _.internalsOf<StorageInternals>(this, 'Storage', 'getItem').storage.delete(String(key))
+ }
+
+ setItem(key: string, value: any): void {
+ _.internalsOf<StorageInternals>(this, 'Storage', 'getItem').storage.set(String(key), String(value))
+ }
+
+ get length() {
+ return _.internalsOf<StorageInternals>(this, 'Storage', 'size').storage.size
+ }
+}
+
+const getStringOrNull = (value: string | void) => typeof value === 'string' ? value : null
+
+export const initStorage = (target: Target, exclude: Set<string>) => {
+ if (exclude.has('Storage') || exclude.has('localStorage')) return
+
+ target.localStorage = Object.create(Storage.prototype)
+
+ const storageInternals = new Map<string, string>()
+
+ _.INTERNALS.set(target.localStorage, {
+ storage: storageInternals
+ } as StorageInternals)
+}
+
+interface StorageInternals {
+ storage: Map<string, string>
+}
+
+interface Target {
+ localStorage: Storage
+}
diff --git a/packages/webapi/src/lib/String.ts b/packages/webapi/src/lib/String.ts
new file mode 100644
index 000000000..49df3261c
--- /dev/null
+++ b/packages/webapi/src/lib/String.ts
@@ -0,0 +1,22 @@
+import * as _ from './utils'
+
+export const replaceAll = {
+ replaceAll(this: string, searchValue: RegExp | string, replaceValue: string | ((substring: string, ...args: any[]) => string)) {
+ return _.__object_isPrototypeOf(RegExp.prototype, searchValue)
+ ? this.replace(searchValue as RegExp, replaceValue as string)
+ : this.replace(new RegExp(_.__string_escapeRegExp(searchValue as string), 'g'), replaceValue as string)
+ }
+}.replaceAll
+
+export const initString = (target: any, exclude: Set<string>) => {
+ if (exclude.has('String') || exclude.has('replaceAll')) return
+
+ const Class = target.String || globalThis.String
+
+ if (!Class.prototype.replaceAll) Object.defineProperty(Class.prototype, 'replaceAll', {
+ value: replaceAll,
+ writable: true,
+ enumerable: false,
+ configurable: true
+ })
+}
diff --git a/packages/webapi/src/lib/StyleSheet.ts b/packages/webapi/src/lib/StyleSheet.ts
new file mode 100644
index 000000000..d4b2d9e33
--- /dev/null
+++ b/packages/webapi/src/lib/StyleSheet.ts
@@ -0,0 +1,24 @@
+import * as _ from './utils'
+
+export class StyleSheet {}
+
+export class CSSStyleSheet extends StyleSheet {
+ async replace(text: string) {
+ void text
+
+ return new CSSStyleSheet()
+ }
+
+ replaceSync(text: string) {
+ void text
+
+ return new CSSStyleSheet()
+ }
+
+ get cssRules() {
+ return []
+ }
+}
+
+_.allowStringTag(StyleSheet)
+_.allowStringTag(CSSStyleSheet)
diff --git a/packages/webapi/src/lib/Timeout.ts b/packages/webapi/src/lib/Timeout.ts
new file mode 100644
index 000000000..53412adfd
--- /dev/null
+++ b/packages/webapi/src/lib/Timeout.ts
@@ -0,0 +1,24 @@
+import { setTimeout as nodeSetTimeout, clearTimeout as nodeClearTimeout } from 'node:timers'
+import * as _ from './utils.js'
+
+const INTERNAL = { tick: 0, pool: new Map }
+
+export function setTimeout<TArgs extends any[], TFunc extends (...args: TArgs) => any>(callback: TFunc, delay = 0, ...args: TArgs): number {
+ const func = _.__function_bind(callback, globalThis)
+ const tick = ++INTERNAL.tick
+ const timeout = nodeSetTimeout(func, delay, ...args)
+
+ INTERNAL.pool.set(tick, timeout)
+
+ return tick
+}
+
+export function clearTimeout(timeoutId: number): void {
+ const timeout = INTERNAL.pool.get(timeoutId)
+
+ if (timeout) {
+ nodeClearTimeout(timeout)
+
+ INTERNAL.pool.delete(timeoutId)
+ }
+}
diff --git a/packages/webapi/src/lib/TreeWalker.ts b/packages/webapi/src/lib/TreeWalker.ts
new file mode 100644
index 000000000..86e32ff20
--- /dev/null
+++ b/packages/webapi/src/lib/TreeWalker.ts
@@ -0,0 +1,55 @@
+import * as _ from './utils'
+
+export class TreeWalker {
+ parentNode(): Node | null {
+ return null
+ }
+
+ firstChild(): Node | null {
+ return null
+ }
+
+ lastChild(): Node | null {
+ return null
+ }
+
+ previousSibling(): Node | null {
+ return null
+ }
+
+ nextSibling(): Node | null {
+ return null
+ }
+
+ previousNode(): Node | null {
+ return null
+ }
+
+ nextNode(): Node | null {
+ return null
+ }
+
+ get currentNode(): Node {
+ const internals = _.internalsOf<TreeWalkerInternals>(this, 'TreeWalker', 'currentNode')
+ return internals.currentNode
+ }
+
+ get root(): Node {
+ const internals = _.internalsOf<TreeWalkerInternals>(this, 'TreeWalker', 'root')
+ return internals.root
+ }
+
+ get whatToShow(): number {
+ const internals = _.internalsOf<TreeWalkerInternals>(this, 'TreeWalker', 'whatToShow')
+ return internals.whatToShow
+ }
+}
+
+_.allowStringTag(TreeWalker)
+
+export interface TreeWalkerInternals {
+ filter: NodeFilter
+ currentNode: Node
+ root: Node
+ whatToShow: number
+}
diff --git a/packages/webapi/src/lib/Window.ts b/packages/webapi/src/lib/Window.ts
new file mode 100644
index 000000000..ac3312c93
--- /dev/null
+++ b/packages/webapi/src/lib/Window.ts
@@ -0,0 +1,49 @@
+import * as _ from './utils'
+
+export class Window extends EventTarget {
+ get self(): this {
+ return this
+ }
+
+ get top(): this {
+ return this
+ }
+
+ get window(): this {
+ return this
+ }
+
+ get innerHeight(): number {
+ return 0
+ }
+
+ get innerWidth(): number {
+ return 0
+ }
+
+ get scrollX(): number {
+ return 0
+ }
+
+ get scrollY(): number {
+ return 0
+ }
+}
+
+_.allowStringTag(Window)
+
+export const initWindow = (target: Target, exclude: Set<string>) => {
+ if (exclude.has('Window') || exclude.has('window')) return
+
+ target.window = target
+}
+
+export interface WindowInternals {
+ document: null
+ location: URL
+ window: this
+}
+
+interface Target extends Record<any, any> {
+ window: this
+}
diff --git a/packages/webapi/src/lib/fetch.ts b/packages/webapi/src/lib/fetch.ts
new file mode 100644
index 000000000..8ab1358e7
--- /dev/null
+++ b/packages/webapi/src/lib/fetch.ts
@@ -0,0 +1,75 @@
+import { default as nodeFetch, Headers, Request, Response } from 'node-fetch/src/index.js'
+import Stream from 'node:stream'
+import * as _ from './utils'
+
+export { Headers, Request, Response }
+
+export const fetch = {
+ fetch(resource: string | URL | Request, init?: Partial<FetchInit>): Promise<Response> {
+ const resourceURL = new URL(
+ _.__object_isPrototypeOf(Request.prototype, resource)
+ ? (resource as Request).url
+ : _.pathToPosix(resource),
+ typeof Object(globalThis.process).cwd === 'function' ? 'file:' + _.pathToPosix(process.cwd()) + '/' : 'file:'
+ )
+
+ if (resourceURL.protocol.toLowerCase() === 'file:') {
+ return import('node:fs').then(
+ fs => {
+ try {
+ const stats = fs.statSync(resourceURL)
+ const body = fs.createReadStream(resourceURL)
+
+ return new Response(
+ body,
+ {
+ status: 200,
+ statusText: '',
+ headers: {
+ 'content-length': String(stats.size),
+ 'date': new Date().toUTCString(),
+ 'last-modified': new Date(stats.mtimeMs).toUTCString(),
+ }
+ }
+ )
+ } catch (error) {
+ const body = new Stream.Readable()
+
+ body._read = () => {}
+ body.push(null)
+
+ return new Response(
+ body,
+ {
+ status: 404,
+ statusText: '',
+ headers: {
+ 'date': new Date().toUTCString(),
+ }
+ }
+ )
+ }
+ }
+ )
+ } else {
+ return nodeFetch(resource, init)
+ }
+ }
+}.fetch
+
+type USVString = ({} & string)
+
+interface FetchInit {
+ body: Blob | BufferSource | FormData | URLSearchParams | ReadableStream | USVString
+ cache: 'default' | 'no-store' | 'reload' | 'no-cache' | 'force-cache' | 'only-if-cached'
+ credentials: 'omit' | 'same-origin' | 'include'
+ headers: Headers | Record<string, string>
+ method: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH' | USVString
+ mode: 'cors' | 'no-cors' | 'same-origin' | USVString
+ redirect: 'follow' | 'manual' | 'error'
+ referrer: USVString
+ referrerPolicy: 'no-referrer' | 'no-referrer-when-downgrade' | 'same-origin' | 'origin' | 'strict-origin' | 'origin-when-cross-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url'
+ integrity: USVString
+ keepalive: boolean
+ signal: AbortSignal
+}
diff --git a/packages/webapi/src/lib/structuredClone.ts b/packages/webapi/src/lib/structuredClone.ts
new file mode 100644
index 000000000..e60252015
--- /dev/null
+++ b/packages/webapi/src/lib/structuredClone.ts
@@ -0,0 +1,4 @@
+import { deserialize } from '@ungap/structured-clone/esm/deserialize.js';
+import { serialize } from '@ungap/structured-clone/esm/serialize.js';
+
+export default (any: any, options: any) => deserialize(serialize(any, options))
diff --git a/packages/webapi/src/lib/utils.ts b/packages/webapi/src/lib/utils.ts
new file mode 100644
index 000000000..e7c2c6115
--- /dev/null
+++ b/packages/webapi/src/lib/utils.ts
@@ -0,0 +1,61 @@
+import { performance } from 'node:perf_hooks'
+
+/** Returns the milliseconds elapsed since January 1, 1970 00:00:00 UTC. */
+export const __date_now = Date.now
+
+/** Returns the function bound to the given object. */
+export const __function_bind = Function.bind.bind(Function.call as unknown as any) as <TArgs extends any[], TFunc extends (...args: TArgs) => any>(callback: TFunc, thisArg: unknown, ...args: TArgs) => TFunc
+
+/** Returns the function called with the specified values. */
+export const __function_call = Function.call.bind(Function.call as unknown as any) as <TArgs extends any, TFunc extends (...args: TArgs[]) => any>(callback: TFunc, thisArg: unknown, ...args: TArgs[]) => ReturnType<TFunc>
+
+/** Returns an object with the specified prototype. */
+export const __object_create = Object.create as { <T extends any = any>(value: T): any extends T ? Record<any, any> : T }
+
+/** Returns whether an object has a property with the specified name. */
+export const __object_hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty) as { <T1 extends object, T2>(object: T1, key: T2): T2 extends keyof T1 ? true : false }
+
+/** Returns a string representation of an object. */
+export const __object_toString = Function.call.bind(Object.prototype.toString) as { (value: any): string }
+
+/** Returns whether the object prototype exists in another object. */
+export const __object_isPrototypeOf = Function.call.bind(Object.prototype.isPrototypeOf) as { <T1 extends object, T2>(p: T1, v: T2): T2 extends T1 ? true : false }
+
+/** Current high resolution millisecond timestamp. */
+export const __performance_now = performance.now as () => number
+
+/** Returns the string escaped for use inside regular expressions. */
+export const __string_escapeRegExp = (value: string) => value.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&')
+
+// @ts-ignore
+export const INTERNALS = new WeakMap<unknown, any>()
+
+export const internalsOf = <T extends object>(target: T | object, className: string, propName: string): T => {
+ const internals: T = INTERNALS.get(target)
+
+ if (!internals) throw new TypeError(`${className}.${propName} can only be used on instances of ${className}`)
+
+ return internals
+}
+
+export const allowStringTag = (value: any) => value.prototype[Symbol.toStringTag] = value.name
+
+/** Returns any kind of path as a posix path. */
+export const pathToPosix = (pathname: any) => String(
+ pathname == null ? '' : pathname
+).replace(
+ // convert slashes
+ /\\+/g, '/'
+).replace(
+ // prefix a slash to drive letters
+ /^(?=[A-Za-z]:\/)/, '/'
+).replace(
+ // encode path characters
+ /%/g, '%25'
+).replace(
+ /\n/g, '%0A'
+).replace(
+ /\r/g, '%0D'
+).replace(
+ /\t/g, '%09'
+)
diff --git a/packages/webapi/src/polyfill.ts b/packages/webapi/src/polyfill.ts
new file mode 100644
index 000000000..81df33185
--- /dev/null
+++ b/packages/webapi/src/polyfill.ts
@@ -0,0 +1,338 @@
+import {
+ AbortController,
+ AbortSignal,
+ Blob,
+ ByteLengthQueuingStrategy,
+ CanvasRenderingContext2D,
+ CharacterData,
+ Comment,
+ CountQueuingStrategy,
+ CSSStyleSheet,
+ CustomElementRegistry,
+ CustomEvent,
+ DOMException,
+ Document,
+ DocumentFragment,
+ Element,
+ Event,
+ EventTarget,
+ File,
+ FormData,
+ HTMLDocument,
+ HTMLElement,
+ HTMLBodyElement,
+ HTMLCanvasElement,
+ HTMLDivElement,
+ HTMLHeadElement,
+ HTMLHtmlElement,
+ HTMLImageElement,
+ HTMLSpanElement,
+ HTMLStyleElement,
+ HTMLTemplateElement,
+ HTMLUnknownElement,
+ Headers,
+ IntersectionObserver,
+ Image,
+ ImageData,
+ MediaQueryList,
+ MutationObserver,
+ Node,
+ NodeFilter,
+ NodeIterator,
+ OffscreenCanvas,
+ ReadableByteStreamController,
+ ReadableStream,
+ ReadableStreamBYOBReader,
+ ReadableStreamBYOBRequest,
+ ReadableStreamDefaultController,
+ ReadableStreamDefaultReader,
+ Request,
+ ResizeObserver,
+ Response,
+ ShadowRoot,
+ Storage,
+ StyleSheet,
+ Text,
+ TransformStream,
+ TreeWalker,
+ URLPattern,
+ WritableStream,
+ WritableStreamDefaultController,
+ WritableStreamDefaultWriter,
+ Window,
+
+ alert,
+ atob,
+ btoa,
+ cancelAnimationFrame,
+ cancelIdleCallback,
+ clearTimeout,
+ fetch,
+ requestAnimationFrame,
+ requestIdleCallback,
+ setTimeout,
+ structuredClone,
+
+ initCustomElementRegistry,
+ initDocument,
+ initMediaQueryList,
+ initObject,
+ initPromise,
+ initRelativeIndexingMethod,
+ initStorage,
+ initString,
+ initWindow,
+} from './ponyfill'
+
+import { exclusions } from './exclusions'
+import { inheritence } from './inheritence'
+
+export {
+ AbortController,
+ AbortSignal,
+ Blob,
+ ByteLengthQueuingStrategy,
+ CanvasRenderingContext2D,
+ CharacterData,
+ Comment,
+ CountQueuingStrategy,
+ CSSStyleSheet,
+ CustomElementRegistry,
+ CustomEvent,
+ DOMException,
+ Document,
+ DocumentFragment,
+ Element,
+ Event,
+ EventTarget,
+ File,
+ FormData,
+ HTMLDocument,
+ HTMLElement,
+ HTMLBodyElement,
+ HTMLCanvasElement,
+ HTMLDivElement,
+ HTMLHeadElement,
+ HTMLHtmlElement,
+ HTMLImageElement,
+ HTMLSpanElement,
+ HTMLStyleElement,
+ HTMLTemplateElement,
+ HTMLUnknownElement,
+ Headers,
+ IntersectionObserver,
+ Image,
+ ImageData,
+ MediaQueryList,
+ MutationObserver,
+ Node,
+ NodeFilter,
+ NodeIterator,
+ OffscreenCanvas,
+ ReadableByteStreamController,
+ ReadableStream,
+ ReadableStreamBYOBReader,
+ ReadableStreamBYOBRequest,
+ ReadableStreamDefaultController,
+ ReadableStreamDefaultReader,
+ Request,
+ ResizeObserver,
+ Response,
+ ShadowRoot,
+ StyleSheet,
+ Text,
+ TransformStream,
+ TreeWalker,
+ URLPattern,
+ WritableStream,
+ WritableStreamDefaultController,
+ WritableStreamDefaultWriter,
+ Window,
+
+ alert,
+ atob,
+ btoa,
+ cancelAnimationFrame,
+ cancelIdleCallback,
+ clearTimeout,
+ fetch,
+ requestAnimationFrame,
+ requestIdleCallback,
+ setTimeout,
+ structuredClone,
+} from './ponyfill.js'
+
+export { pathToPosix } from './lib/utils'
+
+export const polyfill = (target: any, options?: PolyfillOptions) => {
+ const webAPIs = {
+ AbortController,
+ AbortSignal,
+ Blob,
+ ByteLengthQueuingStrategy,
+ CanvasRenderingContext2D,
+ CharacterData,
+ Comment,
+ CountQueuingStrategy,
+ CSSStyleSheet,
+ CustomElementRegistry,
+ CustomEvent,
+ Document,
+ DocumentFragment,
+ DOMException,
+ Element,
+ Event,
+ EventTarget,
+ File,
+ FormData,
+ HTMLDocument,
+ HTMLElement,
+ HTMLBodyElement,
+ HTMLCanvasElement,
+ HTMLDivElement,
+ HTMLHeadElement,
+ HTMLHtmlElement,
+ HTMLImageElement,
+ HTMLSpanElement,
+ HTMLStyleElement,
+ HTMLTemplateElement,
+ HTMLUnknownElement,
+ Headers,
+ IntersectionObserver,
+ Image,
+ ImageData,
+ MediaQueryList,
+ MutationObserver,
+ Node,
+ NodeFilter,
+ NodeIterator,
+ OffscreenCanvas,
+ ReadableByteStreamController,
+ ReadableStream,
+ ReadableStreamBYOBReader,
+ ReadableStreamBYOBRequest,
+ ReadableStreamDefaultController,
+ ReadableStreamDefaultReader,
+ Request,
+ ResizeObserver,
+ Response,
+ ShadowRoot,
+ Storage,
+ StyleSheet,
+ Text,
+ TransformStream,
+ TreeWalker,
+ URLPattern,
+ WritableStream,
+ WritableStreamDefaultController,
+ WritableStreamDefaultWriter,
+ Window,
+
+ alert,
+ atob,
+ btoa,
+ cancelAnimationFrame,
+ cancelIdleCallback,
+ clearTimeout,
+ fetch,
+ requestAnimationFrame,
+ requestIdleCallback,
+ setTimeout,
+ structuredClone,
+ }
+
+ // initialize exclude options
+ const excludeOptions = new Set(
+ typeof Object(options).exclude === 'string'
+ ? String(Object(options).exclude).trim().split(/\s+/)
+ : Array.isArray(Object(options).exclude)
+ ? Object(options).exclude.reduce(
+ (array: string[], entry: unknown) => array.splice(array.length, 0, ...(typeof entry === 'string' ? entry.trim().split(/\s+/) : [])) && array,
+ []
+ )
+ : []
+ ) as Set<string>
+
+ // expand exclude options using exclusion shorthands
+ for (const excludeOption of excludeOptions) {
+ if (excludeOption in exclusions) {
+ for (const exclusion of exclusions[excludeOption as keyof typeof exclusions]) {
+ excludeOptions.add(exclusion)
+ }
+ }
+ }
+
+ // apply each WebAPI
+ for (const name of Object.keys(webAPIs)) {
+ // skip WebAPIs that are excluded
+ if (excludeOptions.has(name)) continue
+
+ // skip WebAPIs that are built-in
+ if (Object.hasOwnProperty.call(target, name)) continue
+
+ // define WebAPIs on the target
+ Object.defineProperty(target, name, { configurable: true, enumerable: true, writable: true, value: webAPIs[name as keyof typeof webAPIs] })
+ }
+
+ // ensure WebAPIs correctly inherit other WebAPIs
+ for (const name of Object.keys(webAPIs)) {
+ // skip WebAPIs that are excluded
+ if (excludeOptions.has(name)) continue
+
+ // skip WebAPIs that do not extend other WebAPIs
+ if (!Object.hasOwnProperty.call(inheritence, name)) continue
+
+ const Class = target[name]
+ const Super = target[inheritence[name as keyof typeof inheritence]]
+
+ // skip WebAPIs that are not available
+ if (!Class || !Super) continue
+
+ // skip WebAPIs that are already inherited correctly
+ if (Object.getPrototypeOf(Class.prototype) === Super.prototype) continue
+
+ // define WebAPIs inheritence
+ Object.setPrototypeOf(Class.prototype, Super.prototype)
+ }
+
+ if (!excludeOptions.has('HTMLDocument') && !excludeOptions.has('HTMLElement')) {
+ initDocument(target, excludeOptions)
+
+ if (!excludeOptions.has('CustomElementRegistry')) {
+ initCustomElementRegistry(target, excludeOptions)
+ }
+ }
+
+ initObject(target, excludeOptions)
+ initMediaQueryList(target, excludeOptions)
+ initPromise(target, excludeOptions)
+ initRelativeIndexingMethod(target, excludeOptions)
+ initStorage(target, excludeOptions)
+ initString(target, excludeOptions)
+ initWindow(target, excludeOptions)
+
+ return target
+}
+
+polyfill.internals = (target: any, name: string) => {
+ const init = {
+ CustomElementRegistry: initCustomElementRegistry,
+ Document: initDocument,
+ MediaQueryList: initMediaQueryList,
+ Object: initObject,
+ Promise: initPromise,
+ RelativeIndexingMethod: initRelativeIndexingMethod,
+ Storage: initStorage,
+ String: initString,
+ Window: initWindow,
+ }
+
+ init[name as keyof typeof init](target, new Set<string>())
+
+ return target
+}
+
+interface PolyfillOptions {
+ exclude?: string | string[]
+ override?: Record<string, { (...args: any[]): any }>
+}
diff --git a/packages/webapi/src/ponyfill.ts b/packages/webapi/src/ponyfill.ts
new file mode 100644
index 000000000..cbbfba909
--- /dev/null
+++ b/packages/webapi/src/ponyfill.ts
@@ -0,0 +1,127 @@
+// @ts-check
+
+import { AbortController, AbortSignal } from 'abort-controller/dist/abort-controller.mjs'
+import { requestAnimationFrame, cancelAnimationFrame } from './lib/AnimationFrame'
+import { atob, btoa } from './lib/Base64'
+import { CharacterData, Comment, Text } from './lib/CharacterData'
+import { File, Blob } from 'fetch-blob/from.js'
+import { CustomEvent } from './lib/CustomEvent'
+import { DOMException } from './lib/DOMException'
+import { TreeWalker } from './lib/TreeWalker'
+import { cancelIdleCallback, requestIdleCallback } from './lib/IdleCallback'
+import { Event, EventTarget } from 'event-target-shim'
+import { fetch, Headers, Request, Response } from './lib/fetch'
+import { FormData } from 'formdata-polyfill/esm.min.js'
+import { ByteLengthQueuingStrategy, CountQueuingStrategy, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, TransformStream, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter } from 'web-streams-polyfill/dist/ponyfill.es6.mjs'
+import { URLPattern } from 'urlpattern-polyfill'
+import { setTimeout, clearTimeout } from './lib/Timeout'
+import structuredClone from './lib/structuredClone'
+
+import { CanvasRenderingContext2D } from './lib/CanvasRenderingContext2D'
+import { CSSStyleSheet, StyleSheet } from './lib/StyleSheet'
+import { CustomElementRegistry, initCustomElementRegistry } from './lib/CustomElementRegistry'
+import { Document, HTMLDocument, initDocument } from './lib/Document'
+import { DocumentFragment, Node, NodeFilter, NodeIterator, ShadowRoot } from './lib/Node'
+import { Element, HTMLElement, HTMLBodyElement, HTMLDivElement, HTMLHeadElement, HTMLHtmlElement, HTMLSpanElement, HTMLStyleElement, HTMLTemplateElement, HTMLUnknownElement } from './lib/Element'
+import { HTMLCanvasElement } from './lib/HTMLCanvasElement'
+import { HTMLImageElement } from './lib/HTMLImageElement'
+import { Image } from './lib/Image'
+import { ImageData } from './lib/ImageData'
+import { IntersectionObserver, MutationObserver, ResizeObserver } from './lib/Observer'
+import { MediaQueryList, initMediaQueryList } from './lib/MediaQueryList'
+import { OffscreenCanvas } from './lib/OffscreenCanvas'
+import { Storage, initStorage } from './lib/Storage'
+import { Window, initWindow } from './lib/Window'
+
+import { alert } from './lib/Alert'
+
+import { initObject } from './lib/Object'
+import { initPromise } from './lib/Promise'
+import { initRelativeIndexingMethod } from './lib/RelativeIndexingMethod'
+import { initString } from './lib/String'
+
+export {
+ AbortController,
+ AbortSignal,
+ Blob,
+ ByteLengthQueuingStrategy,
+ CanvasRenderingContext2D,
+ CharacterData,
+ Comment,
+ CountQueuingStrategy,
+ CSSStyleSheet,
+ CustomElementRegistry,
+ CustomEvent,
+ DOMException,
+ Document,
+ DocumentFragment,
+ Element,
+ Event,
+ EventTarget,
+ File,
+ FormData,
+ Headers,
+ HTMLBodyElement,
+ HTMLCanvasElement,
+ HTMLDivElement,
+ HTMLDocument,
+ HTMLElement,
+ HTMLHeadElement,
+ HTMLHtmlElement,
+ HTMLImageElement,
+ HTMLSpanElement,
+ HTMLStyleElement,
+ HTMLTemplateElement,
+ HTMLUnknownElement,
+ Image,
+ ImageData,
+ IntersectionObserver,
+ MediaQueryList,
+ MutationObserver,
+ Node,
+ NodeFilter,
+ NodeIterator,
+ OffscreenCanvas,
+ ReadableByteStreamController,
+ ReadableStream,
+ ReadableStreamBYOBReader,
+ ReadableStreamBYOBRequest,
+ ReadableStreamDefaultController,
+ ReadableStreamDefaultReader,
+ Request,
+ ResizeObserver,
+ Response,
+ ShadowRoot,
+ Storage,
+ StyleSheet,
+ Text,
+ TransformStream,
+ TreeWalker,
+ URLPattern,
+ WritableStream,
+ WritableStreamDefaultController,
+ WritableStreamDefaultWriter,
+ Window,
+
+ alert,
+ atob,
+ btoa,
+ cancelAnimationFrame,
+ cancelIdleCallback,
+ clearTimeout,
+ fetch,
+ requestAnimationFrame,
+ requestIdleCallback,
+ setTimeout,
+ structuredClone,
+
+ initCustomElementRegistry,
+ initDocument,
+ initMediaQueryList,
+ initObject,
+ initPromise,
+ initRelativeIndexingMethod,
+ initStorage,
+ initString,
+ initWindow,
+}
diff --git a/packages/webapi/src/types.d.ts b/packages/webapi/src/types.d.ts
new file mode 100644
index 000000000..7f8f96eec
--- /dev/null
+++ b/packages/webapi/src/types.d.ts
@@ -0,0 +1,6 @@
+declare module '@ungap/structured-clone/esm/index.js'
+declare module '@ungap/structured-clone/esm/deserialize.js'
+declare module '@ungap/structured-clone/esm/serialize.js'
+declare module 'abort-controller/dist/abort-controller.mjs'
+declare module 'node-fetch/src/index.js'
+declare module 'web-streams-polyfill/dist/ponyfill.es6.mjs'