/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at . */ import { getAllTraces, getTraceFrames, getIsCurrentlyTracing, getCurrentThread, getSourceByActorId, getSourceActor, } from "../selectors/index"; import { NO_SEARCH_VALUE } from "../reducers/tracer-frames"; import { createLocation } from "../utils/location"; import { getOriginalLocation } from "../utils/source-maps"; import { selectLocation } from "./sources/select.js"; const { TRACER_FIELDS_INDEXES, } = require("resource://devtools/server/actors/tracer.js"); /** * Called when tracing is toggled ON/OFF on a particular thread. */ export function tracingToggled(thread, enabled, traceValues) { return { type: "TRACING_TOGGLED", thread, enabled, traceValues, }; } export function clearTracerData() { return { type: "TRACING_CLEAR", }; } export function addTraces(traces) { return async function ({ dispatch, getState }) { if (!getIsCurrentlyTracing(getState())) { return null; } return dispatch({ type: "ADD_TRACES", traces, }); }; } export function selectTrace(traceIndex) { return async function (thunkArgs) { const { dispatch, getState } = thunkArgs; const traces = getAllTraces(getState()); const trace = traces[traceIndex]; if (!trace) { return; } // Ignore DOM Event traces, which aren't related to a particular location in source. let location = null; if (trace[TRACER_FIELDS_INDEXES.TYPE] != "event") { const frameIndex = trace[TRACER_FIELDS_INDEXES.FRAME_INDEX]; const frames = getTraceFrames(getState()); const frame = frames[frameIndex]; const source = getSourceByActorId(getState(), frame.sourceId); const sourceActor = getSourceActor(getState(), frame.sourceId); location = createLocation({ source, sourceActor, line: frame.line, column: frame.column, }); location = await getOriginalLocation(location, thunkArgs); } // For now, the tracer only consider the top level thread const thread = getCurrentThread(getState()); dispatch({ type: "SELECT_TRACE", traceIndex, location, thread, }); if (location) { // We disable the yellow flashing highlighting as the currently selected traced line // will have a permanent highlight. await dispatch(selectLocation(location, { highlight: false })); } await dispatch(updateSelectedLocationTraces(location)); }; } export function setLocalAndRemoteRuntimeVersion( localPlatformVersion, remotePlatformVersion ) { return { type: "SET_RUNTIME_VERSIONS", localPlatformVersion, remotePlatformVersion, }; } // Calls to the searchTraceArguments can either be synchronous (for primitives) // or async (for everything else). // This means that an old call can resolve after a more recent one and override // the UI. let currentSearchSymbol; export function searchTraceArguments(searchString) { return async function ({ dispatch, client, panel }) { // Ignore any starting and ending spaces in the query string searchString = searchString.trim(); const searchSymbol = Symbol("CURRENT_SEARCH_SYMBOL"); currentSearchSymbol = searchSymbol; // Reset back to no search if the query is empty if (!searchString) { dispatch({ type: "SET_TRACE_SEARCH_STRING", searchValueOrGrip: NO_SEARCH_VALUE, }); return; } // `JSON.parse("undefined")` throws, but we still want to support searching for this special value // without having to evaluate to the server if (searchString === "undefined") { dispatch({ type: "SET_TRACE_SEARCH_STRING", searchValueOrGrip: undefined, }); return; } // First check on the frontend if that's a primitive, // in which case, we can compute the value without evaling in the server. try { const value = JSON.parse(searchString); // Ignore any object value, as we won't have any match anyway. // We can only search for existing page objects. if (typeof value == "object" && value !== null) { dispatch({ type: "SET_TRACE_SEARCH_EXCEPTION", errorMessage: "Invalid search. Can only search for existing page JS objects", }); return; } dispatch({ type: "SET_TRACE_SEARCH_STRING", searchValueOrGrip: value, }); return; } catch (e) {} // If the inspector is opened, and a node is currently selected, // try to fetch its actor ID in order to make '$0' to work in evaluations const inspector = panel.toolbox.getPanel("inspector"); const selectedNodeActor = inspector?.selection?.nodeFront?.actorID; let { result, exception } = await client.evaluate(`(${searchString})`, { selectedNodeActor, evalInTracer: true, }); if (currentSearchSymbol != searchSymbol) { // Stop handling this evaluation result if the action was called more // recently. return; } if (result.type == "null") { result = null; } else if (result.type == "undefined") { result = undefined; } if (exception) { const { preview } = exception.getGrip(); const errorMessage = `${preview.name}: ${preview.message}`; dispatch({ type: "SET_TRACE_SEARCH_EXCEPTION", errorMessage, }); } else { // If we refered to an object, the `result` will be an ObjectActorFront // for which we retrieve its current "form" (a.k.a. grip). // Otherwise `result` will be a primitive JS value (boolean, number, string,...) const searchValueOrGrip = result && result.getGrip ? result.getGrip() : result; dispatch({ type: "SET_TRACE_SEARCH_STRING", searchValueOrGrip, }); } }; } export function updateSelectedLocationTraces(selectedLocation) { return async function ({ getState, dispatch }) { if (!selectedLocation) { dispatch({ type: "SET_SELECTED_LOCACTION_TRACES", selectedLocationTraces: null, }); return; } const state = getState(); let location = selectedLocation; // When an original location is selected, we should be fetching the matching generated location from reducers if (selectedLocation.source.isOriginal) { location = state.sources.selectedGeneratedLocation; } const allTraces = getAllTraces(state); const frames = getTraceFrames(state); // By computing this from the selectLocation action, we compute this once per location change, // but it may be relevant to try to cache traces per location (file, line and column) // to avoid having to go through all the traces on each location change. const selectedLocationTraces = allTraces.filter(trace => { const frameIndex = trace[TRACER_FIELDS_INDEXES.FRAME_INDEX]; const frame = frames[frameIndex]; return ( frame && frame.sourceId == location.sourceActor.id && frame.line == location.line && (!location.column || frame.column == location.column) ); }); dispatch({ type: "SET_SELECTED_LOCACTION_TRACES", selectedLocationTraces: !selectedLocationTraces.length ? null : selectedLocationTraces, }); }; }