"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
});
