Repository: capricorn86/happy-dom
Pull request #765 (2023-02-21 00:45:09)

Task/682 pollyfill messageevent since node doesnt initialize properties per the spec


Patch:
diff --git a/README.md b/README.md
index 69bff11a..887d7eb4 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
 ![Happy DOM Logo](https://github.com/capricorn86/happy-dom/raw/master/docs/happy-dom-logo.jpg)
 
-
 # About
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG [DOM](https://dom.spec.whatwg.org/) and [HTML](https://html.spec.whatwg.org/multipage/).
@@ -9,43 +8,35 @@ The goal of [Happy DOM](https://github.com/capricorn86/happy-dom) is to emulate
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) focuses heavily on performance and can be used as an alternative to [JSDOM](https://github.com/jsdom/jsdom).
 
-
 [Read more about how to use Happy DOM](https://github.com/capricorn86/happy-dom/tree/master/packages/happy-dom)
 
-
-
 ### DOM Features
 
-- Custom Elements (Web Components)
+-   Custom Elements (Web Components)
 
-- Shadow Root (Shadow DOM)
+-   Shadow Root (Shadow DOM)
 
-- Declarative Shadow DOM
+-   Declarative Shadow DOM
 
-- Mutation Observer
+-   Mutation Observer
 
-- Tree Walker
+-   Tree Walker
 
-- Fetch
-  
+-   Fetch
 
 And much more..
 
-
-
 ### Works With
 
-- [Google LitHTML](https://lit-html.polymer-project.org)
+-   [Google LitHTML](https://lit-html.polymer-project.org)
 
-- [Google LitElement](https://lit-element.polymer-project.org)
+-   [Google LitElement](https://lit-element.polymer-project.org)
 
-- [React](https://reactjs.org)
-
-- [Angular](https://angular.io/)
-
-- [Vue](https://vuejs.org/)
+-   [React](https://reactjs.org)
 
+-   [Angular](https://angular.io/)
 
+-   [Vue](https://vuejs.org/)
 
 # Packages
 
@@ -75,8 +66,6 @@ This package has been deprecated.
 
 Happy DOM now supports [Declarative Shadow DOM](https://github.com/capricorn86/happy-dom/tree/master/packages/happy-dom#server-side-rendering-of-web-components) which can be used for server-side rendering of web components.
 
-
-
 # Performance
 
 | Operation                            | JSDOM   | Happy DOM |
@@ -95,4 +84,8 @@ Happy DOM now supports [Declarative Shadow DOM](https://github.com/capricorn86/h
 
 # Contributing
 
-[Read more about how to develop and contribute](https://github.com/capricorn86/happy-dom/blob/master/docs/contributing.md)
\ No newline at end of file
+[Read more about how to develop and contribute](https://github.com/capricorn86/happy-dom/blob/master/docs/contributing.md)
+
+# Sponsors
+
+[![RTVision](https://avatars.githubusercontent.com/u/8292810?s=200&v=4)](https://rtvision.com/)
diff --git a/packages/global-registrator/README.md b/packages/global-registrator/README.md
index 19fc3c46..c6d89968 100644
--- a/packages/global-registrator/README.md
+++ b/packages/global-registrator/README.md
@@ -1,6 +1,5 @@
 ![Happy DOM Logo](https://github.com/capricorn86/happy-dom/raw/master/docs/happy-dom-logo.jpg)
 
-
 # About
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG [DOM](https://dom.spec.whatwg.org/) and [HTML](https://html.spec.whatwg.org/multipage/).
@@ -11,7 +10,6 @@ The goal of [Happy DOM](https://github.com/capricorn86/happy-dom) is to emulate
 
 This package contains a utility that registers [Happy DOM](https://github.com/capricorn86/happy-dom) globally, which makes it possible to use [Happy DOM](https://github.com/capricorn86/happy-dom) for testing in a Node environment.
 
-
 ### DOM Features
 
 - Custom Elements (Web Components)
@@ -28,8 +26,6 @@ This package contains a utility that registers [Happy DOM](https://github.com/ca
 
 And much more..
 
-  
-
 ### Works With
 
 - [Google LitHTML](https://lit-html.polymer-project.org)
@@ -42,18 +38,12 @@ And much more..
 
 - [Vue](https://vuejs.org/)
 
-  
-
-
 # Installation
 
 ```bash
 npm install @happy-dom/global-registrator --save-dev
 ```
 
-
-
-
 # Usage
 
 ## Register
@@ -68,7 +58,7 @@ document.body.innerHTML = `<button>My button</button>`;
 const button = document.querySelector('button');
 
 // Outputs: "My button"
-console.log(button.innerText)
+console.log(button.innerText);
 ```
 
 ## Unregister
@@ -81,10 +71,9 @@ GlobalRegistrator.register();
 GlobalRegistrator.unregister();
 
 // Outputs: "undefined"
-console.log(global.document)
+console.log(global.document);
 ```
 
-
 # Performance
 
 | Operation                            | JSDOM   | Happy DOM |
@@ -101,3 +90,6 @@ console.log(global.document)
 
 [See how the test was done here](https://github.com/capricorn86/happy-dom-performance-test)
 
+# Sponsors
+
+[![RTVision](https://avatars.githubusercontent.com/u/8292810?s=200&v=4)](https://rtvision.com/)
diff --git a/packages/happy-dom/README.md b/packages/happy-dom/README.md
index de847fa4..ae6b7c22 100644
--- a/packages/happy-dom/README.md
+++ b/packages/happy-dom/README.md
@@ -257,6 +257,7 @@ const window = new Window({
 		disableJavaScriptFileLoading: true,
 		disableJavaScriptEvaluation: true,
 		disableCSSFileLoading: true,
+		disableIframePageLoading: true,
 		enableFileSystemHttpRequests: true
 	}
 });
@@ -270,6 +271,7 @@ const window = new Window();
 window.happyDOM.settings.disableJavaScriptFileLoading = true;
 window.happyDOM.settings.disableJavaScriptEvaluation = true;
 window.happyDOM.settings.disableCSSFileLoading = true;
+window.happyDOM.settings.disableIframePageLoading = true;
 window.happyDOM.settings.enableFileSystemHttpRequests = true;
 ```
 
@@ -283,7 +285,11 @@ Set it to "true" to completely disable JavaScript evaluation. Defaults to "false
 
 **disableCSSFileLoading**
 
-Set it to "true" to disable CSS file loading using the HTMLLinkElement. Defaults to "false".
+Set it to "true" to disable CSS file loading in HTMLLinkElement. Defaults to "false".
+
+**disableIframePageLoading**
+
+Set it to "true" to disable page loading in HTMLIframeElement. Defaults to "false".
 
 **enableFileSystemHttpRequests**
 
@@ -312,3 +318,7 @@ Happy DOM provide with a package called [@happy-dom/jest-environment](https://gi
 # Global Registration
 
 Happy DOM provide with a package called [@happy-dom/global-registrator](https://github.com/capricorn86/happy-dom/tree/master/packages/global-registrator) that can register Happy DOM globally. It makes it possible to use Happy DOM for testing in a Node environment.
+
+# Sponsors
+
+[![RTVision](https://avatars.githubusercontent.com/u/8292810?s=200&v=4)](https://rtvision.com/)
diff --git a/packages/happy-dom/src/config/ElementTag.ts b/packages/happy-dom/src/config/ElementTag.ts
index 49f15720..034c460d 100644
--- a/packages/happy-dom/src/config/ElementTag.ts
+++ b/packages/happy-dom/src/config/ElementTag.ts
@@ -21,6 +21,7 @@ import HTMLButtonElement from '../nodes/html-button-element/HTMLButtonElement';
 import HTMLAudioElement from '../nodes/html-audio-element/HTMLAudioElement';
 import HTMLVideoElement from '../nodes/html-video-element/HTMLVideoElement';
 import HTMLAnchorElement from '../nodes/html-anchor-element/HTMLAnchorElement';
+import HTMLIframeElement from '../nodes/html-iframe-element/HTMLIframeElement';
 
 export default {
 	A: HTMLAnchorElement,
@@ -94,7 +95,7 @@ export default {
 	HR: HTMLElement,
 	HTML: HTMLElement,
 	I: HTMLElement,
-	IFRAME: HTMLElement,
+	IFRAME: HTMLIframeElement,
 	INS: HTMLElement,
 	KBD: HTMLElement,
 	LEGEND: HTMLElement,
diff --git a/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts b/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
index 3b712841..6622795a 100644
--- a/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
+++ b/packages/happy-dom/src/config/NonImplemenetedElementClasses.ts
@@ -45,7 +45,6 @@ export default [
 	'HTMLTableSectionElement',
 	'HTMLFrameElement',
 	'HTMLFrameSetElement',
-	'HTMLIFrameElement',
 	'HTMLEmbedElement',
 	'HTMLObjectElement',
 	'HTMLParamElement',
diff --git a/packages/happy-dom/src/event/IMessagePort.ts b/packages/happy-dom/src/event/IMessagePort.ts
new file mode 100644
index 00000000..1af798dc
--- /dev/null
+++ b/packages/happy-dom/src/event/IMessagePort.ts
@@ -0,0 +1,26 @@
+import IEventTarget from './IEventTarget';
+
+/**
+ * Message port.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
+ */
+export default interface IMessagePort extends IEventTarget {
+	/**
+	 * Sends a message from the port, and optionally, transfers ownership of objects to other browsing contexts.
+	 *
+	 * @param type Event type.
+	 * @param listener Listener.
+	 */
+	postMessage(message: unknown, transerList: unknown[]): void;
+
+	/**
+	 * Starts the sending of messages queued on the port.
+	 */
+	start(): void;
+
+	/**
+	 * Disconnects the port, so it is no longer active. This stops the flow of messages to that port.
+	 */
+	close(): void;
+}
diff --git a/packages/happy-dom/src/event/MessagePort.ts b/packages/happy-dom/src/event/MessagePort.ts
new file mode 100644
index 00000000..cb7a6733
--- /dev/null
+++ b/packages/happy-dom/src/event/MessagePort.ts
@@ -0,0 +1,33 @@
+import EventTarget from './EventTarget';
+import IMessagePort from './IMessagePort';
+
+/**
+ * Message port.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
+ */
+export default abstract class MessagePort extends EventTarget implements IMessagePort {
+	/**
+	 * Sends a message from the port, and optionally, transfers ownership of objects to other browsing contexts.
+	 *
+	 * @param _message Message.
+	 * @param _transerList Transfer list.
+	 */
+	public postMessage(_message: unknown, _transerList: unknown[]): void {
+		// TODO: Implement
+	}
+
+	/**
+	 * Starts the sending of messages queued on the port.
+	 */
+	public start(): void {
+		// TODO: Implement
+	}
+
+	/**
+	 * Disconnects the port, so it is no longer active. This stops the flow of messages to that port.
+	 */
+	public close(): void {
+		// TODO: Implement
+	}
+}
diff --git a/packages/happy-dom/src/event/events/IMessageEventInit.ts b/packages/happy-dom/src/event/events/IMessageEventInit.ts
new file mode 100644
index 00000000..7d1e6339
--- /dev/null
+++ b/packages/happy-dom/src/event/events/IMessageEventInit.ts
@@ -0,0 +1,11 @@
+import IEventInit from '../IEventInit';
+import IWindow from '../../window/IWindow';
+import IMessagePort from '../IMessagePort';
+
+export default interface IMessageEventInit extends IEventInit {
+	data?: unknown | null;
+	origin?: string;
+	lastEventId?: string;
+	source?: IWindow | null;
+	ports?: IMessagePort[];
+}
diff --git a/packages/happy-dom/src/event/events/MessageEvent.ts b/packages/happy-dom/src/event/events/MessageEvent.ts
new file mode 100644
index 00000000..fc95ecc3
--- /dev/null
+++ b/packages/happy-dom/src/event/events/MessageEvent.ts
@@ -0,0 +1,32 @@
+import IWindow from '../../window/IWindow';
+import Event from '../Event';
+import IMessagePort from '../IMessagePort';
+import IMessageEventInit from './IMessageEventInit';
+
+/**
+ * Message event.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
+ */
+export default class MessageEvent extends Event {
+	public data?: unknown | null = null;
+	public origin?: string = '';
+	public lastEventId?: string = '';
+	public source?: IWindow | null = null;
+	public ports?: IMessagePort[] = [];
+
+	/**
+	 * Constructor.
+	 *
+	 * @param type Event type.
+	 * @param [eventInit] Event init.
+	 */
+	constructor(type: string, eventInit?: IMessageEventInit) {
+		super(type, eventInit);
+		this.data = eventInit?.data !== undefined ? eventInit.data : null;
+		this.origin = eventInit?.origin || '';
+		this.lastEventId = eventInit?.lastEventId || '';
+		this.source = eventInit?.source || null;
+		this.ports = eventInit?.ports || [];
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIframeElement.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIframeElement.ts
new file mode 100644
index 00000000..566a6a2b
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIframeElement.ts
@@ -0,0 +1,266 @@
+import { URL } from 'url';
+import Event from '../../event/Event';
+import ErrorEvent from '../../event/events/ErrorEvent';
+import IWindow from '../../window/IWindow';
+import Window from '../../window/Window';
+import IDocument from '../document/IDocument';
+import HTMLElement from '../html-element/HTMLElement';
+import INode from '../node/INode';
+import IframeCrossOriginWindow from './IframeCrossOriginWindow';
+import IHTMLIframeElement from './IHTMLIframeElement';
+
+/**
+ * HTML Iframe Element.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLIframeElement.
+ */
+export default class HTMLIframeElement extends HTMLElement implements IHTMLIframeElement {
+	// Events
+	public onload: (event: Event) => void | null = null;
+	public onerror: (event: Event) => void | null = null;
+
+	// Private
+	#contentWindow: IWindow | IframeCrossOriginWindow | null = null;
+
+	/**
+	 * Returns source.
+	 *
+	 * @returns Source.
+	 */
+	public get src(): string {
+		return this.getAttribute('src') || '';
+	}
+
+	/**
+	 * Sets source.
+	 *
+	 * @param src Source.
+	 */
+	public set src(src: string) {
+		this.setAttribute('src', src);
+	}
+
+	/**
+	 * Returns allow.
+	 *
+	 * @returns Allow.
+	 */
+	public get allow(): string {
+		return this.getAttribute('allow') || '';
+	}
+
+	/**
+	 * Sets allow.
+	 *
+	 * @param allow Allow.
+	 */
+	public set allow(allow: string) {
+		this.setAttribute('allow', allow);
+	}
+
+	/**
+	 * Returns height.
+	 *
+	 * @returns Height.
+	 */
+	public get height(): string {
+		return this.getAttribute('height') || '';
+	}
+
+	/**
+	 * Sets height.
+	 *
+	 * @param height Height.
+	 */
+	public set height(height: string) {
+		this.setAttribute('height', height);
+	}
+
+	/**
+	 * Returns width.
+	 *
+	 * @returns Width.
+	 */
+	public get width(): string {
+		return this.getAttribute('width') || '';
+	}
+
+	/**
+	 * Sets width.
+	 *
+	 * @param width Width.
+	 */
+	public set width(width: string) {
+		this.setAttribute('width', width);
+	}
+
+	/**
+	 * Returns name.
+	 *
+	 * @returns Name.
+	 */
+	public get name(): string {
+		return this.getAttribute('name') || '';
+	}
+
+	/**
+	 * Sets name.
+	 *
+	 * @param name Name.
+	 */
+	public set name(name: string) {
+		this.setAttribute('name', name);
+	}
+
+	/**
+	 * Returns sandbox.
+	 *
+	 * @returns Sandbox.
+	 */
+	public get sandbox(): string {
+		return this.getAttribute('sandbox') || '';
+	}
+
+	/**
+	 * Sets sandbox.
+	 *
+	 * @param sandbox Sandbox.
+	 */
+	public set sandbox(sandbox: string) {
+		this.setAttribute('sandbox', sandbox);
+	}
+
+	/**
+	 * Returns srcdoc.
+	 *
+	 * @returns Srcdoc.
+	 */
+	public get srcdoc(): string {
+		return this.getAttribute('srcdoc') || '';
+	}
+
+	/**
+	 * Sets sandbox.
+	 *
+	 * @param srcdoc Srcdoc.
+	 */
+	public set srcdoc(srcdoc: string) {
+		this.setAttribute('srcdoc', srcdoc);
+	}
+
+	/**
+	 * Returns content document.
+	 *
+	 * @returns Content document.
+	 */
+	public get contentDocument(): IDocument | null {
+		return (<IWindow>this.#contentWindow)?.document || null;
+	}
+
+	/**
+	 * Returns content window.
+	 *
+	 * @returns Content window.
+	 */
+	public get contentWindow(): IWindow | IframeCrossOriginWindow | null {
+		return this.#contentWindow || null;
+	}
+
+	/**
+	 * @override
+	 */
+	public override _connectToNode(parentNode: INode = null): void {
+		const isConnected = this.isConnected;
+		const isParentConnected = parentNode ? parentNode.isConnected : false;
+
+		super._connectToNode(parentNode);
+
+		if (
+			isParentConnected &&
+			isConnected !== isParentConnected &&
+			!this.ownerDocument.defaultView.happyDOM.settings.disableIframePageLoading
+		) {
+			const src = this.src;
+
+			if (src !== null) {
+				const contentWindow = new (<typeof Window>this.ownerDocument.defaultView.constructor)({
+					url: src,
+					settings: {
+						...this.ownerDocument.defaultView.happyDOM.settings
+					}
+				});
+
+				(<IWindow>contentWindow.parent) = this.ownerDocument.defaultView;
+				(<IWindow>contentWindow.top) = this.ownerDocument.defaultView;
+
+				if (src === 'about:blank') {
+					this.#contentWindow = contentWindow;
+					return;
+				}
+
+				if (src.startsWith('javascript:')) {
+					this.#contentWindow = contentWindow;
+					this.#contentWindow.eval(src.replace('javascript:', ''));
+					return;
+				}
+
+				const originURL = this.ownerDocument.defaultView.location;
+				const targetURL = new URL(src, originURL);
+				const isCORS =
+					(originURL.hostname !== targetURL.hostname &&
+						!originURL.hostname.endsWith(targetURL.hostname)) ||
+					originURL.protocol !== targetURL.protocol;
+
+				const onError = (error): void => {
+					this.dispatchEvent(
+						new ErrorEvent('error', {
+							message: error.message,
+							error
+						})
+					);
+					this.ownerDocument.defaultView.dispatchEvent(
+						new ErrorEvent('error', {
+							message: error.message,
+							error
+						})
+					);
+					if (
+						!this['_listeners']['error'] &&
+						!this.ownerDocument.defaultView['_listeners']['error']
+					) {
+						this.ownerDocument.defaultView.console.error(error);
+					}
+				};
+
+				this.#contentWindow = null;
+				this.ownerDocument.defaultView
+					.fetch(src)
+					.then((response) => {
+						response
+							.text()
+							.then((text) => {
+								this.#contentWindow = isCORS
+									? new IframeCrossOriginWindow(this.ownerDocument.defaultView, contentWindow)
+									: contentWindow;
+								contentWindow.document.write(text);
+								this.dispatchEvent(new Event('load'));
+							})
+							.catch(onError);
+					})
+					.catch(onError);
+			}
+		}
+	}
+
+	/**
+	 * Clones a node.
+	 *
+	 * @override
+	 * @param [deep=false] "true" to clone deep.
+	 * @returns Cloned node.
+	 */
+	public cloneNode(deep = false): IHTMLIframeElement {
+		return <IHTMLIframeElement>super.cloneNode(deep);
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-iframe-element/IHTMLIframeElement.ts b/packages/happy-dom/src/nodes/html-iframe-element/IHTMLIframeElement.ts
new file mode 100644
index 00000000..1c326cd4
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-iframe-element/IHTMLIframeElement.ts
@@ -0,0 +1,27 @@
+import Event from '../../event/Event';
+import IWindow from '../../window/IWindow';
+import IDocument from '../document/IDocument';
+import IHTMLElement from '../html-element/IHTMLElement';
+import IframeCrossOriginWindow from './IframeCrossOriginWindow';
+
+/**
+ * HTML Iframe Element.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/HTMLIframeElement.
+ */
+export default interface IHTMLIframeElement extends IHTMLElement {
+	src: string | null;
+	allow: string | null;
+	height: string | null;
+	width: string | null;
+	name: string | null;
+	sandbox: string | null;
+	srcdoc: string | null;
+	readonly contentDocument: IDocument | null;
+	readonly contentWindow: IWindow | IframeCrossOriginWindow | null;
+
+	// Events
+	onload: (event: Event) => void | null;
+	onerror: (event: Event) => void | null;
+}
diff --git a/packages/happy-dom/src/nodes/html-iframe-element/IframeCrossOriginWindow.ts b/packages/happy-dom/src/nodes/html-iframe-element/IframeCrossOriginWindow.ts
new file mode 100644
index 00000000..77821503
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-iframe-element/IframeCrossOriginWindow.ts
@@ -0,0 +1,49 @@
+import Location from '../../location/Location';
+import EventTarget from '../../event/EventTarget';
+import IWindow from '../../window/IWindow';
+
+/**
+ * Browser window with limited access due to CORS restrictions in iframes.
+ */
+export default class IframeCrossOriginWindow extends EventTarget {
+	public readonly self = this;
+	public readonly window = this;
+	public readonly parent: IWindow;
+	public readonly top: IWindow;
+
+	private _targetWindow: IWindow;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param parent Parent window.
+	 * @param target Target window.
+	 */
+	constructor(parent: IWindow, target: IWindow) {
+		super();
+
+		this.parent = parent;
+		this.top = parent;
+		this._targetWindow = target;
+	}
+
+	/**
+	 * Returns location.
+	 *
+	 * @returns Location.
+	 */
+	public get location(): Location {
+		return this._targetWindow.location;
+	}
+
+	/**
+	 * 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 {
+		this._targetWindow.postMessage(message, targetOrigin, transfer);
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts
index b04dd4d1..6e3839ed 100644
--- a/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts
+++ b/packages/happy-dom/src/nodes/html-link-element/HTMLLinkElement.ts
@@ -250,13 +250,14 @@ export default class HTMLLinkElement extends HTMLElement implements IHTMLLinkEle
 	/**
 	 * @override
 	 */
-	public _connectToNode(parentNode: INode = null): void {
+	public override _connectToNode(parentNode: INode = null): void {
 		const isConnected = this.isConnected;
 		const isParentConnected = parentNode ? parentNode.isConnected : false;
 
 		super._connectToNode(parentNode);
 
 		if (
+			isParentConnected &&
 			isConnected !== isParentConnected &&
 			this._evaluateCSS &&
 			!this.ownerDocument.defaultView.happyDOM.settings.disableCSSFileLoading
diff --git a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts
index 6affe075..f14561f6 100644
--- a/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts
+++ b/packages/happy-dom/src/nodes/html-script-element/HTMLScriptElement.ts
@@ -182,13 +182,13 @@ export default class HTMLScriptElement extends HTMLElement implements IHTMLScrip
 	/**
 	 * @override
 	 */
-	public _connectToNode(parentNode: INode = null): void {
+	public override _connectToNode(parentNode: INode = null): void {
 		const isConnected = this.isConnected;
 		const isParentConnected = parentNode ? parentNode.isConnected : false;
 
 		super._connectToNode(parentNode);
 
-		if (isConnected !== isParentConnected && this._evaluateScript) {
+		if (isParentConnected && isConnected !== isParentConnected && this._evaluateScript) {
 			const src = this.getAttributeNS(null, 'src');
 
 			if (src !== null) {
diff --git a/packages/happy-dom/src/window/IHappyDOMOptions.ts b/packages/happy-dom/src/window/IHappyDOMOptions.ts
new file mode 100644
index 00000000..5cf96244
--- /dev/null
+++ b/packages/happy-dom/src/window/IHappyDOMOptions.ts
@@ -0,0 +1,15 @@
+/**
+ * Happy DOM options.
+ */
+export default interface IHappyDOMOptions {
+	innerWidth?: number;
+	innerHeight?: number;
+	url?: string;
+	settings?: {
+		disableJavaScriptEvaluation?: boolean;
+		disableJavaScriptFileLoading?: boolean;
+		disableCSSFileLoading?: boolean;
+		disableIframePageLoading?: boolean;
+		enableFileSystemHttpRequests?: boolean;
+	};
+}
diff --git a/packages/happy-dom/src/window/IHappyDOMSettings.ts b/packages/happy-dom/src/window/IHappyDOMSettings.ts
index cb19d869..9e9490c4 100644
--- a/packages/happy-dom/src/window/IHappyDOMSettings.ts
+++ b/packages/happy-dom/src/window/IHappyDOMSettings.ts
@@ -5,5 +5,6 @@ export default interface IHappyDOMSettings {
 	disableJavaScriptEvaluation: boolean;
 	disableJavaScriptFileLoading: boolean;
 	disableCSSFileLoading: boolean;
+	disableIframePageLoading: boolean;
 	enableFileSystemHttpRequests: boolean;
 }
diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts
index 88e7db41..20820b2f 100644
--- a/packages/happy-dom/src/window/IWindow.ts
+++ b/packages/happy-dom/src/window/IWindow.ts
@@ -24,6 +24,7 @@ import HTMLMediaElement from '../nodes/html-media-element/HTMLMediaElement';
 import HTMLAudioElement from '../nodes/html-audio-element/HTMLAudioElement';
 import HTMLVideoElement from '../nodes/html-video-element/HTMLVideoElement';
 import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement';
+import HTMLIframeElement from '../nodes/html-iframe-element/HTMLIframeElement';
 import SVGSVGElement from '../nodes/svg-element/SVGSVGElement';
 import SVGElement from '../nodes/svg-element/SVGElement';
 import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement';
@@ -73,6 +74,8 @@ import InputEvent from '../event/events/InputEvent';
 import UIEvent from '../event/UIEvent';
 import ErrorEvent from '../event/events/ErrorEvent';
 import StorageEvent from '../event/events/StorageEvent';
+import MessageEvent from '../event/events/MessageEvent';
+import MessagePort from '../event/MessagePort';
 import Screen from '../screen/Screen';
 import AsyncTaskManager from '../async-task-manager/AsyncTaskManager';
 import Storage from '../storage/Storage';
@@ -142,6 +145,7 @@ export default interface IWindow extends IEventTarget, NodeJS.Global {
 	readonly HTMLAudioElement: typeof HTMLAudioElement;
 	readonly HTMLVideoElement: typeof HTMLVideoElement;
 	readonly HTMLBaseElement: typeof HTMLBaseElement;
+	readonly HTMLIframeElement: typeof HTMLIframeElement;
 	readonly HTMLDialogElement: typeof HTMLDialogElement;
 	readonly Attr: typeof Attr;
 	readonly NamedNodeMap: typeof NamedNodeMap;
@@ -175,6 +179,8 @@ export default interface IWindow extends IEventTarget, NodeJS.Global {
 	readonly InputEvent: typeof InputEvent;
 	readonly ErrorEvent: typeof ErrorEvent;
 	readonly StorageEvent: typeof StorageEvent;
+	readonly MessageEvent: typeof MessageEvent;
+	readonly MessagePort: typeof MessagePort;
 	readonly ProgressEvent: typeof ProgressEvent;
 	readonly MediaQueryListEvent: typeof MediaQueryListEvent;
 	readonly EventTarget: typeof EventTarget;
@@ -374,4 +380,12 @@ export default interface IWindow extends IEventTarget, NodeJS.Global {
 	 * @returns An ASCII string containing decoded data from encodedData.
 	 */
 	atob(data: unknown): string;
+
+	/**
+	 * 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 listener Listener.
+	 */
+	postMessage(message: unknown, targetOrigin?: string, transfer?: unknown[]): void;
 }
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index bcc72ec4..5c7ce8f1 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -25,6 +25,7 @@ import HTMLMediaElement from '../nodes/html-media-element/HTMLMediaElement';
 import HTMLAudioElement from '../nodes/html-audio-element/HTMLAudioElement';
 import HTMLVideoElement from '../nodes/html-video-element/HTMLVideoElement';
 import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement';
+import HTMLIframeElement from '../nodes/html-iframe-element/HTMLIframeElement';
 import HTMLDialogElement from '../nodes/html-dialog-element/HTMLDialogElement';
 import SVGSVGElement from '../nodes/svg-element/SVGSVGElement';
 import SVGElement from '../nodes/svg-element/SVGElement';
@@ -39,9 +40,11 @@ import Event from '../event/Event';
 import CustomEvent from '../event/events/CustomEvent';
 import AnimationEvent from '../event/events/AnimationEvent';
 import KeyboardEvent from '../event/events/KeyboardEvent';
+import MessageEvent from '../event/events/MessageEvent';
 import ProgressEvent from '../event/events/ProgressEvent';
 import MediaQueryListEvent from '../event/events/MediaQueryListEvent';
 import EventTarget from '../event/EventTarget';
+import MessagePort from '../event/MessagePort';
 import { URL, URLSearchParams } from 'url';
 import Location from '../location/Location';
 import NonImplementedEventTypes from '../event/NonImplementedEventTypes';
@@ -115,9 +118,10 @@ import Attr from '../nodes/attr/Attr';
 import NamedNodeMap from '../named-node-map/NamedNodeMap';
 import IElement from '../nodes/element/IElement';
 import ProcessingInstruction from '../nodes/processing-instruction/ProcessingInstruction';
-import IHappyDOMSettings from './IHappyDOMSettings';
 import RequestInfo from '../fetch/RequestInfo';
 import FileList from '../nodes/html-input-element/FileList';
+import DOMExceptionNameEnum from '../exception/DOMExceptionNameEnum';
+import IHappyDOMOptions from './IHappyDOMOptions';
 
 const ORIGINAL_SET_TIMEOUT = setTimeout;
 const ORIGINAL_CLEAR_TIMEOUT = clearTimeout;
@@ -159,6 +163,7 @@ export default class Window extends EventTarget implements IWindow {
 			disableJavaScriptEvaluation: false,
 			disableJavaScriptFileLoading: false,
 			disableCSSFileLoading: false,
+			disableIframePageLoading: false,
 			enableFileSystemHttpRequests: false
 		}
 	};
@@ -183,6 +188,7 @@ export default class Window extends EventTarget implements IWindow {
 	public readonly HTMLAudioElement = HTMLAudioElement;
 	public readonly HTMLVideoElement = HTMLVideoElement;
 	public readonly HTMLBaseElement = HTMLBaseElement;
+	public readonly HTMLIframeElement = HTMLIframeElement;
 	public readonly HTMLDialogElement = HTMLDialogElement;
 	public readonly Attr = Attr;
 	public readonly NamedNodeMap = NamedNodeMap;
@@ -208,6 +214,7 @@ export default class Window extends EventTarget implements IWindow {
 	public readonly CustomEvent = CustomEvent;
 	public readonly AnimationEvent = AnimationEvent;
 	public readonly KeyboardEvent = KeyboardEvent;
+	public readonly MessageEvent = MessageEvent;
 	public readonly MouseEvent = MouseEvent;
 	public readonly PointerEvent = PointerEvent;
 	public readonly FocusEvent = FocusEvent;
@@ -218,6 +225,7 @@ export default class Window extends EventTarget implements IWindow {
 	public readonly ProgressEvent = ProgressEvent;
 	public readonly MediaQueryListEvent = MediaQueryListEvent;
 	public readonly EventTarget = EventTarget;
+	public readonly MessagePort = MessagePort;
 	public readonly DataTransfer = DataTransfer;
 	public readonly DataTransferItem = DataTransferItem;
 	public readonly DataTransferItemList = DataTransferItemList;
@@ -370,12 +378,7 @@ export default class Window extends EventTarget implements IWindow {
 	 * @param [options.url] URL.
 	 * @param [options.settings] Settings.
 	 */
-	constructor(options?: {
-		innerWidth?: number;
-		innerHeight?: number;
-		url?: string;
-		settings?: IHappyDOMSettings;
-	}) {
+	constructor(options?: IHappyDOMOptions) {
 		super();
 
 		this.customElements = new CustomElementRegistry();
@@ -732,6 +735,42 @@ export default class Window extends EventTarget implements IWindow {
 		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 (targetOrigin && targetOrigin !== '*' && this.location.origin !== targetOrigin) {
+			throw new 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 DOMException(
+				`Failed to execute 'postMessage' on 'Window': The provided message cannot be serialized.`,
+				DOMExceptionNameEnum.invalidStateError
+			);
+		}
+
+		this.dispatchEvent(
+			new MessageEvent('message', {
+				data: message,
+				origin: this.parent.location.origin,
+				source: this.parent,
+				lastEventId: ''
+			})
+		);
+	}
+
 	/**
 	 * Setup of VM context.
 	 */
diff --git a/packages/jest-environment/README.md b/packages/jest-environment/README.md
index ff62137c..309a9f7d 100644
--- a/packages/jest-environment/README.md
+++ b/packages/jest-environment/README.md
@@ -1,6 +1,5 @@
 ![Happy DOM Logo](https://github.com/capricorn86/happy-dom/raw/master/docs/happy-dom-logo.jpg)
 
-
 # About
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG [DOM](https://dom.spec.whatwg.org/) and [HTML](https://html.spec.whatwg.org/multipage/).
@@ -11,7 +10,6 @@ The goal of [Happy DOM](https://github.com/capricorn86/happy-dom) is to emulate
 
 This package makes it possible to use [Happy DOM](https://github.com/capricorn86/happy-dom) with [Jest](https://jestjs.io/).
 
-
 ### DOM Features
 
 - Custom Elements (Web Components)
@@ -28,8 +26,6 @@ This package makes it possible to use [Happy DOM](https://github.com/capricorn86
 
 And much more..
 
-  
-
 ### Works With
 
 - [Google LitHTML](https://lit-html.polymer-project.org)
@@ -42,66 +38,56 @@ And much more..
 
 - [Vue](https://vuejs.org/)
 
-  
-
-
 # Installation
 
 ```bash
 npm install @happy-dom/jest-environment --save-dev
 ```
 
-
-
-
 # Setup
 
 Jest uses `node` as test environment by default. In order to tell Jest to use a different environment we will either have to set a CLI attribute, define it in "package.json" or add a property to your Jest config file.
 
-
-
 ## CLI
 
 1. Edit your "package.json" file.
 2. Add "--env=@happy-dom/jest-environment" as an attribute to your Jest command.
 
-    ```json
-    {
-        "scripts": {
-            "test": "jest --env=@happy-dom/jest-environment"
-        }
-    }
-    ```
+   ```json
+   {
+   	"scripts": {
+   		"test": "jest --env=@happy-dom/jest-environment"
+   	}
+   }
+   ```
 
 3. Save the file.
 
-
 ## In "package.json"
 
 1. Edit your "package.json" file.
 2. Add the following to it:
 
-    ```json
-    {
-        "jest": {
-            "testEnvironment": "@happy-dom/jest-environment"
-        }
-    }
-    ```
+   ```json
+   {
+   	"jest": {
+   		"testEnvironment": "@happy-dom/jest-environment"
+   	}
+   }
+   ```
 
 3. Save the file.
 
-
-
 ## Configuration File
+
 1. Edit your Jest config file (usually jest.config.js)
 2. Add the following to it:
 
-    ```json
-    {
-      "testEnvironment": "@happy-dom/jest-environment"
-    }
-    ```
+   ```json
+   {
+   	"testEnvironment": "@happy-dom/jest-environment"
+   }
+   ```
 
 3. Save the file.
 
@@ -109,21 +95,20 @@ Jest uses `node` as test environment by default. In order to tell Jest to use a
 
 Happy DOM exposes two functions that may be useful when testing asynchrounous code.
 
-
 **whenAsyncComplete()**
 
 Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that is resolved when all async tasks has been completed.
 
 ```javascript
 describe('scrollToTop()', () => {
-    it('scrolls to top using the built in browser animation', async () => {
-        element.scrollToTop();
+	it('scrolls to top using the built in browser animation', async () => {
+		element.scrollToTop();
 
-        // Waits for asynchronous tasks like setTimeout(), requestAnimationFrame() etc. to complete
-        await happyDOM.whenAsyncComplete();
+		// Waits for asynchronous tasks like setTimeout(), requestAnimationFrame() etc. to complete
+		await happyDOM.whenAsyncComplete();
 
-        expect(document.documentElement.scrollTop).toBe(0);
-    });
+		expect(document.documentElement.scrollTop).toBe(0);
+	});
 });
 ```
 
@@ -133,14 +118,14 @@ This method will cancel all running async tasks.
 
 ```javascript
 describe('runAnimation()', () => {
-    it('runs animation', () => {
-        element.runAnimation();
+	it('runs animation', () => {
+		element.runAnimation();
 
-        // Cancels all asynchronous tasks like setTimeout(), requestAnimationFrame() etc.
-        happyDOM.cancelAsync();
+		// Cancels all asynchronous tasks like setTimeout(), requestAnimationFrame() etc.
+		happyDOM.cancelAsync();
 
-        expect(element.animationCompleted).toBe(true);
-    });
+		expect(element.animationCompleted).toBe(true);
+	});
 });
 ```
 
@@ -160,3 +145,6 @@ describe('runAnimation()', () => {
 
 [See how the test was done here](https://github.com/capricorn86/happy-dom-performance-test)
 
+# Sponsors
+
+[![RTVision](https://avatars.githubusercontent.com/u/8292810?s=200&v=4)](https://rtvision.com/)


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-iframe-element/HTMLIframeElement.test.ts b/packages/happy-dom/test/nodes/html-iframe-element/HTMLIframeElement.test.ts
new file mode 100644
index 00000000..bae297f1
--- /dev/null
+++ b/packages/happy-dom/test/nodes/html-iframe-element/HTMLIframeElement.test.ts
@@ -0,0 +1,154 @@
+import Window from '../../../src/window/Window';
+import IWindow from '../../../src/window/IWindow';
+import IDocument from '../../../src/nodes/document/IDocument';
+import IHTMLIframeElement from '../../../src/nodes/html-iframe-element/IHTMLIframeElement';
+import IResponse from '../../../src/fetch/IResponse';
+import ErrorEvent from '../../../src/event/events/ErrorEvent';
+import IframeCrossOriginWindow from '../../../src/nodes/html-iframe-element/IframeCrossOriginWindow';
+import MessageEvent from '../../../src/event/events/MessageEvent';
+
+describe('HTMLIframeElement', () => {
+	let window: IWindow;
+	let document: IDocument;
+	let element: IHTMLIframeElement;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+		element = <IHTMLIframeElement>document.createElement('iframe');
+	});
+
+	describe('Object.prototype.toString', () => {
+		it('Returns `[object HTMLIframeElement]`', () => {
+			expect(Object.prototype.toString.call(element)).toBe('[object HTMLIframeElement]');
+		});
+	});
+
+	for (const property of ['src', 'allow', 'height', 'width', 'name', 'sandbox', 'srcdoc']) {
+		describe(`get ${property}()`, () => {
+			it(`Returns the "${property}" attribute.`, () => {
+				element.setAttribute(property, 'value');
+				expect(element[property]).toBe('value');
+			});
+		});
+
+		describe(`set ${property}()`, () => {
+			it(`Sets the attribute "${property}".`, () => {
+				element[property] = 'value';
+				expect(element.getAttribute(property)).toBe('value');
+			});
+		});
+	}
+
+	describe('get contentWindow()', () => {
+		it('Returns content window for "about:blank".', () => {
+			element.src = 'about:blank';
+			expect(element.contentDocument).toBe(null);
+			document.body.appendChild(element);
+			expect(element.contentWindow === element.contentDocument.defaultView).toBe(true);
+			expect(element.contentDocument.documentElement.innerHTML).toBe('<head></head><body></body>');
+		});
+
+		it('Returns content window for "javascript:scroll(10, 20)".', () => {
+			element.src = 'javascript:scroll(10, 20)';
+			document.body.appendChild(element);
+			expect(element.contentWindow === element.contentDocument.defaultView).toBe(true);
+			expect(element.contentDocument.documentElement.scrollLeft).toBe(10);
+			expect(element.contentDocument.documentElement.scrollTop).toBe(20);
+		});
+
+		it('Returns content window for URL with same origin.', (done) => {
+			const responseHTML = '<html><head></head><body>Test</body></html>';
+			let fetchedURL = null;
+
+			jest.spyOn(window, 'fetch').mockImplementation((url: string) => {
+				fetchedURL = url;
+				return Promise.resolve(<IResponse>{
+					text: () => Promise.resolve(responseHTML),
+					ok: true
+				});
+			});
+
+			window.happyDOM.setURL('https://localhost:8080');
+			element.src = 'https://localhost:8080/iframe.html';
+			element.addEventListener('load', () => {
+				expect(fetchedURL).toBe('https://localhost:8080/iframe.html');
+				expect(element.contentWindow === element.contentDocument.defaultView).toBe(true);
+				expect(`<html>${element.contentDocument.documentElement.innerHTML}</html>`).toBe(
+					responseHTML
+				);
+				done();
+			});
+			document.body.appendChild(element);
+		});
+
+		it('Returns instance of IframeCrossOriginWindow for URL with different origin.', (done) => {
+			const iframeOrigin = 'https://other.origin.com';
+			const iframeSrc = iframeOrigin + '/iframe.html';
+			const documentOrigin = 'https://localhost:8080';
+			let fetchedURL = null;
+
+			jest.spyOn(window, 'fetch').mockImplementation((url: string) => {
+				fetchedURL = url;
+				return Promise.resolve(<IResponse>{
+					text: () => Promise.resolve('<html><head></head><body>Test</body></html>'),
+					ok: true
+				});
+			});
+
+			window.happyDOM.setURL(documentOrigin);
+			element.src = iframeSrc;
+			element.addEventListener('load', () => {
+				const message = 'test';
+				let triggeredEvent: MessageEvent | null = null;
+				expect(fetchedURL).toBe(iframeSrc);
+				expect(element.contentWindow instanceof IframeCrossOriginWindow).toBe(true);
+				expect(element.contentWindow.location.href).toBe(iframeSrc);
+				expect(element.contentWindow.self === element.contentWindow).toBe(true);
+				expect(element.contentWindow.window === element.contentWindow).toBe(true);
+				expect(element.contentWindow.parent === window).toBe(true);
+				expect(element.contentWindow.top === window).toBe(true);
+				element.contentWindow['_targetWindow'].addEventListener(
+					'message',
+					(event: MessageEvent) => (triggeredEvent = event)
+				);
+				element.contentWindow.postMessage(message, iframeOrigin);
+				expect(element.contentDocument).toBe(null);
+				expect(triggeredEvent.data).toBe(message);
+				expect(triggeredEvent.origin).toBe(documentOrigin);
+				expect(triggeredEvent.source === window).toBe(true);
+				expect(triggeredEvent.lastEventId).toBe('');
+				done();
+			});
+			document.body.appendChild(element);
+		});
+
+		it('Dispatches an error event when the page fails to load.', (done) => {
+			const error = new Error('Error');
+
+			jest.spyOn(window, 'fetch').mockImplementation(() => {
+				return Promise.resolve(<IResponse>{
+					text: () => Promise.reject(error),
+					ok: true
+				});
+			});
+
+			element.src = 'https://localhost:8080/iframe.html';
+			element.addEventListener('error', (event: ErrorEvent) => {
+				expect(event.message).toBe(error.message);
+				expect(event.error).toBe(error);
+				done();
+			});
+			document.body.appendChild(element);
+		});
+	});
+
+	describe('get contentDocument()', () => {
+		it('Returns content document for "about:blank".', () => {
+			element.src = 'about:blank';
+			expect(element.contentDocument).toBe(null);
+			document.body.appendChild(element);
+			expect(element.contentDocument.documentElement.innerHTML).toBe('<head></head><body></body>');
+		});
+	});
+});
diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts
index c5054340..0cbfb850 100644
--- a/packages/happy-dom/test/window/Window.test.ts
+++ b/packages/happy-dom/test/window/Window.test.ts
@@ -13,6 +13,7 @@ import DOMException from '../../src/exception/DOMException';
 import DOMExceptionNameEnum from '../../src/exception/DOMExceptionNameEnum';
 import CustomElement from '../../test/CustomElement';
 import { URL } from 'url';
+import MessageEvent from '../../src/event/events/MessageEvent';
 
 describe('Window', () => {
 	let window: IWindow;
@@ -973,4 +974,68 @@ describe('Window', () => {
 			);
 		});
 	});
+
+	describe('postMessage()', () => {
+		it('Posts a message.', function () {
+			const message = 'test';
+			const parentOrigin = 'https://localhost:8080';
+			const parent = new Window({
+				url: parentOrigin
+			});
+			let triggeredEvent: MessageEvent | null = null;
+
+			(<Window>window.parent) = parent;
+
+			window.addEventListener('message', (event) => (triggeredEvent = event));
+			window.postMessage(message);
+
+			expect(triggeredEvent.data).toBe(message);
+			expect(triggeredEvent.origin).toBe(parentOrigin);
+			expect(triggeredEvent.source).toBe(parent);
+			expect(triggeredEvent.lastEventId).toBe('');
+
+			window.postMessage(message, '*');
+
+			expect(triggeredEvent.data).toBe(message);
+			expect(triggeredEvent.origin).toBe(parentOrigin);
+			expect(triggeredEvent.source).toBe(parent);
+			expect(triggeredEvent.lastEventId).toBe('');
+		});
+
+		it('Posts a data object as message.', function () {
+			const message = {
+				test: 'test'
+			};
+			let triggeredEvent: MessageEvent | null = null;
+
+			window.addEventListener('message', (event) => (triggeredEvent = event));
+			window.postMessage(message);
+
+			expect(triggeredEvent.data).toBe(message);
+		});
+
+		it("Throws an exception if the provided object can't be serialized.", function () {
+			expect(() => window.postMessage(window)).toThrowError(
+				new DOMException(
+					`Failed to execute 'postMessage' on 'Window': The provided message cannot be serialized.`,
+					DOMExceptionNameEnum.invalidStateError
+				)
+			);
+		});
+
+		it('Throws an exception if the target origin differs from the document origin.', function () {
+			const message = 'test';
+			const targetOrigin = 'https://localhost:8081';
+			const documentOrigin = 'https://localhost:8080';
+
+			window.happyDOM.setURL(documentOrigin);
+
+			expect(() => window.postMessage(message, targetOrigin)).toThrowError(
+				new DOMException(
+					`Failed to execute 'postMessage' on 'Window': The target origin provided ('${targetOrigin}') does not match the recipient window\'s origin ('${documentOrigin}').`,
+					DOMExceptionNameEnum.securityError
+				)
+			);
+		});
+	});
 });