pause.js (10436B)
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 { getThreadPauseState } from "../reducers/pause"; 6 import { getSelectedSource, getSelectedLocation } from "./sources"; 7 import { getBlackBoxRanges } from "./source-blackbox"; 8 import { getSelectedTraceSource } from "./tracer"; 9 10 // eslint-disable-next-line 11 import { getSelectedLocation as _getSelectedLocation } from "../utils/selected-location"; 12 import { isFrameBlackBoxed } from "../utils/source"; 13 import { createSelector } from "devtools/client/shared/vendor/reselect"; 14 15 export const getSelectedFrame = createSelector( 16 (state, thread) => state.pause.threads[thread || getCurrentThread(state)], 17 threadPauseState => { 18 if (!threadPauseState) { 19 return null; 20 } 21 const { selectedFrameId, frames } = threadPauseState; 22 if (frames) { 23 return frames.find(frame => frame.id == selectedFrameId); 24 } 25 return null; 26 } 27 ); 28 29 export const getVisibleSelectedFrame = createSelector( 30 getSelectedLocation, 31 state => getSelectedFrame(state, getCurrentThread(state)), 32 (selectedLocation, selectedFrame) => { 33 if (!selectedFrame) { 34 return null; 35 } 36 37 const { id, displayName } = selectedFrame; 38 39 return { 40 id, 41 displayName, 42 location: _getSelectedLocation(selectedFrame, selectedLocation), 43 }; 44 } 45 ); 46 47 export function getContext(state) { 48 return state.pause.cx; 49 } 50 51 export function getThreadContext(state) { 52 return state.pause.threadcx; 53 } 54 55 export function getNavigateCounter(state) { 56 return state.pause.threadcx.navigateCounter; 57 } 58 59 export function getPauseReason(state, thread) { 60 return getThreadPauseState(state.pause, thread).why; 61 } 62 63 export function getShouldBreakpointsPaneOpenOnPause(state, thread) { 64 return getThreadPauseState(state.pause, thread) 65 .shouldBreakpointsPaneOpenOnPause; 66 } 67 68 export function getPauseCommand(state, thread) { 69 return getThreadPauseState(state.pause, thread).command; 70 } 71 72 export function isStepping(state, thread) { 73 return ["stepIn", "stepOver", "stepOut"].includes( 74 getPauseCommand(state, thread) 75 ); 76 } 77 78 export function getCurrentThread(state) { 79 return getThreadContext(state).thread; 80 } 81 82 export function getIsPaused(state, thread) { 83 return getThreadPauseState(state.pause, thread).isPaused; 84 } 85 86 export function getIsCurrentThreadPaused(state) { 87 return getIsPaused(state, getCurrentThread(state)); 88 } 89 90 export function isEvaluatingExpression(state, thread) { 91 return getThreadPauseState(state.pause, thread).command === "expression"; 92 } 93 94 export function getIsWaitingOnBreak(state, thread) { 95 return getThreadPauseState(state.pause, thread).isWaitingOnBreak; 96 } 97 98 export function getShouldPauseOnDebuggerStatement(state) { 99 return state.pause.shouldPauseOnDebuggerStatement; 100 } 101 102 export function getShouldPauseOnExceptions(state) { 103 return state.pause.shouldPauseOnExceptions; 104 } 105 106 export function getShouldPauseOnCaughtExceptions(state) { 107 return state.pause.shouldPauseOnCaughtExceptions; 108 } 109 110 export function getFrames(state, thread) { 111 const { frames, framesLoading } = getThreadPauseState(state.pause, thread); 112 return framesLoading ? null : frames; 113 } 114 115 export const getCurrentThreadFrames = createSelector( 116 state => { 117 const { frames, framesLoading } = getThreadPauseState( 118 state.pause, 119 getCurrentThread(state) 120 ); 121 if (framesLoading) { 122 return []; 123 } 124 return frames; 125 }, 126 getBlackBoxRanges, 127 (frames, blackboxedRanges) => { 128 return frames.filter(frame => !isFrameBlackBoxed(frame, blackboxedRanges)); 129 } 130 ); 131 132 function getGeneratedFrameId(frameId) { 133 if (frameId.includes("-originalFrame")) { 134 // The mapFrames can add original stack frames -- get generated frameId. 135 return frameId.substr(0, frameId.lastIndexOf("-originalFrame")); 136 } 137 return frameId; 138 } 139 // This is Environment Scope information from the platform. 140 // See https://searchfox.org/mozilla-central/rev/b0e8e4ceb46cb3339cdcb90310fcc161ef4b9e3e/devtools/server/actors/environment.js#42-81 141 export function getGeneratedFrameScope(state, frame) { 142 if (!frame) { 143 return null; 144 } 145 return getFrameScopes(state, frame.thread).generated[ 146 getGeneratedFrameId(frame.id) 147 ]; 148 } 149 150 export function getOriginalFrameScope(state, frame) { 151 if (!frame) { 152 return null; 153 } 154 // Only compute original scope if we are currently showing an original source. 155 const source = getSelectedSource(state); 156 if (!source || !source.isOriginal) { 157 return null; 158 } 159 160 const original = getFrameScopes(state, frame.thread).original[ 161 getGeneratedFrameId(frame.id) 162 ]; 163 164 if (original && (original.pending || original.scope)) { 165 return original; 166 } 167 168 return null; 169 } 170 171 // This is only used by tests 172 export function getFrameScopes(state, thread) { 173 return getThreadPauseState(state.pause, thread).frameScopes; 174 } 175 176 export function getSelectedFrameBindings(state, thread) { 177 const scopes = getFrameScopes(state, thread); 178 const selectedFrameId = getSelectedFrameId(state, thread); 179 if (!scopes || !selectedFrameId) { 180 return null; 181 } 182 183 const frameScope = scopes.generated[selectedFrameId]; 184 if (!frameScope || frameScope.pending) { 185 return null; 186 } 187 188 let currentScope = frameScope.scope; 189 let frameBindings = []; 190 while (currentScope && currentScope.type != "object") { 191 if (currentScope.bindings) { 192 const bindings = Object.keys(currentScope.bindings.variables); 193 const args = [].concat( 194 ...currentScope.bindings.arguments.map(argument => 195 Object.keys(argument) 196 ) 197 ); 198 199 frameBindings = [...frameBindings, ...bindings, ...args]; 200 } 201 currentScope = currentScope.parent; 202 } 203 204 return frameBindings; 205 } 206 207 export function getSelectedScope(state) { 208 const frame = getSelectedFrame(state); 209 if (!frame) { 210 return null; 211 } 212 213 let scopes; 214 // For non-pretty printed original sources 215 if ( 216 frame.location.source.isOriginal && 217 !frame.location.source.isPrettyPrinted && 218 !frame.generatedLocation?.source.isWasm 219 ) { 220 scopes = getOriginalFrameScope(state, frame)?.scope; 221 // Fallback to the generated scopes if there are no original scopes 222 if (!scopes) { 223 scopes = getGeneratedFrameScope(state, frame)?.scope; 224 } 225 } else { 226 // For generated sources 227 // For pretty printed sources - Even though are seen as original sources they do not include any rename of variables/function names. 228 scopes = getGeneratedFrameScope(state, frame)?.scope; 229 } 230 return scopes; 231 } 232 233 export function getSelectedOriginalScope(state, thread) { 234 const frame = getSelectedFrame(state, thread); 235 return getOriginalFrameScope(state, frame); 236 } 237 238 export function getSelectedScopeMappings(state, thread) { 239 const frameId = getSelectedFrameId(state, thread); 240 if (!frameId) { 241 return null; 242 } 243 244 return getFrameScopes(state, thread).mappings[frameId]; 245 } 246 247 export function getSelectedFrameId(state, thread) { 248 return getThreadPauseState(state.pause, thread).selectedFrameId; 249 } 250 251 export function isTopFrameSelected(state, thread) { 252 const selectedFrameId = getSelectedFrameId(state, thread); 253 // Consider that the top frame is selected when none is specified, 254 // which happens when a JS Tracer frame is selected. 255 if (!selectedFrameId) { 256 return true; 257 } 258 const topFrame = getTopFrame(state, thread); 259 return selectedFrameId == topFrame?.id; 260 } 261 262 export function getTopFrame(state, thread) { 263 const frames = getFrames(state, thread); 264 return frames?.[0]; 265 } 266 267 // getTopFrame wouldn't return the top frame if the frames are still being fetched 268 export function getCurrentlyFetchedTopFrame(state, thread) { 269 const { frames } = getThreadPauseState(state.pause, thread); 270 return frames?.[0]; 271 } 272 273 export function hasFrame(state, frame) { 274 // Don't use getFrames as it returns null when the frames are still loading 275 const { frames } = getThreadPauseState(state.pause, frame.thread); 276 if (!frames) { 277 return false; 278 } 279 // Compare IDs and not frame objects as they get cloned during mapping 280 return frames.some(f => f.id == frame.id); 281 } 282 283 export function getSkipPausing(state) { 284 return state.pause.skipPausing; 285 } 286 287 export function isMapScopesEnabled(state) { 288 return state.pause.mapScopes; 289 } 290 291 /** 292 * This selector only returns inline previews object for current paused location. 293 * (it ignores the JS Tracer and ignore the selected location, which may different from paused location) 294 */ 295 export function getSelectedFrameInlinePreviews(state) { 296 const thread = getCurrentThread(state); 297 const frame = getSelectedFrame(state, thread); 298 if (!frame) { 299 return null; 300 } 301 return getThreadPauseState(state.pause, thread).inlinePreview[ 302 getGeneratedFrameId(frame.id) 303 ]; 304 } 305 306 /** 307 * This selector returns the inline previews object for the selected location. 308 * It considers both paused and traced previews and will only return values 309 * if it matches the currently selected location. 310 */ 311 export function getInlinePreviews(state) { 312 const selectedSource = getSelectedSource(state); 313 if (!selectedSource) { 314 return null; 315 } 316 317 // We first check if a frame in the JS Tracer was selected and generated its previews 318 if (state.tracerFrames?.previews) { 319 const selectedTraceSource = getSelectedTraceSource(state); 320 if (selectedTraceSource) { 321 if (selectedTraceSource.id == selectedSource.id) { 322 return state.tracerFrames?.previews; 323 } 324 325 // If the "selected" versus "tracing selected" sources don't match, it means that we selected the original source 326 // while the traced source is the generated one. We don't yet support showing inline previews in this configuration. 327 return null; 328 } 329 } 330 331 // Otherwise, we fallback to look if we were paused and the inline preview is available 332 const thread = getCurrentThread(state); 333 const frame = getSelectedFrame(state, thread); 334 // When we are paused, we also check if the selected source matches the paused original or generated location. 335 if ( 336 !frame || 337 (frame.location.source.id != selectedSource.id && 338 frame.generatedLocation.source.id != selectedSource.id) 339 ) { 340 return null; 341 } 342 return getThreadPauseState(state.pause, thread).inlinePreview[ 343 getGeneratedFrameId(frame.id) 344 ]; 345 } 346 347 export function getLastExpandedScopes(state, thread) { 348 return getThreadPauseState(state.pause, thread).lastExpandedScopes; 349 }