mapScopes.js (5630B)
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 getSettledSourceTextContent, 7 isMapScopesEnabled, 8 getSelectedFrame, 9 getGeneratedFrameScope, 10 getOriginalFrameScope, 11 getFirstSourceActorForGeneratedSource, 12 } from "../../selectors/index"; 13 import { 14 loadOriginalSourceText, 15 loadGeneratedSourceText, 16 } from "../sources/loadSourceText"; 17 import { validateSelectedFrame } from "../../utils/context"; 18 const { 19 PROMISE, 20 } = require("resource://devtools/client/shared/redux/middleware/promise.js"); 21 22 import { log } from "../../utils/log"; 23 24 import { buildMappedScopes } from "../../utils/pause/mapScopes/index"; 25 import { isFulfilled } from "../../utils/async-value"; 26 27 import { getMappedLocation } from "../../utils/source-maps"; 28 29 const expressionRegex = /\bfp\(\)/g; 30 31 export async function buildOriginalScopes( 32 selectedFrame, 33 client, 34 generatedScopes 35 ) { 36 if (!selectedFrame.originalVariables) { 37 throw new TypeError("(frame.originalVariables: XScopeVariables)"); 38 } 39 const originalVariables = selectedFrame.originalVariables; 40 const frameBase = originalVariables.frameBase || ""; 41 42 const inputs = []; 43 for (let i = 0; i < originalVariables.vars.length; i++) { 44 const { expr } = originalVariables.vars[i]; 45 const expression = expr 46 ? expr.replace(expressionRegex, frameBase) 47 : "void 0"; 48 49 inputs[i] = expression; 50 } 51 52 const results = await client.evaluateExpressions(inputs, { 53 frameId: selectedFrame.id, 54 }); 55 56 const variables = {}; 57 for (let i = 0; i < originalVariables.vars.length; i++) { 58 const { name } = originalVariables.vars[i]; 59 variables[name] = { value: results[i].result }; 60 } 61 62 const bindings = { 63 arguments: [], 64 variables, 65 }; 66 67 const { actor } = await generatedScopes; 68 const scope = { 69 type: "function", 70 scopeKind: "", 71 actor, 72 bindings, 73 parent: null, 74 function: null, 75 block: null, 76 }; 77 return { 78 mappings: {}, 79 scope, 80 }; 81 } 82 83 export function toggleMapScopes() { 84 return async function ({ dispatch, getState }) { 85 if (isMapScopesEnabled(getState())) { 86 dispatch({ type: "TOGGLE_MAP_SCOPES", mapScopes: false }); 87 return; 88 } 89 90 dispatch({ type: "TOGGLE_MAP_SCOPES", mapScopes: true }); 91 92 // Ignore the call if there is no selected frame (we are not paused?) 93 const state = getState(); 94 const selectedFrame = getSelectedFrame(state); 95 if (!selectedFrame) { 96 return; 97 } 98 99 if (getOriginalFrameScope(getState(), selectedFrame)) { 100 return; 101 } 102 103 // Also ignore the call if we didn't fetch the scopes for the selected frame 104 const scopes = getGeneratedFrameScope(getState(), selectedFrame); 105 if (!scopes) { 106 return; 107 } 108 109 dispatch(mapScopes(selectedFrame, Promise.resolve(scopes.scope))); 110 }; 111 } 112 113 export function mapScopes(selectedFrame, scopes) { 114 return async function (thunkArgs) { 115 const { getState, dispatch, client } = thunkArgs; 116 117 await dispatch({ 118 type: "MAP_SCOPES", 119 selectedFrame, 120 [PROMISE]: (async function () { 121 if (selectedFrame.isOriginal && selectedFrame.originalVariables) { 122 return buildOriginalScopes(selectedFrame, client, scopes); 123 } 124 125 // getMappedScopes is only specific to the sources where we map the variables 126 // in scope and so only need a thread context. Assert that we are on the same thread 127 // before retrieving a thread context. 128 validateSelectedFrame(getState(), selectedFrame); 129 130 return dispatch(getMappedScopes(scopes, selectedFrame)); 131 })(), 132 }); 133 }; 134 } 135 136 /** 137 * Get scopes mapped for a precise location. 138 * 139 * @param {Promise} scopes 140 * Can be null. Result of Commands.js's client.getFrameScopes 141 * @param {Objects locations 142 * Frame object, or custom object with 'location' and 'generatedLocation' attributes. 143 */ 144 export function getMappedScopes(scopes, locations) { 145 return async function (thunkArgs) { 146 const { getState, dispatch } = thunkArgs; 147 const generatedSource = locations.generatedLocation.source; 148 const source = locations.location.source; 149 150 if ( 151 !isMapScopesEnabled(getState()) || 152 !source || 153 !generatedSource || 154 generatedSource.isWasm || 155 source.isPrettyPrinted || 156 !source.isOriginal 157 ) { 158 return null; 159 } 160 161 // Load source text for the original source 162 await dispatch(loadOriginalSourceText(source)); 163 164 const generatedSourceActor = getFirstSourceActorForGeneratedSource( 165 getState(), 166 generatedSource.id 167 ); 168 169 // Also load source text for its corresponding generated source 170 await dispatch(loadGeneratedSourceText(generatedSourceActor)); 171 172 try { 173 const content = 174 // load original source text content 175 getSettledSourceTextContent(getState(), locations.location); 176 177 return await buildMappedScopes( 178 source, 179 content && isFulfilled(content) 180 ? content.value 181 : { type: "text", value: "", contentType: undefined }, 182 locations, 183 await scopes, 184 thunkArgs 185 ); 186 } catch (e) { 187 log(e); 188 return null; 189 } 190 }; 191 } 192 193 /** 194 * Used to map variables used within conditional and log breakpoints. 195 */ 196 export function getMappedScopesForLocation(location) { 197 return async function (thunkArgs) { 198 const { dispatch } = thunkArgs; 199 const mappedLocation = await getMappedLocation(location, thunkArgs); 200 return dispatch(getMappedScopes(null, mappedLocation)); 201 }; 202 }