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 }