inlinePreview.js (7110B)
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 getSelectedFrameInlinePreviews, 7 getSelectedLocation, 8 getSelectedScope, 9 } from "../../selectors/index"; 10 import { features } from "../../utils/prefs"; 11 import { getEditor } from "../../utils/editor/index"; 12 import { validateSelectedFrame } from "../../utils/context"; 13 14 /** 15 * Update the inline previews for the currently selected frame. 16 */ 17 export function generateInlinePreview(selectedFrame) { 18 return async function (thunkArgs) { 19 const { dispatch, getState } = thunkArgs; 20 if (!features.inlinePreview) { 21 return null; 22 } 23 24 // Avoid regenerating inline previews when we already have preview data 25 if (getSelectedFrameInlinePreviews(getState())) { 26 return null; 27 } 28 29 const scope = getSelectedScope(getState()); 30 if (!scope || !scope.bindings) { 31 return null; 32 } 33 34 const allPreviews = await getPreviews(selectedFrame, scope, thunkArgs); 35 // Sort previews by line and column so they're displayed in the right order in the editor 36 allPreviews.sort((previewA, previewB) => { 37 if (previewA.line < previewB.line) { 38 return -1; 39 } 40 if (previewA.line > previewB.line) { 41 return 1; 42 } 43 // If we have the same line number 44 return previewA.column < previewB.column ? -1 : 1; 45 }); 46 47 const previews = {}; 48 for (const preview of allPreviews) { 49 const { line } = preview; 50 if (!previews[line]) { 51 previews[line] = []; 52 } 53 previews[line].push(preview); 54 } 55 56 validateSelectedFrame(getState(), selectedFrame); 57 58 return dispatch({ 59 type: "ADD_INLINE_PREVIEW", 60 selectedFrame, 61 previews, 62 }); 63 }; 64 } 65 /** 66 * Creates all the previews 67 * 68 * @param {object} selectedFrame 69 * @param {object} scope - Scope from the platform 70 * @param {object} thunkArgs 71 * @returns 72 */ 73 async function getPreviews(selectedFrame, scope, thunkArgs) { 74 const { client, getState } = thunkArgs; 75 76 // It's important to use selectedLocation, because we don't know 77 // if we'll be viewing the original or generated frame location 78 const selectedLocation = getSelectedLocation(getState()); 79 if (!selectedLocation) { 80 return []; 81 } 82 83 const editor = getEditor(); 84 if (editor.isWasm) { 85 return []; 86 } 87 88 const allPreviews = []; 89 const seenBindings = {}; 90 91 const bindingReferences = await editor.getBindingReferences( 92 selectedLocation, 93 scope 94 ); 95 validateSelectedFrame(getState(), selectedFrame); 96 97 for (const level in bindingReferences) { 98 for (const name in bindingReferences[level]) { 99 const valueActorID = bindingReferences[level][name].value?.actor; 100 // Ignore any binding with the same value which has already been displayed. 101 // This might occur if a variable gets hoisted and is available to the local and global scope. 102 if (seenBindings[name] && seenBindings[name] == valueActorID) { 103 continue; 104 } 105 const previews = await generatePreviewsForBinding( 106 bindingReferences[level][name], 107 selectedLocation.line, 108 name, 109 client, 110 selectedFrame.thread 111 ); 112 seenBindings[name] = valueActorID; 113 allPreviews.push(...previews); 114 } 115 } 116 return allPreviews; 117 } 118 119 /** 120 * Generates the previews from the binding information 121 * 122 * @param {object} bindingData - Scope binding data from the AST about a particular variable/argument at a particular level in the scope. 123 * @param {number} pausedOnLine - The current line we are paused on 124 * @param {string} name - Name of binding from the platfom scopes 125 * @param {object} client - Client object for loading properties 126 * @param {object} thread - Thread used to get the expressions values 127 * @returns 128 */ 129 async function generatePreviewsForBinding( 130 bindingData, 131 pausedOnLine, 132 name, 133 client, 134 thread 135 ) { 136 if (!bindingData) { 137 return []; 138 } 139 140 // Show a variable only once ( an object and it's child property are 141 // counted as different ) 142 const identifiers = new Set(); 143 const previews = []; 144 // We start from end as we want to show values besides variable 145 // located nearest to the breakpoint 146 for (let i = bindingData.refs.length - 1; i >= 0; i--) { 147 const ref = bindingData.refs[i]; 148 // Lines in CM6 is 1-based 149 const line = ref.start.line; 150 const column = ref.start.column; 151 // We don't want to render inline preview below the paused line 152 if (line >= pausedOnLine) { 153 continue; 154 } 155 156 if (bindingData.value == undefined) { 157 continue; 158 } 159 160 const { displayName, displayValue } = await getExpressionNameAndValue( 161 name, 162 bindingData.value, 163 ref, 164 client, 165 thread 166 ); 167 168 // Variable with same name exists, display value of current or 169 // closest to the current scope's variable 170 if (identifiers.has(displayName)) { 171 continue; 172 } 173 identifiers.add(displayName); 174 175 previews.push({ 176 line, 177 column, 178 // This attribute helps distinguish pause from trace previews 179 type: "paused", 180 name: displayName, 181 value: displayValue, 182 }); 183 } 184 return previews; 185 } 186 187 /** 188 * Get the name and value details to be displayed in the inline preview 189 * 190 * @param {string} name - Binding name 191 * @param {string} value - Binding value which is the Enviroment object actor form 192 * @param {object} ref - Binding reference 193 * @param {object} client - Client object for loading properties 194 * @param {string} thread - Thread used to get the expression values 195 * @returns 196 */ 197 async function getExpressionNameAndValue(name, value, ref, client, thread) { 198 let displayName = name; 199 let displayValue = value; 200 // We want to show values of properties of objects only and not 201 // function calls on other data types like someArr.forEach etc.. 202 let properties = null; 203 if (value.actor && value.class === "Object") { 204 properties = await client.loadObjectProperties( 205 { 206 name, 207 path: name, 208 contents: { value }, 209 }, 210 thread 211 ); 212 } 213 214 let { meta } = ref; 215 // Presence of meta property means expression contains child property 216 // reference eg: objName.propName 217 while (meta) { 218 // Only variables of type Object will have properties 219 if (!properties) { 220 displayName += `.${meta.property}`; 221 // Initially properties will be an array, after that it will be an object 222 } else if (displayValue === value) { 223 const property = properties.find(prop => prop.name === meta.property); 224 displayValue = property?.contents.value; 225 displayName += `.${meta.property}`; 226 } else if (displayValue?.preview?.ownProperties) { 227 const { ownProperties } = displayValue.preview; 228 Object.keys(ownProperties).forEach(prop => { 229 if (prop === meta.property) { 230 displayValue = ownProperties[prop].value; 231 displayName += `.${meta.property}`; 232 } 233 }); 234 } 235 meta = meta.parent; 236 } 237 238 return { displayName, displayValue }; 239 }