tor-browser

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

inlinePreview.js (7110B)


      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  getSelectedFrameInlinePreviews,
      7  getSelectedLocation,
      8  getSelectedScope,
      9 } from "../../selectors/index";
     10 import { features } from "../../utils/prefs";
     11 import { getEditor } from "../../utils/editor/index";
     12 import { validateSelectedFrame } from "../../utils/context";
     13 
     14 /**
     15 * Update the inline previews for the currently selected frame.
     16 */
     17 export function generateInlinePreview(selectedFrame) {
     18  return async function (thunkArgs) {
     19    const { dispatch, getState } = thunkArgs;
     20    if (!features.inlinePreview) {
     21      return null;
     22    }
     23 
     24    // Avoid regenerating inline previews when we already have preview data
     25    if (getSelectedFrameInlinePreviews(getState())) {
     26      return null;
     27    }
     28 
     29    const scope = getSelectedScope(getState());
     30    if (!scope || !scope.bindings) {
     31      return null;
     32    }
     33 
     34    const allPreviews = await getPreviews(selectedFrame, scope, thunkArgs);
     35    // Sort previews by line and column so they're displayed in the right order in the editor
     36    allPreviews.sort((previewA, previewB) => {
     37      if (previewA.line < previewB.line) {
     38        return -1;
     39      }
     40      if (previewA.line > previewB.line) {
     41        return 1;
     42      }
     43      // If we have the same line number
     44      return previewA.column < previewB.column ? -1 : 1;
     45    });
     46 
     47    const previews = {};
     48    for (const preview of allPreviews) {
     49      const { line } = preview;
     50      if (!previews[line]) {
     51        previews[line] = [];
     52      }
     53      previews[line].push(preview);
     54    }
     55 
     56    validateSelectedFrame(getState(), selectedFrame);
     57 
     58    return dispatch({
     59      type: "ADD_INLINE_PREVIEW",
     60      selectedFrame,
     61      previews,
     62    });
     63  };
     64 }
     65 /**
     66 * Creates all the previews
     67 *
     68 * @param {object} selectedFrame
     69 * @param {object} scope - Scope from the platform
     70 * @param {object} thunkArgs
     71 * @returns
     72 */
     73 async function getPreviews(selectedFrame, scope, thunkArgs) {
     74  const { client, getState } = thunkArgs;
     75 
     76  // It's important to use selectedLocation, because we don't know
     77  // if we'll be viewing the original or generated frame location
     78  const selectedLocation = getSelectedLocation(getState());
     79  if (!selectedLocation) {
     80    return [];
     81  }
     82 
     83  const editor = getEditor();
     84  if (editor.isWasm) {
     85    return [];
     86  }
     87 
     88  const allPreviews = [];
     89  const seenBindings = {};
     90 
     91  const bindingReferences = await editor.getBindingReferences(
     92    selectedLocation,
     93    scope
     94  );
     95  validateSelectedFrame(getState(), selectedFrame);
     96 
     97  for (const level in bindingReferences) {
     98    for (const name in bindingReferences[level]) {
     99      const valueActorID = bindingReferences[level][name].value?.actor;
    100      // Ignore any binding with the same value which has already been displayed.
    101      // This might occur if a variable gets hoisted and is available to the local and global scope.
    102      if (seenBindings[name] && seenBindings[name] == valueActorID) {
    103        continue;
    104      }
    105      const previews = await generatePreviewsForBinding(
    106        bindingReferences[level][name],
    107        selectedLocation.line,
    108        name,
    109        client,
    110        selectedFrame.thread
    111      );
    112      seenBindings[name] = valueActorID;
    113      allPreviews.push(...previews);
    114    }
    115  }
    116  return allPreviews;
    117 }
    118 
    119 /**
    120 * Generates the previews from the binding information
    121 *
    122 * @param {object} bindingData - Scope binding data from the AST about a particular variable/argument at a particular level in the scope.
    123 * @param {number} pausedOnLine - The current line we are paused on
    124 * @param {string} name - Name of binding from the platfom scopes
    125 * @param {object} client - Client object for loading properties
    126 * @param {object} thread - Thread used to get the expressions values
    127 * @returns
    128 */
    129 async function generatePreviewsForBinding(
    130  bindingData,
    131  pausedOnLine,
    132  name,
    133  client,
    134  thread
    135 ) {
    136  if (!bindingData) {
    137    return [];
    138  }
    139 
    140  // Show a variable only once ( an object and it's child property are
    141  // counted as different )
    142  const identifiers = new Set();
    143  const previews = [];
    144  // We start from end as we want to show values besides variable
    145  // located nearest to the breakpoint
    146  for (let i = bindingData.refs.length - 1; i >= 0; i--) {
    147    const ref = bindingData.refs[i];
    148    // Lines in CM6 is 1-based
    149    const line = ref.start.line;
    150    const column = ref.start.column;
    151    // We don't want to render inline preview below the paused line
    152    if (line >= pausedOnLine) {
    153      continue;
    154    }
    155 
    156    if (bindingData.value == undefined) {
    157      continue;
    158    }
    159 
    160    const { displayName, displayValue } = await getExpressionNameAndValue(
    161      name,
    162      bindingData.value,
    163      ref,
    164      client,
    165      thread
    166    );
    167 
    168    // Variable with same name exists, display value of current or
    169    // closest to the current scope's variable
    170    if (identifiers.has(displayName)) {
    171      continue;
    172    }
    173    identifiers.add(displayName);
    174 
    175    previews.push({
    176      line,
    177      column,
    178      // This attribute helps distinguish pause from trace previews
    179      type: "paused",
    180      name: displayName,
    181      value: displayValue,
    182    });
    183  }
    184  return previews;
    185 }
    186 
    187 /**
    188 * Get the name and value details to be displayed in the inline preview
    189 *
    190 * @param {string} name - Binding name
    191 * @param {string} value - Binding value which is the Enviroment object actor form
    192 * @param {object} ref - Binding reference
    193 * @param {object} client - Client object for loading properties
    194 * @param {string} thread - Thread used to get the expression values
    195 * @returns
    196 */
    197 async function getExpressionNameAndValue(name, value, ref, client, thread) {
    198  let displayName = name;
    199  let displayValue = value;
    200  // We want to show values of properties of objects only and not
    201  // function calls on other data types like someArr.forEach etc..
    202  let properties = null;
    203  if (value.actor && value.class === "Object") {
    204    properties = await client.loadObjectProperties(
    205      {
    206        name,
    207        path: name,
    208        contents: { value },
    209      },
    210      thread
    211    );
    212  }
    213 
    214  let { meta } = ref;
    215  // Presence of meta property means expression contains child property
    216  // reference eg: objName.propName
    217  while (meta) {
    218    // Only variables of type Object will have properties
    219    if (!properties) {
    220      displayName += `.${meta.property}`;
    221      // Initially properties will be an array, after that it will be an object
    222    } else if (displayValue === value) {
    223      const property = properties.find(prop => prop.name === meta.property);
    224      displayValue = property?.contents.value;
    225      displayName += `.${meta.property}`;
    226    } else if (displayValue?.preview?.ownProperties) {
    227      const { ownProperties } = displayValue.preview;
    228      Object.keys(ownProperties).forEach(prop => {
    229        if (prop === meta.property) {
    230          displayValue = ownProperties[prop].value;
    231          displayName += `.${meta.property}`;
    232        }
    233      });
    234    }
    235    meta = meta.parent;
    236  }
    237 
    238  return { displayName, displayValue };
    239 }