tor-browser

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

preview.js (7055B)


      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 import { isConsole } from "../utils/preview";
      6 import { getGrip, getFront } from "../utils/evaluation-result";
      7 
      8 import {
      9  isLineInScope,
     10  isSelectedFrameVisible,
     11  getSelectedSource,
     12  getSelectedLocation,
     13  getSelectedFrame,
     14  getCurrentThread,
     15  getSelectedException,
     16  getSelectedTraceIndex,
     17  getAllTraces,
     18 } from "../selectors/index";
     19 
     20 import { getMappedExpression } from "./expressions";
     21 const {
     22  TRACER_FIELDS_INDEXES,
     23 } = require("resource://devtools/server/actors/tracer.js");
     24 
     25 async function findExpressionMatches(state, editor, tokenPos) {
     26  const location = getSelectedLocation(state);
     27  if (!location) {
     28    return [];
     29  }
     30  return editor.findBestMatchExpressions(tokenPos);
     31 }
     32 
     33 /**
     34 * Get a preview object for the currently selected frame in the JS Tracer.
     35 *
     36 * @param {object} target
     37 *        The hovered DOM Element within CodeMirror rendering.
     38 * @param {object} tokenPos
     39 *        The CodeMirror position object for the hovered token.
     40 * @param {object} editor
     41 *        The CodeMirror editor object.
     42 */
     43 export function getTracerPreview(target, tokenPos, editor) {
     44  return async thunkArgs => {
     45    const { getState } = thunkArgs;
     46    const selectedTraceIndex = getSelectedTraceIndex(getState());
     47    if (selectedTraceIndex == null) {
     48      return null;
     49    }
     50 
     51    const trace = getAllTraces(getState())[selectedTraceIndex];
     52 
     53    // We may be selecting a mutation trace, which doesn't expose any value,
     54    // so only consider method calls.
     55    if (trace[TRACER_FIELDS_INDEXES.TYPE] != "enter") {
     56      return null;
     57    }
     58 
     59    const matches = await findExpressionMatches(getState(), editor, tokenPos);
     60    if (!matches.length) {
     61      return null;
     62    }
     63 
     64    let { expression, location } = matches[0];
     65    const source = getSelectedSource(getState());
     66    if (location && source.isOriginal) {
     67      const thread = getCurrentThread(getState());
     68      const mapResult = await getMappedExpression(
     69        expression,
     70        thread,
     71        thunkArgs
     72      );
     73      if (mapResult) {
     74        expression = mapResult.expression;
     75      }
     76    }
     77 
     78    const argumentValues = trace[TRACER_FIELDS_INDEXES.ENTER_ARGS];
     79    const argumentNames = trace[TRACER_FIELDS_INDEXES.ENTER_ARG_NAMES];
     80    if (!argumentNames || !argumentValues) {
     81      return null;
     82    }
     83 
     84    const argumentIndex = argumentNames.indexOf(expression);
     85    if (argumentIndex == -1) {
     86      return null;
     87    }
     88 
     89    const result = argumentValues[argumentIndex];
     90    // Values are either primitives, or an Object Front
     91    const resultGrip = result?.getGrip ? result?.getGrip() : result;
     92 
     93    const root = {
     94      // Force updating the ObjectInspector when hovering same-name variable on another trace.
     95      // See ObjectInspector.getNodeKey.
     96      path: `${selectedTraceIndex}-${expression}`,
     97      contents: {
     98        value: resultGrip,
     99        front: getFront(result),
    100      },
    101    };
    102    return {
    103      previewType: "tracer",
    104      target,
    105      tokenPos,
    106      cursorPos: target.getBoundingClientRect(),
    107      expression,
    108      root,
    109      resultGrip,
    110    };
    111  };
    112 }
    113 
    114 /**
    115 * Get a preview object for the currently paused frame, if paused.
    116 *
    117 * @param {object} target
    118 *        The hovered DOM Element within CodeMirror rendering.
    119 * @param {object} tokenPos
    120 *        The CodeMirror position object for the hovered token.
    121 * @param {object} editor
    122 *        The CodeMirror editor object.
    123 */
    124 export function getPausedPreview(target, tokenPos, editor) {
    125  return async thunkArgs => {
    126    const { getState, client } = thunkArgs;
    127    if (
    128      !isSelectedFrameVisible(getState()) ||
    129      !isLineInScope(getState(), tokenPos.line)
    130    ) {
    131      return null;
    132    }
    133 
    134    const source = getSelectedSource(getState());
    135    if (!source) {
    136      return null;
    137    }
    138    const thread = getCurrentThread(getState());
    139    const selectedFrame = getSelectedFrame(getState());
    140    if (!selectedFrame) {
    141      return null;
    142    }
    143    const matches = await findExpressionMatches(getState(), editor, tokenPos);
    144    if (!matches.length) {
    145      return null;
    146    }
    147 
    148    let { expression, location } = matches[0];
    149 
    150    if (isConsole(expression)) {
    151      return null;
    152    }
    153 
    154    if (location && source.isOriginal) {
    155      const mapResult = await getMappedExpression(
    156        expression,
    157        thread,
    158        thunkArgs
    159      );
    160      if (mapResult) {
    161        expression = mapResult.expression;
    162      }
    163    }
    164 
    165    const { result, hasException, exception } = await client.evaluate(
    166      expression,
    167      {
    168        frameId: selectedFrame.id,
    169      }
    170    );
    171 
    172    // The evaluation shouldn't return an exception.
    173    if (hasException) {
    174      const errorClass = exception?.getGrip()?.class || "Error";
    175      throw new Error(
    176        `Debugger internal exception: Preview for <${expression}> threw a ${errorClass}`
    177      );
    178    }
    179 
    180    const resultGrip = getGrip(result);
    181 
    182    // Error case occurs for a token that follows an errored evaluation
    183    // https://github.com/firefox-devtools/debugger/pull/8056
    184    // Accommodating for null allows us to show preview for falsy values
    185    // line "", false, null, Nan, and more
    186    if (resultGrip === null) {
    187      return null;
    188    }
    189 
    190    // Handle cases where the result is invisible to the debugger
    191    // and not possible to preview. Bug 1548256
    192    if (
    193      resultGrip &&
    194      resultGrip.class &&
    195      typeof resultGrip.class === "string" &&
    196      resultGrip.class.includes("InvisibleToDebugger")
    197    ) {
    198      return null;
    199    }
    200 
    201    const root = {
    202      path: expression,
    203      contents: {
    204        value: resultGrip,
    205        front: getFront(result),
    206      },
    207    };
    208 
    209    return {
    210      previewType: "pause",
    211      target,
    212      tokenPos,
    213      cursorPos: target.getBoundingClientRect(),
    214      expression,
    215      root,
    216      resultGrip,
    217    };
    218  };
    219 }
    220 
    221 export function getExceptionPreview(target, tokenPos, editor) {
    222  return async ({ getState }) => {
    223    const matches = await findExpressionMatches(getState(), editor, tokenPos);
    224    if (!matches.length) {
    225      return null;
    226    }
    227    let exception;
    228    // Lezer might return multiple matches in certain scenarios.
    229    // Example: For this expression `[].inlineException()` is likely to throw an exception,
    230    // but if the user hovers over `inlineException` lezer finds 2 matches :
    231    // 1) `inlineException` for `PropertyName`,
    232    // 2) `[].inlineException()` for `MemberExpression`
    233    // Babel seems to only include the `inlineException`.
    234    for (const match of matches) {
    235      const tokenColumnStart = match.location.start.column + 1;
    236      exception = getSelectedException(
    237        getState(),
    238        tokenPos.line,
    239        tokenColumnStart
    240      );
    241      if (exception) {
    242        break;
    243      }
    244    }
    245 
    246    if (!exception) {
    247      return null;
    248    }
    249 
    250    return {
    251      target,
    252      tokenPos,
    253      cursorPos: target.getBoundingClientRect(),
    254      exception,
    255    };
    256  };
    257 }