tor-browser

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

tracing.js (7520B)


      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  getAllTraces,
      7  getTraceFrames,
      8  getIsCurrentlyTracing,
      9  getCurrentThread,
     10  getSourceByActorId,
     11  getSourceActor,
     12 } from "../selectors/index";
     13 import { NO_SEARCH_VALUE } from "../reducers/tracer-frames";
     14 import { createLocation } from "../utils/location";
     15 import { getOriginalLocation } from "../utils/source-maps";
     16 
     17 import { selectLocation } from "./sources/select.js";
     18 const {
     19  TRACER_FIELDS_INDEXES,
     20 } = require("resource://devtools/server/actors/tracer.js");
     21 
     22 /**
     23 * Called when tracing is toggled ON/OFF on a particular thread.
     24 */
     25 export function tracingToggled(thread, enabled, traceValues) {
     26  return {
     27    type: "TRACING_TOGGLED",
     28    thread,
     29    enabled,
     30    traceValues,
     31  };
     32 }
     33 
     34 export function clearTracerData() {
     35  return {
     36    type: "TRACING_CLEAR",
     37  };
     38 }
     39 
     40 export function addTraces(traces) {
     41  return async function ({ dispatch, getState }) {
     42    if (!getIsCurrentlyTracing(getState())) {
     43      return null;
     44    }
     45 
     46    return dispatch({
     47      type: "ADD_TRACES",
     48      traces,
     49    });
     50  };
     51 }
     52 
     53 export function selectTrace(traceIndex) {
     54  return async function (thunkArgs) {
     55    const { dispatch, getState } = thunkArgs;
     56    const traces = getAllTraces(getState());
     57    const trace = traces[traceIndex];
     58    if (!trace) {
     59      return;
     60    }
     61 
     62    // Ignore DOM Event traces, which aren't related to a particular location in source.
     63    let location = null;
     64    if (trace[TRACER_FIELDS_INDEXES.TYPE] != "event") {
     65      const frameIndex = trace[TRACER_FIELDS_INDEXES.FRAME_INDEX];
     66      const frames = getTraceFrames(getState());
     67      const frame = frames[frameIndex];
     68      const source = getSourceByActorId(getState(), frame.sourceId);
     69      const sourceActor = getSourceActor(getState(), frame.sourceId);
     70      location = createLocation({
     71        source,
     72        sourceActor,
     73        line: frame.line,
     74        column: frame.column,
     75      });
     76      location = await getOriginalLocation(location, thunkArgs);
     77    }
     78 
     79    // For now, the tracer only consider the top level thread
     80    const thread = getCurrentThread(getState());
     81    dispatch({
     82      type: "SELECT_TRACE",
     83      traceIndex,
     84      location,
     85      thread,
     86    });
     87 
     88    if (location) {
     89      // We disable the yellow flashing highlighting as the currently selected traced line
     90      // will have a permanent highlight.
     91      await dispatch(selectLocation(location, { highlight: false }));
     92    }
     93 
     94    await dispatch(updateSelectedLocationTraces(location));
     95  };
     96 }
     97 
     98 export function setLocalAndRemoteRuntimeVersion(
     99  localPlatformVersion,
    100  remotePlatformVersion
    101 ) {
    102  return {
    103    type: "SET_RUNTIME_VERSIONS",
    104    localPlatformVersion,
    105    remotePlatformVersion,
    106  };
    107 }
    108 
    109 // Calls to the searchTraceArguments can either be synchronous (for primitives)
    110 // or async (for everything else).
    111 // This means that an old call can resolve after a more recent one and override
    112 // the UI.
    113 let currentSearchSymbol;
    114 
    115 export function searchTraceArguments(searchString) {
    116  return async function ({ dispatch, client, panel }) {
    117    // Ignore any starting and ending spaces in the query string
    118    searchString = searchString.trim();
    119 
    120    const searchSymbol = Symbol("CURRENT_SEARCH_SYMBOL");
    121    currentSearchSymbol = searchSymbol;
    122 
    123    // Reset back to no search if the query is empty
    124    if (!searchString) {
    125      dispatch({
    126        type: "SET_TRACE_SEARCH_STRING",
    127        searchValueOrGrip: NO_SEARCH_VALUE,
    128      });
    129      return;
    130    }
    131 
    132    // `JSON.parse("undefined")` throws, but we still want to support searching for this special value
    133    // without having to evaluate to the server
    134    if (searchString === "undefined") {
    135      dispatch({
    136        type: "SET_TRACE_SEARCH_STRING",
    137        searchValueOrGrip: undefined,
    138      });
    139      return;
    140    }
    141 
    142    // First check on the frontend if that's a primitive,
    143    // in which case, we can compute the value without evaling in the server.
    144    try {
    145      const value = JSON.parse(searchString);
    146      // Ignore any object value, as we won't have any match anyway.
    147      // We can only search for existing page objects.
    148      if (typeof value == "object" && value !== null) {
    149        dispatch({
    150          type: "SET_TRACE_SEARCH_EXCEPTION",
    151          errorMessage:
    152            "Invalid search. Can only search for existing page JS objects",
    153        });
    154        return;
    155      }
    156      dispatch({
    157        type: "SET_TRACE_SEARCH_STRING",
    158        searchValueOrGrip: value,
    159      });
    160      return;
    161    } catch (e) {}
    162 
    163    // If the inspector is opened, and a node is currently selected,
    164    // try to fetch its actor ID in order to make '$0' to work in evaluations
    165    const inspector = panel.toolbox.getPanel("inspector");
    166    const selectedNodeActor = inspector?.selection?.nodeFront?.actorID;
    167 
    168    let { result, exception } = await client.evaluate(`(${searchString})`, {
    169      selectedNodeActor,
    170      evalInTracer: true,
    171    });
    172 
    173    if (currentSearchSymbol != searchSymbol) {
    174      // Stop handling this evaluation result if the action was called more
    175      // recently.
    176      return;
    177    }
    178 
    179    if (result.type == "null") {
    180      result = null;
    181    } else if (result.type == "undefined") {
    182      result = undefined;
    183    }
    184 
    185    if (exception) {
    186      const { preview } = exception.getGrip();
    187      const errorMessage = `${preview.name}: ${preview.message}`;
    188      dispatch({
    189        type: "SET_TRACE_SEARCH_EXCEPTION",
    190        errorMessage,
    191      });
    192    } else {
    193      // If we refered to an object, the `result` will be an ObjectActorFront
    194      // for which we retrieve its current "form" (a.k.a. grip).
    195      // Otherwise `result` will be a primitive JS value (boolean, number, string,...)
    196      const searchValueOrGrip =
    197        result && result.getGrip ? result.getGrip() : result;
    198 
    199      dispatch({
    200        type: "SET_TRACE_SEARCH_STRING",
    201        searchValueOrGrip,
    202      });
    203    }
    204  };
    205 }
    206 
    207 export function updateSelectedLocationTraces(selectedLocation) {
    208  return async function ({ getState, dispatch }) {
    209    if (!selectedLocation) {
    210      dispatch({
    211        type: "SET_SELECTED_LOCACTION_TRACES",
    212        selectedLocationTraces: null,
    213      });
    214      return;
    215    }
    216    const state = getState();
    217 
    218    let location = selectedLocation;
    219    // When an original location is selected, we should be fetching the matching generated location from reducers
    220    if (selectedLocation.source.isOriginal) {
    221      location = state.sources.selectedGeneratedLocation;
    222    }
    223 
    224    const allTraces = getAllTraces(state);
    225    const frames = getTraceFrames(state);
    226 
    227    // By computing this from the selectLocation action, we compute this once per location change,
    228    // but it may be relevant to try to cache traces per location (file, line and column)
    229    // to avoid having to go through all the traces on each location change.
    230    const selectedLocationTraces = allTraces.filter(trace => {
    231      const frameIndex = trace[TRACER_FIELDS_INDEXES.FRAME_INDEX];
    232      const frame = frames[frameIndex];
    233      return (
    234        frame &&
    235        frame.sourceId == location.sourceActor.id &&
    236        frame.line == location.line &&
    237        (!location.column || frame.column == location.column)
    238      );
    239    });
    240 
    241    dispatch({
    242      type: "SET_SELECTED_LOCACTION_TRACES",
    243      selectedLocationTraces: !selectedLocationTraces.length
    244        ? null
    245        : selectedLocationTraces,
    246    });
    247  };
    248 }