preview.js (7055B)
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 { isConsole } from "../utils/preview"; 6 import { getGrip, getFront } from "../utils/evaluation-result"; 7 8 import { 9 isLineInScope, 10 isSelectedFrameVisible, 11 getSelectedSource, 12 getSelectedLocation, 13 getSelectedFrame, 14 getCurrentThread, 15 getSelectedException, 16 getSelectedTraceIndex, 17 getAllTraces, 18 } from "../selectors/index"; 19 20 import { getMappedExpression } from "./expressions"; 21 const { 22 TRACER_FIELDS_INDEXES, 23 } = require("resource://devtools/server/actors/tracer.js"); 24 25 async function findExpressionMatches(state, editor, tokenPos) { 26 const location = getSelectedLocation(state); 27 if (!location) { 28 return []; 29 } 30 return editor.findBestMatchExpressions(tokenPos); 31 } 32 33 /** 34 * Get a preview object for the currently selected frame in the JS Tracer. 35 * 36 * @param {object} target 37 * The hovered DOM Element within CodeMirror rendering. 38 * @param {object} tokenPos 39 * The CodeMirror position object for the hovered token. 40 * @param {object} editor 41 * The CodeMirror editor object. 42 */ 43 export function getTracerPreview(target, tokenPos, editor) { 44 return async thunkArgs => { 45 const { getState } = thunkArgs; 46 const selectedTraceIndex = getSelectedTraceIndex(getState()); 47 if (selectedTraceIndex == null) { 48 return null; 49 } 50 51 const trace = getAllTraces(getState())[selectedTraceIndex]; 52 53 // We may be selecting a mutation trace, which doesn't expose any value, 54 // so only consider method calls. 55 if (trace[TRACER_FIELDS_INDEXES.TYPE] != "enter") { 56 return null; 57 } 58 59 const matches = await findExpressionMatches(getState(), editor, tokenPos); 60 if (!matches.length) { 61 return null; 62 } 63 64 let { expression, location } = matches[0]; 65 const source = getSelectedSource(getState()); 66 if (location && source.isOriginal) { 67 const thread = getCurrentThread(getState()); 68 const mapResult = await getMappedExpression( 69 expression, 70 thread, 71 thunkArgs 72 ); 73 if (mapResult) { 74 expression = mapResult.expression; 75 } 76 } 77 78 const argumentValues = trace[TRACER_FIELDS_INDEXES.ENTER_ARGS]; 79 const argumentNames = trace[TRACER_FIELDS_INDEXES.ENTER_ARG_NAMES]; 80 if (!argumentNames || !argumentValues) { 81 return null; 82 } 83 84 const argumentIndex = argumentNames.indexOf(expression); 85 if (argumentIndex == -1) { 86 return null; 87 } 88 89 const result = argumentValues[argumentIndex]; 90 // Values are either primitives, or an Object Front 91 const resultGrip = result?.getGrip ? result?.getGrip() : result; 92 93 const root = { 94 // Force updating the ObjectInspector when hovering same-name variable on another trace. 95 // See ObjectInspector.getNodeKey. 96 path: `${selectedTraceIndex}-${expression}`, 97 contents: { 98 value: resultGrip, 99 front: getFront(result), 100 }, 101 }; 102 return { 103 previewType: "tracer", 104 target, 105 tokenPos, 106 cursorPos: target.getBoundingClientRect(), 107 expression, 108 root, 109 resultGrip, 110 }; 111 }; 112 } 113 114 /** 115 * Get a preview object for the currently paused frame, if paused. 116 * 117 * @param {object} target 118 * The hovered DOM Element within CodeMirror rendering. 119 * @param {object} tokenPos 120 * The CodeMirror position object for the hovered token. 121 * @param {object} editor 122 * The CodeMirror editor object. 123 */ 124 export function getPausedPreview(target, tokenPos, editor) { 125 return async thunkArgs => { 126 const { getState, client } = thunkArgs; 127 if ( 128 !isSelectedFrameVisible(getState()) || 129 !isLineInScope(getState(), tokenPos.line) 130 ) { 131 return null; 132 } 133 134 const source = getSelectedSource(getState()); 135 if (!source) { 136 return null; 137 } 138 const thread = getCurrentThread(getState()); 139 const selectedFrame = getSelectedFrame(getState()); 140 if (!selectedFrame) { 141 return null; 142 } 143 const matches = await findExpressionMatches(getState(), editor, tokenPos); 144 if (!matches.length) { 145 return null; 146 } 147 148 let { expression, location } = matches[0]; 149 150 if (isConsole(expression)) { 151 return null; 152 } 153 154 if (location && source.isOriginal) { 155 const mapResult = await getMappedExpression( 156 expression, 157 thread, 158 thunkArgs 159 ); 160 if (mapResult) { 161 expression = mapResult.expression; 162 } 163 } 164 165 const { result, hasException, exception } = await client.evaluate( 166 expression, 167 { 168 frameId: selectedFrame.id, 169 } 170 ); 171 172 // The evaluation shouldn't return an exception. 173 if (hasException) { 174 const errorClass = exception?.getGrip()?.class || "Error"; 175 throw new Error( 176 `Debugger internal exception: Preview for <${expression}> threw a ${errorClass}` 177 ); 178 } 179 180 const resultGrip = getGrip(result); 181 182 // Error case occurs for a token that follows an errored evaluation 183 // https://github.com/firefox-devtools/debugger/pull/8056 184 // Accommodating for null allows us to show preview for falsy values 185 // line "", false, null, Nan, and more 186 if (resultGrip === null) { 187 return null; 188 } 189 190 // Handle cases where the result is invisible to the debugger 191 // and not possible to preview. Bug 1548256 192 if ( 193 resultGrip && 194 resultGrip.class && 195 typeof resultGrip.class === "string" && 196 resultGrip.class.includes("InvisibleToDebugger") 197 ) { 198 return null; 199 } 200 201 const root = { 202 path: expression, 203 contents: { 204 value: resultGrip, 205 front: getFront(result), 206 }, 207 }; 208 209 return { 210 previewType: "pause", 211 target, 212 tokenPos, 213 cursorPos: target.getBoundingClientRect(), 214 expression, 215 root, 216 resultGrip, 217 }; 218 }; 219 } 220 221 export function getExceptionPreview(target, tokenPos, editor) { 222 return async ({ getState }) => { 223 const matches = await findExpressionMatches(getState(), editor, tokenPos); 224 if (!matches.length) { 225 return null; 226 } 227 let exception; 228 // Lezer might return multiple matches in certain scenarios. 229 // Example: For this expression `[].inlineException()` is likely to throw an exception, 230 // but if the user hovers over `inlineException` lezer finds 2 matches : 231 // 1) `inlineException` for `PropertyName`, 232 // 2) `[].inlineException()` for `MemberExpression` 233 // Babel seems to only include the `inlineException`. 234 for (const match of matches) { 235 const tokenColumnStart = match.location.start.column + 1; 236 exception = getSelectedException( 237 getState(), 238 tokenPos.line, 239 tokenColumnStart 240 ); 241 if (exception) { 242 break; 243 } 244 } 245 246 if (!exception) { 247 return null; 248 } 249 250 return { 251 target, 252 tokenPos, 253 cursorPos: target.getBoundingClientRect(), 254 exception, 255 }; 256 }; 257 }