"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.ContextRecorder = void 0;
exports.generateFrameSelector = generateFrameSelector;
var _events = require("events");
var recorderSource = _interopRequireWildcard(require("../../generated/recorderSource"));
var _utils = require("../../utils");
var _timeoutRunner = require("../../utils/timeoutRunner");
var _browserContext = require("../browserContext");
var _languages = require("../codegen/languages");
var _frames = require("../frames");
var _page = require("../page");
var _recorderRunner = require("./recorderRunner");
var _throttledFile = require("./throttledFile");
var _recorderCollection = require("./recorderCollection");
var _language = require("../codegen/language");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

class ContextRecorder extends _events.EventEmitter {
  constructor(context, params, delegate) {
    super();
    this._collection = void 0;
    this._pageAliases = new Map();
    this._lastPopupOrdinal = 0;
    this._lastDialogOrdinal = -1;
    this._lastDownloadOrdinal = -1;
    this._timers = new Set();
    this._context = void 0;
    this._params = void 0;
    this._delegate = void 0;
    this._recorderSources = void 0;
    this._throttledOutputFile = null;
    this._orderedLanguages = [];
    this._listeners = [];
    this._context = context;
    this._params = params;
    this._delegate = delegate;
    this._recorderSources = [];
    const language = params.language || context.attribution.playwright.options.sdkLanguage;
    this.setOutput(language, params.outputFile);

    // Make a copy of options to modify them later.
    const languageGeneratorOptions = {
      browserName: context._browser.options.name,
      launchOptions: {
        headless: false,
        ...params.launchOptions
      },
      contextOptions: {
        ...params.contextOptions
      },
      deviceName: params.device,
      saveStorage: params.saveStorage
    };
    const collection = new _recorderCollection.RecorderCollection(params.mode === 'recording');
    collection.on('change', () => {
      this._recorderSources = [];
      for (const languageGenerator of this._orderedLanguages) {
        var _this$_throttledOutpu;
        const {
          header,
          footer,
          actionTexts,
          text
        } = (0, _language.generateCode)(collection.actions(), languageGenerator, languageGeneratorOptions);
        const source = {
          isRecorded: true,
          label: languageGenerator.name,
          group: languageGenerator.groupName,
          id: languageGenerator.id,
          text,
          header,
          footer,
          actions: actionTexts,
          language: languageGenerator.highlighter,
          highlight: []
        };
        source.revealLine = text.split('\n').length - 1;
        this._recorderSources.push(source);
        if (languageGenerator === this._orderedLanguages[0]) (_this$_throttledOutpu = this._throttledOutputFile) === null || _this$_throttledOutpu === void 0 || _this$_throttledOutpu.setContent(source.text);
      }
      this.emit(ContextRecorder.Events.Change, {
        sources: this._recorderSources,
        primaryFileName: this._orderedLanguages[0].id
      });
    });
    context.on(_browserContext.BrowserContext.Events.BeforeClose, () => {
      var _this$_throttledOutpu2;
      (_this$_throttledOutpu2 = this._throttledOutputFile) === null || _this$_throttledOutpu2 === void 0 || _this$_throttledOutpu2.flush();
    });
    this._listeners.push(_utils.eventsHelper.addEventListener(process, 'exit', () => {
      var _this$_throttledOutpu3;
      (_this$_throttledOutpu3 = this._throttledOutputFile) === null || _this$_throttledOutpu3 === void 0 || _this$_throttledOutpu3.flush();
    }));
    this._collection = collection;
  }
  setOutput(codegenId, outputFile) {
    var _this$_collection;
    const languages = (0, _languages.languageSet)();
    const primaryLanguage = [...languages].find(l => l.id === codegenId);
    if (!primaryLanguage) throw new Error(`\n===============================\nUnsupported language: '${codegenId}'\n===============================\n`);
    languages.delete(primaryLanguage);
    this._orderedLanguages = [primaryLanguage, ...languages];
    this._throttledOutputFile = outputFile ? new _throttledFile.ThrottledFile(outputFile) : null;
    (_this$_collection = this._collection) === null || _this$_collection === void 0 || _this$_collection.restart();
  }
  languageName(id) {
    for (const lang of this._orderedLanguages) {
      if (!id || lang.id === id) return lang.highlighter;
    }
    return 'javascript';
  }
  async install() {
    this._context.on(_browserContext.BrowserContext.Events.Page, page => this._onPage(page));
    for (const page of this._context.pages()) this._onPage(page);
    this._context.on(_browserContext.BrowserContext.Events.Dialog, dialog => this._onDialog(dialog.page()));

    // Input actions that potentially lead to navigation are intercepted on the page and are
    // performed by the Playwright.
    await this._context.exposeBinding('__pw_recorderPerformAction', false, (source, action) => this._performAction(source.frame, action));

    // Other non-essential actions are simply being recorded.
    await this._context.exposeBinding('__pw_recorderRecordAction', false, (source, action) => this._recordAction(source.frame, action));
    await this._context.extendInjectedScript(recorderSource.source);
  }
  setEnabled(enabled) {
    this._collection.setEnabled(enabled);
  }
  dispose() {
    for (const timer of this._timers) clearTimeout(timer);
    this._timers.clear();
    _utils.eventsHelper.removeEventListeners(this._listeners);
  }
  async _onPage(page) {
    // First page is called page, others are called popup1, popup2, etc.
    const frame = page.mainFrame();
    page.on('close', () => {
      this._collection.addAction({
        frame: this._describeMainFrame(page),
        committed: true,
        action: {
          name: 'closePage',
          signals: []
        }
      });
      this._pageAliases.delete(page);
    });
    frame.on(_frames.Frame.Events.InternalNavigation, event => {
      if (event.isPublic) this._onFrameNavigated(frame, page);
    });
    page.on(_page.Page.Events.Download, () => this._onDownload(page));
    const suffix = this._pageAliases.size ? String(++this._lastPopupOrdinal) : '';
    const pageAlias = 'page' + suffix;
    this._pageAliases.set(page, pageAlias);
    if (page.opener()) {
      this._onPopup(page.opener(), page);
    } else {
      this._collection.addAction({
        frame: this._describeMainFrame(page),
        committed: true,
        action: {
          name: 'openPage',
          url: page.mainFrame().url(),
          signals: []
        }
      });
    }
  }
  clearScript() {
    this._collection.restart();
    if (this._params.mode === 'recording') {
      for (const page of this._context.pages()) this._onFrameNavigated(page.mainFrame(), page);
    }
  }
  _describeMainFrame(page) {
    return {
      pageAlias: this._pageAliases.get(page),
      framePath: []
    };
  }
  async _describeFrame(frame) {
    return {
      pageAlias: this._pageAliases.get(frame._page),
      framePath: await generateFrameSelector(frame)
    };
  }
  testIdAttributeName() {
    return this._params.testIdAttributeName || this._context.selectors().testIdAttributeName() || 'data-testid';
  }
  async _performAction(frame, action) {
    var _this$_delegate$rewri, _this$_delegate;
    // Commit last action so that no further signals are added to it.
    this._collection.commitLastAction();
    const frameDescription = await this._describeFrame(frame);
    const actionInContext = {
      frame: frameDescription,
      action,
      description: undefined
    };
    await ((_this$_delegate$rewri = (_this$_delegate = this._delegate).rewriteActionInContext) === null || _this$_delegate$rewri === void 0 ? void 0 : _this$_delegate$rewri.call(_this$_delegate, this._pageAliases, actionInContext));
    this._collection.willPerformAction(actionInContext);
    const success = await (0, _recorderRunner.performAction)(this._pageAliases, actionInContext);
    if (success) {
      this._collection.didPerformAction(actionInContext);
      this._setCommittedAfterTimeout(actionInContext);
    } else {
      this._collection.performedActionFailed(actionInContext);
    }
  }
  async _recordAction(frame, action) {
    var _this$_delegate$rewri2, _this$_delegate2;
    // Commit last action so that no further signals are added to it.
    this._collection.commitLastAction();
    const frameDescription = await this._describeFrame(frame);
    const actionInContext = {
      frame: frameDescription,
      action,
      description: undefined
    };
    await ((_this$_delegate$rewri2 = (_this$_delegate2 = this._delegate).rewriteActionInContext) === null || _this$_delegate$rewri2 === void 0 ? void 0 : _this$_delegate$rewri2.call(_this$_delegate2, this._pageAliases, actionInContext));
    this._setCommittedAfterTimeout(actionInContext);
    this._collection.addAction(actionInContext);
  }
  _setCommittedAfterTimeout(actionInContext) {
    const timer = setTimeout(() => {
      // Commit the action after 5 seconds so that no further signals are added to it.
      actionInContext.committed = true;
      this._timers.delete(timer);
    }, (0, _utils.isUnderTest)() ? 500 : 5000);
    this._timers.add(timer);
  }
  _onFrameNavigated(frame, page) {
    const pageAlias = this._pageAliases.get(page);
    this._collection.signal(pageAlias, frame, {
      name: 'navigation',
      url: frame.url()
    });
  }
  _onPopup(page, popup) {
    const pageAlias = this._pageAliases.get(page);
    const popupAlias = this._pageAliases.get(popup);
    this._collection.signal(pageAlias, page.mainFrame(), {
      name: 'popup',
      popupAlias
    });
  }
  _onDownload(page) {
    const pageAlias = this._pageAliases.get(page);
    ++this._lastDownloadOrdinal;
    this._collection.signal(pageAlias, page.mainFrame(), {
      name: 'download',
      downloadAlias: this._lastDownloadOrdinal ? String(this._lastDownloadOrdinal) : ''
    });
  }
  _onDialog(page) {
    const pageAlias = this._pageAliases.get(page);
    ++this._lastDialogOrdinal;
    this._collection.signal(pageAlias, page.mainFrame(), {
      name: 'dialog',
      dialogAlias: this._lastDialogOrdinal ? String(this._lastDialogOrdinal) : ''
    });
  }
}
exports.ContextRecorder = ContextRecorder;
ContextRecorder.Events = {
  Change: 'change'
};
async function generateFrameSelector(frame) {
  const selectorPromises = [];
  while (frame) {
    const parent = frame.parentFrame();
    if (!parent) break;
    selectorPromises.push(generateFrameSelectorInParent(parent, frame));
    frame = parent;
  }
  const result = await Promise.all(selectorPromises);
  return result.reverse();
}
async function generateFrameSelectorInParent(parent, frame) {
  const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
    try {
      const frameElement = await frame.frameElement();
      if (!frameElement || !parent) return;
      const utility = await parent._utilityContext();
      const injected = await utility.injectedScript();
      const selector = await injected.evaluate((injected, element) => {
        return injected.generateSelectorSimple(element);
      }, frameElement);
      return selector;
    } catch (e) {
      return e.toString();
    }
  }, (0, _utils.monotonicTime)() + 2000);
  if (!result.timedOut && result.result) return result.result;
  if (frame.name()) return `iframe[name=${(0, _utils.quoteCSSAttributeValue)(frame.name())}]`;
  return `iframe[src=${(0, _utils.quoteCSSAttributeValue)(frame.url())}]`;
}