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