"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.setupCreate = setupCreate;
exports.createFrame = createFrame;
exports.createWasmOriginalFrame = createWasmOriginalFrame;
exports.waitForSourceToBeRegisteredInStore = waitForSourceToBeRegisteredInStore;
exports.makeScriptSourceId = makeScriptSourceId;
exports.makeStyleSheetSourceId = makeStyleSheetSourceId;
exports.createGeneratedSource = createGeneratedSource;
exports.createStyleSheet = createStyleSheet;
exports.createSourceMapOriginalSource = createSourceMapOriginalSource;
exports.createPrettyPrintOriginalSource = createPrettyPrintOriginalSource;
exports.createScriptSourceActor = createScriptSourceActor;
exports.createStyleSheetActor = createStyleSheetActor;
exports.createPause = createPause;
exports.createThread = createThread;
exports.createBreakpoint = createBreakpoint;
loader.lazyRequireGetter(this, "_index", "devtools/client/debugger/src/selectors/index");
loader.lazyRequireGetter(this, "_prefs", "devtools/client/debugger/src/utils/prefs");
loader.lazyRequireGetter(this, "_source", "devtools/client/debugger/src/utils/source");
loader.lazyRequireGetter(this, "_location", "devtools/client/debugger/src/utils/location");
loader.lazyRequireGetter(this, "_getURL", "devtools/client/debugger/src/utils/sources-tree/getURL");

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
// This module converts Firefox specific types to the generic types
const ResourceCommand = require("resource://devtools/shared/commands/resource/resource-command.js");

let store;
/**
 * This function is to be called first before any other
 * and allow having access to any instances of classes that are
 * useful for this module
 *
 * @param {object} dependencies
 * @param {object} dependencies.store
 *                 The redux store object of the debugger frontend.
 */

function setupCreate(dependencies) {
  store = dependencies.store;
}

async function createFrame(thread, frame, index = 0) {
  // Because of throttling, the source related to the top frame may be available a bit late.
  const sourceActor = await waitForSourceActorToBeRegisteredInStore(frame.where.actor);
  const location = (0, _location.createLocation)({
    source: sourceActor.sourceObject,
    sourceActor,
    line: frame.where.line,
    column: frame.where.column
  });
  return {
    id: frame.actorID,
    thread,
    displayName: frame.displayName,
    location,
    generatedLocation: location,
    this: frame.this,
    index,
    asyncCause: frame.asyncCause,
    state: frame.state,
    type: frame.type
  };
}
/**
 * Create an original frame object. Similar to the frame objects generated by createFrame.
 * But with additional information related to SourceMap processing.
 * For example, location and generatedLocation will be different.
 *
 * @param {Function} getState
 * @param {object} frame
 *        The frame for the generated location, i.e. WASM binary code.
 * @param {string} id
 *        The new ID to use for the new frame object.
 * @param {object} originalFrame
 *        An object crafted by the SourceMap Worker with additional information
 *        about the original source code. i.e. the Rust, C++, whatever original source code.
 *        See XScope.search() for definition of this object.
 */


function createWasmOriginalFrame(generatedFrame, id, originalFrame, originalFrameLocation) {
  return {
    id,
    thread: generatedFrame.thread,
    displayName: originalFrame.displayName,
    location: originalFrameLocation,
    generatedLocation: generatedFrame.generatedLocation,
    this: generatedFrame.this,
    index: generatedFrame.index,
    asyncCause: generatedFrame.asyncCause,
    state: generatedFrame.state,
    type: generatedFrame.type,
    // All additional fields only available for WASM original frames
    isOriginal: true,
    originalDisplayName: originalFrame.displayName,
    originalVariables: originalFrame.variables
  };
}
/**
 * This method wait for the given source actor to be registered in Redux store.
 *
 * @param {string} sourceActorId
 *                 Actor ID of the source to be waiting for.
 */


async function waitForSourceActorToBeRegisteredInStore(sourceActorId) {
  if (!(0, _index.hasSourceActor)(store.getState(), sourceActorId)) {
    await new Promise(resolve => {
      const unsubscribe = store.subscribe(check);
      let currentSize = null;

      function check() {
        const previousSize = currentSize;
        currentSize = store.getState().sourceActors.mutableSourceActors.size; // For perf reason, avoid any extra computation if sources did not change

        if (previousSize == currentSize) {
          return;
        }

        if ((0, _index.hasSourceActor)(store.getState(), sourceActorId)) {
          unsubscribe();
          resolve();
        }
      }
    });
  }

  return (0, _index.getSourceActor)(store.getState(), sourceActorId);
}
/**
 * This method wait for the given source to be registered in Redux store.
 *
 * @param {string} sourceId
 *                 The id of the source to be waiting for.
 */


async function waitForSourceToBeRegisteredInStore(sourceId) {
  return new Promise(resolve => {
    if ((0, _index.hasSource)(store.getState(), sourceId)) {
      resolve();
      return;
    }

    const unsubscribe = store.subscribe(check);
    let currentSize = null;

    function check() {
      const previousSize = currentSize;
      currentSize = (0, _index.getSourceCount)(store.getState()); // For perf reason, avoid any extra computation if sources did not change

      if (previousSize == currentSize) {
        return;
      }

      if ((0, _index.hasSource)(store.getState(), sourceId)) {
        unsubscribe();
        resolve();
      }
    }
  });
} // Compute the reducer's source ID for a given source front/resource.
//
// We have four kind of "sources":
//   * "sources" in sources.js reducer, which map to 1 or many:
//   * "source actors" in source-actors.js reducer, which map 1 for 1 with:
//   * "SOURCE" resources coming from ResourceCommand API
//   * SourceFront, which are retrieved via `ThreadFront.source(sourceResource)`
//
// Note that SOURCE resources are actually the "form" of the SourceActor,
// with the addition of `resourceType` and `targetFront` attributes.
//
// Unfortunately, the debugger frontend interacts with these 4 type of objects.
// The last three actually try to represent the exact same thing.
//
// Here this method received a SOURCE resource (the 3rd bullet point)


function makeScriptSourceId(sourceResource) {
  // Allows Jest to use custom, simplier IDs
  if ("mockedJestID" in sourceResource) {
    return sourceResource.mockedJestID;
  } // By default, within a given target, all sources will be grouped by URL.
  // You will be having a unique Source object in sources.js reducer,
  // while you might have many Source Actor objects in source-actors.js reducer.
  //
  // There is two distinct usecases here:
  // * HTML pages, which will have one source object which represents the whole HTML page
  //   and it will relate to many source actors. One for each inline <script> tag.
  //   Each script tag's source actor will actually return the whole content of the html page
  //   and not only this one script tag content.
  // * Scripts with the same URL injected many times.
  //   For example, two <script src=""> with the same location
  //   Or by using eval("...// # SourceURL=")
  //   All the scripts will be grouped under a unique Source object, while having dedicated
  //   Source Actor objects.
  //   An important point this time is that each actor may have a different source text content.
  //   For now, the debugger arbitrarily picks the first source actor's text content and never
  //   updates it. (See bug 1751063)


  if (sourceResource.url) {
    return `source-url-${sourceResource.url}`;
  } // Otherwise, we are processing a source without URL.
  // This is typically evals, console evaluations, setTimeout/setInterval strings,
  // DOM event handler strings (i.e. `<div onclick="foo">`), ...
  // The main way to interact with them is to use a debugger statement from them,
  // or have other panels ask the debugger to open them (like DOM event handlers from the inspector).
  // We can register transient breakpoints against them (i.e. they will only apply to the current source actor instance)


  return `source-actor-${sourceResource.actor}`;
}

function makeStyleSheetSourceId(sourceResource) {
  return `source-url-${sourceResource.href}`;
}
/**
 * Create the source object for a generated source that is stored in the sources.js reducer.
 * These generated sources relate to JS code which run in the
 * debugged runtime (as oppose to original sources
 * which are only available in debugger's environment).
 *
 * @param {SOURCE} sourceResource
 *        SOURCE resource coming from the ResourceCommand API.
 *        This represents the `SourceActor` from the server codebase.
 */


function createGeneratedSource(sourceResource) {
  return { ...createSourceObject({
      id: makeScriptSourceId(sourceResource),
      url: sourceResource.url,
      extensionName: sourceResource.extensionName,
      isExtension: sourceResource.url && (0, _source.isUrlExtension)(sourceResource.url) || false
    }),
    // Script specific properties
    isWasm: !!_prefs.features.wasm && sourceResource.introductionType === "wasm",
    isHTML: !!sourceResource.isInlineSource,
    type: ResourceCommand.TYPES.SOURCE
  };
}
/**
 * Create the source object for a stylesheet source that is stored in the sources.js reducer.
 * These sources relate to CSS
 *
 * @param {*} stylesheetResource
 * @returns
 */


function createStyleSheet(stylesheetResource) {
  return { ...createSourceObject({
      id: makeStyleSheetSourceId(stylesheetResource),
      url: stylesheetResource.href
    }),
    // Stylesheet specific properties
    isStyleSheet: true,
    type: ResourceCommand.TYPES.STYLESHEET
  };
}
/**
 * Create the source object that is stored in sources.js reducer.
 *
 * This is an internal helper to this module to ensure all sources have the same shape.
 * Do not use it outside of this module!
 */


function createSourceObject({
  id,
  url,
  extensionName = null,
  isWasm = false,
  isExtension = false,
  isPrettyPrinted = false,
  isOriginal = false,
  isHTML = false,
  isStyleSheet = false,
  generatedSource = null,
  type
}) {
  // Ensure removing the internal :formatted suffix for the display URL object.
  const displayURL = (0, _getURL.getDisplayURL)(isPrettyPrinted ? url.replace(/:formatted$/, "") : url, extensionName);
  return {
    // The ID, computed by:
    // * `makeScriptSourceId` for generated,
    // * `generatedToOriginalId` for both source map and pretty printed original,
    id,
    // Absolute URL for the source. This may be a fake URL for pretty printed sources
    url,
    // A (slightly tweaked) URL object to represent the source URL.
    // The URL object is augmented of a "group" attribute and some other standard attributes
    // are modified from their typical value. See getDisplayURL implementation.
    displayURL,
    // Short label for this source.
    //
    // * For inlined/eval sources without a URL, the name will refer to the internal source ID,
    // * For pretty printed source, we take care to ignore the internal ":formatted" suffix used in the URL,
    // * For index files, i.e. sources loaded without a filename, they will be named "(index)".
    // * Special characters are decoded from the URL string.
    // (most of this is done by getDisplayURL)
    shortName: url ? (0, _source.getRawSourceURL)(displayURL.filename) : (0, _source.getFormattedSourceId)(id),
    // Same as short name, but with the query parameters.
    longName: url ? (0, _source.getRawSourceURL)(displayURL.filename + displayURL.search) : (0, _source.getFormattedSourceId)(id),
    // Only set for generated sources that are WebExtension sources.
    // This is especially useful to display the extension name for content scripts
    // that executes against the page we are debugging.
    extensionName,
    // Will be true if the source URL starts with moz-extension://,
    // which most likely means the source is a content script.
    // (Note that when debugging an add-on all generated sources will most likely have this flag set to true)
    isExtension,
    // True if WASM is enabled *and* the generated source is a WASM source (This is only used for script sources)
    isWasm,
    // True if this source is an HTML and relates to many sources actors,
    // one for each of its inline <script> (This is only used for script sources)
    isHTML,
    // True, if this is an original pretty printed source
    isPrettyPrinted,
    // True for source map original files, as well as pretty printed sources
    isOriginal,
    // True for only style sheet sources,
    isStyleSheet,
    // If this is an original/pretty printed source, reference to the related generated/minimized source
    generatedSource,
    // This property defines the type of source object
    type
  };
}
/**
 * Create the source object for a source mapped original source that is stored in sources.js reducer.
 * These original sources referred to by source maps.
 * This isn't code that runs in the runtime, so it isn't associated with anything
 * on the server side. It is associated with a generated source for the related bundle file
 * which itself relates to an actual code that runs in the runtime.
 *
 * @param {string} id
 *        The ID of the source, computed by source map codebase.
 * @param {string} url
 *        The URL of the original source file.
 * @param {object} generatedSource
 *        The Source object for the related generated source this original source maps to.
 */


function createSourceMapOriginalSource(id, url, generatedSource) {
  return { ...createSourceObject({
      id,
      url,
      isOriginal: true,
      generatedSource
    }),
    type: ResourceCommand.TYPES.SOURCE
  };
}
/**
 * Create the source object for a pretty printed original source that is stored in sources.js reducer.
 * These original pretty printed sources aren't code that run in the runtime,
 * so it isn't associated with anything on the server side.
 * It is associated with a generated source for the non-pretty-printed file
 * which itself relates to an actual code that runs in the runtime.
 *
 * @param {string} id
 *        The ID of the source, computed by pretty print.
 * @param {string} url
 *        The URL of the pretty-printed source file.
 *        This URL doesn't work. It is the URL of the non-pretty-printed file with ":formated" suffix.
 * @param {object} generatedSource
 *        The Source object for the related minimized source that related to this pretty printed source.
 */


function createPrettyPrintOriginalSource(id, url, generatedSource) {
  return { ...createSourceObject({
      id,
      url,
      isOriginal: true,
      isPrettyPrinted: true,
      generatedSource
    }),
    type: ResourceCommand.TYPES.SOURCE
  };
}
/**
 * Create the "source actor" object that is stored in source-actor.js reducer.
 * This will represent server's source actor in the reducer universe.
 *
 * @param {SOURCE} sourceResource
 *        SOURCE resource coming from the ResourceCommand API.
 *        This represents the `SourceActor` from the server codebase.
 * @param {object} sourceObject
 *        Source object stored in redux, i.e. created via createSourceObject.
 */


function createScriptSourceActor(sourceResource, sourceObject) {
  const actorId = sourceResource.actor;
  return {
    id: actorId,
    actor: actorId,
    // As sourceResource is only SourceActor's form and not the SourceFront,
    // we have to go through the target to retrieve the related ThreadActor's ID.
    // TODO: Remove this when we move to depend on targets. See Bug 2041750
    thread: sourceResource.targetFront.getCachedFront("thread").actorID,
    targetFront: sourceResource.targetFront,
    sourceObject,
    sourceMapBaseURL: sourceResource.sourceMapBaseURL,
    sourceMapURL: sourceResource.sourceMapURL,
    introductionType: sourceResource.introductionType,
    sourceStartLine: sourceResource.sourceStartLine,
    sourceStartColumn: sourceResource.sourceStartColumn,
    sourceLength: sourceResource.sourceLength
  };
}
/**
 * Creates a source actor object for a syle sheet that is stored in source-actor.js reducer.
 * This will represent server's source actor in the reducer universe.
 *
 * @param {STYLESHEET} stylesheetResource
 *        STYLESHEET resource coming from the ResourceCommand API.
 *        This represents the `StyleSheetsActor` from the server codebase.
 * @param {object} styleSheetObject
 *        style sheet object stored in redux, i.e. created via createSourceObject.
 */


function createStyleSheetActor(stylesheetResource, styleSheetObject) {
  return {
    id: stylesheetResource.resourceId,
    actor: stylesheetResource.resourceId,
    targetFront: stylesheetResource.targetFront,
    // TODO: Remove this when we move to depend on targets. See Bug 2041750
    thread: stylesheetResource.targetFront.getCachedFront("thread").actorID,
    // `source` is the reducer source ID
    source: makeStyleSheetSourceId(stylesheetResource),
    sourceObject: styleSheetObject,
    sourceMapBaseURL: stylesheetResource.sourceMapBaseURL,
    sourceMapURL: stylesheetResource.sourceMapURL,
    // There is no `url` for stylesheets, lets use the href
    url: stylesheetResource.href
  };
}
/**
 * Create a pause info object passed to paused action from
 * the THREAD_STATE "paused" resource.
 */


async function createPause(threadActorID, pausedThreadState) {
  const frame = await createFrame(threadActorID, pausedThreadState.frame);
  return {
    thread: threadActorID,
    frame,
    why: pausedThreadState.why
  };
}

function createThread(targetFront) {
  // When debugging a Web Extension, the top level target is always the fallback document.
  // It isn't really a top level document as it won't be the parent of any other.
  // So only print its name.
  const name = targetFront.isTopLevel && !targetFront.commands.descriptorFront.isWebExtension ? L10N.getStr("mainThread") : targetFront.name;
  return {
    actor: targetFront.targetForm.threadActor,
    url: targetFront.url,
    isTopLevel: targetFront.isTopLevel,
    targetType: targetFront.targetType,
    name,
    serviceWorkerStatus: targetFront.debuggerServiceWorkerStatus,
    processID: targetFront.processID,
    innerWindowId: targetFront.innerWindowId
  };
}
/**
 * Defines the shape of a breakpoint
 */


function createBreakpoint({
  id,
  thread,
  disabled = false,
  options = {},
  location,
  generatedLocation,
  text,
  originalText
}) {
  return {
    // The unique identifier (string) for the breakpoint, for details on its format and creation See `makeBreakpointId`
    id,
    // The thread actor id (string) which the source this breakpoint is created in belongs to
    thread,
    // This (boolean) specifies if the breakpoint is disabled or not
    disabled,
    // This (object) stores extra information about the breakpoint, which defines the type of the breakpoint (i.e conditional breakpoints, log points)
    // {
    //    condition: <Boolean>,
    //    logValue: <String>,
    //    hidden: <Boolean>
    // }
    options,
    // The location (object) information for the original source, for details on its format and structure See `createLocation`
    location,
    // The location (object) information for the generated source, for details on its format and structure See `createLocation`
    generatedLocation,
    // The text (string) on the line which the brekpoint is set in the generated source
    text,
    // The text (string) on the line which the breakpoint is set in the original source
    originalText
  };
}