"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports._mergeLexicalScopesBindings = _mergeLexicalScopesBindings;
exports.getScopeItemPath = getScopeItemPath;
exports.getScopesItemsForSelectedFrame = getScopesItemsForSelectedFrame;

var objectInspector = _interopRequireWildcard(require("resource://devtools/client/shared/components/object-inspector/index.js"));

loader.lazyRequireGetter(this, "_index2", "devtools/client/debugger/src/utils/pause/frames/index");

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

/* 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 file contains utility functions which supports the structure & display of
// scopes information in Scopes panel.
const {
  utils: {
    node: {
      NODE_TYPES
    }
  }
} = objectInspector; // The heading that should be displayed for the scope

function _getScopeTitle(type, scope) {
  if (type === "block" && scope.block && scope.block.displayName) {
    return scope.block.displayName;
  }

  if (type === "function" && scope.function) {
    return scope.function.displayName ? (0, _index2.simplifyDisplayName)(scope.function.displayName) : L10N.getStr("anonymousFunction");
  }

  return L10N.getStr("scopes.block");
}

function _getThisVariable(this_, path) {
  if (!this_) {
    return null;
  }

  return {
    name: "<this>",
    path: `${path}/<this>`,
    contents: {
      value: this_
    }
  };
}
/**
 * Builds a tree of nodes representing all the variables and arguments
 * for the bindings from a scope.
 *
 * Each binding => { variables: Array, arguments: Array }
 * Each binding argument => [name: string, contents: BindingContents]
 *
 * @param {Array} bindings
 * @param {String} parentName
 * @returns
 */


function _getBindingVariables(bindings, parentName) {
  if (!bindings) {
    return [];
  }

  const nodes = [];

  const addNode = (name, contents) => nodes.push({
    name,
    contents,
    path: `${parentName}/${name}`
  });

  for (const arg of bindings.arguments) {
    // `arg` is an object which only has a single property whose name is the name of the
    // argument. So here we can directly pick the first (and only) entry of `arg`
    const [name, contents] = Object.entries(arg)[0];
    addNode(name, contents);
  }

  for (const name in bindings.variables) {
    addNode(name, bindings.variables[name]);
  }

  return nodes;
}
/**
 * This generates the scope item for rendering in the scopes panel.
 *
 * @param {*} scope
 * @param {*} selectedFrame
 * @param {*} frameScopes
 * @param {*} why
 * @param {*} scopeIndex
 * @returns
 */


function _getScopeItem(scope, selectedFrame, frameScopes, why, scopeIndex) {
  const {
    type,
    actor
  } = scope;
  const isLocalScope = scope.actor === frameScopes.actor;
  const key = `${actor}-${scopeIndex}`;

  if (type === "function" || type === "block") {
    const {
      bindings
    } = scope;

    let vars = _getBindingVariables(bindings, key); // show exception, return, and this variables in innermost scope


    if (isLocalScope) {
      vars = vars.concat(_getFrameExceptionOrReturnedValueVariables(why, key));
      let thisDesc_ = selectedFrame.this;

      if (bindings && "this" in bindings) {
        // The presence of "this" means we're rendering a "this" binding
        // generated from mapScopes and this can override the binding
        // provided by the current frame.
        thisDesc_ = bindings.this ? bindings.this.value : null;
      }

      const this_ = _getThisVariable(thisDesc_, key);

      if (this_) {
        vars.push(this_);
      }
    }

    if (vars?.length) {
      const title = _getScopeTitle(type, scope) || "";
      vars.sort((a, b) => a.name.localeCompare(b.name));
      return {
        name: title,
        path: key,
        contents: vars,
        type: NODE_TYPES.BLOCK
      };
    }
  } else if (type === "object" && scope.object) {
    let value = scope.object; // If this is the global window scope, mark it as such so that it will
    // preview Window: Global instead of Window: Window

    if (value.class === "Window") {
      value = { ...value,
        displayClass: "Global"
      };
    }

    return {
      name: scope.object.class,
      path: key,
      contents: {
        value
      }
    };
  }

  return null;
}
/**
 * Merge the scope bindings for lexical scopes and its parent function body scopes
 * Note: block scopes are not merged. See browser_dbg-merge-scopes.js for test examples
 * to better understand the scenario,
 *
 * @param {*} scope
 * @param {*} parentScope
 * @param {*} item
 * @param {*} parentItem
 * @returns
 */


function _mergeLexicalScopesBindings(scope, parentScope, item, parentItem) {
  if (scope.scopeKind == "function lexical" && parentScope.type == "function") {
    const contents = item.contents.concat(parentItem.contents);
    contents.sort((a, b) => a.name.localeCompare(b.name));
    return {
      name: parentItem.name,
      path: parentItem.path,
      contents,
      type: NODE_TYPES.BLOCK
    };
  }

  return null;
}
/**
 * Returns a string path for an scope item which can be used
 * in different pauses for a thread.
 *
 * @param {Object} item
 * @returns
 */


function getScopeItemPath(item) {
  // Calling toString() on item.path allows symbols to be handled.
  return item.path.toString();
} // Generate variables when the function throws an exception or returned a value.


function _getFrameExceptionOrReturnedValueVariables(why, path) {
  const vars = [];

  if (why && why.frameFinished) {
    const {
      frameFinished
    } = why; // Always display a `throw` property if present, even if it is falsy.

    if (Object.prototype.hasOwnProperty.call(frameFinished, "throw")) {
      vars.push({
        name: "<exception>",
        path: `${path}/<exception>`,
        contents: {
          value: frameFinished.throw
        }
      });
    }

    if (Object.prototype.hasOwnProperty.call(frameFinished, "return")) {
      const returned = frameFinished.return; // Do not display undefined. Do display falsy values like 0 and false. The
      // protocol grip for undefined is a JSON object: { type: "undefined" }.

      if (typeof returned !== "object" || returned.type !== "undefined") {
        vars.push({
          name: "<return>",
          path: `${path}/<return>`,
          contents: {
            value: returned
          }
        });
      }
    }
  }

  return vars;
}
/**
 * Generates the scope items (for scopes related to selected frame) to be rendered in the scope panel
 * @param {*} why
 * @param {*} selectedFrame
 * @param {*} frameScopes
 * @returns
 */


function getScopesItemsForSelectedFrame(why, selectedFrame, frameScopes) {
  if (!why || !selectedFrame) {
    return null;
  }

  if (!frameScopes) {
    return null;
  }

  const scopes = [];
  let currentScope = frameScopes;
  let currentScopeIndex = 1;
  let prevScope = null,
      prevScopeItem = null;

  while (currentScope) {
    let currentScopeItem = _getScopeItem(currentScope, selectedFrame, frameScopes, why, currentScopeIndex);

    if (currentScopeItem) {
      const mergedItem = prevScope && prevScopeItem ? _mergeLexicalScopesBindings(prevScope, currentScope, prevScopeItem, currentScopeItem) : null;

      if (mergedItem) {
        currentScopeItem = mergedItem;
        scopes.pop();
      }

      scopes.push(currentScopeItem);
    }

    prevScope = currentScope;
    prevScopeItem = currentScopeItem;
    currentScopeIndex++;
    currentScope = currentScope.parent;
  }

  return scopes;
}