From 8d125f6614a61bf89a960be8e5703b1d1a5fe830 Mon Sep 17 00:00:00 2001 From: jeffwcx Date: Wed, 10 Apr 2024 22:15:49 +0800 Subject: [PATCH 1/3] feat: [#1398] Add support for iframe srcdoc --- .../src/browser/types/IGoToOptions.ts | 4 + .../utilities/BrowserFrameNavigator.ts | 69 +++++---- .../HTMLIFrameElementNamedNodeMap.ts | 21 ++- .../HTMLIFrameElementPageLoader.ts | 22 ++- .../HTMLIFrameElement.test.ts | 132 +++++++++++++++++- 5 files changed, 211 insertions(+), 37 deletions(-) diff --git a/packages/happy-dom/src/browser/types/IGoToOptions.ts b/packages/happy-dom/src/browser/types/IGoToOptions.ts index 7b051546c..a3708c520 100644 --- a/packages/happy-dom/src/browser/types/IGoToOptions.ts +++ b/packages/happy-dom/src/browser/types/IGoToOptions.ts @@ -14,4 +14,8 @@ export default interface IGoToOptions extends IReloadOptions { * Referrer policy. */ referrerPolicy?: IRequestReferrerPolicy; + /** + * If `srcdoc` is set, it can be used to replace the data obtained by fetch. + */ + substituteData?: string; } diff --git a/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts b/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts index 5f6b010ae..d1cd05f3a 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts @@ -99,18 +99,21 @@ export default class BrowserFrameNavigator { } (frame.childFrames) = []; + const parentWindow = frame.window.parent; frame.window[PropertySymbol.destroy](); frame[PropertySymbol.asyncTaskManager].destroy(); frame[PropertySymbol.asyncTaskManager] = new AsyncTaskManager(); (frame.window) = new windowClass(frame, { url: targetURL.href, width, height }); + ((frame.window.top)) = parentWindow; + ((frame.window.parent)) = parentWindow; (frame.window.devicePixelRatio) = devicePixelRatio; if (referrer) { frame.window.document[PropertySymbol.referrer] = referrer; } - if (targetURL.protocol === 'about:') { + if (targetURL.protocol === 'about:' && goToOptions?.substituteData === void 0) { return null; } @@ -138,37 +141,45 @@ export default class BrowserFrameNavigator { } }; - try { - response = await frame.window.fetch(targetURL.href, { - referrer, - referrerPolicy: goToOptions?.referrerPolicy || 'origin', - signal: abortController.signal, - method: method || (formData ? 'POST' : 'GET'), - headers: goToOptions?.hard ? { 'Cache-Control': 'no-cache' } : undefined, - body: formData + if (goToOptions?.substituteData !== void 0) { + responseText = goToOptions.substituteData; + response = new frame.window.Response(goToOptions.substituteData, { + headers: { 'Content-Type': 'text/html' } }); - - // Handles the "X-Frame-Options" header for child frames. - if (frame.parentFrame) { - const originURL = frame.parentFrame.window.location; - const xFrameOptions = response.headers?.get('X-Frame-Options')?.toLowerCase(); - const isSameOrigin = originURL.origin === targetURL.origin || targetURL.origin === 'null'; - - if (xFrameOptions === 'deny' || (xFrameOptions === 'sameorigin' && !isSameOrigin)) { - throw new Error( - `Refused to display '${url}' in a frame because it set 'X-Frame-Options' to '${xFrameOptions}'.` - ); + } else { + try { + response = await frame.window.fetch(targetURL.href, { + referrer, + referrerPolicy: goToOptions?.referrerPolicy || 'origin', + signal: abortController.signal, + method: method || (formData ? 'POST' : 'GET'), + headers: goToOptions?.hard ? { 'Cache-Control': 'no-cache' } : undefined, + body: formData + }); + + // Handles the "X-Frame-Options" header for child frames. + if (frame.parentFrame) { + const originURL = frame.parentFrame.window.location; + const xFrameOptions = response.headers?.get('X-Frame-Options')?.toLowerCase(); + const isSameOrigin = originURL.origin === targetURL.origin || targetURL.origin === 'null'; + + if (xFrameOptions === 'deny' || (xFrameOptions === 'sameorigin' && !isSameOrigin)) { + throw new Error( + `Refused to display '${url}' in a frame because it set 'X-Frame-Options' to '${xFrameOptions}'.` + ); + } } - } - - responseText = await response.text(); - } catch (error) { - finalize(); - throw error; - } - if (!response.ok) { - frame.page.console.error(`GET ${targetURL.href} ${response.status} (${response.statusText})`); + responseText = await response.text(); + } catch (error) { + finalize(); + throw error; + } + if (!response.ok) { + frame.page.console.error( + `GET ${targetURL.href} ${response.status} (${response.statusText})` + ); + } } // Fixes issue where evaluating the response can throw an error. diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts index 28a8be90e..63f8c479a 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts @@ -45,9 +45,13 @@ export default class HTMLIFrameElementNamedNodeMap extends HTMLElementNamedNodeM */ public override setNamedItem(item: Attr): Attr | null { const replacedAttribute = super.setNamedItem(item); - + if (item[PropertySymbol.name] === 'srcdoc') { + this.#pageLoader.loadPage(); + } + // If the src attribute and the srcdoc attribute are both specified together, the srcdoc attribute takes priority. if ( item[PropertySymbol.name] === 'src' && + this[PropertySymbol.ownerElement].getAttribute('srcdoc') === null && item[PropertySymbol.value] && item[PropertySymbol.value] !== replacedAttribute?.[PropertySymbol.value] ) { @@ -70,6 +74,21 @@ export default class HTMLIFrameElementNamedNodeMap extends HTMLElementNamedNodeM return replacedAttribute || null; } + /** + * @override + */ + public override [PropertySymbol.removeNamedItem](name: string): Attr | null { + const removedItem = super[PropertySymbol.removeNamedItem](name); + if ( + removedItem && + (removedItem[PropertySymbol.name] === 'srcdoc' || removedItem[PropertySymbol.name] === 'src') + ) { + this.#pageLoader.loadPage(); + } + + return removedItem; + } + /** * * @param tokens diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts index 6783a5f68..40d74c837 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts @@ -10,6 +10,7 @@ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import BrowserFrameURL from '../../browser/utilities/BrowserFrameURL.js'; import BrowserFrameFactory from '../../browser/utilities/BrowserFrameFactory.js'; import IRequestReferrerPolicy from '../../fetch/types/IRequestReferrerPolicy.js'; +import IGoToOptions from '../../browser/types/IGoToOptions.js'; /** * HTML Iframe page loader. @@ -51,10 +52,13 @@ export default class HTMLIFrameElementPageLoader { this.#contentWindowContainer.window = null; return; } - + const srcdoc = this.#element.getAttribute('srcdoc'); const window = this.#element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow]; const originURL = this.#browserParentFrame.window.location; - const targetURL = BrowserFrameURL.getRelativeURL(this.#browserParentFrame, this.#element.src); + const targetURL = BrowserFrameURL.getRelativeURL( + this.#browserParentFrame, + srcdoc !== null ? 'about:srcdoc' : this.#element.src + ); if (this.#browserIFrame && this.#browserIFrame.window.location.href === targetURL.href) { return; @@ -83,11 +87,17 @@ export default class HTMLIFrameElementPageLoader { ((this.#browserIFrame.window.parent)) = parentWindow; + const gotoOptions: IGoToOptions = { + referrer: originURL.origin, + referrerPolicy: this.#element.referrerPolicy + }; + + if (srcdoc !== null) { + gotoOptions.substituteData = srcdoc; + } + this.#browserIFrame - .goto(targetURL.href, { - referrer: originURL.origin, - referrerPolicy: this.#element.referrerPolicy - }) + .goto(targetURL.href, gotoOptions) .then(() => this.#element.dispatchEvent(new Event('load'))) .catch((error) => WindowErrorUtility.dispatchError(this.#element, error)); 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 index 74d750243..79de967cc 100644 --- a/packages/happy-dom/test/nodes/html-iframe-element/HTMLIFrameElement.test.ts +++ b/packages/happy-dom/test/nodes/html-iframe-element/HTMLIFrameElement.test.ts @@ -35,7 +35,7 @@ describe('HTMLIFrameElement', () => { }); }); - for (const property of ['src', 'allow', 'height', 'width', 'name', 'srcdoc']) { + for (const property of ['src', 'allow', 'height', 'width', 'name']) { describe(`get ${property}()`, () => { it(`Returns the "${property}" attribute.`, () => { element.setAttribute(property, 'value'); @@ -126,6 +126,105 @@ describe('HTMLIFrameElement', () => { }); }); + describe('get srcdoc()', () => { + it('Returns string', () => { + expect(element.srcdoc).toBe(''); + element.srcdoc = '
'; + expect(element.getAttribute('srcdoc')).toBe('
'); + }); + }); + + describe('set srcdoc()', () => { + it("Navigate the element's browsing context to a resource whose Content-Type is text/html", async () => { + const actualHTML = await new Promise((resolve) => { + element.srcdoc = '
TEST
'; + element.addEventListener('load', () => { + resolve(element.contentDocument?.documentElement.innerHTML); + }); + document.body.appendChild(element); + }); + expect(actualHTML).toBe('
TEST
'); + }); + + it('Takes priority, when the src attribute and the srcdoc attribute are both specified together', async () => { + const browser = new Browser(); + const page = browser.newPage(); + const window = page.mainFrame.window; + const document = window.document; + const element = document.createElement('iframe'); + const url = await new Promise((resolve) => { + element.srcdoc = 'TEST'; + element.src = 'https://localhost:8080/iframe.html'; + element.addEventListener('load', () => { + resolve(page.mainFrame.childFrames[0].url); + }); + document.appendChild(element); + }); + expect(url).toBe('about:srcdoc'); + }); + + it('Resolve the value of the src attribute when the srcdoc attribute has been removed', async () => { + const browser = new Browser(); + const page = browser.newPage(); + const window = page.mainFrame.window; + const document = window.document; + const element = document.createElement('iframe'); + page.mainFrame.url = 'https://localhost:8080'; + const responseHTML = 'Test'; + + vi.spyOn(BrowserWindow.prototype, 'fetch').mockImplementation((url: IRequestInfo) => { + return Promise.resolve(({ + text: () => Promise.resolve(responseHTML), + ok: true, + headers: new Headers() + })); + }); + const frameUrl = 'https://localhost:8080/iframe.html'; + const actualFrameUrl = await new Promise((resolve) => { + element.srcdoc = responseHTML; + element.src = frameUrl; + const firstLoad = (): void => { + expect(page.mainFrame.childFrames[0].url).toBe('about:srcdoc'); + element.removeEventListener('load', firstLoad); + element.addEventListener('load', () => { + resolve(page.mainFrame.childFrames[0].url); + }); + element.removeAttribute('srcdoc'); + }; + element.addEventListener('load', firstLoad); + document.body.appendChild(element); + }); + expect(actualFrameUrl).toBe(frameUrl); + }); + + it('Execute code in the script', async () => { + const message = await new Promise((resolve) => { + element.srcdoc = ` + + `; + element.addEventListener('load', () => { + element.contentWindow?.postMessage('MESSAGE', '*'); + }); + window.addEventListener( + 'message', + (e) => { + const data = (e).data; + resolve(data); + }, + false + ); + document.body.appendChild(element); + expect(element.contentWindow?.parent === window).toBe(true); + }); + expect(message).toMatchObject({ msg: 'loaded' }); + }); + }); + describe('get contentWindow()', () => { it('Returns content window for "about:blank".', () => { element.src = 'about:blank'; @@ -421,6 +520,36 @@ describe('HTMLIFrameElement', () => { document.body.appendChild(element); }); }); + + it('Remain at the initial about:blank page when none of the srcdoc/src attributes are set', async () => { + const browser = new Browser(); + const page = browser.newPage(); + const window = page.mainFrame.window; + const document = window.document; + const element = document.createElement('iframe'); + page.mainFrame.url = 'https://localhost:8080'; + + vi.spyOn(BrowserWindow.prototype, 'fetch').mockImplementation((url: IRequestInfo) => { + return Promise.resolve(({ + text: () => Promise.resolve('Test'), + ok: true, + headers: new Headers() + })); + }); + const actualFrameUrl = await new Promise((resolve) => { + element.src = 'https://localhost:8080/iframe.html'; + const firstLoad = (): void => { + element.removeEventListener('load', firstLoad); + element.addEventListener('load', () => { + resolve(page.mainFrame.childFrames[0].url); + }); + element.removeAttribute('src'); + }; + element.addEventListener('load', firstLoad); + document.body.appendChild(element); + }); + expect(actualFrameUrl).toBe('about:blank'); + }); }); describe('get contentDocument()', () => { @@ -428,6 +557,7 @@ describe('HTMLIFrameElement', () => { element.src = 'about:blank'; expect(element.contentDocument).toBe(null); document.body.appendChild(element); + expect(element.contentWindow?.parent === window).toBe(true); expect(element.contentDocument?.documentElement.innerHTML).toBe(''); }); }); From 907281ac8a80afce2b1e521c77373da5c1d9f0c7 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Mon, 6 May 2024 23:21:46 +0200 Subject: [PATCH 2/3] chore: [#1398] Fixes review findings --- .../src/browser/types/IGoToOptions.ts | 4 - .../utilities/BrowserFrameNavigator.ts | 73 +++++++++---------- .../HTMLIFrameElementNamedNodeMap.ts | 4 +- .../HTMLIFrameElementPageLoader.ts | 62 ++++++++++------ 4 files changed, 77 insertions(+), 66 deletions(-) diff --git a/packages/happy-dom/src/browser/types/IGoToOptions.ts b/packages/happy-dom/src/browser/types/IGoToOptions.ts index a3708c520..7b051546c 100644 --- a/packages/happy-dom/src/browser/types/IGoToOptions.ts +++ b/packages/happy-dom/src/browser/types/IGoToOptions.ts @@ -14,8 +14,4 @@ export default interface IGoToOptions extends IReloadOptions { * Referrer policy. */ referrerPolicy?: IRequestReferrerPolicy; - /** - * If `srcdoc` is set, it can be used to replace the data obtained by fetch. - */ - substituteData?: string; } diff --git a/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts b/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts index d1cd05f3a..cc5b1a331 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserFrameNavigator.ts @@ -93,27 +93,28 @@ export default class BrowserFrameNavigator { const width = frame.window.innerWidth; const height = frame.window.innerHeight; const devicePixelRatio = frame.window.devicePixelRatio; + const parentWindow = frame.window.parent !== frame.window ? frame.window.parent : null; + const topWindow = frame.window.top !== frame.window ? frame.window.top : null; for (const childFrame of frame.childFrames) { BrowserFrameFactory.destroyFrame(childFrame); } (frame.childFrames) = []; - const parentWindow = frame.window.parent; frame.window[PropertySymbol.destroy](); frame[PropertySymbol.asyncTaskManager].destroy(); frame[PropertySymbol.asyncTaskManager] = new AsyncTaskManager(); (frame.window) = new windowClass(frame, { url: targetURL.href, width, height }); - ((frame.window.top)) = parentWindow; - ((frame.window.parent)) = parentWindow; + (frame.window.parent) = parentWindow; + (frame.window.top) = topWindow; (frame.window.devicePixelRatio) = devicePixelRatio; if (referrer) { frame.window.document[PropertySymbol.referrer] = referrer; } - if (targetURL.protocol === 'about:' && goToOptions?.substituteData === void 0) { + if (targetURL.protocol === 'about:') { return null; } @@ -141,45 +142,37 @@ export default class BrowserFrameNavigator { } }; - if (goToOptions?.substituteData !== void 0) { - responseText = goToOptions.substituteData; - response = new frame.window.Response(goToOptions.substituteData, { - headers: { 'Content-Type': 'text/html' } + try { + response = await frame.window.fetch(targetURL.href, { + referrer, + referrerPolicy: goToOptions?.referrerPolicy || 'origin', + signal: abortController.signal, + method: method || (formData ? 'POST' : 'GET'), + headers: goToOptions?.hard ? { 'Cache-Control': 'no-cache' } : undefined, + body: formData }); - } else { - try { - response = await frame.window.fetch(targetURL.href, { - referrer, - referrerPolicy: goToOptions?.referrerPolicy || 'origin', - signal: abortController.signal, - method: method || (formData ? 'POST' : 'GET'), - headers: goToOptions?.hard ? { 'Cache-Control': 'no-cache' } : undefined, - body: formData - }); - - // Handles the "X-Frame-Options" header for child frames. - if (frame.parentFrame) { - const originURL = frame.parentFrame.window.location; - const xFrameOptions = response.headers?.get('X-Frame-Options')?.toLowerCase(); - const isSameOrigin = originURL.origin === targetURL.origin || targetURL.origin === 'null'; - - if (xFrameOptions === 'deny' || (xFrameOptions === 'sameorigin' && !isSameOrigin)) { - throw new Error( - `Refused to display '${url}' in a frame because it set 'X-Frame-Options' to '${xFrameOptions}'.` - ); - } - } - responseText = await response.text(); - } catch (error) { - finalize(); - throw error; - } - if (!response.ok) { - frame.page.console.error( - `GET ${targetURL.href} ${response.status} (${response.statusText})` - ); + // Handles the "X-Frame-Options" header for child frames. + if (frame.parentFrame) { + const originURL = frame.parentFrame.window.location; + const xFrameOptions = response.headers?.get('X-Frame-Options')?.toLowerCase(); + const isSameOrigin = originURL.origin === targetURL.origin || targetURL.origin === 'null'; + + if (xFrameOptions === 'deny' || (xFrameOptions === 'sameorigin' && !isSameOrigin)) { + throw new Error( + `Refused to display '${url}' in a frame because it set 'X-Frame-Options' to '${xFrameOptions}'.` + ); + } } + + responseText = await response.text(); + } catch (error) { + finalize(); + throw error; + } + + if (!response.ok) { + frame.page.console.error(`GET ${targetURL.href} ${response.status} (${response.statusText})`); } // Fixes issue where evaluating the response can throw an error. diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts index 63f8c479a..ce55262af 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementNamedNodeMap.ts @@ -45,13 +45,15 @@ export default class HTMLIFrameElementNamedNodeMap extends HTMLElementNamedNodeM */ public override setNamedItem(item: Attr): Attr | null { const replacedAttribute = super.setNamedItem(item); + if (item[PropertySymbol.name] === 'srcdoc') { this.#pageLoader.loadPage(); } + // If the src attribute and the srcdoc attribute are both specified together, the srcdoc attribute takes priority. if ( item[PropertySymbol.name] === 'src' && - this[PropertySymbol.ownerElement].getAttribute('srcdoc') === null && + this[PropertySymbol.ownerElement][PropertySymbol.attributes]['srcdoc']?.value === undefined && item[PropertySymbol.value] && item[PropertySymbol.value] !== replacedAttribute?.[PropertySymbol.value] ) { diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts index 40d74c837..a1304a359 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts @@ -10,7 +10,6 @@ import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js'; import BrowserFrameURL from '../../browser/utilities/BrowserFrameURL.js'; import BrowserFrameFactory from '../../browser/utilities/BrowserFrameFactory.js'; import IRequestReferrerPolicy from '../../fetch/types/IRequestReferrerPolicy.js'; -import IGoToOptions from '../../browser/types/IGoToOptions.js'; /** * HTML Iframe page loader. @@ -20,6 +19,7 @@ export default class HTMLIFrameElementPageLoader { #contentWindowContainer: { window: BrowserWindow | CrossOriginBrowserWindow | null }; #browserParentFrame: IBrowserFrame; #browserIFrame: IBrowserFrame; + #srcdoc: string | null = null; /** * Constructor. @@ -45,20 +45,45 @@ export default class HTMLIFrameElementPageLoader { */ public loadPage(): void { if (!this.#element[PropertySymbol.isConnected]) { - if (this.#browserIFrame) { - BrowserFrameFactory.destroyFrame(this.#browserIFrame); - this.#browserIFrame = null; - } - this.#contentWindowContainer.window = null; + this.unloadPage(); return; } + const srcdoc = this.#element.getAttribute('srcdoc'); const window = this.#element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow]; + + if (srcdoc !== null) { + if (this.#srcdoc === srcdoc) { + return; + } + + this.unloadPage(); + + this.#browserIFrame = BrowserFrameFactory.createChildFrame(this.#browserParentFrame); + this.#browserIFrame.url = 'about:srcdoc'; + + this.#contentWindowContainer.window = this.#browserIFrame.window; + + (this.#browserIFrame.window.top) = this.#browserParentFrame.window.top; + (this.#browserIFrame.window.parent) = this.#browserParentFrame.window; + + this.#browserIFrame.window.document.open(); + this.#browserIFrame.window.document.write(srcdoc); + + this.#srcdoc = srcdoc; + + this.#element[PropertySymbol.ownerDocument][PropertySymbol.ownerWindow].requestAnimationFrame( + () => this.#element.dispatchEvent(new Event('load')) + ); + return; + } + + if (this.#srcdoc !== null) { + this.unloadPage(); + } + const originURL = this.#browserParentFrame.window.location; - const targetURL = BrowserFrameURL.getRelativeURL( - this.#browserParentFrame, - srcdoc !== null ? 'about:srcdoc' : this.#element.src - ); + const targetURL = BrowserFrameURL.getRelativeURL(this.#browserParentFrame, this.#element.src); if (this.#browserIFrame && this.#browserIFrame.window.location.href === targetURL.href) { return; @@ -87,17 +112,11 @@ export default class HTMLIFrameElementPageLoader { ((this.#browserIFrame.window.parent)) = parentWindow; - const gotoOptions: IGoToOptions = { - referrer: originURL.origin, - referrerPolicy: this.#element.referrerPolicy - }; - - if (srcdoc !== null) { - gotoOptions.substituteData = srcdoc; - } - this.#browserIFrame - .goto(targetURL.href, gotoOptions) + .goto(targetURL.href, { + referrer: originURL.origin, + referrerPolicy: this.#element.referrerPolicy + }) .then(() => this.#element.dispatchEvent(new Event('load'))) .catch((error) => WindowErrorUtility.dispatchError(this.#element, error)); @@ -109,11 +128,12 @@ export default class HTMLIFrameElementPageLoader { /** * Unloads an iframe page. */ - public unloadPage(): void { + private unloadPage(): void { if (this.#browserIFrame) { BrowserFrameFactory.destroyFrame(this.#browserIFrame); this.#browserIFrame = null; } this.#contentWindowContainer.window = null; + this.#srcdoc = null; } } From d48fd274a426e2378bdddc8f53d135dccb4ba13f Mon Sep 17 00:00:00 2001 From: David Ortner Date: Mon, 6 May 2024 23:23:11 +0200 Subject: [PATCH 3/3] chore: [#1398] Fixes review findings --- .../nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts index a1304a359..c690a8ded 100644 --- a/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts +++ b/packages/happy-dom/src/nodes/html-iframe-element/HTMLIFrameElementPageLoader.ts @@ -128,7 +128,7 @@ export default class HTMLIFrameElementPageLoader { /** * Unloads an iframe page. */ - private unloadPage(): void { + public unloadPage(): void { if (this.#browserIFrame) { BrowserFrameFactory.destroyFrame(this.#browserIFrame); this.#browserIFrame = null;