function.mjs (6027B)
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 /* eslint no-shadow: ["error", { "allow": ["name"] }] */ 6 7 import PropTypes from "resource://devtools/client/shared/vendor/react-prop-types.mjs"; 8 import { 9 button, 10 span, 11 } from "resource://devtools/client/shared/vendor/react-dom-factories.mjs"; 12 13 import { getGripType, cropString, wrapRender } from "./rep-utils.mjs"; 14 import { MODE } from "./constants.mjs"; 15 16 const IGNORED_SOURCE_URLS = ["debugger eval code"]; 17 18 /** 19 * This component represents a template for Function objects. 20 */ 21 22 FunctionRep.propTypes = { 23 object: PropTypes.object.isRequired, 24 onViewSourceInDebugger: PropTypes.func, 25 shouldRenderTooltip: PropTypes.bool, 26 }; 27 28 function FunctionRep(props) { 29 const { 30 object: grip, 31 onViewSourceInDebugger, 32 recordTelemetryEvent, 33 shouldRenderTooltip, 34 } = props; 35 36 let jumpToDefinitionButton; 37 38 // Test to see if we should display the link back to the original function definition 39 if ( 40 onViewSourceInDebugger && 41 grip.location && 42 grip.location.url && 43 !IGNORED_SOURCE_URLS.includes(grip.location.url) 44 ) { 45 jumpToDefinitionButton = button({ 46 className: "jump-definition", 47 draggable: false, 48 title: "Jump to definition", 49 onClick: async e => { 50 // Stop the event propagation so we don't trigger ObjectInspector 51 // expand/collapse. 52 e.stopPropagation(); 53 if (recordTelemetryEvent) { 54 recordTelemetryEvent("jump_to_definition"); 55 } 56 57 onViewSourceInDebugger(grip.location); 58 }, 59 }); 60 } 61 62 const elProps = { 63 "data-link-actor-id": grip.actor, 64 className: "objectBox objectBox-function", 65 // Set dir="ltr" to prevent parentheses from 66 // appearing in the wrong direction 67 dir: "ltr", 68 }; 69 70 const parameterNames = (grip.parameterNames || []).filter(Boolean); 71 const fnTitle = getFunctionTitle(grip, props); 72 const fnName = getFunctionName(grip, props); 73 74 if (grip.isClassConstructor) { 75 const classTitle = getClassTitle(grip, props); 76 const classBodyTooltip = getClassBody(parameterNames, true, props); 77 const classTooltip = `${classTitle ? classTitle.props.children : ""}${ 78 fnName ? fnName : "" 79 }${classBodyTooltip.join("")}`; 80 81 elProps.title = shouldRenderTooltip ? classTooltip : null; 82 83 return span( 84 elProps, 85 classTitle, 86 fnName, 87 ...getClassBody(parameterNames, false, props), 88 jumpToDefinitionButton 89 ); 90 } 91 92 const fnTooltip = `${fnTitle ? fnTitle.props.children : ""}${ 93 fnName ? fnName : "" 94 }(${parameterNames.join(", ")})`; 95 96 elProps.title = shouldRenderTooltip ? fnTooltip : null; 97 98 const returnSpan = span( 99 elProps, 100 fnTitle, 101 fnName, 102 "(", 103 ...getParams(parameterNames), 104 ")", 105 jumpToDefinitionButton 106 ); 107 108 return returnSpan; 109 } 110 111 function getClassTitle() { 112 return span( 113 { 114 className: "objectTitle", 115 }, 116 "class " 117 ); 118 } 119 120 function getFunctionTitle(grip, props) { 121 const { mode } = props; 122 123 if (mode === MODE.TINY && !grip.isGenerator && !grip.isAsync) { 124 return null; 125 } 126 127 let title = mode === MODE.TINY ? "" : "function "; 128 129 if (grip.isGenerator) { 130 title = mode === MODE.TINY ? "* " : "function* "; 131 } 132 133 if (grip.isAsync) { 134 title = `${"async" + " "}${title}`; 135 } 136 137 return span( 138 { 139 className: "objectTitle", 140 }, 141 title 142 ); 143 } 144 145 /** 146 * Returns a ReactElement representing the function name. 147 * 148 * @param {object} grip : Function grip 149 * @param {object} props: Function rep props 150 */ 151 function getFunctionName(grip, props = {}) { 152 let { functionName } = props; 153 let name; 154 155 if (functionName) { 156 const end = functionName.length - 1; 157 functionName = 158 functionName.startsWith('"') && functionName.endsWith('"') 159 ? functionName.substring(1, end) 160 : functionName; 161 } 162 163 if ( 164 grip.displayName != undefined && 165 functionName != undefined && 166 grip.displayName != functionName 167 ) { 168 name = `${functionName}:${grip.displayName}`; 169 } else { 170 name = cleanFunctionName( 171 grip.userDisplayName || 172 grip.displayName || 173 grip.name || 174 props.functionName || 175 "" 176 ); 177 } 178 179 return cropString(name, 100); 180 } 181 182 const objectProperty = /([\w\d\$]+)$/; 183 const arrayProperty = /\[(.*?)\]$/; 184 const functionProperty = /([\w\d]+)[\/\.<]*?$/; 185 const annonymousProperty = /([\w\d]+)\(\^\)$/; 186 187 /** 188 * Decodes an anonymous naming scheme that 189 * spider monkey implements based on "Naming Anonymous JavaScript Functions" 190 * http://johnjbarton.github.io/nonymous/index.html 191 * 192 * @param {string} name : Function name to clean up 193 * @returns String 194 */ 195 function cleanFunctionName(name) { 196 for (const reg of [ 197 objectProperty, 198 arrayProperty, 199 functionProperty, 200 annonymousProperty, 201 ]) { 202 const match = reg.exec(name); 203 if (match) { 204 return match[1]; 205 } 206 } 207 208 return name; 209 } 210 211 function getClassBody(constructorParams, textOnly = false, props) { 212 const { mode } = props; 213 214 if (mode === MODE.TINY) { 215 return []; 216 } 217 218 return [" {", ...getClassConstructor(textOnly, constructorParams), "}"]; 219 } 220 221 function getClassConstructor(textOnly = false, parameterNames) { 222 if (parameterNames.length === 0) { 223 return []; 224 } 225 226 if (textOnly) { 227 return [` constructor(${parameterNames.join(", ")}) `]; 228 } 229 return [" constructor(", ...getParams(parameterNames), ") "]; 230 } 231 232 function getParams(parameterNames) { 233 return parameterNames.flatMap((param, index, arr) => { 234 return [ 235 span({ className: "param" }, param), 236 index === arr.length - 1 ? "" : span({ className: "delimiter" }, ", "), 237 ]; 238 }); 239 } 240 241 // Registration 242 function supportsObject(grip, noGrip = false) { 243 return getGripType(grip, noGrip) === "Function"; 244 } 245 246 const rep = wrapRender(FunctionRep); 247 248 // Exports from this module 249 export { rep, supportsObject, cleanFunctionName, getFunctionName };