"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var bidiPage_exports = {}; __export(bidiPage_exports, { BidiPage: () => BidiPage, addMainBinding: () => addMainBinding, kPlaywrightBindingChannel: () => kPlaywrightBindingChannel }); module.exports = __toCommonJS(bidiPage_exports); var import_utils = require("../../utils"); var import_eventsHelper = require("../utils/eventsHelper"); var import_browserContext = require("../browserContext"); var dialog = __toESM(require("../dialog")); var dom = __toESM(require("../dom")); var import_page = require("../page"); var import_bidiExecutionContext = require("./bidiExecutionContext"); var import_bidiInput = require("./bidiInput"); var import_bidiNetworkManager = require("./bidiNetworkManager"); var import_bidiPdf = require("./bidiPdf"); var bidi = __toESM(require("./third_party/bidiProtocol")); const UTILITY_WORLD_NAME = "__playwright_utility_world__"; const kPlaywrightBindingChannel = "playwrightChannel"; class BidiPage { constructor(browserContext, bidiSession, opener) { this._sessionListeners = []; this._initScriptIds = []; this._session = bidiSession; this._opener = opener; this.rawKeyboard = new import_bidiInput.RawKeyboardImpl(bidiSession); this.rawMouse = new import_bidiInput.RawMouseImpl(bidiSession); this.rawTouchscreen = new import_bidiInput.RawTouchscreenImpl(bidiSession); this._realmToContext = /* @__PURE__ */ new Map(); this._page = new import_page.Page(this, browserContext); this._browserContext = browserContext; this._networkManager = new import_bidiNetworkManager.BidiNetworkManager(this._session, this._page, this._onNavigationResponseStarted.bind(this)); this._pdf = new import_bidiPdf.BidiPDF(this._session); this._page.on(import_page.Page.Events.FrameDetached, (frame) => this._removeContextsForFrame(frame, false)); this._sessionListeners = [ import_eventsHelper.eventsHelper.addEventListener(bidiSession, "script.realmCreated", this._onRealmCreated.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "script.message", this._onScriptMessage.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.contextDestroyed", this._onBrowsingContextDestroyed.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.navigationStarted", this._onNavigationStarted.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.navigationAborted", this._onNavigationAborted.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.navigationFailed", this._onNavigationFailed.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.fragmentNavigated", this._onFragmentNavigated.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.domContentLoaded", this._onDomContentLoaded.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.load", this._onLoad.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "browsingContext.userPromptOpened", this._onUserPromptOpened.bind(this)), import_eventsHelper.eventsHelper.addEventListener(bidiSession, "log.entryAdded", this._onLogEntryAdded.bind(this)) ]; this._initialize().then( () => this._page.reportAsNew(this._opener?._page), (error) => this._page.reportAsNew(this._opener?._page, error) ); } async _initialize() { this._onFrameAttached(this._session.sessionId, null); await Promise.all([ this.updateHttpCredentials(), this.updateRequestInterception(), this._installMainBinding(), this._addAllInitScripts() ]); } async _addAllInitScripts() { return Promise.all(this._page.allInitScripts().map((initScript) => this.addInitScript(initScript))); } didClose() { this._session.dispose(); import_eventsHelper.eventsHelper.removeEventListeners(this._sessionListeners); this._page._didClose(); } _onFrameAttached(frameId, parentFrameId) { return this._page._frameManager.frameAttached(frameId, parentFrameId); } _removeContextsForFrame(frame, notifyFrame) { for (const [contextId, context] of this._realmToContext) { if (context.frame === frame) { this._realmToContext.delete(contextId); if (notifyFrame) frame._contextDestroyed(context); } } } _onRealmCreated(realmInfo) { if (this._realmToContext.has(realmInfo.realm)) return; if (realmInfo.type !== "window") return; const frame = this._page._frameManager.frame(realmInfo.context); if (!frame) return; let worldName; if (!realmInfo.sandbox) { worldName = "main"; this._touchUtilityWorld(realmInfo.context); } else if (realmInfo.sandbox === UTILITY_WORLD_NAME) { worldName = "utility"; } else { return; } const delegate = new import_bidiExecutionContext.BidiExecutionContext(this._session, realmInfo); const context = new dom.FrameExecutionContext(delegate, frame, worldName); frame._contextCreated(worldName, context); this._realmToContext.set(realmInfo.realm, context); } async _touchUtilityWorld(context) { await this._session.sendMayFail("script.evaluate", { expression: "1 + 1", target: { context, sandbox: UTILITY_WORLD_NAME }, serializationOptions: { maxObjectDepth: 10, maxDomDepth: 10 }, awaitPromise: true, userActivation: true }); } _onRealmDestroyed(params) { const context = this._realmToContext.get(params.realm); if (!context) return false; this._realmToContext.delete(params.realm); context.frame._contextDestroyed(context); return true; } // TODO: route the message directly to the browser _onBrowsingContextDestroyed(params) { this._browserContext._browser._onBrowsingContextDestroyed(params); } _onNavigationStarted(params) { const frameId = params.context; this._page._frameManager.frameRequestedNavigation(frameId, params.navigation); const url = params.url.toLowerCase(); if (url.startsWith("file:") || url.startsWith("data:") || url === "about:blank") { const frame = this._page._frameManager.frame(frameId); if (frame) this._page._frameManager.frameCommittedNewDocumentNavigation( frameId, params.url, "", params.navigation, /* initial */ false ); } } // TODO: there is no separate event for committed navigation, so we approximate it with responseStarted. _onNavigationResponseStarted(params) { const frameId = params.context; const frame = this._page._frameManager.frame(frameId); (0, import_utils.assert)(frame); this._page._frameManager.frameCommittedNewDocumentNavigation( frameId, params.response.url, "", params.navigation, /* initial */ false ); } _onDomContentLoaded(params) { const frameId = params.context; this._page._frameManager.frameLifecycleEvent(frameId, "domcontentloaded"); } _onLoad(params) { this._page._frameManager.frameLifecycleEvent(params.context, "load"); } _onNavigationAborted(params) { this._page._frameManager.frameAbortedNavigation(params.context, "Navigation aborted", params.navigation || void 0); } _onNavigationFailed(params) { this._page._frameManager.frameAbortedNavigation(params.context, "Navigation failed", params.navigation || void 0); } _onFragmentNavigated(params) { this._page._frameManager.frameCommittedSameDocumentNavigation(params.context, params.url); } _onUserPromptOpened(event) { this._page.emitOnContext(import_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog( this._page, event.type, event.message, async (accept, userText) => { await this._session.send("browsingContext.handleUserPrompt", { context: event.context, accept, userText }); }, event.defaultValue )); } _onLogEntryAdded(params) { if (params.type !== "console") return; const entry = params; const context = this._realmToContext.get(params.source.realm); if (!context) return; const callFrame = params.stackTrace?.callFrames[0]; const location = callFrame ?? { url: "", lineNumber: 1, columnNumber: 1 }; this._page._addConsoleMessage(entry.method, entry.args.map((arg) => (0, import_bidiExecutionContext.createHandle)(context, arg)), location, params.text || void 0); } async navigateFrame(frame, url, referrer) { const { navigation } = await this._session.send("browsingContext.navigate", { context: frame._id, url }); return { newDocumentId: navigation || void 0 }; } async updateExtraHTTPHeaders() { } async updateEmulateMedia() { } async updateUserAgent() { } async bringToFront() { await this._session.send("browsingContext.activate", { context: this._session.sessionId }); } async updateEmulatedViewportSize() { const options = this._browserContext._options; const deviceSize = this._page.emulatedSize(); if (deviceSize === null) return; const viewportSize = deviceSize.viewport; await this._session.send("browsingContext.setViewport", { context: this._session.sessionId, viewport: { width: viewportSize.width, height: viewportSize.height }, devicePixelRatio: options.deviceScaleFactor || 1 }); } async updateRequestInterception() { await this._networkManager.setRequestInterception(this._page.needsRequestInterception()); } async updateOffline() { } async updateHttpCredentials() { await this._networkManager.setCredentials(this._browserContext._options.httpCredentials); } async updateFileChooserInterception() { } async reload() { await this._session.send("browsingContext.reload", { context: this._session.sessionId, // ignoreCache: true, wait: bidi.BrowsingContext.ReadinessState.Interactive }); } async goBack() { return await this._session.send("browsingContext.traverseHistory", { context: this._session.sessionId, delta: -1 }).then(() => true).catch(() => false); } async goForward() { return await this._session.send("browsingContext.traverseHistory", { context: this._session.sessionId, delta: 1 }).then(() => true).catch(() => false); } async requestGC() { throw new Error("Method not implemented."); } // TODO: consider calling this only when bindings are added. // TODO: delete this method once we can add preload script for persistent context. async _installMainBinding() { if (this._browserContext._browserContextId) return; const functionDeclaration = addMainBinding.toString(); const args = [{ type: "channel", value: { channel: kPlaywrightBindingChannel, ownership: bidi.Script.ResultOwnership.Root } }]; const promises = []; promises.push(this._session.send("script.addPreloadScript", { functionDeclaration, arguments: args })); promises.push(this._session.send("script.callFunction", { functionDeclaration, arguments: args, target: toBidiExecutionContext(await this._page.mainFrame()._mainContext())._target, awaitPromise: false, userActivation: false })); await Promise.all(promises); } async _onScriptMessage(event) { if (event.channel !== kPlaywrightBindingChannel) return; const pageOrError = await this._page.waitForInitializedOrError(); if (pageOrError instanceof Error) return; const context = this._realmToContext.get(event.source.realm); if (!context) return; if (event.data.type !== "string") return; await this._page._onBindingCalled(event.data.value, context); } async addInitScript(initScript) { const { script } = await this._session.send("script.addPreloadScript", { // TODO: remove function call from the source. functionDeclaration: `() => { return ${initScript.source} }`, // TODO: push to iframes? contexts: [this._session.sessionId] }); if (!initScript.internal) this._initScriptIds.push(script); } async removeNonInternalInitScripts() { const promises = this._initScriptIds.map((script) => this._session.send("script.removePreloadScript", { script })); this._initScriptIds = []; await Promise.all(promises); } async closePage(runBeforeUnload) { await this._session.send("browsingContext.close", { context: this._session.sessionId, promptUnload: runBeforeUnload }); } async setBackgroundColor(color) { } async takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, scale) { const rect = documentRect || viewportRect; const { data } = await this._session.send("browsingContext.captureScreenshot", { context: this._session.sessionId, format: { type: `image/${format === "png" ? "png" : "jpeg"}`, quality: quality ? quality / 100 : 0.8 }, origin: documentRect ? "document" : "viewport", clip: { type: "box", ...rect } }); return Buffer.from(data, "base64"); } async getContentFrame(handle) { const executionContext = toBidiExecutionContext(handle._context); const frameId = await executionContext.contentFrameIdForFrame(handle); if (!frameId) return null; return this._page._frameManager.frame(frameId); } async getOwnerFrame(handle) { const windowHandle = await handle.evaluateHandle((node) => { const doc = node.ownerDocument ?? node; return doc.defaultView; }); if (!windowHandle) return null; const executionContext = toBidiExecutionContext(handle._context); return executionContext.frameIdForWindowHandle(windowHandle); } async getBoundingBox(handle) { const box = await handle.evaluate((element) => { if (!(element instanceof Element)) return null; const rect = element.getBoundingClientRect(); return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; }); if (!box) return null; const position = await this._framePosition(handle._frame); if (!position) return null; box.x += position.x; box.y += position.y; return box; } // TODO: move to Frame. async _framePosition(frame) { if (frame === this._page.mainFrame()) return { x: 0, y: 0 }; const element = await frame.frameElement(); const box = await element.boundingBox(); if (!box) return null; const style = await element.evaluateInUtility(([injected, iframe]) => injected.describeIFrameStyle(iframe), {}).catch((e) => "error:notconnected"); if (style === "error:notconnected" || style === "transformed") return null; box.x += style.left; box.y += style.top; return box; } async scrollRectIntoViewIfNeeded(handle, rect) { return await handle.evaluateInUtility(([injected, node]) => { node.scrollIntoView({ block: "center", inline: "center", behavior: "instant" }); }, null).then(() => "done").catch((e) => { if (e instanceof Error && e.message.includes("Node is detached from document")) return "error:notconnected"; if (e instanceof Error && e.message.includes("Node does not have a layout object")) return "error:notvisible"; throw e; }); } async setScreencastOptions(options) { } rafCountForStablePosition() { return 1; } async getContentQuads(handle) { const quads = await handle.evaluateInUtility(([injected, node]) => { if (!node.isConnected) return "error:notconnected"; const rects = node.getClientRects(); if (!rects) return null; return [...rects].map((rect) => [ { x: rect.left, y: rect.top }, { x: rect.right, y: rect.top }, { x: rect.right, y: rect.bottom }, { x: rect.left, y: rect.bottom } ]); }, null); if (!quads || quads === "error:notconnected") return quads; const position = await this._framePosition(handle._frame); if (!position) return null; quads.forEach((quad) => quad.forEach((point) => { point.x += position.x; point.y += position.y; })); return quads; } async setInputFilePaths(handle, paths) { const fromContext = toBidiExecutionContext(handle._context); await this._session.send("input.setFiles", { context: this._session.sessionId, element: await fromContext.nodeIdForElementHandle(handle), files: paths }); } async adoptElementHandle(handle, to) { const fromContext = toBidiExecutionContext(handle._context); const nodeId = await fromContext.nodeIdForElementHandle(handle); const executionContext = toBidiExecutionContext(to); return await executionContext.remoteObjectForNodeId(to, nodeId); } async getAccessibilityTree(needle) { throw new Error("Method not implemented."); } async inputActionEpilogue() { } async resetForReuse() { } async pdf(options) { return this._pdf.generate(options); } async getFrameElement(frame) { const parent = frame.parentFrame(); if (!parent) throw new Error("Frame has been detached."); const parentContext = await parent._mainContext(); const list = await parentContext.evaluateHandle(() => { return [...document.querySelectorAll("iframe,frame")]; }); const length = await list.evaluate((list2) => list2.length); let foundElement = null; for (let i = 0; i < length; i++) { const element = await list.evaluateHandle((list2, i2) => list2[i2], i); const candidate = await element.contentFrame(); if (frame === candidate) { foundElement = element; break; } else { element.dispose(); } } list.dispose(); if (!foundElement) throw new Error("Frame has been detached."); return foundElement; } shouldToggleStyleSheetToSyncAnimations() { return true; } } function addMainBinding(callback) { globalThis["__playwright__binding__"] = callback; } function toBidiExecutionContext(executionContext) { return executionContext.delegate; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { BidiPage, addMainBinding, kPlaywrightBindingChannel });