1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/chrome/reps", 7 "arch/javascripttool", 8 "firebug/lib/events", 9 "firebug/lib/wrapper", 10 "firebug/js/stackFrame", 11 "firebug/lib/css", 12 "firebug/lib/array", 13 "firebug/lib/dom", 14 "firebug/chrome/menu" 15 ], 16 function(Obj, Firebug, FirebugReps, JavaScriptTool, Events, Wrapper, StackFrame, 17 Css, Arr, Dom, Menu) { 18 19 // ********************************************************************************************* // 20 // Constants 21 22 const Cc = Components.classes; 23 const Ci = Components.interfaces; 24 25 // ********************************************************************************************* // 26 // Callstack Panel 27 28 /** 29 * @Panel This panel is responsible for displaying a call-stack (list of function calls) 30 * at specified point of Javascript execution. It's used as a side panel for the Script 31 * panel. 32 */ 33 Firebug.CallstackPanel = function() {} 34 Firebug.CallstackPanel.prototype = Obj.extend(Firebug.Panel, 35 /** @lends Firebug.CallstackPanel */ 36 { 37 name: "callstack", 38 parentPanel: "script", 39 order: 1, 40 enableA11y: true, 41 deriveA11yFrom: "console", 42 43 initialize: function(context, doc) 44 { 45 Firebug.Panel.initialize.apply(this, arguments); 46 47 Firebug.connection.addListener(this); 48 }, 49 50 destroy: function(state) 51 { 52 Firebug.connection.removeListener(this); 53 54 Firebug.Panel.destroy.apply(this, arguments); 55 }, 56 57 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 58 59 onStartDebugging: function(context, frame) 60 { 61 // if we get a show() call then create and set new location 62 delete this.location; 63 64 // then we should reshow 65 if (this.visible) 66 this.show(); 67 68 if (FBTrace.DBG_STACK) 69 FBTrace.sysout("callstack; onStartDebugging "+this.visible, this) 70 }, 71 72 onStopDebugging: function(context) 73 { 74 if (FBTrace.DBG_STACK) 75 FBTrace.sysout("callstack; onStopDebugging ") 76 77 // clear the view 78 this.showStackTrace(null); 79 }, 80 81 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 82 83 show: function(state) 84 { 85 if (!this.location) 86 { 87 this.location = StackFrame.buildStackTrace(JavaScriptTool.Turn.currentFrame); 88 this.updateLocation(this.location); 89 } 90 // then we are lazy 91 92 if (FBTrace.DBG_STACK) 93 FBTrace.sysout("callstack.show state: "+state+" this.location: "+this.location, 94 {state: state, panel: this, 95 currentFrame: JavaScriptTool.Turn.currentFrame}); 96 97 if (state) 98 { 99 if (state.callstackToggles) 100 { 101 var frameElts = this.panelNode.getElementsByClassName("objectBox-stackFrame"); 102 for (var i = 0; i < frameElts.length; i++) 103 { 104 if (state.callstackToggles[i]) 105 FirebugReps.StackFrame.expandArguments(frameElts[i]); 106 } 107 } 108 109 if (state.selectedCallStackFrameIndex) 110 { 111 this.selectFrame(state.selectedCallStackFrameIndex) 112 } 113 } 114 }, 115 116 hide: function(state) 117 { 118 var frameElts = this.panelNode.getElementsByClassName("objectBox-stackFrame"); 119 state.callstackToggles = []; 120 for (var i = 0; i < frameElts.length; i++) 121 { 122 var item = frameElts[i]; 123 if (item.classList.contains("opened")) 124 state.callstackToggles[i] = true; 125 126 if (item.getAttribute("selected") == "true") 127 state.selectedCallStackFrameIndex = i + 1; // traces are 1 base 128 } 129 130 if (FBTrace.DBG_STACK) 131 FBTrace.sysout("callstack.hide state: "+state, state); 132 }, 133 134 supportsObject: function(object, type) 135 { 136 return (object instanceof StackFrame.StackTrace) || 137 (object instanceof Ci.jsdIStackFrame) || 138 (object instanceof StackFrame.StackFrame); 139 }, 140 141 // this.selection is a StackFrame in our this.location 142 updateSelection: function(object) 143 { 144 if (!this.location) // then we are lazy 145 { 146 this.location = StackFrame.buildStackTrace(JavaScriptTool.Turn.currentFrame); 147 this.updateLocation(this.location); 148 } 149 150 // The selection object should be StackFrame 151 if (object instanceof StackFrame.StackFrame) 152 { 153 var trace = this.location; 154 var frameIndex = object.getFrameIndex(); 155 if (frameIndex) 156 { 157 trace.currentFrameIndex = frameIndex; 158 this.selectFrame(frameIndex); 159 } 160 161 if (FBTrace.DBG_STACK) 162 FBTrace.sysout("Callstack updateSelection index:"+trace.currentFrameIndex+ 163 " StackFrame "+object, object); 164 } 165 else if (object instanceof Ci.jsdIStackFrame) 166 { 167 var trace = this.location; 168 if (trace) 169 { 170 trace.frames.forEach(function selectMatching(frame) 171 { 172 if (frame.nativeFrame === object) 173 this.select(frame); 174 }, this); 175 } 176 } 177 }, 178 179 // this.location is a StackTrace 180 updateLocation: function(object) 181 { 182 if (FBTrace.DBG_STACK) 183 FBTrace.sysout("callstack; updateLocation "+object, object); 184 185 // All paths lead to showStackTrace 186 if (object instanceof StackFrame.StackTrace) 187 this.showStackTrace(object); 188 else if (object instanceof Ci.jsdIStackFrame) 189 this.navigate(StackFrame.getCorrectedStackTrace(object, this.context)); 190 else if (object instanceof StackFrame.StackFrame) 191 this.showStackFrame(object); 192 }, 193 194 showStackFrame: function(frame) 195 { 196 var trace = StackFrame.buildStackTrace(frame); 197 this.navigate(trace); 198 }, 199 200 showStackTrace: function(trace) 201 { 202 Dom.clearNode(this.panelNode); 203 204 Css.setClass(this.panelNode, "objectBox-stackTrace"); 205 206 if (trace && trace.frames.length != 0) 207 { 208 var rep = Firebug.getRep(trace, this.context); 209 210 if (FBTrace.DBG_STACK) 211 FBTrace.sysout("callstack showStackFrame with "+trace.frames.length+" frames using " 212 +rep+" into "+this.panelNode, {trace: trace, rep:rep, node:this.panelNode}); 213 214 rep.tag.replace({object:trace}, this.panelNode); 215 216 if (trace.currentFrameIndex) 217 this.select(trace[trace.currentFrameIndex]); 218 219 Events.dispatch(this.fbListeners, "onStackCreated", [this]); 220 } 221 else 222 { 223 FirebugReps.Warning.tag.replace({object: "callstack.Execution_not_stopped"}, this.panelNode); 224 } 225 }, 226 227 selectFrame: function(frameIndex) 228 { 229 var frameElts = this.panelNode.getElementsByClassName("objectBox-stackFrame"); 230 this.selectItem(frameElts[frameIndex - 1]); 231 }, 232 233 selectItem: function(item) 234 { 235 if (this.selectedItem) 236 this.selectedItem.removeAttribute("selected"); 237 238 this.selectedItem = item; 239 240 if (item) 241 item.setAttribute("selected", "true"); 242 }, 243 244 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 245 // Menus 246 247 getOptionsMenuItems: function() 248 { 249 // an option handled by chrome.js 250 var items = [ 251 Menu.optionMenu("OmitObjectPathStack", "omitObjectPathStack", 252 "callstack.option.tip.Omit_Object_Path_Stack"), 253 ]; 254 return items; 255 }, 256 257 getContextMenuItems: function(nada, target) 258 { 259 var items = [ 260 { 261 label: "callstack.Expand_All", 262 tooltiptext: "callstack.tip.Expand_All", 263 command: Obj.bindFixed(this.onExpandAll, this, target) 264 }, 265 { 266 label: "callstack.Collapse_All", 267 tooltiptext: "callstack.tip.Collapse_All", 268 command: Obj.bindFixed(this.onCollapseAll, this, target) 269 } 270 ]; 271 return items; 272 }, 273 274 onExpandAll: function() 275 { 276 var elements = this.panelNode.querySelectorAll(".objectBox-stackFrame"); 277 for (var i=0; i<elements.length; i++) 278 FirebugReps.StackFrame.expandArguments(elements[i]); 279 }, 280 281 onCollapseAll: function() 282 { 283 var elements = this.panelNode.querySelectorAll(".objectBox-stackFrame"); 284 for (var i=0; i<elements.length; i++) 285 FirebugReps.StackFrame.collapseArguments(elements[i]); 286 }, 287 288 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 289 // Referents xxxHonza, xxxJJB: what is this? Incomplete feature for finding all 290 // references to a function 291 292 showReferents: function() 293 { 294 // Find obj.functionName for the currently executing function 295 // The general case is (expr_for_this).(expr_for_fn)(). 296 // expr navigates us using names from the scope chain 297 delete this.parent; 298 299 var frame = this.context.currentFrame; 300 var fnName = StackFrame.getFunctionName(frame.script, this.context, frame, true); 301 302 var referents = this.getReferents(frame, fnName); 303 }, 304 }); 305 306 // ********************************************************************************************* // 307 308 function Referent(containerName, container, propertyName, obj) 309 { 310 this._firebug = true; 311 // Reverse order, deep is first 312 this.values = [container]; 313 this.names = [propertyName, containerName]; 314 this.object = obj; 315 } 316 317 Referent.prototype = 318 { 319 getContainer: function() 320 { 321 return this.container; 322 }, 323 324 /** 325 * A string of identifiers separated by dots such that container[string] gives obj 326 */ 327 getObjectPathExpression: function() 328 { 329 this.objectPathExpr = Arr.cloneArray(this.names).reverse().join('.'); 330 return this.objectPathExpr; 331 }, 332 333 getObjectPathObjects: function() 334 { 335 this.objChain = Arr.cloneArray(this.values); 336 this.objChain.push(this.object); 337 this.objChain.reverse(); 338 return this.objChain; 339 }, 340 341 prependPath: function(p, segmentObject) 342 { 343 this.names.push(p); 344 this.values.push(segmentObject); 345 }, 346 }; 347 348 // ********************************************************************************************* // 349 350 function getReferents(frame, fnName) 351 { 352 if (FBTrace.DBG_STACK) 353 FBTrace.sysout('showReferents '+frame, frame); 354 355 // lookup the name of the function using frame.eval() -> function object 356 // use 'this' as a lookup scope since function calls can be obj.fn or just fn 357 var js = "with (this) {"+fnName +";}"; 358 359 var result = {}; 360 var ok = frame.eval(js, "", 1, result); 361 if (ok) 362 { 363 if (result.value instanceof Ci.jsdIValue) 364 { 365 if (FBTrace.DBG_STACK) 366 FBTrace.sysout("Firebug.Debugger.showReferents evaled "+js+" and got "+ 367 result.value, result); 368 369 try 370 { 371 var fn = result.value.getWrappedValue(); 372 var thisObject = Wrapper.unwrapIValueObject(frame.thisValue, Firebug.viewChrome); 373 var referents = findObjectPropertyPath("this", thisObject, fn, []); 374 375 if (FBTrace.DBG_STACK) 376 FBTrace.sysout("Firebug.Debugger.showReferents found from thisObject "+ 377 referents.length, {thisObject: thisObject, fn: fn, referents: referents}); 378 379 var containingScope = Wrapper.unwrapIValueObject(result.value.jsParent, 380 Firebug.viewwChrome); 381 382 if (FBTrace.DBG_STACK) 383 FBTrace.sysout("Firebug.Debugger.showReferents containingScope from "+ 384 result.value.jsParent.jsClassName, containingScope); 385 386 var scopeReferents = findObjectPropertyPath(result.value.jsParent.jsClassName, 387 containingScope, fn, []); 388 // Do we need to look in the entire scope chain? I think yes 389 390 if (FBTrace.DBG_STACK) 391 FBTrace.sysout("Firebug.Debugger.showReferents found scope referents "+ 392 scopeReferents.length, {containingScope: containingScope, fn: fn, 393 referents: scopeReferents}); 394 395 referents = referents.concat(scopeReferents); 396 FBTrace.sysout("Firebug.Debugger.showReferents found total referents "+ 397 referents.length, {fn: fn, referents: referents}); 398 399 for (var i = 0; i < referents.length; i++) 400 { 401 if (FBTrace.DBG_STACK) 402 FBTrace.sysout("Firebug.Debugger.showReferents found referent "+ 403 referents[i].getObjectPathExpression(), {fn: fn, referent: referents[i], 404 path:referents[i].getObjectPathObjects() }); 405 } 406 } 407 catch(exc) 408 { 409 if (FBTrace.DBG_STACK || FBTrace.DBG_ERRORS) 410 FBTrace.sysout("Firebug.Debugger.showReferents FAILED: "+exc, exc); 411 } 412 } 413 else 414 { 415 if (FBTrace.DBG_STACK || FBTrace.DBG_ERRORS) 416 FBTrace.sysout("Firebug.Debugger.showReferents evaled "+js+ 417 " but result.value not instanceof Ci.jsdIValue "+result.value, result); 418 } 419 return referents; 420 } 421 else 422 { 423 if (FBTrace.DBG_STACK || FBTrace.DBG_ERRORS) 424 FBTrace.sysout("Firebug.Debugger.showReferents eval failed with "+ok+" result "+ 425 result.value, result); 426 } 427 } 428 429 // ********************************************************************************************* // 430 // Registration 431 432 Firebug.registerPanel(Firebug.CallstackPanel); 433 434 return Firebug.CallstackPanel; 435 436 // ********************************************************************************************* // 437 }); 438