"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 tracing_exports = {};
__export(tracing_exports, {
  Tracing: () => Tracing
});
module.exports = __toCommonJS(tracing_exports);
var import_fs = __toESM(require("fs"));
var import_os = __toESM(require("os"));
var import_path = __toESM(require("path"));
var import_snapshotter = require("./snapshotter");
var import_protocolMetainfo = require("../../../utils/isomorphic/protocolMetainfo");
var import_assert = require("../../../utils/isomorphic/assert");
var import_time = require("../../../utils/isomorphic/time");
var import_manualPromise = require("../../../utils/isomorphic/manualPromise");
var import_eventsHelper = require("../../utils/eventsHelper");
var import_crypto = require("../../utils/crypto");
var import_userAgent = require("../../utils/userAgent");
var import_artifact = require("../../artifact");
var import_browserContext = require("../../browserContext");
var import_dispatcher = require("../../dispatchers/dispatcher");
var import_errors = require("../../errors");
var import_fileUtils = require("../../utils/fileUtils");
var import_harTracer = require("../../har/harTracer");
var import_instrumentation = require("../../instrumentation");
var import_progress = require("../../progress");
const version = 8;
class Tracing extends import_instrumentation.SdkObject {
  constructor(context, tracesDir) {
    super(context, "tracing");
    this._fs = new import_fileUtils.SerializedFS();
    this._screencastListeners = [];
    this._pageTracingRecorders = /* @__PURE__ */ new Map();
    this._eventListeners = [];
    this._isStopping = false;
    this._allResources = /* @__PURE__ */ new Set();
    this._pendingHarEntries = /* @__PURE__ */ new Set();
    this._context = context;
    this._precreatedTracesDir = tracesDir;
    this._harTracer = new import_harTracer.HarTracer(context, null, this, {
      content: "attach",
      includeTraceInfo: true,
      recordRequestOverrides: false,
      waitForContentOnStop: false
    });
    const testIdAttributeName = "selectors" in context ? context.selectors().testIdAttributeName() : void 0;
    this._contextCreatedEvent = {
      version,
      type: "context-options",
      origin: "library",
      browserName: "",
      playwrightVersion: (0, import_userAgent.getPlaywrightVersion)(),
      options: {},
      platform: process.platform,
      wallTime: 0,
      monotonicTime: 0,
      sdkLanguage: this._sdkLanguage(),
      testIdAttributeName,
      contextId: context.guid
    };
    if (context instanceof import_browserContext.BrowserContext) {
      this._snapshotter = new import_snapshotter.Snapshotter(context, this);
      (0, import_assert.assert)(tracesDir, "tracesDir must be specified for BrowserContext");
      this._contextCreatedEvent.browserName = context._browser.options.name;
      this._contextCreatedEvent.channel = context._browser.options.channel;
      this._contextCreatedEvent.options = context._options;
    }
  }
  _sdkLanguage() {
    return this._context instanceof import_browserContext.BrowserContext ? this._context._browser.sdkLanguage() : this._context.attribution.playwright.options.sdkLanguage;
  }
  async resetForReuse(progress) {
    await this.stopChunk(progress, { mode: "discard" }).catch(() => {
    });
    await this.stop(progress);
    if (this._snapshotter)
      await progress.race(this._snapshotter.resetForReuse());
  }
  start(options) {
    if (this._isStopping)
      throw new Error("Cannot start tracing while stopping");
    if (this._state)
      throw new Error("Tracing has been already started");
    this._contextCreatedEvent.sdkLanguage = this._sdkLanguage();
    const traceName = options.name || (0, import_crypto.createGuid)();
    const tracesDir = this._createTracesDirIfNeeded();
    this._state = {
      options,
      traceName,
      tracesDir,
      traceFile: import_path.default.join(tracesDir, traceName + ".trace"),
      networkFile: import_path.default.join(tracesDir, traceName + ".network"),
      resourcesDir: import_path.default.join(tracesDir, "resources"),
      chunkOrdinal: 0,
      traceSha1s: /* @__PURE__ */ new Set(),
      networkSha1s: /* @__PURE__ */ new Set(),
      recording: false,
      callIds: /* @__PURE__ */ new Set(),
      groupStack: []
    };
    this._fs.mkdir(this._state.resourcesDir);
    this._fs.writeFile(this._state.networkFile, "");
    if (options.snapshots)
      this._harTracer.start({ omitScripts: !options.live });
  }
  async startChunk(progress, options = {}) {
    if (this._state && this._state.recording)
      await this.stopChunk(progress, { mode: "discard" });
    if (!this._state)
      throw new Error("Must start tracing before starting a new chunk");
    if (this._isStopping)
      throw new Error("Cannot start a trace chunk while stopping");
    this._state.recording = true;
    this._state.callIds.clear();
    const preserveNetworkResources = this._context instanceof import_browserContext.BrowserContext;
    if (options.name && options.name !== this._state.traceName)
      this._changeTraceName(this._state, options.name, preserveNetworkResources);
    else
      this._allocateNewTraceFile(this._state);
    if (!preserveNetworkResources)
      this._fs.writeFile(this._state.networkFile, "");
    this._fs.mkdir(import_path.default.dirname(this._state.traceFile));
    const event = {
      ...this._contextCreatedEvent,
      title: options.title,
      wallTime: Date.now(),
      monotonicTime: (0, import_time.monotonicTime)()
    };
    this._appendTraceEvent(event);
    this._context.instrumentation.addListener(this, this._context);
    this._eventListeners.push(
      import_eventsHelper.eventsHelper.addEventListener(this._context, import_browserContext.BrowserContext.Events.Console, this._onConsoleMessage.bind(this)),
      import_eventsHelper.eventsHelper.addEventListener(this._context, import_browserContext.BrowserContext.Events.PageError, this._onPageError.bind(this))
    );
    if (this._state.options.screenshots)
      this._startScreencast();
    if (this._state.options.snapshots)
      await this._snapshotter?.start();
    return { traceName: this._state.traceName };
  }
  _currentGroupId() {
    return this._state?.groupStack.length ? this._state.groupStack[this._state.groupStack.length - 1] : void 0;
  }
  group(name, location, metadata) {
    if (!this._state)
      return;
    const stackFrames = [];
    const { file, line, column } = location ?? metadata.location ?? {};
    if (file) {
      stackFrames.push({
        file,
        line: line ?? 0,
        column: column ?? 0
      });
    }
    const event = {
      type: "before",
      callId: metadata.id,
      startTime: metadata.startTime,
      title: name,
      class: "Tracing",
      method: "tracingGroup",
      params: {},
      stepId: metadata.stepId,
      stack: stackFrames
    };
    if (this._currentGroupId())
      event.parentId = this._currentGroupId();
    this._state.groupStack.push(event.callId);
    this._appendTraceEvent(event);
  }
  groupEnd() {
    if (!this._state)
      return;
    const callId = this._state.groupStack.pop();
    if (!callId)
      return;
    const event = {
      type: "after",
      callId,
      endTime: (0, import_time.monotonicTime)()
    };
    this._appendTraceEvent(event);
  }
  _startScreencast() {
    if (!(this._context instanceof import_browserContext.BrowserContext))
      return;
    for (const page of this._context.pages())
      this._startScreencastInPage(page);
    this._screencastListeners.push(
      import_eventsHelper.eventsHelper.addEventListener(this._context, import_browserContext.BrowserContext.Events.Page, this._startScreencastInPage.bind(this))
    );
  }
  _stopScreencast() {
    import_eventsHelper.eventsHelper.removeEventListeners(this._screencastListeners);
    for (const recorder of this._pageTracingRecorders.values())
      recorder.dispose();
    this._pageTracingRecorders.clear();
  }
  _allocateNewTraceFile(state) {
    const suffix = state.chunkOrdinal ? `-chunk${state.chunkOrdinal}` : ``;
    state.chunkOrdinal++;
    state.traceFile = import_path.default.join(state.tracesDir, `${state.traceName}${suffix}.trace`);
  }
  _changeTraceName(state, name, preserveNetworkResources) {
    state.traceName = name;
    state.chunkOrdinal = 0;
    this._allocateNewTraceFile(state);
    const newNetworkFile = import_path.default.join(state.tracesDir, name + ".network");
    if (preserveNetworkResources)
      this._fs.copyFile(state.networkFile, newNetworkFile);
    state.networkFile = newNetworkFile;
  }
  async stop(progress) {
    if (!this._state)
      return;
    if (this._isStopping)
      throw new Error(`Tracing is already stopping`);
    if (this._state.recording)
      throw new Error(`Must stop trace file before stopping tracing`);
    this._closeAllGroups();
    this._harTracer.stop();
    this.flushHarEntries();
    const promise = progress ? progress.race(this._fs.syncAndGetError()) : this._fs.syncAndGetError();
    await promise.finally(() => {
      this._state = void 0;
    });
  }
  async deleteTmpTracesDir() {
    if (this._tracesTmpDir)
      await (0, import_fileUtils.removeFolders)([this._tracesTmpDir]);
  }
  _createTracesDirIfNeeded() {
    if (this._precreatedTracesDir)
      return this._precreatedTracesDir;
    this._tracesTmpDir = import_fs.default.mkdtempSync(import_path.default.join(import_os.default.tmpdir(), "playwright-tracing-"));
    return this._tracesTmpDir;
  }
  abort() {
    this._snapshotter?.dispose();
    this._harTracer.stop();
  }
  async flush() {
    this.abort();
    await this._fs.syncAndGetError();
  }
  _closeAllGroups() {
    while (this._currentGroupId())
      this.groupEnd();
  }
  async stopChunk(progress, params) {
    if (this._isStopping)
      throw new Error(`Tracing is already stopping`);
    this._isStopping = true;
    if (!this._state || !this._state.recording) {
      this._isStopping = false;
      if (params.mode !== "discard")
        throw new Error(`Must start tracing before stopping`);
      return {};
    }
    this._closeAllGroups();
    this._context.instrumentation.removeListener(this);
    import_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
    if (this._state.options.screenshots)
      this._stopScreencast();
    if (this._state.options.snapshots)
      this._snapshotter?.stop();
    this.flushHarEntries();
    const newNetworkFile = import_path.default.join(this._state.tracesDir, this._state.traceName + `-pwnetcopy-${this._state.chunkOrdinal}.network`);
    const entries = [];
    entries.push({ name: "trace.trace", value: this._state.traceFile });
    entries.push({ name: "trace.network", value: newNetworkFile });
    for (const sha1 of /* @__PURE__ */ new Set([...this._state.traceSha1s, ...this._state.networkSha1s]))
      entries.push({ name: import_path.default.join("resources", sha1), value: import_path.default.join(this._state.resourcesDir, sha1) });
    this._state.traceSha1s = /* @__PURE__ */ new Set();
    if (params.mode === "discard") {
      this._isStopping = false;
      this._state.recording = false;
      return {};
    }
    this._fs.copyFile(this._state.networkFile, newNetworkFile);
    const zipFileName = this._state.traceFile + ".zip";
    if (params.mode === "archive")
      this._fs.zip(entries, zipFileName);
    const promise = progress ? progress.race(this._fs.syncAndGetError()) : this._fs.syncAndGetError();
    const error = await promise.catch((e) => e);
    this._isStopping = false;
    if (this._state)
      this._state.recording = false;
    if (error) {
      if (!(0, import_progress.isAbortError)(error) && this._context instanceof import_browserContext.BrowserContext && !this._context._browser.isConnected())
        return {};
      throw error;
    }
    if (params.mode === "entries")
      return { entries };
    const artifact = new import_artifact.Artifact(this._context, zipFileName);
    artifact.reportFinished();
    return { artifact };
  }
  async _captureSnapshot(snapshotName, sdkObject, metadata) {
    if (!snapshotName || !sdkObject.attribution.page)
      return;
    await this._snapshotter?.captureSnapshot(sdkObject.attribution.page, metadata.id, snapshotName).catch(() => {
    });
  }
  _shouldCaptureSnapshot(sdkObject, metadata, phase) {
    if (!sdkObject.attribution.page || !this._snapshotter?.started())
      return;
    const metainfo = (0, import_protocolMetainfo.getMetainfo)(metadata);
    if (!metainfo?.snapshot)
      return false;
    switch (phase) {
      case "before":
        return !metainfo.input || !!metainfo.isAutoWaiting;
      case "input":
        return !!metainfo.input;
      case "after":
        return true;
    }
  }
  onBeforeCall(sdkObject, metadata, parentId) {
    const event = createBeforeActionTraceEvent(metadata, parentId ?? this._currentGroupId());
    if (!event)
      return Promise.resolve();
    this._temporarilyDisableThrottling(sdkObject.attribution.page);
    if (this._shouldCaptureSnapshot(sdkObject, metadata, "before"))
      event.beforeSnapshot = `before@${metadata.id}`;
    this._state?.callIds.add(metadata.id);
    this._appendTraceEvent(event);
    return this._captureSnapshot(event.beforeSnapshot, sdkObject, metadata);
  }
  onBeforeInputAction(sdkObject, metadata) {
    if (!this._state?.callIds.has(metadata.id))
      return Promise.resolve();
    const event = createInputActionTraceEvent(metadata);
    if (!event)
      return Promise.resolve();
    this._temporarilyDisableThrottling(sdkObject.attribution.page);
    if (this._shouldCaptureSnapshot(sdkObject, metadata, "input"))
      event.inputSnapshot = `input@${metadata.id}`;
    this._appendTraceEvent(event);
    return this._captureSnapshot(event.inputSnapshot, sdkObject, metadata);
  }
  onCallLog(sdkObject, metadata, logName, message) {
    if (!this._state?.callIds.has(metadata.id))
      return;
    if (metadata.internal)
      return;
    if (logName !== "api")
      return;
    const event = createActionLogTraceEvent(metadata, message);
    if (event)
      this._appendTraceEvent(event);
  }
  onAfterCall(sdkObject, metadata) {
    if (!this._state?.callIds.has(metadata.id))
      return Promise.resolve();
    this._state?.callIds.delete(metadata.id);
    const event = createAfterActionTraceEvent(metadata);
    if (!event)
      return Promise.resolve();
    this._temporarilyDisableThrottling(sdkObject.attribution.page);
    if (this._shouldCaptureSnapshot(sdkObject, metadata, "after"))
      event.afterSnapshot = `after@${metadata.id}`;
    this._appendTraceEvent(event);
    return this._captureSnapshot(event.afterSnapshot, sdkObject, metadata);
  }
  onEntryStarted(entry) {
    this._pendingHarEntries.add(entry);
  }
  onEntryFinished(entry) {
    this._pendingHarEntries.delete(entry);
    const event = { type: "resource-snapshot", snapshot: entry };
    const visited = visitTraceEvent(event, this._state.networkSha1s);
    this._fs.appendFile(
      this._state.networkFile,
      JSON.stringify(visited) + "\n",
      true
      /* flush */
    );
  }
  flushHarEntries() {
    const harLines = [];
    for (const entry of this._pendingHarEntries) {
      const event = { type: "resource-snapshot", snapshot: entry };
      const visited = visitTraceEvent(event, this._state.networkSha1s);
      harLines.push(JSON.stringify(visited));
    }
    this._pendingHarEntries.clear();
    if (harLines.length)
      this._fs.appendFile(
        this._state.networkFile,
        harLines.join("\n") + "\n",
        true
        /* flush */
      );
  }
  onContentBlob(sha1, buffer) {
    this._appendResource(sha1, buffer);
  }
  onSnapshotterBlob(blob) {
    this._appendResource(blob.sha1, blob.buffer);
  }
  onFrameSnapshot(snapshot) {
    this._appendTraceEvent({ type: "frame-snapshot", snapshot });
  }
  _onConsoleMessage(message) {
    const event = {
      type: "console",
      messageType: message.type(),
      text: message.text(),
      args: message.args().map((a) => ({ preview: a.toString(), value: a.rawValue() })),
      location: message.location(),
      time: (0, import_time.monotonicTime)(),
      pageId: message.page()?.guid
    };
    this._appendTraceEvent(event);
  }
  onDialog(dialog) {
    const event = {
      type: "event",
      time: (0, import_time.monotonicTime)(),
      class: "BrowserContext",
      method: "dialog",
      params: { pageId: dialog.page().guid, type: dialog.type(), message: dialog.message(), defaultValue: dialog.defaultValue() }
    };
    this._appendTraceEvent(event);
  }
  onDownload(page, download) {
    const event = {
      type: "event",
      time: (0, import_time.monotonicTime)(),
      class: "BrowserContext",
      method: "download",
      params: { pageId: page.guid, url: download.url, suggestedFilename: download.suggestedFilename() }
    };
    this._appendTraceEvent(event);
  }
  onPageOpen(page) {
    const event = {
      type: "event",
      time: (0, import_time.monotonicTime)(),
      class: "BrowserContext",
      method: "page",
      params: { pageId: page.guid, openerPageId: page.opener()?.guid }
    };
    this._appendTraceEvent(event);
  }
  onPageClose(page) {
    const event = {
      type: "event",
      time: (0, import_time.monotonicTime)(),
      class: "BrowserContext",
      method: "pageClosed",
      params: { pageId: page.guid }
    };
    this._appendTraceEvent(event);
  }
  _onPageError(error, page) {
    const event = {
      type: "event",
      time: (0, import_time.monotonicTime)(),
      class: "BrowserContext",
      method: "pageError",
      params: { error: (0, import_errors.serializeError)(error) },
      pageId: page.guid
    };
    this._appendTraceEvent(event);
  }
  _temporarilyDisableThrottling(page) {
    if (page)
      this._pageTracingRecorders.get(page)?.temporarilyDisableThrottling();
  }
  _startScreencastInPage(page) {
    const prefix = page.guid;
    const onFrame = (params) => {
      const suffix = Date.now();
      const sha1 = `${prefix}-${suffix}.jpeg`;
      const event = {
        type: "screencast-frame",
        pageId: page.guid,
        sha1,
        width: params.viewportWidth,
        height: params.viewportHeight,
        timestamp: (0, import_time.monotonicTime)(),
        frameSwapWallTime: params.frameSwapWallTime
      };
      this._appendResource(sha1, params.buffer);
      this._appendTraceEvent(event);
    };
    this._pageTracingRecorders.set(page, new ScreencastTracingRecorder(page.screencast, onFrame));
  }
  _appendTraceEvent(event) {
    const visited = visitTraceEvent(event, this._state.traceSha1s);
    const flush = this._state.options.live || event.type !== "event" && event.type !== "console" && event.type !== "log";
    this._fs.appendFile(this._state.traceFile, JSON.stringify(visited) + "\n", flush);
  }
  _appendResource(sha1, buffer) {
    if (this._allResources.has(sha1))
      return;
    this._allResources.add(sha1);
    const resourcePath = import_path.default.join(this._state.resourcesDir, sha1);
    this._fs.writeFile(
      resourcePath,
      buffer,
      true
      /* skipIfExists */
    );
  }
}
function visitTraceEvent(object, sha1s) {
  if (Array.isArray(object))
    return object.map((o) => visitTraceEvent(o, sha1s));
  if (object instanceof import_dispatcher.Dispatcher)
    return `<${object._type}>`;
  if (object instanceof Buffer)
    return `<Buffer>`;
  if (object instanceof Date)
    return object;
  if (typeof object === "object") {
    const result = {};
    for (const key in object) {
      if (key === "sha1" || key === "_sha1" || key.endsWith("Sha1")) {
        const sha1 = object[key];
        if (sha1)
          sha1s.add(sha1);
      }
      result[key] = visitTraceEvent(object[key], sha1s);
    }
    return result;
  }
  return object;
}
function createBeforeActionTraceEvent(metadata, parentId) {
  if (metadata.internal || metadata.method.startsWith("tracing"))
    return null;
  const event = {
    type: "before",
    callId: metadata.id,
    startTime: metadata.startTime,
    title: metadata.title,
    class: metadata.type,
    method: metadata.method,
    params: metadata.params,
    stepId: metadata.stepId,
    pageId: metadata.pageId
  };
  if (parentId)
    event.parentId = parentId;
  return event;
}
function createInputActionTraceEvent(metadata) {
  if (metadata.internal || metadata.method.startsWith("tracing"))
    return null;
  return {
    type: "input",
    callId: metadata.id,
    point: metadata.point
  };
}
function createActionLogTraceEvent(metadata, message) {
  if (metadata.internal || metadata.method.startsWith("tracing"))
    return null;
  return {
    type: "log",
    callId: metadata.id,
    time: (0, import_time.monotonicTime)(),
    message
  };
}
function createAfterActionTraceEvent(metadata) {
  if (metadata.internal || metadata.method.startsWith("tracing"))
    return null;
  return {
    type: "after",
    callId: metadata.id,
    endTime: metadata.endTime,
    error: metadata.error?.error,
    result: metadata.result,
    point: metadata.point
  };
}
const throttledRate = 200;
const unthrottleDuration = 500;
class ScreencastTracingRecorder {
  constructor(screencast, onFrame) {
    this._unthrottledUntil = 0;
    this._screencast = screencast;
    this._client = {
      onFrame: (frame) => {
        const time = (0, import_time.monotonicTime)();
        if (time < this._unthrottledUntil) {
          onFrame(frame);
          return;
        }
        if (this._pendingAck)
          return;
        onFrame(frame);
        this._pendingAck = new import_manualPromise.ManualPromise();
        this._timer = setTimeout(() => this._clearPendingAck(), throttledRate);
        return this._pendingAck;
      },
      gracefulClose: () => this.dispose(),
      dispose: () => this.dispose()
    };
    this._screencast.addClient(this._client);
  }
  dispose() {
    this._screencast.removeClient(this._client);
    this._clearPendingAck();
  }
  temporarilyDisableThrottling() {
    this._unthrottledUntil = (0, import_time.monotonicTime)() + unthrottleDuration;
    this._clearPendingAck();
  }
  _clearPendingAck() {
    this._pendingAck?.resolve();
    this._pendingAck = void 0;
    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = void 0;
    }
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  Tracing
});
