From 91b1df552f57f414278b63b1001eef9f64eee832 Mon Sep 17 00:00:00 2001 From: David Ortner Date: Fri, 12 Jan 2024 00:26:51 +0100 Subject: [PATCH] #466@trivial: Adds support for BrowserFrame.viewport. --- packages/happy-dom/src/PropertySymbol.ts | 3 + packages/happy-dom/src/browser/Browser.ts | 6 +- .../happy-dom/src/browser/BrowserContext.ts | 5 +- .../happy-dom/src/browser/BrowserFrame.ts | 6 +- packages/happy-dom/src/browser/BrowserPage.ts | 18 +- .../src/browser/DefaultBrowserPageViewport.ts | 7 + .../detached-browser/DetachedBrowser.ts | 6 +- .../DetachedBrowserContext.ts | 4 +- .../detached-browser/DetachedBrowserFrame.ts | 5 +- .../detached-browser/DetachedBrowserPage.ts | 18 +- .../happy-dom/src/browser/types/IBrowser.ts | 4 +- .../src/browser/types/IBrowserContext.ts | 4 +- .../src/browser/types/IBrowserFrame.ts | 5 +- .../src/browser/types/IBrowserPage.ts | 6 +- .../src/browser/types/IBrowserPageViewport.ts | 6 +- .../types/IOptionalBrowserPageViewport.ts | 5 + .../browser/utilities/BrowserFrameFactory.ts | 3 +- .../utilities/BrowserFrameValidator.ts | 10 +- .../browser/utilities/BrowserPageUtility.ts | 31 --- packages/happy-dom/src/index.ts | 2 + .../happy-dom/src/window/BrowserWindow.ts | 191 +++++++++++++++--- .../happy-dom/src/window/DetachedWindowAPI.ts | 4 +- .../happy-dom/src/window/IBrowserWindow.ts | 23 ++- packages/happy-dom/src/window/Window.ts | 14 +- .../src/window/WindowPageOpenUtility.ts | 11 +- .../happy-dom/test/browser/Browser.test.ts | 7 - .../test/browser/BrowserContext.test.ts | 7 - .../test/browser/BrowserFrame.test.ts | 25 ++- .../test/browser/BrowserPage.test.ts | 34 ++++ .../detached-browser/DetachedBrowser.test.ts | 9 - .../DetachedBrowserContext.test.ts | 9 - .../DetachedBrowserFrame.test.ts | 25 ++- .../DetachedBrowserPage.test.ts | 38 +++- .../test/window/BrowserWindow.test.ts | 118 ++++++++++- 34 files changed, 504 insertions(+), 165 deletions(-) create mode 100644 packages/happy-dom/src/browser/DefaultBrowserPageViewport.ts create mode 100644 packages/happy-dom/src/browser/types/IOptionalBrowserPageViewport.ts diff --git a/packages/happy-dom/src/PropertySymbol.ts b/packages/happy-dom/src/PropertySymbol.ts index 355027e30..d78a074b4 100644 --- a/packages/happy-dom/src/PropertySymbol.ts +++ b/packages/happy-dom/src/PropertySymbol.ts @@ -79,3 +79,6 @@ export const width = Symbol('width'); export const window = Symbol('window'); export const windowResizeListener = Symbol('windowResizeListener'); export const mutationObservers = Symbol('mutationObservers'); +export const openerFrame = Symbol('openerFrame'); +export const openerWindow = Symbol('openerFrame'); +export const popup = Symbol('popup'); diff --git a/packages/happy-dom/src/browser/Browser.ts b/packages/happy-dom/src/browser/Browser.ts index 8783ebf1f..0df1f0407 100644 --- a/packages/happy-dom/src/browser/Browser.ts +++ b/packages/happy-dom/src/browser/Browser.ts @@ -4,7 +4,6 @@ import IOptionalBrowserSettings from './types/IOptionalBrowserSettings.js'; import BrowserSettingsFactory from './BrowserSettingsFactory.js'; import BrowserPage from './BrowserPage.js'; import IBrowser from './types/IBrowser.js'; -import BrowserFrame from './BrowserFrame.js'; /** * Browser. @@ -94,13 +93,12 @@ export default class Browser implements IBrowser { /** * Creates a new page. * - * @param [opener] Opener. * @returns Page. */ - public newPage(opener?: BrowserFrame): BrowserPage { + public newPage(): BrowserPage { if (this.contexts.length === 0) { throw new Error('No default context. The browser has been closed.'); } - return this.contexts[0].newPage(opener); + return this.contexts[0].newPage(); } } diff --git a/packages/happy-dom/src/browser/BrowserContext.ts b/packages/happy-dom/src/browser/BrowserContext.ts index d9c527e81..f0123eb1e 100644 --- a/packages/happy-dom/src/browser/BrowserContext.ts +++ b/packages/happy-dom/src/browser/BrowserContext.ts @@ -3,7 +3,6 @@ import ICookieContainer from '../cookie/types/ICookieContainer.js'; import ResponseCache from '../fetch/cache/response/ResponseCache.js'; import IResponseCache from '../fetch/cache/response/IResponseCache.js'; import Browser from './Browser.js'; -import BrowserFrame from './BrowserFrame.js'; import BrowserPage from './BrowserPage.js'; import IBrowserContext from './types/IBrowserContext.js'; import IPreflightResponseCache from '../fetch/cache/preflight/IPreflightResponseCache.js'; @@ -80,12 +79,10 @@ export default class BrowserContext implements IBrowserContext { /** * Creates a new page. * - * @param [opener] Opener. * @returns Page. */ - public newPage(opener?: BrowserFrame): BrowserPage { + public newPage(): BrowserPage { const page = new BrowserPage(this); - ((page.mainFrame.opener)) = opener || null; this.pages.push(page); return page; } diff --git a/packages/happy-dom/src/browser/BrowserFrame.ts b/packages/happy-dom/src/browser/BrowserFrame.ts index 2882c9726..fccaff3c8 100644 --- a/packages/happy-dom/src/browser/BrowserFrame.ts +++ b/packages/happy-dom/src/browser/BrowserFrame.ts @@ -3,6 +3,8 @@ import * as PropertySymbol from '../PropertySymbol.js'; import AsyncTaskManager from '../async-task-manager/AsyncTaskManager.js'; import IBrowserFrame from './types/IBrowserFrame.js'; import BrowserWindow from '../window/BrowserWindow.js'; +import IBrowserWindow from '../window/IBrowserWindow.js'; +import ICrossOriginBrowserWindow from '../window/ICrossOriginBrowserWindow.js'; import Location from '../location/Location.js'; import IResponse from '../fetch/types/IResponse.js'; import IGoToOptions from './types/IGoToOptions.js'; @@ -21,12 +23,14 @@ import IDocument from '../nodes/document/IDocument.js'; export default class BrowserFrame implements IBrowserFrame { public readonly childFrames: BrowserFrame[] = []; public readonly parentFrame: BrowserFrame | null = null; - public readonly opener: BrowserFrame | null = null; public readonly page: BrowserPage; public readonly window: BrowserWindow; public [PropertySymbol.asyncTaskManager] = new AsyncTaskManager(); public [PropertySymbol.exceptionObserver]: BrowserFrameExceptionObserver | null = null; public [PropertySymbol.listeners]: { navigation: Array<() => void> } = { navigation: [] }; + public [PropertySymbol.openerFrame]: IBrowserFrame | null = null; + public [PropertySymbol.openerWindow]: IBrowserWindow | ICrossOriginBrowserWindow | null = null; + public [PropertySymbol.popup] = false; /** * Constructor. diff --git a/packages/happy-dom/src/browser/BrowserPage.ts b/packages/happy-dom/src/browser/BrowserPage.ts index 4f3f966be..77c665105 100644 --- a/packages/happy-dom/src/browser/BrowserPage.ts +++ b/packages/happy-dom/src/browser/BrowserPage.ts @@ -1,5 +1,4 @@ import VirtualConsolePrinter from '../console/VirtualConsolePrinter.js'; -import IBrowserPageViewport from './types/IBrowserPageViewport.js'; import BrowserFrame from './BrowserFrame.js'; import BrowserContext from './BrowserContext.js'; import VirtualConsole from '../console/VirtualConsole.js'; @@ -9,6 +8,10 @@ import { Script } from 'vm'; import IGoToOptions from './types/IGoToOptions.js'; import IResponse from '../fetch/types/IResponse.js'; import IReloadOptions from './types/IReloadOptions.js'; +import IBrowserPageViewport from './types/IBrowserPageViewport.js'; +import IOptionalBrowserPageViewport from './types/IOptionalBrowserPageViewport.js'; +import DefaultBrowserPageViewport from './DefaultBrowserPageViewport.js'; +import Event from '../event/Event.js'; /** * Browser page. @@ -18,6 +21,7 @@ export default class BrowserPage implements IBrowserPage { public readonly mainFrame: BrowserFrame; public readonly context: BrowserContext; public readonly console: Console; + public readonly viewport: IBrowserPageViewport = Object.assign({}, DefaultBrowserPageViewport); /** * Constructor. @@ -114,8 +118,16 @@ export default class BrowserPage implements IBrowserPage { * * @param viewport Viewport. */ - public setViewport(viewport: IBrowserPageViewport): void { - BrowserPageUtility.setViewport(this, viewport); + public setViewport(viewport: IOptionalBrowserPageViewport): void { + const previousViewport = Object.assign({}, this.viewport); + Object.assign(this.viewport, viewport); + if ( + previousViewport.width !== this.viewport.width || + previousViewport.height !== this.viewport.height || + previousViewport.devicePixelRatio !== this.viewport.devicePixelRatio + ) { + this.mainFrame.window.dispatchEvent(new Event('resize')); + } } /** diff --git a/packages/happy-dom/src/browser/DefaultBrowserPageViewport.ts b/packages/happy-dom/src/browser/DefaultBrowserPageViewport.ts new file mode 100644 index 000000000..616bc4b03 --- /dev/null +++ b/packages/happy-dom/src/browser/DefaultBrowserPageViewport.ts @@ -0,0 +1,7 @@ +import IBrowserPageViewport from './types/IBrowserPageViewport.js'; + +export default { + width: 1024, + height: 768, + devicePixelRatio: 1 +}; diff --git a/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts b/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts index 77fcc17b1..4959d35fb 100644 --- a/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts +++ b/packages/happy-dom/src/browser/detached-browser/DetachedBrowser.ts @@ -5,7 +5,6 @@ import BrowserSettingsFactory from '../BrowserSettingsFactory.js'; import DetachedBrowserPage from './DetachedBrowserPage.js'; import IBrowser from '../types/IBrowser.js'; import IBrowserFrame from '../types/IBrowserFrame.js'; -import DetachedBrowserFrame from './DetachedBrowserFrame.js'; import IBrowserWindow from '../../window/IBrowserWindow.js'; /** @@ -101,13 +100,12 @@ export default class DetachedBrowser implements IBrowser { /** * Creates a new page. * - * @param [opener] Opener. * @returns Page. */ - public newPage(opener?: DetachedBrowserFrame): DetachedBrowserPage { + public newPage(): DetachedBrowserPage { if (this.contexts.length === 0) { throw new Error('No default context. The browser has been closed.'); } - return this.contexts[0].newPage(opener); + return this.contexts[0].newPage(); } } diff --git a/packages/happy-dom/src/browser/detached-browser/DetachedBrowserContext.ts b/packages/happy-dom/src/browser/detached-browser/DetachedBrowserContext.ts index be8d7fda1..c1e73419a 100644 --- a/packages/happy-dom/src/browser/detached-browser/DetachedBrowserContext.ts +++ b/packages/happy-dom/src/browser/detached-browser/DetachedBrowserContext.ts @@ -1,7 +1,6 @@ import DetachedBrowser from './DetachedBrowser.js'; import DetachedBrowserPage from './DetachedBrowserPage.js'; import IBrowserContext from '../types/IBrowserContext.js'; -import DetachedBrowserFrame from './DetachedBrowserFrame.js'; import ICookieContainer from '../../cookie/types/ICookieContainer.js'; import CookieContainer from '../../cookie/CookieContainer.js'; import ResponseCache from '../../fetch/cache/response/ResponseCache.js'; @@ -85,9 +84,8 @@ export default class DetachedBrowserContext implements IBrowserContext { * @param [opener] Opener. * @returns Page. */ - public newPage(opener?: DetachedBrowserFrame): DetachedBrowserPage { + public newPage(): DetachedBrowserPage { const page = new DetachedBrowserPage(this); - ((page.mainFrame.opener)) = opener || null; this.pages.push(page); return page; } diff --git a/packages/happy-dom/src/browser/detached-browser/DetachedBrowserFrame.ts b/packages/happy-dom/src/browser/detached-browser/DetachedBrowserFrame.ts index 4abd4520f..f602d9136 100644 --- a/packages/happy-dom/src/browser/detached-browser/DetachedBrowserFrame.ts +++ b/packages/happy-dom/src/browser/detached-browser/DetachedBrowserFrame.ts @@ -14,6 +14,7 @@ import IReloadOptions from '../types/IReloadOptions.js'; import BrowserErrorCaptureEnum from '../enums/BrowserErrorCaptureEnum.js'; import BrowserFrameExceptionObserver from '../utilities/BrowserFrameExceptionObserver.js'; import IDocument from '../../nodes/document/IDocument.js'; +import ICrossOriginBrowserWindow from '../../window/ICrossOriginBrowserWindow.js'; /** * Browser frame used when constructing a Window instance without a browser. @@ -21,13 +22,15 @@ import IDocument from '../../nodes/document/IDocument.js'; export default class DetachedBrowserFrame implements IBrowserFrame { public readonly childFrames: DetachedBrowserFrame[] = []; public readonly parentFrame: DetachedBrowserFrame | null = null; - public readonly opener: DetachedBrowserFrame | null = null; public readonly page: DetachedBrowserPage; // Needs to be injected from the outside when the browser frame is constructed. public window: IBrowserWindow; public [PropertySymbol.asyncTaskManager] = new AsyncTaskManager(); public [PropertySymbol.exceptionObserver]: BrowserFrameExceptionObserver | null = null; public [PropertySymbol.listeners]: { navigation: Array<() => void> } = { navigation: [] }; + public [PropertySymbol.openerFrame]: IBrowserFrame | null = null; + public [PropertySymbol.openerWindow]: IBrowserWindow | ICrossOriginBrowserWindow | null = null; + public [PropertySymbol.popup] = false; /** * Constructor. diff --git a/packages/happy-dom/src/browser/detached-browser/DetachedBrowserPage.ts b/packages/happy-dom/src/browser/detached-browser/DetachedBrowserPage.ts index 0b041decc..943d27463 100644 --- a/packages/happy-dom/src/browser/detached-browser/DetachedBrowserPage.ts +++ b/packages/happy-dom/src/browser/detached-browser/DetachedBrowserPage.ts @@ -1,5 +1,4 @@ import VirtualConsolePrinter from '../../console/VirtualConsolePrinter.js'; -import IBrowserPageViewport from '../types/IBrowserPageViewport.js'; import DetachedBrowserFrame from './DetachedBrowserFrame.js'; import DetachedBrowserContext from './DetachedBrowserContext.js'; import VirtualConsole from '../../console/VirtualConsole.js'; @@ -9,6 +8,10 @@ import IGoToOptions from '../types/IGoToOptions.js'; import IResponse from '../../fetch/types/IResponse.js'; import BrowserPageUtility from '../utilities/BrowserPageUtility.js'; import IReloadOptions from '../types/IReloadOptions.js'; +import DefaultBrowserPageViewport from '../DefaultBrowserPageViewport.js'; +import IOptionalBrowserPageViewport from '../types/IOptionalBrowserPageViewport.js'; +import IBrowserPageViewport from '../types/IBrowserPageViewport.js'; +import Event from '../../event/Event.js'; /** * Detached browser page used when constructing a Window instance without a browser. @@ -18,6 +21,7 @@ export default class DetachedBrowserPage implements IBrowserPage { public readonly mainFrame: DetachedBrowserFrame; public readonly context: DetachedBrowserContext; public readonly console: Console; + public readonly viewport: IBrowserPageViewport = Object.assign({}, DefaultBrowserPageViewport); /** * Constructor. @@ -126,8 +130,16 @@ export default class DetachedBrowserPage implements IBrowserPage { * * @param viewport Viewport. */ - public setViewport(viewport: IBrowserPageViewport): void { - BrowserPageUtility.setViewport(this, viewport); + public setViewport(viewport: IOptionalBrowserPageViewport): void { + const previousViewport = Object.assign({}, this.viewport); + Object.assign(this.viewport, viewport); + if ( + previousViewport.width !== this.viewport.width || + previousViewport.height !== this.viewport.height || + previousViewport.devicePixelRatio !== this.viewport.devicePixelRatio + ) { + this.mainFrame.window.dispatchEvent(new Event('resize')); + } } /** diff --git a/packages/happy-dom/src/browser/types/IBrowser.ts b/packages/happy-dom/src/browser/types/IBrowser.ts index fa3fb884b..34fd479c4 100644 --- a/packages/happy-dom/src/browser/types/IBrowser.ts +++ b/packages/happy-dom/src/browser/types/IBrowser.ts @@ -1,5 +1,4 @@ import IBrowserContext from './IBrowserContext.js'; -import IBrowserFrame from './IBrowserFrame.js'; import IBrowserPage from './IBrowserPage.js'; import IBrowserSettings from './IBrowserSettings.js'; @@ -41,8 +40,7 @@ export default interface IBrowser { /** * Creates a new page. * - * @param [opener] Opener. * @returns Page. */ - newPage(opener?: IBrowserFrame): IBrowserPage; + newPage(): IBrowserPage; } diff --git a/packages/happy-dom/src/browser/types/IBrowserContext.ts b/packages/happy-dom/src/browser/types/IBrowserContext.ts index 3d6fec4e1..c4b4b01bc 100644 --- a/packages/happy-dom/src/browser/types/IBrowserContext.ts +++ b/packages/happy-dom/src/browser/types/IBrowserContext.ts @@ -1,7 +1,6 @@ import ICookieContainer from '../../cookie/types/ICookieContainer.js'; import IResponseCache from '../../fetch/cache/response/IResponseCache.js'; import IBrowser from './IBrowser.js'; -import IBrowserFrame from './IBrowserFrame.js'; import IBrowserPage from './IBrowserPage.js'; import IPreflightResponseCache from '../../fetch/cache/preflight/IPreflightResponseCache.js'; @@ -35,8 +34,7 @@ export default interface IBrowserContext { /** * Creates a new page. * - * @param [opener] Opener. * @returns Page. */ - newPage(opener?: IBrowserFrame): IBrowserPage; + newPage(): IBrowserPage; } diff --git a/packages/happy-dom/src/browser/types/IBrowserFrame.ts b/packages/happy-dom/src/browser/types/IBrowserFrame.ts index 3d58cfe4d..f3dc8d5a1 100644 --- a/packages/happy-dom/src/browser/types/IBrowserFrame.ts +++ b/packages/happy-dom/src/browser/types/IBrowserFrame.ts @@ -8,6 +8,7 @@ import IGoToOptions from './IGoToOptions.js'; import { Script } from 'vm'; import IReloadOptions from './IReloadOptions.js'; import BrowserFrameExceptionObserver from '../utilities/BrowserFrameExceptionObserver.js'; +import ICrossOriginBrowserWindow from '../../window/ICrossOriginBrowserWindow.js'; /** * Browser frame. @@ -15,7 +16,6 @@ import BrowserFrameExceptionObserver from '../utilities/BrowserFrameExceptionObs export default interface IBrowserFrame { readonly childFrames: IBrowserFrame[]; readonly parentFrame: IBrowserFrame | null; - readonly opener: IBrowserFrame | null; readonly page: IBrowserPage; readonly window: IBrowserWindow; readonly document: IDocument; @@ -24,6 +24,9 @@ export default interface IBrowserFrame { [PropertySymbol.asyncTaskManager]: AsyncTaskManager; [PropertySymbol.exceptionObserver]: BrowserFrameExceptionObserver | null; [PropertySymbol.listeners]: { navigation: Array<() => void> }; + [PropertySymbol.openerFrame]: IBrowserFrame | null; + [PropertySymbol.openerWindow]: IBrowserWindow | ICrossOriginBrowserWindow | null; + [PropertySymbol.popup]: boolean; /** * Returns a promise that is resolved when all resources has been loaded, fetch has completed, and all async tasks such as timers are complete. diff --git a/packages/happy-dom/src/browser/types/IBrowserPage.ts b/packages/happy-dom/src/browser/types/IBrowserPage.ts index 262ce60a3..3e16ddee9 100644 --- a/packages/happy-dom/src/browser/types/IBrowserPage.ts +++ b/packages/happy-dom/src/browser/types/IBrowserPage.ts @@ -1,4 +1,4 @@ -import IBrowserPageViewport from './IBrowserPageViewport.js'; +import IBrowserPageViewport from '../types/IBrowserPageViewport.js'; import VirtualConsolePrinter from '../../console/VirtualConsolePrinter.js'; import IBrowserFrame from './IBrowserFrame.js'; import IBrowserContext from './IBrowserContext.js'; @@ -6,6 +6,7 @@ import { Script } from 'vm'; import IGoToOptions from './IGoToOptions.js'; import IResponse from '../../fetch/types/IResponse.js'; import IReloadOptions from './IReloadOptions.js'; +import IOptionalBrowserPageViewport from './IOptionalBrowserPageViewport.js'; /** * Browser page. @@ -16,6 +17,7 @@ export default interface IBrowserPage { readonly context: IBrowserContext; readonly console: Console; readonly frames: IBrowserFrame[]; + readonly viewport: IBrowserPageViewport; content: string; url: string; @@ -49,7 +51,7 @@ export default interface IBrowserPage { * * @param viewport Viewport. */ - setViewport(viewport: IBrowserPageViewport): void; + setViewport(viewport: IOptionalBrowserPageViewport): void; /** * Go to a page. diff --git a/packages/happy-dom/src/browser/types/IBrowserPageViewport.ts b/packages/happy-dom/src/browser/types/IBrowserPageViewport.ts index 4e5ce1442..9a952f9e1 100644 --- a/packages/happy-dom/src/browser/types/IBrowserPageViewport.ts +++ b/packages/happy-dom/src/browser/types/IBrowserPageViewport.ts @@ -1,5 +1,5 @@ export default interface IBrowserPageViewport { - width?: number; - height?: number; - devicePixelRatio?: number; + width: number; + height: number; + devicePixelRatio: number; } diff --git a/packages/happy-dom/src/browser/types/IOptionalBrowserPageViewport.ts b/packages/happy-dom/src/browser/types/IOptionalBrowserPageViewport.ts new file mode 100644 index 000000000..d80c11958 --- /dev/null +++ b/packages/happy-dom/src/browser/types/IOptionalBrowserPageViewport.ts @@ -0,0 +1,5 @@ +export default interface IOptionalBrowserPageViewport { + width?: number; + height?: number; + devicePixelRatio?: number; +} diff --git a/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts b/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts index f6008e4b2..f506ae6bc 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserFrameFactory.ts @@ -45,7 +45,8 @@ export default class BrowserFrameFactory { frame.window[PropertySymbol.destroy](); (frame.page) = null; (frame.window) = null; - (frame.opener) = null; + frame[PropertySymbol.openerFrame] = null; + frame[PropertySymbol.openerWindow] = null; if (!frame.childFrames.length) { return frame[PropertySymbol.asyncTaskManager] diff --git a/packages/happy-dom/src/browser/utilities/BrowserFrameValidator.ts b/packages/happy-dom/src/browser/utilities/BrowserFrameValidator.ts index 0a51da7e8..28ba9b15c 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserFrameValidator.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserFrameValidator.ts @@ -2,6 +2,7 @@ import IBrowserFrame from '../types/IBrowserFrame.js'; import { URL } from 'url'; import BrowserNavigationCrossOriginPolicyEnum from '../enums/BrowserNavigationCrossOriginPolicyEnum.js'; import DetachedBrowserFrame from '../detached-browser/DetachedBrowserFrame.js'; +import * as PropertySymbol from '../../PropertySymbol.js'; /** * Browser frame validator. @@ -18,10 +19,9 @@ export default class BrowserFrameValidator { const settings = frame.page.context.browser.settings; let fromURL = frame.page.mainFrame.window.location; - if (frame.opener) { - fromURL = frame.opener.window.location; - } - if (frame.parentFrame) { + if (frame[PropertySymbol.openerFrame]) { + fromURL = frame[PropertySymbol.openerFrame].window.location; + } else if (frame.parentFrame) { fromURL = frame.parentFrame.window.location; } @@ -74,7 +74,7 @@ export default class BrowserFrameValidator { return false; } - if (settings.navigation.disableChildPageNavigation && !!frame.opener) { + if (settings.navigation.disableChildPageNavigation && !!frame[PropertySymbol.openerFrame]) { return false; } diff --git a/packages/happy-dom/src/browser/utilities/BrowserPageUtility.ts b/packages/happy-dom/src/browser/utilities/BrowserPageUtility.ts index c71f109c2..d4e2f41ca 100644 --- a/packages/happy-dom/src/browser/utilities/BrowserPageUtility.ts +++ b/packages/happy-dom/src/browser/utilities/BrowserPageUtility.ts @@ -1,7 +1,5 @@ import IBrowserFrame from '../types/IBrowserFrame.js'; import IBrowserPage from '../types/IBrowserPage.js'; -import IBrowserPageViewport from '../types/IBrowserPageViewport.js'; -import Event from '../../event/Event.js'; import IVirtualConsolePrinter from '../../console/types/IVirtualConsolePrinter.js'; import IBrowserContext from '../types/IBrowserContext.js'; import BrowserFrameFactory from './BrowserFrameFactory.js'; @@ -20,35 +18,6 @@ export default class BrowserPageUtility { return this.findFrames(page.mainFrame); } - /** - * Sets the viewport. - * - * @param page Page. - * @param viewport Viewport. - */ - public static setViewport(page: IBrowserPage, viewport: IBrowserPageViewport): void { - if ( - (viewport.width !== undefined && page.mainFrame.window.innerWidth !== viewport.width) || - (viewport.height !== undefined && page.mainFrame.window.innerHeight !== viewport.height) - ) { - if (viewport.width !== undefined && page.mainFrame.window.innerWidth !== viewport.width) { - (page.mainFrame.window.innerWidth) = viewport.width; - (page.mainFrame.window.outerWidth) = viewport.width; - } - - if (viewport.height !== undefined && page.mainFrame.window.innerHeight !== viewport.height) { - (page.mainFrame.window.innerHeight) = viewport.height; - (page.mainFrame.window.outerHeight) = viewport.height; - } - - page.mainFrame.window.dispatchEvent(new Event('resize')); - } - - if (viewport.devicePixelRatio !== undefined) { - (page.mainFrame.window.devicePixelRatio) = viewport.devicePixelRatio; - } - } - /** * Aborts all ongoing operations and destroys the page. * diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts index 05d69d5cd..e7dc4db50 100644 --- a/packages/happy-dom/src/index.ts +++ b/packages/happy-dom/src/index.ts @@ -172,6 +172,7 @@ import type IBrowser from './browser/types/IBrowser.js'; import type IBrowserContext from './browser/types/IBrowserContext.js'; import type IBrowserFrame from './browser/types/IBrowserFrame.js'; import type IBrowserPage from './browser/types/IBrowserPage.js'; +import type ICrossOriginBrowserWindow from './window/ICrossOriginBrowserWindow.js'; export type { IAnimationEventInit, @@ -181,6 +182,7 @@ export type { IBrowserContext, IBrowserFrame, IBrowserPage, + ICrossOriginBrowserWindow, IClipboardEventInit, IComment, ICustomEventInit, diff --git a/packages/happy-dom/src/window/BrowserWindow.ts b/packages/happy-dom/src/window/BrowserWindow.ts index 2b8eb4e05..ca97a1689 100644 --- a/packages/happy-dom/src/window/BrowserWindow.ts +++ b/packages/happy-dom/src/window/BrowserWindow.ts @@ -405,21 +405,15 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow public readonly history: History; public readonly navigator: Navigator; public readonly console: Console; - public readonly opener: IBrowserWindow | null = null; public readonly self: IBrowserWindow = this; public readonly top: IBrowserWindow = this; public readonly parent: IBrowserWindow = this; public readonly window: IBrowserWindow = this; public readonly globalThis: IBrowserWindow = this; public readonly screen: Screen; - public readonly devicePixelRatio = 1; public readonly sessionStorage: Storage; public readonly localStorage: Storage; public readonly performance = PerfHooks.performance; - public readonly innerWidth: number = 1024; - public readonly innerHeight: number = 768; - public readonly outerWidth: number = 1024; - public readonly outerHeight: number = 768; public readonly screenLeft: number = 0; public readonly screenTop: number = 0; public readonly screenX: number = 0; @@ -504,6 +498,11 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow #clearInterval: (id: NodeJS.Timeout) => void; #queueMicrotask: (callback: Function) => void; #browserFrame: IBrowserFrame; + #innerWidth: number | null = null; + #innerHeight: number | null = null; + #outerWidth: number | null = null; + #outerHeight: number | null = null; + #devicePixelRatio: number | null = null; /** * Constructor. @@ -511,13 +510,8 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow * @param browserFrame Browser frame. * @param [options] Options. * @param [options.url] URL. - * @param [options.width] Window width. Defaults to "1024". - * @param [options.height] Window height. Defaults to "768". */ - constructor( - browserFrame: IBrowserFrame, - options?: { url?: string; width?: number; height?: number } - ) { + constructor(browserFrame: IBrowserFrame, options?: { url?: string }) { super(); this.#browserFrame = browserFrame; @@ -538,20 +532,6 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow WindowBrowserSettingsReader.setSettings(this, this.#browserFrame.page.context.browser.settings); - if (options) { - if (options.width !== undefined) { - if (options.width !== undefined && this.innerWidth !== options.width) { - (this.innerWidth) = options.width; - (this.outerWidth) = options.width; - } - } - - if (options.height !== undefined && this.innerHeight !== options.height) { - (this.innerHeight) = options.height; - (this.outerHeight) = options.height; - } - } - // Binds all methods to "this", so that it will use the correct context when called globally. for (const key of Object.getOwnPropertyNames(BrowserWindow.prototype).concat( Object.getOwnPropertyNames(EventTarget.prototype) @@ -684,6 +664,15 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow }); } + /** + * Returns opener. + * + * @returns Opener. + */ + public get opener(): IBrowserWindow | ICrossOriginBrowserWindow | null { + return this.#browserFrame[PropertySymbol.openerWindow]; + } + /** * The number of pixels that the document is currently scrolled horizontally. * @@ -729,6 +718,114 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow return new CSS(); } + /** + * Returns inner width. + * + * @returns Inner width. + */ + public get innerWidth(): number { + if (this.#innerWidth === null) { + return this.#browserFrame.page.viewport.width; + } + return this.#innerWidth; + } + + /** + * Sets inner width. + * + * @param value Inner width. + */ + public set innerWidth(value: number) { + this.#innerWidth = value; + } + + /** + * Returns inner height. + * + * @returns Inner height. + */ + public get innerHeight(): number { + // It seems like this value can be defined according to spec, but changing it has no effect on the actual viewport. + if (this.#innerHeight === null) { + return this.#browserFrame.page.viewport.height; + } + return this.#innerHeight; + } + + /** + * Sets inner height. + * + * @param value Inner height. + */ + public set innerHeight(value: number) { + this.#innerHeight = value; + } + + /** + * Returns outer width. + * + * @returns Outer width. + */ + public get outerWidth(): number { + // It seems like this value can be defined according to spec, but changing it has no effect on the actual viewport. + if (this.#outerWidth === null) { + return this.#browserFrame.page.viewport.width; + } + return this.#outerWidth; + } + + /** + * Sets outer width. + * + * @param value Outer width. + */ + public set outerWidth(value: number) { + this.#outerWidth = value; + } + + /** + * Returns outer height. + * + * @returns Outer height. + */ + public get outerHeight(): number { + if (this.#outerHeight === null) { + return this.#browserFrame.page.viewport.height; + } + return this.#outerHeight; + } + + /** + * Sets outer height. + * + * @param value Outer height. + */ + public set outerHeight(value: number) { + this.#outerHeight = value; + } + + /** + * Returns device pixel ratio. + * + * @returns Device pixel ratio. + */ + public get devicePixelRatio(): number { + // It seems like this value can be defined according to spec, but changing it has no effect on the actual viewport. + if (this.#devicePixelRatio === null) { + return this.#browserFrame.page.viewport.devicePixelRatio; + } + return this.#devicePixelRatio; + } + + /** + * Sets device pixel ratio. + * + * @param value Device pixel ratio. + */ + public set devicePixelRatio(value: number) { + this.#devicePixelRatio = value; + } + /** * Returns an object containing the values of all CSS properties of an element. * @@ -1067,6 +1164,48 @@ export default class BrowserWindow extends EventTarget implements IBrowserWindow ); } + /** + * Resizes the window. + * + * @param width Width. + * @param height Height. + */ + public resizeTo(width: number, height: number): void { + if (!width || !height) { + throw new DOMException( + `Failed to execute 'resizeTo' on 'Window': 2 arguments required, but only ${arguments.length} present.` + ); + } + + // We can only resize the window if it is a popup. + if (this.#browserFrame[PropertySymbol.popup]) { + this.#browserFrame.page.setViewport({ width, height }); + } + } + + /** + * Resizes the current window by a specified amount. + * + * @param width Width. + * @param height Height. + */ + public resizeBy(width: number, height: number): void { + if (!width || !height) { + throw new DOMException( + `Failed to execute 'resizeBy' on 'Window': 2 arguments required, but only ${arguments.length} present.` + ); + } + + // We can only resize the window if it is a popup. + if (this.#browserFrame[PropertySymbol.popup]) { + const viewport = this.#browserFrame.page.viewport; + this.#browserFrame.page.setViewport({ + width: viewport.width + width, + height: viewport.height + height + }); + } + } + /** * Setup of VM context. */ diff --git a/packages/happy-dom/src/window/DetachedWindowAPI.ts b/packages/happy-dom/src/window/DetachedWindowAPI.ts index 11c009a97..a645ceed7 100644 --- a/packages/happy-dom/src/window/DetachedWindowAPI.ts +++ b/packages/happy-dom/src/window/DetachedWindowAPI.ts @@ -1,5 +1,5 @@ import VirtualConsolePrinter from '../console/VirtualConsolePrinter.js'; -import IBrowserPageViewport from '../browser/types/IBrowserPageViewport.js'; +import IOptionalBrowserPageViewport from '../browser/types/IOptionalBrowserPageViewport.js'; import IBrowserFrame from '../browser/types/IBrowserFrame.js'; import IBrowserSettings from '../browser/types/IBrowserSettings.js'; @@ -85,7 +85,7 @@ export default class DetachedWindowAPI { * * @param viewport Viewport. */ - public setViewport(viewport: IBrowserPageViewport): void { + public setViewport(viewport: IOptionalBrowserPageViewport): void { this.#browserFrame.page.setViewport(viewport); } diff --git a/packages/happy-dom/src/window/IBrowserWindow.ts b/packages/happy-dom/src/window/IBrowserWindow.ts index c03672db6..3df28a7ee 100644 --- a/packages/happy-dom/src/window/IBrowserWindow.ts +++ b/packages/happy-dom/src/window/IBrowserWindow.ts @@ -398,11 +398,11 @@ export default interface IBrowserWindow extends IEventTarget, INodeJSGlobal { readonly window: IBrowserWindow; readonly globalThis: IBrowserWindow; readonly screen: Screen; - readonly devicePixelRatio: number; - readonly innerWidth: number; - readonly innerHeight: number; - readonly outerWidth: number; - readonly outerHeight: number; + innerWidth: number; + innerHeight: number; + outerWidth: number; + outerHeight: number; + devicePixelRatio: number; readonly screenLeft: number; readonly screenTop: number; readonly screenX: number; @@ -571,6 +571,19 @@ export default interface IBrowserWindow extends IEventTarget, INodeJSGlobal { */ postMessage(message: unknown, targetOrigin?: string, transfer?: unknown[]): void; + /** + * Resizes the window. + * + * @param width Width. + * @param height Height. + */ + resizeTo(width: number, height: number): void; + + /** + * Resizes the current window by a specified amount. + */ + resizeBy(width: number, height: number): void; + /** * Destroys the window. */ diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index b6b12e2bb..bbfdeb065 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -41,12 +41,18 @@ export default class Window extends BrowserWindow implements IWindow { console: options?.console, settings: options?.settings }); - const browserFrame = browser.defaultContext.pages[0].mainFrame; + const browserPage = browser.defaultContext.pages[0]; + const browserFrame = browserPage.mainFrame; + + if (options && (options.width || options.height || options.innerWidth || options.innerHeight)) { + Object.assign(browserPage.viewport, { + width: options.width || options.innerWidth || browserPage.viewport.width, + height: options.height || options.innerHeight || browserPage.viewport.height + }); + } super(browserFrame, { - url: options?.url, - width: options?.width ?? options?.innerWidth, - height: options?.height ?? options?.innerHeight + url: options?.url }); browserFrame.window = this; diff --git a/packages/happy-dom/src/window/WindowPageOpenUtility.ts b/packages/happy-dom/src/window/WindowPageOpenUtility.ts index 7967f8725..d55c361ff 100644 --- a/packages/happy-dom/src/window/WindowPageOpenUtility.ts +++ b/packages/happy-dom/src/window/WindowPageOpenUtility.ts @@ -4,6 +4,7 @@ import IBrowserFrame from '../browser/types/IBrowserFrame.js'; import FetchCORSUtility from '../fetch/utilities/FetchCORSUtility.js'; import ICrossOriginBrowserWindow from './ICrossOriginBrowserWindow.js'; import BrowserFrameURL from '../browser/utilities/BrowserFrameURL.js'; +import * as PropertySymbol from '../PropertySymbol.js'; /** * Window page open handler. @@ -45,8 +46,9 @@ export default class WindowPageOpenUtility { break; case '_blank': default: - const newPage = browserFrame.page.context.newPage(browserFrame); + const newPage = browserFrame.page.context.newPage(); targetFrame = newPage.mainFrame; + targetFrame[PropertySymbol.openerFrame] = browserFrame; break; } @@ -66,6 +68,8 @@ export default class WindowPageOpenUtility { } if (features.popup && target !== '_self' && target !== '_top' && target !== '_parent') { + targetFrame[PropertySymbol.popup] = true; + if (features?.width || features?.height) { targetFrame.page.setViewport({ width: features?.width, @@ -100,9 +104,10 @@ export default class WindowPageOpenUtility { !features.noopener && !features.noreferrer && browserFrame.window && + targetFrame[PropertySymbol.openerFrame] && targetFrame.window !== browserFrame.window ) { - (targetFrame.window.opener) = isCORS + targetFrame[PropertySymbol.openerWindow] = isCORS ? new CrossOriginBrowserWindow(browserFrame.window) : browserFrame.window; } @@ -156,7 +161,7 @@ export default class WindowPageOpenUtility { const [key, value] = part.split('='); switch (key) { case 'popup': - result.popup = value === 'yes' || value === '1' || value === 'true'; + result.popup = !value || value === 'yes' || value === '1' || value === 'true'; break; case 'width': case 'innerWidth': diff --git a/packages/happy-dom/test/browser/Browser.test.ts b/packages/happy-dom/test/browser/Browser.test.ts index de4ad40a4..380637c97 100644 --- a/packages/happy-dom/test/browser/Browser.test.ts +++ b/packages/happy-dom/test/browser/Browser.test.ts @@ -163,12 +163,5 @@ describe('Browser', () => { await browser.close(); expect(() => browser.newPage()).toThrow('No default context. The browser has been closed.'); }); - - it('Supports opener as parameter.', () => { - const browser = new Browser(); - const page1 = browser.newPage(); - const page2 = browser.newPage(page1.mainFrame); - expect(page2.mainFrame.opener).toBe(page1.mainFrame); - }); }); }); diff --git a/packages/happy-dom/test/browser/BrowserContext.test.ts b/packages/happy-dom/test/browser/BrowserContext.test.ts index c5d49df1f..ed349586b 100644 --- a/packages/happy-dom/test/browser/BrowserContext.test.ts +++ b/packages/happy-dom/test/browser/BrowserContext.test.ts @@ -83,12 +83,5 @@ describe('BrowserContext', () => { expect(browser.defaultContext.pages.length).toBe(1); expect(browser.defaultContext.pages[0]).toBe(page); }); - - it('Supports opener as parameter.', () => { - const browser = new Browser(); - const page1 = browser.defaultContext.newPage(); - const page2 = browser.defaultContext.newPage(page1.mainFrame); - expect(page2.mainFrame.opener).toBe(page1.mainFrame); - }); }); }); diff --git a/packages/happy-dom/test/browser/BrowserFrame.test.ts b/packages/happy-dom/test/browser/BrowserFrame.test.ts index 58984f7f5..6654dbc1e 100644 --- a/packages/happy-dom/test/browser/BrowserFrame.test.ts +++ b/packages/happy-dom/test/browser/BrowserFrame.test.ts @@ -13,6 +13,7 @@ import BrowserFrameFactory from '../../src/browser/utilities/BrowserFrameFactory import BrowserErrorCaptureEnum from '../../src/browser/enums/BrowserErrorCaptureEnum'; import Headers from '../../src/fetch/Headers'; import IHTMLAnchorElement from '../../src/nodes/html-anchor-element/IHTMLAnchorElement'; +import * as PropertySymbol from '../../src/PropertySymbol'; describe('BrowserFrame', () => { afterEach(() => { @@ -401,7 +402,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -429,7 +431,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -480,7 +483,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'http://github.com'; @@ -507,7 +511,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -534,7 +539,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; await childPage.mainFrame.goto('http://github.com/capricorn86/happy-dom'); @@ -559,7 +565,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -661,7 +668,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; await childPage.mainFrame.goto('http://localhost:9999'); @@ -684,7 +692,8 @@ describe('BrowserFrame', () => { } }); const page = browser.newPage(); - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; const response = await childPage.mainFrame.goto('http://localhost:9999'); diff --git a/packages/happy-dom/test/browser/BrowserPage.test.ts b/packages/happy-dom/test/browser/BrowserPage.test.ts index 1310aba28..18d675bb9 100644 --- a/packages/happy-dom/test/browser/BrowserPage.test.ts +++ b/packages/happy-dom/test/browser/BrowserPage.test.ts @@ -7,6 +7,8 @@ import IResponse from '../../src/fetch/types/IResponse'; import { describe, it, expect, afterEach, vi } from 'vitest'; import IGoToOptions from '../../src/browser/types/IGoToOptions'; import BrowserFrameFactory from '../../src/browser/utilities/BrowserFrameFactory'; +import Event from '../../src/event/Event'; +import DefaultBrowserPageViewport from '../../src/browser/DefaultBrowserPageViewport'; describe('BrowserPage', () => { afterEach(() => { @@ -54,6 +56,21 @@ describe('BrowserPage', () => { }); }); + describe('get viewport()', () => { + it('Returns a default viewport.', () => { + const browser = new Browser(); + const page = browser.defaultContext.newPage(); + expect(page.viewport).toEqual(DefaultBrowserPageViewport); + }); + + it('Returns defined viewport.', () => { + const browser = new Browser(); + const page = browser.defaultContext.newPage(); + page.setViewport({ width: 100, height: 100, devicePixelRatio: 2 }); + expect(page.viewport).toEqual({ width: 100, height: 100, devicePixelRatio: 2 }); + }); + }); + describe('get frames()', () => { it('Returns the frames.', () => { const browser = new Browser(); @@ -187,34 +204,51 @@ describe('BrowserPage', () => { it('Sets the viewport width.', () => { const browser = new Browser(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ width: 100 }); + expect(page.viewport.width).toBe(100); expect(page.mainFrame.window.innerWidth).toBe(100); expect(page.mainFrame.window.outerWidth).toBe(100); + expect(event).toBeInstanceOf(Event); }); it('Sets the viewport height.', () => { const browser = new Browser(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ height: 100 }); + expect(page.viewport.height).toBe(100); expect(page.mainFrame.window.innerHeight).toBe(100); expect(page.mainFrame.window.outerHeight).toBe(100); + expect(event).toBeInstanceOf(Event); }); it('Sets the viewport width and height.', () => { const browser = new Browser(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ width: 100, height: 100 }); + expect(page.viewport.width).toBe(100); + expect(page.viewport.height).toBe(100); expect(page.mainFrame.window.innerWidth).toBe(100); expect(page.mainFrame.window.outerWidth).toBe(100); expect(page.mainFrame.window.innerHeight).toBe(100); expect(page.mainFrame.window.outerHeight).toBe(100); + expect(event).toBeInstanceOf(Event); }); it('Sets the viewport device scale factor.', () => { const browser = new Browser(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ devicePixelRatio: 2 }); + expect(page.viewport.devicePixelRatio).toBe(2); expect(page.mainFrame.window.devicePixelRatio).toBe(2); + expect(event).toBeInstanceOf(Event); }); }); diff --git a/packages/happy-dom/test/browser/detached-browser/DetachedBrowser.test.ts b/packages/happy-dom/test/browser/detached-browser/DetachedBrowser.test.ts index 79812f9eb..29616a24c 100644 --- a/packages/happy-dom/test/browser/detached-browser/DetachedBrowser.test.ts +++ b/packages/happy-dom/test/browser/detached-browser/DetachedBrowser.test.ts @@ -152,14 +152,5 @@ describe('DetachedBrowser', () => { await browser.close(); expect(() => browser.newPage()).toThrow('No default context. The browser has been closed.'); }); - - it('Supports opener as parameter.', () => { - const window = new Window(); - const browser = new DetachedBrowser(BrowserWindow); - browser.defaultContext.pages[0].mainFrame.window = window; - const page1 = browser.newPage(); - const page2 = browser.newPage(page1.mainFrame); - expect(page2.mainFrame.opener).toBe(page1.mainFrame); - }); }); }); diff --git a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserContext.test.ts b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserContext.test.ts index afa1ad12b..122c5bb5f 100644 --- a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserContext.test.ts +++ b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserContext.test.ts @@ -93,14 +93,5 @@ describe('DetachedBrowserContext', () => { expect(browser.defaultContext.pages[0].mainFrame.window).toBe(window); expect(browser.defaultContext.pages[1]).toBe(page); }); - - it('Supports opener as parameter.', () => { - const window = new Window(); - const browser = new DetachedBrowser(BrowserWindow); - browser.defaultContext.pages[0].mainFrame.window = window; - const page1 = browser.defaultContext.newPage(); - const page2 = browser.defaultContext.newPage(page1.mainFrame); - expect(page2.mainFrame.opener).toBe(page1.mainFrame); - }); }); }); diff --git a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts index c433b9b0c..fc2eb3085 100644 --- a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts +++ b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserFrame.test.ts @@ -14,6 +14,7 @@ import BrowserFrameFactory from '../../../src/browser/utilities/BrowserFrameFact import BrowserErrorCaptureEnum from '../../../src/browser/enums/BrowserErrorCaptureEnum'; import Headers from '../../../src/fetch/Headers'; import IHTMLAnchorElement from '../../../src/nodes/html-anchor-element/IHTMLAnchorElement'; +import * as PropertySymbol from '../../../src/PropertySymbol'; describe('DetachedBrowserFrame', () => { afterEach(() => { @@ -423,7 +424,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -452,7 +454,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -505,7 +508,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'http://github.com'; @@ -533,7 +537,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -561,7 +566,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; await childPage.mainFrame.goto('http://github.com/capricorn86/happy-dom'); @@ -587,7 +593,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; page.mainFrame.url = 'https://github.com'; @@ -693,7 +700,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; await childPage.mainFrame.goto('http://localhost:9999'); @@ -717,7 +725,8 @@ describe('DetachedBrowserFrame', () => { }); browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.defaultContext.pages[0]; - const childPage = browser.newPage(page.mainFrame); + const childPage = browser.newPage(); + childPage.mainFrame[PropertySymbol.openerFrame] = page.mainFrame; const oldWindow = childPage.mainFrame.window; const response = await childPage.mainFrame.goto('http://localhost:9999'); diff --git a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts index 934681d4b..dbc9b7ecf 100644 --- a/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts +++ b/packages/happy-dom/test/browser/detached-browser/DetachedBrowserPage.test.ts @@ -8,6 +8,8 @@ import IResponse from '../../../src/fetch/types/IResponse'; import { describe, it, expect, afterEach, vi } from 'vitest'; import IGoToOptions from '../../../src/browser/types/IGoToOptions'; import BrowserFrameFactory from '../../../src/browser/utilities/BrowserFrameFactory'; +import Event from '../../../src/event/Event'; +import DefaultBrowserPageViewport from '../../../src/browser/DefaultBrowserPageViewport'; describe('DetachedBrowserPage', () => { afterEach(() => { @@ -56,6 +58,21 @@ describe('DetachedBrowserPage', () => { }); }); + describe('get viewport()', () => { + it('Returns a default viewport.', () => { + const browser = new DetachedBrowser(BrowserWindow); + const page = browser.defaultContext.newPage(); + expect(page.viewport).toEqual(DefaultBrowserPageViewport); + }); + + it('Returns defined viewport.', () => { + const browser = new DetachedBrowser(BrowserWindow); + const page = browser.defaultContext.newPage(); + page.setViewport({ width: 100, height: 100, devicePixelRatio: 2 }); + expect(page.viewport).toEqual({ width: 100, height: 100, devicePixelRatio: 2 }); + }); + }); + describe('get frames()', () => { it('Returns the frames.', () => { const browser = new DetachedBrowser(BrowserWindow); @@ -198,39 +215,52 @@ describe('DetachedBrowserPage', () => { describe('setViewport()', () => { it('Sets the viewport width.', () => { const browser = new DetachedBrowser(BrowserWindow); - browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ width: 100 }); + expect(page.viewport.width).toBe(100); expect(page.mainFrame.window.innerWidth).toBe(100); expect(page.mainFrame.window.outerWidth).toBe(100); + expect(event).toBeInstanceOf(Event); }); it('Sets the viewport height.', () => { const browser = new DetachedBrowser(BrowserWindow); - browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ height: 100 }); + expect(page.viewport.height).toBe(100); expect(page.mainFrame.window.innerHeight).toBe(100); expect(page.mainFrame.window.outerHeight).toBe(100); + expect(event).toBeInstanceOf(Event); }); it('Sets the viewport width and height.', () => { const browser = new DetachedBrowser(BrowserWindow); - browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ width: 100, height: 100 }); + expect(page.viewport.width).toBe(100); + expect(page.viewport.height).toBe(100); expect(page.mainFrame.window.innerWidth).toBe(100); expect(page.mainFrame.window.outerWidth).toBe(100); expect(page.mainFrame.window.innerHeight).toBe(100); expect(page.mainFrame.window.outerHeight).toBe(100); + expect(event).toBeInstanceOf(Event); }); it('Sets the viewport device scale factor.', () => { const browser = new DetachedBrowser(BrowserWindow); - browser.defaultContext.pages[0].mainFrame.window = new Window(); const page = browser.newPage(); + let event: Event | null = null; + page.mainFrame.window.addEventListener('resize', (e) => (event = e)); page.setViewport({ devicePixelRatio: 2 }); + expect(page.viewport.devicePixelRatio).toBe(2); expect(page.mainFrame.window.devicePixelRatio).toBe(2); + expect(event).toBeInstanceOf(Event); }); }); diff --git a/packages/happy-dom/test/window/BrowserWindow.test.ts b/packages/happy-dom/test/window/BrowserWindow.test.ts index 47ed73686..8d921709d 100644 --- a/packages/happy-dom/test/window/BrowserWindow.test.ts +++ b/packages/happy-dom/test/window/BrowserWindow.test.ts @@ -31,6 +31,7 @@ import CrossOriginBrowserWindow from '../../src/window/CrossOriginBrowserWindow. import BrowserFrameFactory from '../../src/browser/utilities/BrowserFrameFactory.js'; import IBrowser from '../../src/browser/types/IBrowser.js'; import IBrowserFrame from '../../src/browser/types/IBrowserFrame.js'; +import IBrowserPage from '../../src/browser/types/IBrowserPage.js'; import BrowserWindow from '../../src/window/BrowserWindow.js'; import '../types.d.js'; @@ -46,13 +47,15 @@ const GET_NAVIGATOR_PLATFORM = (): string => { describe('BrowserWindow', () => { let browser: IBrowser; + let browserPage: IBrowserPage; let browserFrame: IBrowserFrame; let window: IBrowserWindow; let document: IDocument; beforeEach(() => { browser = new Browser(); - browserFrame = browser.newPage().mainFrame; + browserPage = browser.newPage(); + browserFrame = browserPage.mainFrame; window = browserFrame.window; document = window.document; window.customElements.define('custom-element', CustomElement); @@ -195,6 +198,81 @@ describe('BrowserWindow', () => { }); }); + describe('get innerWidth()', () => { + it('Returns the viewport width.', () => { + browserPage.setViewport({ width: 100 }); + expect(window.innerWidth).toBe(100); + }); + }); + + describe('set innerWidth()', () => { + it('Sets inner width.', () => { + window.innerWidth = 100; + expect(window.innerWidth).toBe(100); + expect(browserPage.viewport.width).toBe(1024); + }); + }); + + describe('get innerHeight()', () => { + it('Returns the viewport height.', () => { + browserPage.setViewport({ height: 100 }); + expect(window.innerHeight).toBe(100); + }); + }); + + describe('set innerHeight()', () => { + it('Sets inner height.', () => { + window.innerHeight = 100; + expect(window.innerHeight).toBe(100); + expect(browserPage.viewport.height).toBe(768); + }); + }); + + describe('get outerWidth()', () => { + it('Returns the viewport width.', () => { + browserPage.setViewport({ width: 100 }); + expect(window.outerWidth).toBe(100); + }); + }); + + describe('set outerWidth()', () => { + it('Sets outer width.', () => { + window.outerWidth = 100; + expect(window.outerWidth).toBe(100); + expect(browserPage.viewport.width).toBe(1024); + }); + }); + + describe('get outerHeight()', () => { + it('Returns the viewport height.', () => { + browserPage.setViewport({ height: 100 }); + expect(window.outerHeight).toBe(100); + }); + }); + + describe('set outerHeight()', () => { + it('Sets outer height.', () => { + window.outerHeight = 100; + expect(window.outerHeight).toBe(100); + expect(browserPage.viewport.height).toBe(768); + }); + }); + + describe('get devicePixelRatio()', () => { + it('Returns the viewport devicePixelRatio.', () => { + browserPage.setViewport({ devicePixelRatio: 2 }); + expect(window.devicePixelRatio).toBe(2); + }); + }); + + describe('set devicePixelRatio()', () => { + it('Sets devicePixelRatio.', () => { + window.devicePixelRatio = 2; + expect(window.devicePixelRatio).toBe(2); + expect(browserPage.viewport.devicePixelRatio).toBe(1); + }); + }); + describe('eval()', () => { it('Respects direct eval.', () => { const result = window.eval(` @@ -1372,4 +1450,42 @@ describe('BrowserWindow', () => { ).toBe(true); }); }); + + describe('resizeTo()', () => { + it(`Doesn't resize a non-popup window.`, () => { + window.resizeTo(100, 200); + expect(window.innerWidth).toBe(1024); + expect(window.innerHeight).toBe(768); + expect(window.outerWidth).toBe(1024); + expect(window.outerHeight).toBe(768); + }); + + it(`Resize a popup window.`, () => { + const newWindow = window.open('', '', 'popup'); + newWindow.resizeTo(100, 200); + expect(newWindow.innerWidth).toBe(100); + expect(newWindow.innerHeight).toBe(200); + expect(newWindow.outerWidth).toBe(100); + expect(newWindow.outerHeight).toBe(200); + }); + }); + + describe('resizeBy()', () => { + it(`Doesn't resize a non-popup window.`, () => { + window.resizeBy(-100, -200); + expect(window.innerWidth).toBe(1024); + expect(window.innerHeight).toBe(768); + expect(window.outerWidth).toBe(1024); + expect(window.outerHeight).toBe(768); + }); + + it(`Resize a popup window.`, () => { + const newWindow = window.open('', '', 'popup'); + newWindow.resizeBy(-100, -200); + expect(newWindow.innerWidth).toBe(1024 - 100); + expect(newWindow.innerHeight).toBe(768 - 200); + expect(newWindow.outerWidth).toBe(1024 - 100); + expect(newWindow.outerHeight).toBe(768 - 200); + }); + }); });