import VirtualConsolePrinter from '../../console/VirtualConsolePrinter.js'; import DetachedBrowserFrame from './DetachedBrowserFrame.js'; import type DetachedBrowserContext from './DetachedBrowserContext.js'; import VirtualConsole from '../../console/VirtualConsole.js'; import type IBrowserPage from '../types/IBrowserPage.js'; import type { Script } from 'vm'; import type IGoToOptions from '../types/IGoToOptions.js'; import type Response from '../../fetch/Response.js'; import BrowserPageUtility from '../utilities/BrowserPageUtility.js'; import type IReloadOptions from '../types/IReloadOptions.js'; import type IOptionalBrowserPageViewport from '../types/IOptionalBrowserPageViewport.js'; import type IBrowserPageViewport from '../types/IBrowserPageViewport.js'; import Event from '../../event/Event.js'; /** * Detached browser page used when constructing a Window instance without a browser. */ export default class DetachedBrowserPage implements IBrowserPage { public readonly virtualConsolePrinter = new VirtualConsolePrinter(); public readonly mainFrame: DetachedBrowserFrame; public readonly context: DetachedBrowserContext; public readonly console: Console; public readonly viewport: IBrowserPageViewport; public readonly closed: boolean = false; /** * Constructor. * * @param context Browser context. */ constructor(context: DetachedBrowserContext) { this.context = context; this.console = context.browser.console ?? new VirtualConsole(this.virtualConsolePrinter); this.mainFrame = new DetachedBrowserFrame(this); this.viewport = { width: context.browser.settings.viewport.width, height: context.browser.settings.viewport.height, devicePixelRatio: context.browser.settings.viewport.devicePixelRatio }; } /** * Returns frames. */ public get frames(): DetachedBrowserFrame[] { return BrowserPageUtility.getFrames(this); } /** * Returns the viewport. */ public get content(): string { return this.mainFrame.content; } /** * Sets the content. * * @param content Content. */ public set content(content) { this.mainFrame.content = content; } /** * Returns the URL. * * @returns URL. */ public get url(): string { return this.mainFrame.url; } /** * Sets the content. * * @param url URL. */ public set url(url) { this.mainFrame.url = url; } /** * Aborts all ongoing operations and destroys the page. */ public close(): Promise { // Using Promise instead of async/await to prevent microtask return new Promise((resolve, reject) => { const context = this.context; BrowserPageUtility.closePage(this) .then(() => { // As we are in a detached page, a context or browser should not exist without a page as there are no references to them. if (context.pages[0] === this) { context.browser.close().then(resolve).catch(reject); } else { resolve(); } }) .catch(reject); }); } /** * Returns a promise that is resolved when all resources has been loaded, fetch has completed, and all async tasks such as timers are complete. */ public waitUntilComplete(): Promise { return this.mainFrame.waitUntilComplete(); } /** * Returns a promise that is resolved when the page has navigated and the response HTML has been written to the document. */ public waitForNavigation(): Promise { return this.mainFrame.waitForNavigation(); } /** * Aborts all ongoing operations. */ public abort(): Promise { return this.mainFrame.abort(); } /** * Evaluates code or a VM Script in the page's context. * * @param script Script. * @returns Result. */ public evaluate(script: string | Script): any { return this.mainFrame.evaluate(script); } /** * Evaluates a module in the page's context. * * @param options Options. * @param options.url URL. * @param options.type Module type. * @param options.code Code. * @returns Module exports. */ public evaluateModule(options: { url?: string; type?: 'esm' | 'css' | 'json'; code?: string; }): Promise> { return this.mainFrame.evaluateModule(options); } /** * Sets the viewport. * * @param viewport 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')); } } /** * Go to a page. * * @param url URL. * @param [options] Options. * @returns Response. */ public goto(url: string, options?: IGoToOptions): Promise { return this.mainFrame.goto(url, options); } /** * Navigates back in history. * * @param [options] Options. */ public goBack(options?: IGoToOptions): Promise { return this.mainFrame.goBack(options); } /** * Navigates forward in history. * * @param [options] Options. */ public goForward(options?: IGoToOptions): Promise { return this.mainFrame.goForward(options); } /** * Navigates a delta in history. * * @param delta Delta. * @param steps * @param [options] Options. */ public goSteps(steps?: number, options?: IGoToOptions): Promise { return this.mainFrame.goSteps(steps, options); } /** * Reloads the current page. * * @param [options] Options. * @returns Response. */ public reload(options?: IReloadOptions): Promise { return this.mainFrame.reload(options); } }