mapFrames.js (5938B)
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 getFrames, 7 getBlackBoxRanges, 8 getSelectedFrame, 9 } from "../../selectors/index"; 10 11 import { isFrameBlackBoxed } from "../../utils/source"; 12 13 import assert from "../../utils/assert"; 14 import { getOriginalLocation } from "../../utils/source-maps"; 15 import { 16 debuggerToSourceMapLocation, 17 sourceMapToDebuggerLocation, 18 } from "../../utils/location"; 19 import { annotateFramesWithLibrary } from "../../utils/pause/frames/annotateFrames"; 20 import { createWasmOriginalFrame } from "../../client/firefox/create"; 21 22 import { getOriginalFunctionDisplayName } from "../sources/index"; 23 24 function getSelectedFrameId(state, thread, frames) { 25 let selectedFrame = getSelectedFrame(state, thread); 26 const blackboxedRanges = getBlackBoxRanges(state); 27 28 if (selectedFrame && !isFrameBlackBoxed(selectedFrame, blackboxedRanges)) { 29 return selectedFrame.id; 30 } 31 32 selectedFrame = frames.find(frame => { 33 return !isFrameBlackBoxed(frame, blackboxedRanges); 34 }); 35 return selectedFrame?.id; 36 } 37 38 async function updateFrameLocation(frame, thunkArgs) { 39 // Ignore WASM original sources 40 if (isWasmOriginalSourceFrame(frame)) { 41 return frame; 42 } 43 44 const location = await getOriginalLocation( 45 frame.generatedLocation || frame.location, 46 thunkArgs, 47 { 48 waitForSource: true, 49 } 50 ); 51 // Avoid instantiating new frame objects if the frame location isn't mapped 52 if (location == frame.location) { 53 return frame; 54 } 55 56 // As we modify frame object, fork it to force causing re-renders 57 return { 58 ...frame, 59 location, 60 generatedLocation: frame.generatedLocation || frame.location, 61 }; 62 } 63 64 function isWasmOriginalSourceFrame(frame) { 65 if (!frame.location.source.isOriginal) { 66 return false; 67 } 68 69 return Boolean(frame.generatedLocation?.source.isWasm); 70 } 71 72 /** 73 * Wasm Source Maps can come with an non-standard "xScopes" attribute 74 * which allows mapping the scope of a given location. 75 */ 76 async function expandWasmFrames(frames, { getState, sourceMapLoader }) { 77 const result = []; 78 for (let i = 0; i < frames.length; ++i) { 79 const frame = frames[i]; 80 if (frame.isOriginal || !isWasmOriginalSourceFrame(frame)) { 81 result.push(frame); 82 continue; 83 } 84 const originalFrames = await sourceMapLoader.getOriginalStackFrames( 85 debuggerToSourceMapLocation(frame.generatedLocation) 86 ); 87 if (!originalFrames) { 88 result.push(frame); 89 continue; 90 } 91 92 assert(!!originalFrames.length, "Expected at least one original frame"); 93 // First entry has no specific location -- use one from the generated frame. 94 originalFrames[0].location = frame.location; 95 96 originalFrames.forEach((originalFrame, j) => { 97 if (!originalFrame.location) { 98 return; 99 } 100 101 // Keep outer most frame with true actor ID, and generate unique 102 // one for the nested frames. 103 const id = j == 0 ? frame.id : `${frame.id}-originalFrame${j}`; 104 const originalFrameLocation = sourceMapToDebuggerLocation( 105 getState(), 106 originalFrame.location 107 ); 108 result.push( 109 createWasmOriginalFrame(frame, id, originalFrame, originalFrameLocation) 110 ); 111 }); 112 } 113 return result; 114 } 115 116 async function updateFrameDisplayName(frame, thunkArgs) { 117 const location = frame.location; 118 // Ignore WASM original, generated and pretty printed sources 119 if ( 120 location.source.isWasm || 121 !location.source.isOriginal || 122 location.source.isPrettyPrinted 123 ) { 124 return frame; 125 } 126 127 // As we now know that this frame relates to an original source... 128 // Compute the frame's originalDisplayName. 129 const originalDisplayName = location.source.isPrettyPrinted 130 ? frame.displayName 131 : await thunkArgs.dispatch(getOriginalFunctionDisplayName(location)); 132 133 // As we modify frame object, fork it to force causing re-renders 134 return { 135 ...frame, 136 originalDisplayName, 137 }; 138 } 139 140 /** 141 * Update the display names of the mapped original frames 142 * 143 * @param {object} thread 144 * @returns 145 */ 146 export function updateAllFrameDisplayNames(thread) { 147 return async function (thunkArgs) { 148 const { dispatch, getState } = thunkArgs; 149 const frames = getFrames(getState(), thread); 150 if (!frames || !frames.length) { 151 return; 152 } 153 154 // Update frame's originalDisplayNames in case it relates to an original source 155 const updatedFrames = await Promise.all( 156 frames.map(frame => updateFrameDisplayName(frame, thunkArgs)) 157 ); 158 159 dispatch({ 160 type: "UPDATE_FRAMES", 161 frames: updatedFrames, 162 thread, 163 }); 164 }; 165 } 166 167 /** 168 * Map call stack frame locations and display names to originals. 169 * e.g. 170 * 1. When the debuggee pauses 171 * 2. When a source is pretty printed 172 * 173 * @memberof actions/pause 174 * @static 175 */ 176 export function mapFrames(thread) { 177 return async function (thunkArgs) { 178 const { dispatch, getState } = thunkArgs; 179 const frames = getFrames(getState(), thread); 180 if (!frames || !frames.length) { 181 return; 182 } 183 184 // Update frame's location/generatedLocation in case it relates to an original source 185 let mappedFrames = await Promise.all( 186 frames.map(frame => updateFrameLocation(frame, thunkArgs)) 187 ); 188 189 mappedFrames = await expandWasmFrames(mappedFrames, thunkArgs); 190 191 // Add the "library" attribute on all frame objects (if relevant) 192 annotateFramesWithLibrary(mappedFrames); 193 194 // After having mapped the frames, we should update the selected frame 195 // just in case the selected frame is now set on a blackboxed original source 196 const selectedFrameId = getSelectedFrameId( 197 getState(), 198 thread, 199 mappedFrames 200 ); 201 202 dispatch({ 203 type: "MAP_FRAMES", 204 thread, 205 frames: mappedFrames, 206 selectedFrameId, 207 }); 208 }; 209 }