tor-browser

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

expressions.js (6414B)


      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 {
      6  getExpression,
      7  getExpressions,
      8  getSelectedSource,
      9  getSelectedScopeMappings,
     10  getSelectedFrameBindings,
     11  getIsPaused,
     12  getSelectedFrame,
     13  getCurrentThread,
     14  isMapScopesEnabled,
     15 } from "../selectors/index";
     16 const {
     17  PROMISE,
     18 } = require("resource://devtools/client/shared/redux/middleware/promise.js");
     19 import { wrapExpression } from "../utils/expressions";
     20 import { features } from "../utils/prefs";
     21 
     22 /**
     23 * Add expression for debugger to watch
     24 *
     25 * @param {string} input
     26 */
     27 export function addExpression(input) {
     28  return async ({ dispatch, getState }) => {
     29    if (!input) {
     30      return null;
     31    }
     32 
     33    // If the expression already exists, only update its evaluation
     34    let expression = getExpression(getState(), input);
     35    if (!expression) {
     36      // This will only display the expression input,
     37      // evaluateExpression will update its value.
     38      dispatch({ type: "ADD_EXPRESSION", input });
     39 
     40      expression = getExpression(getState(), input);
     41      // When there is an expression error, we won't store the expression
     42      if (!expression) {
     43        return null;
     44      }
     45    }
     46 
     47    return dispatch(evaluateExpression(expression));
     48  };
     49 }
     50 
     51 export function autocomplete(input, cursor) {
     52  return async ({ dispatch, getState, client }) => {
     53    if (!input) {
     54      return;
     55    }
     56    const thread = getCurrentThread(getState());
     57    const selectedFrame = getSelectedFrame(getState());
     58    const result = await client.autocomplete(input, cursor, selectedFrame?.id);
     59    // Pass both selectedFrame and thread in case selectedFrame is null
     60    dispatch({ type: "AUTOCOMPLETE", selectedFrame, thread, input, result });
     61  };
     62 }
     63 
     64 export function clearAutocomplete() {
     65  return { type: "CLEAR_AUTOCOMPLETE" };
     66 }
     67 
     68 export function updateExpression(input, expression) {
     69  return async ({ dispatch }) => {
     70    if (!input) {
     71      return;
     72    }
     73 
     74    dispatch({
     75      type: "UPDATE_EXPRESSION",
     76      expression,
     77      input,
     78    });
     79 
     80    await dispatch(evaluateExpressionsForCurrentContext());
     81  };
     82 }
     83 
     84 /**
     85 *
     86 * @param {object} expression
     87 * @param {string} expression.input
     88 */
     89 export function deleteExpression(expression) {
     90  return {
     91    type: "DELETE_EXPRESSION",
     92    input: expression.input,
     93  };
     94 }
     95 
     96 export function evaluateExpressionsForCurrentContext() {
     97  return async ({ getState, dispatch }) => {
     98    const selectedFrame = getSelectedFrame(getState());
     99    await dispatch(evaluateExpressions(selectedFrame));
    100  };
    101 }
    102 
    103 /**
    104 * Update all the expressions by querying the server for updated values.
    105 *
    106 * @param {object} selectedFrame
    107 *        If defined, will evaluate the expression against this given frame,
    108 *        otherwise it will use the global scope of the thread.
    109 */
    110 export function evaluateExpressions(selectedFrame) {
    111  return async function ({ dispatch, getState, client }) {
    112    const expressions = getExpressions(getState());
    113    const inputs = expressions.map(({ input }) => input);
    114    // Fallback to global scope of the current thread when selectedFrame is null
    115    const thread = selectedFrame?.thread || getCurrentThread(getState());
    116    const results = await client.evaluateExpressions(inputs, {
    117      // We will only have a specific frame when passing a Selected frame context.
    118      frameId: selectedFrame?.id,
    119      threadId: thread,
    120    });
    121    // Pass both selectedFrame and thread in case selectedFrame is null
    122    dispatch({
    123      type: "EVALUATE_EXPRESSIONS",
    124 
    125      selectedFrame,
    126      // As `selectedFrame` can be null, pass `thread` to help
    127      // the reducer know what is the related thread of this action.
    128      thread,
    129 
    130      inputs,
    131      results,
    132    });
    133  };
    134 }
    135 
    136 function evaluateExpression(expression) {
    137  return async function (thunkArgs) {
    138    let { input } = expression;
    139    if (!input) {
    140      console.warn("Expressions should not be empty");
    141      return null;
    142    }
    143 
    144    const { dispatch, getState, client } = thunkArgs;
    145    const thread = getCurrentThread(getState());
    146    const selectedFrame = getSelectedFrame(getState());
    147 
    148    const selectedSource = getSelectedSource(getState());
    149    // Only map when we are paused and if the currently selected source is original,
    150    // and the paused location is also original.
    151    if (
    152      selectedFrame &&
    153      selectedSource &&
    154      selectedFrame.location.source.isOriginal &&
    155      selectedSource.isOriginal
    156    ) {
    157      const mapResult = await getMappedExpression(
    158        input,
    159        selectedFrame.thread,
    160        thunkArgs
    161      );
    162      if (mapResult) {
    163        input = mapResult.expression;
    164      }
    165    }
    166 
    167    // Pass both selectedFrame and thread in case selectedFrame is null
    168    return dispatch({
    169      type: "EVALUATE_EXPRESSION",
    170      selectedFrame,
    171      // When we aren't passing a frame, we have to pass a thread to the pause reducer
    172      thread: selectedFrame ? null : thread,
    173      input: expression.input,
    174      [PROMISE]: client.evaluate(wrapExpression(input), {
    175        // When evaluating against the global scope (when not paused)
    176        // frameId will be null here.
    177        frameId: selectedFrame?.id,
    178      }),
    179    });
    180  };
    181 }
    182 
    183 /**
    184 * Gets information about original variable names from the source map
    185 * and replaces all possible generated names.
    186 */
    187 export function getMappedExpression(expression, thread, thunkArgs) {
    188  const { getState, parserWorker } = thunkArgs;
    189  const mappings = getSelectedScopeMappings(getState(), thread);
    190  const bindings = getSelectedFrameBindings(getState(), thread);
    191 
    192  // We bail early if we do not need to map the expression. This is important
    193  // because mapping an expression can be slow if the parserWorker
    194  // worker is busy doing other work.
    195  //
    196  // 1. there are no mappings - we do not need to map original expressions
    197  // 2. does not contain `await` - we do not need to map top level awaits
    198  // 3. does not contain `=` - we do not need to map assignments
    199  const shouldMapScopes = isMapScopesEnabled(getState()) && mappings;
    200  if (!shouldMapScopes && !expression.match(/(await|=)/)) {
    201    return null;
    202  }
    203 
    204  return parserWorker.mapExpression(
    205    expression,
    206    mappings,
    207    bindings || [],
    208    features.mapExpressionBindings && getIsPaused(getState(), thread),
    209    features.mapAwaitExpression
    210  );
    211 }