tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

scopes.js (7373B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
      4 
      5 // This file contains utility functions which supports the structure & display of
      6 // scopes information in Scopes panel.
      7 
      8 import * as objectInspector from "resource://devtools/client/shared/components/object-inspector/index.js";
      9 import { simplifyDisplayName } from "../pause/frames/index";
     10 
     11 const {
     12  utils: {
     13    node: { NODE_TYPES },
     14  },
     15 } = objectInspector;
     16 
     17 // The heading that should be displayed for the scope
     18 function _getScopeTitle(type, scope) {
     19  if (type === "block" && scope.block && scope.block.displayName) {
     20    return scope.block.displayName;
     21  }
     22 
     23  if (type === "function" && scope.function) {
     24    return scope.function.displayName
     25      ? simplifyDisplayName(scope.function.displayName)
     26      : L10N.getStr("anonymousFunction");
     27  }
     28  return L10N.getStr("scopes.block");
     29 }
     30 
     31 function _getThisVariable(this_, path) {
     32  if (!this_) {
     33    return null;
     34  }
     35 
     36  return {
     37    name: "<this>",
     38    path: `${path}/<this>`,
     39    contents: { value: this_ },
     40  };
     41 }
     42 
     43 /**
     44 * Builds a tree of nodes representing all the variables and arguments
     45 * for the bindings from a scope.
     46 *
     47 * Each binding => { variables: Array, arguments: Array }
     48 * Each binding argument => [name: string, contents: BindingContents]
     49 *
     50 * @param {Array} bindings
     51 * @param {string} parentName
     52 * @returns
     53 */
     54 function _getBindingVariables(bindings, parentName) {
     55  if (!bindings) {
     56    return [];
     57  }
     58 
     59  const nodes = [];
     60  const addNode = (name, contents) =>
     61    nodes.push({ name, contents, path: `${parentName}/${name}` });
     62 
     63  for (const arg of bindings.arguments) {
     64    // `arg` is an object which only has a single property whose name is the name of the
     65    // argument. So here we can directly pick the first (and only) entry of `arg`
     66    const [name, contents] = Object.entries(arg)[0];
     67    addNode(name, contents);
     68  }
     69 
     70  for (const name in bindings.variables) {
     71    addNode(name, bindings.variables[name]);
     72  }
     73 
     74  return nodes;
     75 }
     76 
     77 /**
     78 * This generates the scope item for rendering in the scopes panel.
     79 *
     80 * @param {*} scope
     81 * @param {*} selectedFrame
     82 * @param {*} frameScopes
     83 * @param {*} why
     84 * @param {*} scopeIndex
     85 * @returns
     86 */
     87 function _getScopeItem(scope, selectedFrame, frameScopes, why, scopeIndex) {
     88  const { type, actor } = scope;
     89 
     90  const isLocalScope = scope.actor === frameScopes.actor;
     91 
     92  const key = `${actor}-${scopeIndex}`;
     93  if (type === "function" || type === "block") {
     94    const { bindings } = scope;
     95 
     96    let vars = _getBindingVariables(bindings, key);
     97 
     98    // show exception, return, and this variables in innermost scope
     99    if (isLocalScope) {
    100      vars = vars.concat(_getFrameExceptionOrReturnedValueVariables(why, key));
    101 
    102      let thisDesc_ = selectedFrame.this;
    103 
    104      if (bindings && "this" in bindings) {
    105        // The presence of "this" means we're rendering a "this" binding
    106        // generated from mapScopes and this can override the binding
    107        // provided by the current frame.
    108        thisDesc_ = bindings.this ? bindings.this.value : null;
    109      }
    110 
    111      const this_ = _getThisVariable(thisDesc_, key);
    112 
    113      if (this_) {
    114        vars.push(this_);
    115      }
    116    }
    117 
    118    if (vars?.length) {
    119      const title = _getScopeTitle(type, scope) || "";
    120      vars.sort((a, b) => a.name.localeCompare(b.name));
    121      return {
    122        name: title,
    123        path: key,
    124        contents: vars,
    125        type: NODE_TYPES.BLOCK,
    126      };
    127    }
    128  } else if (type === "object" && scope.object) {
    129    let value = scope.object;
    130    // If this is the global window scope, mark it as such so that it will
    131    // preview Window: Global instead of Window: Window
    132    if (value.class === "Window") {
    133      value = { ...value, displayClass: "Global" };
    134    }
    135    return {
    136      name: scope.object.class,
    137      path: key,
    138      contents: { value },
    139    };
    140  }
    141 
    142  return null;
    143 }
    144 /**
    145 * Merge the scope bindings for lexical scopes and its parent function body scopes
    146 * Note: block scopes are not merged. See browser_dbg-merge-scopes.js for test examples
    147 * to better understand the scenario,
    148 *
    149 * @param {*} scope
    150 * @param {*} parentScope
    151 * @param {*} item
    152 * @param {*} parentItem
    153 * @returns
    154 */
    155 export function _mergeLexicalScopesBindings(
    156  scope,
    157  parentScope,
    158  item,
    159  parentItem
    160 ) {
    161  if (scope.scopeKind == "function lexical" && parentScope.type == "function") {
    162    const contents = item.contents.concat(parentItem.contents);
    163    contents.sort((a, b) => a.name.localeCompare(b.name));
    164 
    165    return {
    166      name: parentItem.name,
    167      path: parentItem.path,
    168      contents,
    169      type: NODE_TYPES.BLOCK,
    170    };
    171  }
    172  return null;
    173 }
    174 
    175 /**
    176 * Returns a string path for an scope item which can be used
    177 * in different pauses for a thread.
    178 *
    179 * @param {object} item
    180 * @returns
    181 */
    182 
    183 export function getScopeItemPath(item) {
    184  // Calling toString() on item.path allows symbols to be handled.
    185  return item.path.toString();
    186 }
    187 
    188 // Generate variables when the function throws an exception or returned a value.
    189 function _getFrameExceptionOrReturnedValueVariables(why, path) {
    190  const vars = [];
    191 
    192  if (why && why.frameFinished) {
    193    const { frameFinished } = why;
    194 
    195    // Always display a `throw` property if present, even if it is falsy.
    196    if (Object.prototype.hasOwnProperty.call(frameFinished, "throw")) {
    197      vars.push({
    198        name: "<exception>",
    199        path: `${path}/<exception>`,
    200        contents: { value: frameFinished.throw },
    201      });
    202    }
    203 
    204    if (Object.prototype.hasOwnProperty.call(frameFinished, "return")) {
    205      const returned = frameFinished.return;
    206 
    207      // Do not display undefined. Do display falsy values like 0 and false. The
    208      // protocol grip for undefined is a JSON object: { type: "undefined" }.
    209      if (typeof returned !== "object" || returned.type !== "undefined") {
    210        vars.push({
    211          name: "<return>",
    212          path: `${path}/<return>`,
    213          contents: { value: returned },
    214        });
    215      }
    216    }
    217  }
    218 
    219  return vars;
    220 }
    221 
    222 /**
    223 * Generates the scope items (for scopes related to selected frame) to be rendered in the scope panel
    224 *
    225 * @param {*} why
    226 * @param {*} selectedFrame
    227 * @param {*} frameScopes
    228 * @returns
    229 */
    230 export function getScopesItemsForSelectedFrame(
    231  why,
    232  selectedFrame,
    233  frameScopes
    234 ) {
    235  if (!why || !selectedFrame) {
    236    return null;
    237  }
    238 
    239  if (!frameScopes) {
    240    return null;
    241  }
    242 
    243  const scopes = [];
    244 
    245  let currentScope = frameScopes;
    246  let currentScopeIndex = 1;
    247 
    248  let prevScope = null,
    249    prevScopeItem = null;
    250 
    251  while (currentScope) {
    252    let currentScopeItem = _getScopeItem(
    253      currentScope,
    254      selectedFrame,
    255      frameScopes,
    256      why,
    257      currentScopeIndex
    258    );
    259 
    260    if (currentScopeItem) {
    261      const mergedItem =
    262        prevScope && prevScopeItem
    263          ? _mergeLexicalScopesBindings(
    264              prevScope,
    265              currentScope,
    266              prevScopeItem,
    267              currentScopeItem
    268            )
    269          : null;
    270      if (mergedItem) {
    271        currentScopeItem = mergedItem;
    272        scopes.pop();
    273      }
    274      scopes.push(currentScopeItem);
    275    }
    276 
    277    prevScope = currentScope;
    278    prevScopeItem = currentScopeItem;
    279    currentScopeIndex++;
    280    currentScope = currentScope.parent;
    281  }
    282 
    283  return scopes;
    284 }