1 /* See license.txt for terms of usage */
  2 
  3 // Debug lines are marked with  at column 120
  4 // Use variable name "fileName" for href returned by JSD, file:/ not same as DOM
  5 // Use variable name "url" for normalizedURL, file:/// comparable to DOM
  6 // Convert from fileName to URL with normalizeURL
  7 // We probably don't need denormalizeURL since we don't send .fileName back to JSD
  8 
  9 // ********************************************************************************************* //
 10 // Constants
 11 
 12 const Cc = Components.classes;
 13 const Ci = Components.interfaces;
 14 const Cu = Components.utils;
 15 
 16 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 17 
 18 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 19 const DebuggerService = Cc["@mozilla.org/js/jsd/debugger-service;1"];
 20 const ConsoleService = Cc["@mozilla.org/consoleservice;1"];
 21 const Timer = Cc["@mozilla.org/timer;1"];
 22 const ObserverServiceFactory = Cc["@mozilla.org/observer-service;1"];
 23 
 24 const jsdIDebuggerService = Ci.jsdIDebuggerService;
 25 const jsdIScript = Ci.jsdIScript;
 26 const jsdIStackFrame = Ci.jsdIStackFrame;
 27 const jsdICallHook = Ci.jsdICallHook;
 28 const jsdIExecutionHook = Ci.jsdIExecutionHook;
 29 const jsdIErrorHook = Ci.jsdIErrorHook;
 30 const jsdIFilter = Components.interfaces.jsdIFilter;
 31 const nsISupports = Ci.nsISupports;
 32 const nsIPrefBranch = Ci.nsIPrefBranch;
 33 const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
 34 const nsIFactory = Ci.nsIFactory;
 35 const nsIConsoleService = Ci.nsIConsoleService;
 36 const nsITimer = Ci.nsITimer;
 37 const nsITimerCallback = Ci.nsITimerCallback;
 38 
 39 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 40 
 41 const NS_ERROR_NO_INTERFACE = Components.results.NS_ERROR_NO_INTERFACE;
 42 const NS_ERROR_NOT_IMPLEMENTED = Components.results.NS_ERROR_NOT_IMPLEMENTED;
 43 const NS_ERROR_NO_AGGREGATION = Components.results.NS_ERROR_NO_AGGREGATION;
 44 
 45 const PCMAP_SOURCETEXT = jsdIScript.PCMAP_SOURCETEXT;
 46 const PCMAP_PRETTYPRINT = jsdIScript.PCMAP_PRETTYPRINT;
 47 
 48 const COLLECT_PROFILE_DATA = jsdIDebuggerService.COLLECT_PROFILE_DATA;
 49 const DISABLE_OBJECT_TRACE = jsdIDebuggerService.DISABLE_OBJECT_TRACE;
 50 const HIDE_DISABLED_FRAMES = jsdIDebuggerService.HIDE_DISABLED_FRAMES;
 51 const DEBUG_WHEN_SET = jsdIDebuggerService.DEBUG_WHEN_SET;
 52 const MASK_TOP_FRAME_ONLY = jsdIDebuggerService.MASK_TOP_FRAME_ONLY;
 53 
 54 const TYPE_FUNCTION_CALL = jsdICallHook.TYPE_FUNCTION_CALL;
 55 const TYPE_FUNCTION_RETURN = jsdICallHook.TYPE_FUNCTION_RETURN;
 56 const TYPE_TOPLEVEL_START = jsdICallHook.TYPE_TOPLEVEL_START;
 57 const TYPE_TOPLEVEL_END = jsdICallHook.TYPE_TOPLEVEL_END;
 58 
 59 const RETURN_CONTINUE = jsdIExecutionHook.RETURN_CONTINUE;
 60 const RETURN_VALUE = jsdIExecutionHook.RETURN_RET_WITH_VAL;
 61 const RETURN_THROW_WITH_VAL = jsdIExecutionHook.RETURN_THROW_WITH_VAL;
 62 const RETURN_CONTINUE_THROW = jsdIExecutionHook.RETURN_CONTINUE_THROW;
 63 
 64 const NS_OS_TEMP_DIR = "TmpD";
 65 
 66 const STEP_OVER = 1;
 67 const STEP_INTO = 2;
 68 const STEP_OUT = 3;
 69 const STEP_SUSPEND = 4;
 70 
 71 const TYPE_ONE_SHOT = nsITimer.TYPE_ONE_SHOT;
 72 
 73 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 74 
 75 const BP_NORMAL = 1;
 76 const BP_MONITOR = 2;
 77 const BP_UNTIL = 4;
 78 const BP_ONRELOAD = 8;  // XXXjjb: This is a mark for the UI to test
 79 const BP_ERROR = 16;
 80 const BP_TRACE = 32; // BP used to initiate traceCalls
 81 
 82 const LEVEL_TOP = 1;
 83 const LEVEL_EVAL = 2;
 84 const LEVEL_EVENT = 3;
 85 
 86 const COMPONENTS_FILTERS = [
 87     new RegExp("^(file:/.*/)extensions/%7B[\\da-fA-F]{8}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{4}-[\\da-fA-F]{12}%7D/components/.*\\.js$"),
 88     new RegExp("^(file:/.*/)extensions/firebug@software\\.joehewitt\\.com/modules/.*\\.js$"),
 89     new RegExp("^(file:/.*/extensions/)\\w+@mozilla\\.org/components/.*\\.js$"),
 90     new RegExp("^(file:/.*/components/)ns[A-Z].*\\.js$"),
 91     new RegExp("^(file:/.*/modules/)firebug-[^\\.]*\\.js$"),
 92     new RegExp("^(file:/.*/Contents/MacOS/extensions/.*/components/).*\\.js$"),
 93     new RegExp("^(file:/.*/modules/).*\\.jsm$"),
 94 ];
 95 
 96 const reDBG = /DBG_(.*)/;
 97 const reXUL = /\.xul$|\.xml$/;
 98 
 99 Cu.import("resource://firebug/prefLoader.js");
100 
101 var getPref = PrefLoader.getPref;
102 
103 // ********************************************************************************************* //
104 // Globals
105 
106 //https://developer.mozilla.org/en/Using_JavaScript_code_modules
107 var EXPORTED_SYMBOLS = ["fbs"];
108 
109 var jsd, prefs;
110 var observerService;
111 
112 var contextCount = 0;
113 
114 var urlFilters = [
115     'chrome://',
116     'XStringBundle',
117     'x-jsd:ppbuffer?type=function', // internal script for pretty printing
118 ];
119 
120 var clients = [];
121 var debuggers = [];
122 var netDebuggers = [];
123 var scriptListeners = [];
124 
125 var hookFrameCount = 0;
126 
127 var haltObject = null;  // For reason unknown, fbs.haltDebugger will not work.
128 
129 var breakpointCount = 0;
130 
131 // These are an optimization I guess, marking whether we are using this feature anywhere.
132 var disabledCount = 0;
133 var monitorCount = 0;
134 var conditionCount = 0;
135 var runningUntil = null;
136 
137 var errorBreakpoints = [];
138 
139 var profileCount = 0;
140 var profileStart;
141 
142 var enabledDebugger = false;
143 var reportNextError = false;
144 var errorInfo = null;
145 
146 var timer = Timer.createInstance(nsITimer);
147 var waitingForTimer = false;
148 
149 Cu.import("resource://firebug/fbtrace.js");
150 
151 // ********************************************************************************************* //
152 
153 function frameId(frame, depth)
154 {
155     if (frame)
156         return frame.script.tag+"@"+frame.line+"^"+depth;
157     else
158         return "noIdForNoframe";
159 }
160 
161 // xxxHonza: duplicated in lib.js, there should be a shared module with this API.
162 function extend(l,r)
163 {
164     var newOb = {};
165     for (var n in l)
166         newOb[n] = l[n];
167     for (var n in r)
168         newOb[n] = r[n];
169     return newOb;
170 }
171 
172 // ********************************************************************************************* //
173 // jsdICallHook or jsdIExecutionHook.
174 
175 var jsdHandlers =
176 {
177     hooks: [],
178 
179     // Stage for activation when 'hook' is called
180     add: function(aHook)
181     {
182         if (!aHook)
183             ERROR("firebug-service.jsdHandlers.add: null hook");
184 
185         this.hooks.push(aHook);
186 
187         if (FBTrace.DBG_FBS_STEP)
188             FBTrace.sysout("fbs.Hooks.add; " + aHook.mode + ", active hooks: " +
189                 this.hooks.length);
190     },
191 
192     remove: function(aHook)
193     {
194         var i = this.hooks.indexOf(aHook);
195         if (i != -1)
196         {
197             this.hooks.splice(i, 1);
198 
199             if (FBTrace.DBG_FBS_STEP)
200                 FBTrace.sysout("fbs.Hooks.remove; " + aHook.mode + ", active hooks: " +
201                     this.hooks.length);
202         }
203         else
204         {
205             ERROR("firebug-service.Hooks.unhook ERROR, no such hook " +
206                 aHook.name, {aHook: aHook, Hooks: this});
207         }
208     },
209 
210     // activate hooks
211     hook: function(frame)
212     {
213         if (FBTrace.DBG_FBS_STEP)
214             FBTrace.sysout("fbs.start hooks " + this.hooks.length + " hooks active " +
215                 frameToString(frame), this);
216 
217         for (var i = 0; i < this.hooks.length; i++)
218         {
219             var aHook = this.hooks[i];
220 
221             aHook.hook(frame);
222 
223             if ("onFunctionCall" in aHook || "onFunctionReturn" in aHook)
224                 fbs.hookFunctions();
225 
226             if ("onInterrupt" in aHook)
227                 fbs.hookInterrupts(frame);
228         }
229     },
230 
231     unhook: function(frame)
232     {
233         if (FBTrace.DBG_FBS_STEP)
234             FBTrace.sysout("fbs.stop hooks "+this.hooks.length+" hooks active", this);
235 
236         this.checkForUnhookFunctions(frame);
237         this.checkForUnhookInterrupts(frame);
238     },
239 
240     checkForUnhookFunctions: function(frame)
241     {
242         for (var i = 0; i < this.hooks.length; i++)
243         {
244             var aHook = this.hooks[i];
245 
246             aHook.unhook(frame);
247 
248             if ("onFunctionCall" in aHook || "onFunctionReturn" in aHook)
249                 return;
250         }
251 
252         fbs.unhookFunctions();
253     },
254 
255     checkForUnhookInterrupts: function(frame)
256     {
257         for (var i = 0; i < this.hooks.length; i++)
258         {
259             var aHook = this.hooks[i];
260 
261             if ("onInterrupt" in aHook)
262                 return;
263         }
264 
265         fbs.unhookInterrupts();   // none found
266     },
267 
268     dispatch: function(methodName, frame, type, rv)
269     {
270         for (var i = 0; i < this.hooks.length; i++)
271         {
272             var aHook = this.hooks[i];
273             if (methodName in aHook)
274             {
275                 if (FBTrace.DBG_FBS_STEP && FBTrace.DBG_DISPATCH)
276                     FBTrace.sysout("fbs.jsdHandler.dispatch " + methodName +
277                         " to " + aHook + " " + getCallFromType(type) + " frame: " +
278                         frameToString(frame), this);
279 
280                 var rc = aHook[methodName].apply(aHook, [frame, type, rv]);
281                 if (typeof(rc) != "undefined" || rc !== RETURN_CONTINUE)
282                     return rc;
283             }
284         }
285 
286         return RETURN_CONTINUE;
287     }
288 };
289 
290 // ********************************************************************************************* //
291 // Break on Next
292 
293 function BreakOnNextCall(debuggr, context)
294 {
295     this.debuggr = debuggr;
296     this.context = context;
297 }
298 
299 BreakOnNextCall.prototype =
300 {
301     mode: "BON",
302 
303     hook: function(frame)
304     {
305     },
306 
307     unhook: function(frame)
308     {
309     },
310 
311     // the frame will be running the calling script
312     hit: function(frame, type)
313     {
314         fbs.cancelBreakOnNextCall(this.debuggr, this.context);
315 
316         var rv = {};
317         return fbs.routeBreakToDebuggr(frame, type, rv, this.debuggr);
318     },
319 
320     onFunctionCall: function(frame, type)
321     {
322         if (!this.context || !this.context.sourceFileByTag)
323             return ERROR("onFunctionCall ERROR invalid context ");
324 
325         var lucky = this.context.getSourceFileByTag(frame.script.tag);
326         if (!lucky) // then function running the frame is not in this context
327         {
328             // then we could be running an outer function from a new compilation unit
329             if (!frame.callingFrame)
330             {
331                 var val = {};
332 
333                 // then the function could have just been added to the context
334                 if (fbs.isTopLevelScript(frame, type, val))
335                     lucky = this.context.getSourceFileByTag(frame.script.tag);
336             }
337         }
338 
339         if (lucky) // then we hit in a function in our context
340         {
341             if (FBTrace.DBG_FBS_STEP)
342                 FBTrace.sysout("fbs.breakOnNextTopFunction hits at "+getCallFromType(type)+" at "+
343                     frame.script.fileName+" tag:"+(lucky?"LUCKY WINNER":frame.script.tag),
344                     framesToString(frame));
345 
346             return this.hit(frame, type);
347         }
348 
349         // else maybe we hit on a event unrelated to our context
350     },
351 };
352 
353 // Stack segments
354 // When frame.callingFrame is null then we are starting a new stack segment
355 //    FrameId                        track call stack
356 //   segment0.fnc1  !callingFrame  !this.callingFrameId
357 //   segment0.fnc2                  this.callingFrameId == segment0.fnc1
358 //   ...
359 //   segment1.fnc44  !callingFrame  this.callingFrameId == fnc2.segment0
360 //   segment1.fnc45
361 //
362 
363 // ********************************************************************************************* //
364 // Stepper: Step Out Implementation
365 
366 /**
367  * @class This object implements "step out" debugger feature. In other words run until
368  * the current function returns, then stop in the caller.
369  */
370 function OutStepper(debuggr, context)
371 {
372     this.debuggr = debuggr;
373     this.context = context;
374 
375     if (!this.debuggr)
376         ERROR("firebug-service.OutStepper no debuggr");
377 }
378 
379 OutStepper.prototype =
380 /** @lends OutStepper */
381 {
382     mode: "STEP_OUT",
383 
384     getCallingFrameId: function(frame)
385     {
386         if (frame.callingFrame)
387             return frameId(frame.callingFrame, this.depth);
388 
389         var debuggr = fbs.reFindDebugger(frame, this.debuggr);
390         if (debuggr && debuggr.breakContext)
391             return debuggr.breakContext.getName(); //  TODO segments
392     },
393 
394     hook: function(frame)
395     {
396         this.depth = 0;
397 
398         if (frame)
399         {
400             this.startFrameId = frameId(frame, 0);
401             this.callingFrameId = this.getCallingFrameId(frame);
402 
403             if (!this.callingFrameId)
404                 ERROR("OutStepper.hook cannot find callingFrame ", this);
405 
406             this.startFrameTag = frame.script.tag;
407         }
408         else
409         {
410             ERROR("OutStepper.hook no frame ");
411         }
412 
413         if (FBTrace.DBG_FBS_STEP)
414             FBTrace.sysout("fbs." + this.mode+" hook with frame "+frameToString(frame)+
415                 " with callingFrameId "+this.callingFrameId, this);
416 
417         return true;
418     },
419 
420     // the frame will be running the called script
421     onFunctionCall: function stepFunctionCall(frame, type)
422     {
423         var callingFrameId = this.getCallingFrameId(frame);
424 
425         if (this.callingFrameId === callingFrameId) // then it is our caller
426         {
427             this.depth++;
428             this.callingFrameId = callingFrameId;  // push new id for stepFunctionReturn
429 
430             if (FBTrace.DBG_FBS_STEP)
431                 FBTrace.sysout("fbs." + this.mode+" stepFunctionCall new depth "+this.depth+
432                     " new callingFrameId "+this.callingFrameId);
433         }
434 
435         // else someone else, ignore it
436     },
437 
438     // the frame will be running the called script
439     onFunctionReturn: function stepFunctionReturn(frame, type)
440     {
441         var callingFrameId = this.getCallingFrameId(frame);
442         if (this.callingFrameId === callingFrameId) // then it is our caller
443         {
444             if (this.depth) // but we are not back to our caller
445             {
446                 this.depth--;
447                 this.callingFrameId = callingFrameId; // recursion
448                 return;
449             }
450             else // then we are back to the frame we started on
451             {
452                 if (frame.callingFrame)
453                     return this.hit(frame.callingFrame, type);
454 
455                 if (FBTrace.DBG_FBS_STEP)
456                     FBTrace.sysout("fbs.OutStepper.onFunctionReturn no calling frame " +
457                         frameToString(frame) + ", " + getCallFromType(type), this);
458 
459                 jsdHandlers.unhook(frame);  // we are done here
460                 jsdHandlers.remove(this);
461                 return;
462             }
463         }
464 
465         // Then we are returning with out ever calling stepFunctionCall,
466         // but on a frame we care about.
467         if (!this.callingFrameId && callingFrameId)
468         {
469             // Then are returning from the frame we care about.
470             if (frame.script.tag === this.startFrameTag)
471             {
472                 if (frame.callingFrame)
473                     return this.hit(frame.callingFrame, type);
474                 else
475                     ERROR("Should be top level just exit", this);
476             }
477 
478             ERROR("Returning from a frame we care about but not one we know " +
479                 frameToString(frame), this);
480         }
481 
482         // else it's is not a frame we care about
483         if (FBTrace.DBG_FBS_STEP)
484             FBTrace.sysout("fbs." + this.mode + ".onFunctionReturn callingFrameId " +
485                 callingFrameId + " called frame " + frameToString(frame), this)
486     },
487 
488     unhook: function(frame)
489     {
490     },
491 
492     hit: function(frame, type, rv)
493     {
494         if (FBTrace.DBG_FBS_STEP)
495             FBTrace.sysout("fbs." + this.mode + " hit " + getCallFromType(type) + " at " +
496                 frameToString(frame), this);
497 
498         var debuggr = fbs.reFindDebugger(frame, this.debuggr);
499         if (debuggr)
500         {
501             jsdHandlers.unhook(frame);
502             jsdHandlers.remove(this);
503 
504             rv = {};
505 
506             return fbs.breakIntoDebugger(debuggr, frame, type);
507         }
508 
509         return ERROR("Hit but debuggr did not match "+this.debuggr.debuggerName+" in frame "+
510             frameToString(frame), this);
511     },
512 
513     toString: function()
514     {
515         if (!this.context.getName)
516             FBTrace.sysout("fbs.this.context.getName ", this.context);
517 
518         return this.mode + " for "+this.context.getName();
519     },
520 };
521 
522 // ********************************************************************************************* //
523 // Stepper: Step Over Implementation
524 
525 /**
526  * @class This oject implements "step over". I's like {@link OutStepper}, but run a single
527  * line in this function.
528  */
529 function LineStepper(debuggr, context)
530 {
531     this.context = context;
532     this.debuggr = debuggr;
533 }
534 
535 LineStepper.prototype = extend(OutStepper.prototype,
536 /** @lends LineStepper */
537 {
538     mode: "STEP_OVER",
539 
540     hook: function hookLineStepper(frame)
541     {
542         OutStepper.prototype.hook.apply(this, arguments); // hook functions
543 
544         this.lineFrameId = frameId(frame, this.depth);
545         this.stepFrameTag = frame.script.tag;
546 
547         if (FBTrace.DBG_FBS_STEP)
548             FBTrace.sysout("fbs." + this.mode + ".hook " + frameToString(frame) +
549                 " with lineFrameId " + this.lineFrameId, this);
550     },
551 
552     unhook: function unhookLineStepper(frame)
553     {
554         if (FBTrace.DBG_FBS_STEP)
555             FBTrace.sysout("fbs." + this.mode + ".hook; unhook " + frameToString(frame));
556     },
557 
558     // jsdIExecutionHook, onExecute
559     onInterrupt: function stepLine(frame, type, rv)
560     {
561         if (this.stepFrameTag !== frame.script.tag) // then we stepped into another function
562         {
563             // We'd have much bettter performance if we set a new OutStepper here then remove
564             // interrupt hook until it hits.
565             return RETURN_CONTINUE;
566         }
567 
568         // Sometimes the same line will have multiple interrupts, so check
569         // a unique id for the line and don't break until it changes
570         var frameLineId = frameId(frame, this.depth);
571 
572         if (FBTrace.DBG_FBS_STEP)
573             FBTrace.sysout("fbs." + this.mode + " interruptHook pc:" + frame.pc +
574                 " frameLineId: " + frameLineId + " vs " + this.lineFrameId + " running " +
575                 frame.script.tag + " of " + frame.script.fileName +
576                 " at " + frame.line + "." + frame.pc, this);
577 
578         if (frameLineId != this.lineFrameId)
579             return this.hit(frame, type, rv);
580         else
581             return RETURN_CONTINUE;
582     },
583 
584     toString: function()
585     {
586         if (!this.context.getName)
587             FBTrace.sysout("fbs.this.context.getName ", this.context);
588 
589         return this.mode + " for "+this.context.getName();
590     },
591 });
592 
593 // ********************************************************************************************* //
594 // Stepper: Step In Implementation
595 
596 /**
597  * @class This oject implements "step in". I's like {@link OutStepper}, but if the line
598  * calls a function, stop on its first line
599  */
600 function IntoStepper(debuggr, context)
601 {
602     this.context = context;
603     this.debuggr = debuggr;
604 }
605 
606 IntoStepper.prototype = extend(LineStepper.prototype,
607 /** @lends IntoStepper */
608 {
609     mode: "STEP_INTO",
610 
611     hook: function(frame)
612     {
613         LineStepper.prototype.hook.apply(this, arguments); // hook functions and interrupts
614     },
615 
616     // the frame will be running the called script
617     onFunctionCall: function intoFunctionCall(frame, type)
618     {
619         var callingFrame = frame.callingFrame;
620         if (callingFrame)
621         {
622             // Skip functions running in the frame that is not in this context (issue 3077)
623             var lucky = this.context.getSourceFileByTag(frame.script.tag);
624             if (!lucky)
625                 return;
626 
627             // then we stepped into from our caller
628             if (this.stepFrameTag === callingFrame.script.tag)
629                 return this.hit(frame, type);
630 
631             // else someone else, ignore it
632             if (FBTrace.DBG_FBS_STEP)
633                 FBTrace.sysout("fbs." + this.mode + ".intoFunctionCall no match " +
634                     this.stepFrameTag + " vs " + callingFrame.script.tag, this);
635         }
636 
637         // else this would be a top level call, do we want to check for
638         // another event from this context?
639     },
640 });
641 
642 // ********************************************************************************************* //
643 // Function Stepper/Tracer
644 
645 /**
646  * Tracer for learning about function stepping, not part of firebug
647  */
648 function LogFunctionStepper()
649 {
650     //xxxjjb: not defined this.initialize();
651 }
652 
653 LogFunctionStepper.prototype =
654 {
655     hook: function()
656     {
657         fbs.inDebuggerSetupStack = true;
658     },
659 
660     stop: function()
661     {
662         delete fbs.stackDescription;
663     },
664 
665     // the frame will be running the called script
666     onFunctionCall: function logFunctionCall(frame, type)
667     {
668         if (fbs.inDebuggerSetupStack) // then we are still in the debugger set up code
669             return;
670 
671         if (!fbs.stackDescription)
672             fbs.stackDescription = { oldestTag: frame.script.tag, depth: 1, entries: [] };
673         else
674             fbs.stackDescription.depth++;
675 
676         this.logFunction(frame, type);
677     },
678 
679     // the frame will be running the called script
680     onFunctionReturn: function logFunctionReturn(frame, type)
681     {
682         if (fbs.inDebuggerSetupStack) // then we are still in the debugger set up code
683         {
684             if (!frame.callingFrame) // then done with setup
685                 delete fbs.inDebuggerSetupStack;
686 
687             return;
688         }
689 
690         if (!fbs.stackDescription)
691             fbs.stackDescription = { oldestTag: "Return first!", depth: 0, entries: [] };
692         fbs.stackDescription.depth--;
693 
694         this.logFunction(frame, type);
695 
696         if (!frame.callingFrame)
697         {
698             var diff = (fbs.stackDescription.oldestTag !== frame.script.tag);
699 
700             if (FBTrace.DBG_FBS_STEP)
701                 FBTrace.sysout("fbs.Stack ends at depth "+fbs.stackDescription.depth +
702                     (diff ? " NO Match on tag " : " tags match"), fbs.stackDescription.entries);
703 
704             fbs.stackDescription.entries = [];
705         }
706 
707         if (!fbs.stackDescription.depth)
708             delete fbs.stackDescription;
709     },
710 
711     logFunction: function(frame, type)
712     {
713         var typeName = getCallFromType(type);
714         var actualFrames = countFrames(frame);
715         fbs.stackDescription.entries.push(""+fbs.stackDescription.depth+
716             ": "+typeName +
717             " (frameCount: "+actualFrames+") " +
718             " oldestTag "+fbs.stackDescription.oldestTag+
719             " running "+frame.script.tag+" of "+frame.script.fileName+" at "+
720             frame.line+"."+frame.pc);
721     },
722 };
723 
724 // ********************************************************************************************* //
725 // Firebug Service
726 
727 var fbs =
728 {
729     initialize: function()
730     {
731         if (FBTrace.DBG_FBS_ERRORS)
732             FBTrace.sysout("fbs.FirebugService Starting");
733 
734         fbs = this;
735 
736         this.wrappedJSObject = this;  // XXXjjb remove this and the one in debugger
737         this.timeStamp = new Date();  /* explore */
738 
739         Components.utils.import("resource://firebug/debuggerHalter.js");
740         fbs.debuggerHalter = debuggerHalter; // ref to a function in a file that passes the jsdIFilter
741 
742         fbs.restoreBreakpoints();
743 
744         this.onDebugRequests = 0;  // the number of times we called onError but did not call onDebug
745 
746 
747         if (FBTrace.DBG_FBS_ERRORS)
748             this.osOut("FirebugService Starting, FBTrace should be up\n");
749 
750         this.profiling = false;
751 
752         prefs = PrefService.getService(nsIPrefBranch);
753         fbs.prefDomain = "extensions.firebug";
754         prefs.addObserver(fbs.prefDomain, fbs, false);
755 
756         observerService = ObserverServiceFactory.getService(Ci.nsIObserverService);
757         observerService.addObserver(QuitApplicationGrantedObserver, "quit-application-granted", false);
758         observerService.addObserver(QuitApplicationRequestedObserver, "quit-application-requested", false);
759         observerService.addObserver(QuitApplicationObserver, "quit-application", false);
760 
761         this.scriptsFilter = "all";
762         this.alwayFilterURLsStarting = ["chrome://chromebug", "x-jsd:ppbuffer"];  // TODO allow override
763         this.onEvalScriptCreated.kind = "eval";
764         this.onTopLevelScriptCreated.kind = "top-level";
765         this.onEventScriptCreated.kind = "event";
766         this.onXULScriptCreated.kind = "xul";
767         this.pendingXULScripts = [];
768 
769         this.onXScriptCreatedByTag = {}; // fbs functions by script tag
770         this.nestedScriptStack = []; // scripts contained in leveledScript that have not been drained
771 
772         if (FBTrace.DBG_FBS_ERRORS)
773             FBTrace.sysout("fbs.FirebugService Initialized");
774     },
775 
776     osOut: function(str)
777     {
778         if (!this.outChannel)
779         {
780             try
781             {
782                 var appShellService = Components.classes["@mozilla.org/appshell/appShellService;1"].
783                     getService(Components.interfaces.nsIAppShellService);
784                 this.hiddenWindow = appShellService.hiddenDOMWindow;
785                 this.outChannel = "hidden";
786             }
787             catch(exc)
788             {
789                 consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
790                 consoleService.logStringMessage("Using consoleService because " +
791                     "nsIAppShellService.hiddenDOMWindow not available "+exc);
792 
793                 this.outChannel = "service";
794             }
795         }
796 
797         if (this.outChannel === "hidden")  // apparently can't call via JS function
798             this.hiddenWindow.dump(str);
799         else
800             consoleService.logStringMessage(str);
801     },
802 
803     shutdown: function()  // call disableDebugger first
804     {
805         timer = null;
806 
807         try
808         {
809             prefs.removeObserver(fbs.prefDomain, fbs, false);
810         }
811         catch (exc)
812         {
813             FBTrace.sysout("fbs.prefs.removeObserver ERROR "+exc, exc);
814         }
815 
816         try
817         {
818             observerService.removeObserver(QuitApplicationGrantedObserver, "quit-application-granted");
819             observerService.removeObserver(QuitApplicationRequestedObserver, "quit-application-requested");
820             observerService.removeObserver(QuitApplicationObserver, "quit-application");
821         }
822         catch (exc)
823         {
824             FBTrace.sysout("fbs.quit-application-observers removeObserver ERROR "+exc, exc);
825         }
826 
827         if (!jsd)
828             return;
829 
830         try
831         {
832             do
833             {
834                 var depth = jsd.exitNestedEventLoop();
835             }
836             while(depth > 0);
837         }
838         catch (exc)
839         {
840             // Seems to be the normal path...
841             // FBTrace.sysout("fbs.FirebugService, attempt to exitNestedEventLoop ERROR "+exc);
842         }
843 
844         // make sure to unregister all the hooks
845         var hookNames = ["error", "script", "breakpoint", "debugger", "debug", "interrupt", 
846             "throw", "topLevel", "function", "debug"];
847         for each (var hook in hookNames)
848         {
849             try {
850                 jsd[hook + "Hook"] = null;
851             }
852             catch (exc)
853             {
854                 FBTrace.sysout("fbs.quit-application-observers removeObserver ERROR "+exc, exc);
855             }
856         }
857         jsd = null;
858     },
859 
860     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
861     // nsISupports
862 
863     QueryInterface: function(iid)
864     {
865         if (!iid.equals(nsISupports))
866             throw NS_ERROR_NO_INTERFACE;
867 
868         return this;
869     },
870 
871     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
872     // nsIObserver
873 
874     observe: function(subject, topic, data)
875     {
876         if(topic != "nsPref:changed") return;
877         fbs.obeyPrefs();
878     },
879 
880     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
881 
882     registerClient: function(client)  // clients are essentially XUL windows
883     {
884         if (!client)
885             throw new Error("firebug-service cannot register client: "+client);
886 
887         clients.push(client);
888         return clients.length;
889     },
890 
891     unregisterClient: function(client)
892     {
893         for (var i = 0; i < clients.length; ++i)
894         {
895             if (clients[i] == client)
896             {
897                 clients.splice(i, 1);
898                 break;
899             }
900         }
901     },
902 
903     // first one in will be last one called. Returns state enabledDebugger
904     registerDebugger: function(debuggrWrapper)
905     {
906         var debuggr = debuggrWrapper.wrappedJSObject;
907 
908         if (debuggr)
909         {
910             var anyDebuggers = (debuggers.length === 1);
911 
912             var i = debuggers.indexOf(debuggr);
913             if (i === -1)
914             {
915                 debuggers.push(debuggr);
916                 if (!anyDebuggers)
917                     this.enableDebugger();
918             }
919 
920             if (FBTrace.DBG_FBS_FINDDEBUGGER  || FBTrace.DBG_ACTIVATION)
921                 FBTrace.sysout("fbs.registerDebugger have "+debuggers.length+
922                     " after reg debuggr.debuggerName: "+debuggr.debuggerName+" we are "+
923                     (enabledDebugger?"enabled":"not enabled")+" " + "On:"+(jsd?jsd.isOn:"no jsd")+
924                     " jsd.pauseDepth:"+(jsd?jsd.pauseDepth:"off"));
925         }
926         else
927         {
928             var err = new Error("firebug-service debuggers must have wrappedJSObject ");
929             err.debuggrWrapper = debuggrWrapper;
930             throw err;
931         }
932 
933         try
934         {
935             if (debuggr.suspendActivity)
936                 netDebuggers.push(debuggr);
937         }
938         catch(exc)
939         {
940         }
941 
942         try
943         {
944             if (debuggr.onScriptCreated) // TODO xxxjjb: I don't know who uses this, remove it?
945                 scriptListeners.push(debuggr);
946         }
947         catch(exc)
948         {
949         }
950 
951         return debuggers.length;  // 1.3.1 return to allow Debugger to check progress
952     },
953 
954     unregisterDebugger: function(debuggrWrapper)
955     {
956         var debuggr = debuggrWrapper.wrappedJSObject;
957 
958         for (var i = 0; i < debuggers.length; ++i)
959         {
960             if (debuggers[i] == debuggr)
961             {
962                 debuggers.splice(i, 1);
963                 break;
964             }
965         }
966 
967         for (var i = 0; i < netDebuggers.length; ++i)
968         {
969             if (netDebuggers[i] == debuggr)
970             {
971                 netDebuggers.splice(i, 1);
972                 break;
973             }
974         }
975 
976         for (var i = 0; i < scriptListeners.length; ++i)
977         {
978             if (scriptListeners[i] == debuggr)
979             {
980                 scriptListeners.splice(i, 1);
981                 break;
982             }
983         }
984 
985         if (debuggers.length == 0)
986             this.disableDebugger();
987 
988         if (FBTrace.DBG_FBS_FINDDEBUGGER || FBTrace.DBG_ACTIVATION)
989             FBTrace.sysout("fbs.unregisterDebugger have "+debuggers.length+
990                 " after unreg debuggr.debuggerName: "+debuggr.debuggerName+" we are "+
991                 (enabledDebugger?"enabled":"not enabled")+" jsd.isOn:"+(jsd?jsd.isOn:"no jsd"));
992 
993         return debuggers.length;
994     },
995 
996     lockDebugger: function()
997     {
998         if (this.locked)
999             return;
1000 
1001         this.locked = true;
1002 
1003         dispatch(debuggers, "onLock", [true]);
1004     },
1005 
1006     unlockDebugger: function()
1007     {
1008         if (!this.locked)
1009             return;
1010 
1011         this.locked = false;
1012 
1013         dispatch(debuggers, "onLock", [false]);
1014     },
1015 
1016     getDebuggerByName: function(name)
1017     {
1018         if (!name)
1019             return;
1020 
1021         for(var i = 0; i < debuggers.length; i++)
1022             if (debuggers[i].debuggerName === name)
1023                 return debuggers[i];
1024     },
1025 
1026     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1027 
1028     forceGarbageCollection: function()
1029     {
1030         jsd.GC(); // Force the engine to perform garbage collection.
1031     },
1032 
1033     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1034 
1035     enterNestedEventLoop: function(callback)
1036     {
1037         try
1038         {
1039             dispatch(netDebuggers, "suspendActivity");
1040             this.activitySuspended = true;
1041 
1042             fbs.nestedEventLoopDepth = jsd.enterNestedEventLoop(
1043             {
1044                 onNest: function()
1045                 {
1046                     dispatch(netDebuggers, "resumeActivity");
1047                     callback.onNest();
1048                 }
1049             });
1050         }
1051         catch(exc)
1052         {
1053             FBTrace.sysout("fbs.enterNestedEventLoop ERROR "+exc, exc);
1054         }
1055         finally
1056         {
1057             dispatch(netDebuggers, "resumeActivity");
1058             this.activitySuspended = false;
1059         }
1060 
1061         return fbs.nestedEventLoopDepth;
1062     },
1063 
1064     exitNestedEventLoop: function()
1065     {
1066         try
1067         {
1068             dispatch(netDebuggers, "suspendActivity");
1069             return jsd.exitNestedEventLoop();
1070         }
1071         catch (exc)
1072         {
1073             if (FBTrace.DBG_FBS_ERRORS)
1074                 FBTrace.sysout("fbs: jsd.exitNestedEventLoop ERROR " + exc, exc);
1075         }
1076     },
1077 
1078     /**
1079      * We are running JS code for Firebug, but we want to break into the debugger with
1080      * a stack frame.
1081      * 
1082      * @param debuggr Debugger object asking for break
1083      * @param fnOfFrame, function(frame) to run on break
1084      */
1085     halt: function(debuggr, fnOfFrame)
1086     {
1087         if (!debuggr || !fnOfFrame)
1088         {
1089             if (FBTrace.DBG_FBS_ERRORS)
1090                 FBTrace.sysout("fbs.halt call ERROR bad arguments", arguments);
1091 
1092             return null;
1093         }
1094 
1095         if (FBTrace.DBG_FBS_BP)
1096             FBTrace.sysout('fbs.halt jsd.isOn:'+jsd.isOn+' jsd.pauseDepth:'+jsd.pauseDepth+
1097                 " fbs.isChromeBlocked "+fbs.isChromeBlocked+"  jsd.debuggerHook: "+
1098                 jsd.debuggerHook, jsd.debuggerHook);
1099 
1100         // store for onDebugger
1101         haltObject = {haltDebugger: debuggr, haltCallBack: fnOfFrame};
1102 
1103         // call onDebugger via hook
1104         fbs.debuggerHalter();
1105         return fbs.haltReturnValue;
1106     },
1107 
1108     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1109     // Break on Next
1110 
1111     // deprecated API
1112     // xxxjjb: BON should be entirely implemented by breakOnNextCall object, right?
1113     // xxxhonza: Debugger.suspend should be removed and replaced by breakOnNextCall
1114 
1115     suspend: function(debuggr, context)
1116     {
1117         fbs.breakOnNextCall(debuggr, context);
1118     },
1119 
1120     breakOnNextCall: function(debuggr, context)
1121     {
1122         dispatch(debuggers, "onBreakingNext", [debuggr, context]);
1123 
1124         if (context.breakOnNextHook)
1125             ERROR("firebug-service.breakOnNextCall already active ", context);
1126 
1127         context.breakOnNextHook = new BreakOnNextCall(debuggr, context);
1128 
1129         jsdHandlers.add(context.breakOnNextHook);
1130         jsdHandlers.hook(); // no frame arg
1131     },
1132 
1133     cancelBreakOnNextCall: function(debuggr, context)
1134     {
1135         jsdHandlers.unhook(/* no frame argument */);
1136         jsdHandlers.remove(context.breakOnNextHook);
1137         delete context.breakOnNextHook;
1138     },
1139 
1140     runUntil: function(sourceFile, lineNo, startFrame, debuggr)
1141     {
1142         // TODO per context
1143         runningUntil = this.addBreakpoint(BP_UNTIL, sourceFile, lineNo, null, debuggr);
1144     },
1145 
1146     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1147 
1148     setBreakpoint: function(sourceFile, lineNo, props, debuggr)
1149     {
1150         var bp = this.addBreakpoint(BP_NORMAL, sourceFile, lineNo, props, debuggr);
1151         if (bp)
1152         {
1153             dispatch(debuggers, "onToggleBreakpoint", [sourceFile.href, lineNo, true, bp]);
1154             fbs.saveBreakpoints(sourceFile.href);  // after every call to onToggleBreakpoint
1155             return true;
1156         }
1157         return false;
1158     },
1159 
1160     clearBreakpoint: function(url, lineNo)
1161     {
1162         var bp = this.removeBreakpoint(BP_NORMAL, url, lineNo);
1163         if (bp)
1164         {
1165             dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, false, bp]);
1166             fbs.saveBreakpoints(url);
1167         }
1168         return bp;
1169     },
1170 
1171     enableBreakpoint: function(url, lineNo)
1172     {
1173         var bp = this.findBreakpoint(url, lineNo);
1174         if (bp && bp.type & BP_NORMAL)
1175         {
1176             bp.disabled &= ~BP_NORMAL;
1177             dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, true, bp]);
1178             fbs.saveBreakpoints(url);
1179             --disabledCount;
1180         }
1181         else
1182         {
1183             if (FBTrace.DBG_FBS_BP)
1184                 FBTrace.sysout("fbs.enableBreakpoint no find for "+lineNo+"@"+url);
1185         }
1186     },
1187 
1188     disableBreakpoint: function(url, lineNo)
1189     {
1190         var bp = this.findBreakpoint(url, lineNo);
1191         if (bp && bp.type & BP_NORMAL)
1192         {
1193             bp.disabled |= BP_NORMAL;
1194             ++disabledCount;
1195             dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, true, bp]);
1196             fbs.saveBreakpoints(url);
1197         }
1198         else
1199         {
1200             if (FBTrace.DBG_FBS_BP)
1201                 FBTrace.sysout("fbs.disableBreakpoint no find for "+lineNo+"@"+url);
1202         }
1203     },
1204 
1205     isBreakpointDisabled: function(url, lineNo)
1206     {
1207         var bp = this.findBreakpoint(url, lineNo);
1208         if (bp && bp.type & BP_NORMAL)
1209             return bp.disabled & BP_NORMAL;
1210         else
1211             return false;
1212     },
1213 
1214     setBreakpointCondition: function(sourceFile, lineNo, condition, debuggr)
1215     {
1216         var bp = this.findBreakpoint(sourceFile.href, lineNo);
1217         if (!bp)
1218             bp = this.addBreakpoint(BP_NORMAL, sourceFile, lineNo, null, debuggr);
1219 
1220         if (!bp)
1221             return;
1222 
1223         if (bp.hitCount <= 0 )
1224         {
1225             if (bp.condition && !condition)
1226                 --conditionCount;
1227             else if (condition && !bp.condition)
1228                 ++conditionCount;
1229         }
1230 
1231         bp.condition = condition;
1232 
1233         dispatch(debuggers, "onToggleBreakpoint", [sourceFile.href, lineNo, true, bp]);
1234 
1235         fbs.saveBreakpoints(sourceFile.href);
1236         return bp;
1237     },
1238 
1239     getBreakpointCondition: function(url, lineNo)
1240     {
1241         var bp = this.findBreakpoint(url, lineNo);
1242         return bp ? bp.condition : "";
1243     },
1244 
1245     clearAllBreakpoints: function(sourceFiles)
1246     {
1247         for (var i=0; i<sourceFiles.length; ++i)
1248         {
1249             var url = sourceFiles[i].href;
1250             if (!url)
1251                 continue;
1252 
1253             var urlBreakpointsTemp = fbs.getBreakpoints(url);
1254 
1255             if (FBTrace.DBG_FBS_BP)
1256             {
1257                 FBTrace.sysout("fbs.clearAllBreakpoints " + url + " urlBreakpoints: " +
1258                     (urlBreakpointsTemp ? urlBreakpointsTemp.length : "null"));
1259             }
1260 
1261             if (!urlBreakpointsTemp)
1262                 continue;
1263 
1264             // Clone before iteration the array is modified within the loop.
1265             var urlBreakpoints = [];
1266             urlBreakpoints.push.apply(urlBreakpoints, urlBreakpointsTemp);
1267 
1268             for (var ibp=0; ibp<urlBreakpoints.length; ibp++)
1269             {
1270                 var bp = urlBreakpoints[ibp];
1271                 this.clearBreakpoint(url, bp.lineNo);
1272             }
1273          }
1274     },
1275 
1276     // url is sourceFile.href, not jsd script.fileName
1277     enumerateBreakpoints: function(url, cb)
1278     {
1279         if (url)
1280         {
1281             var urlBreakpointsTemp = fbs.getBreakpoints(url);
1282             if (urlBreakpointsTemp)
1283             {
1284                 // Clone before iteration (the array can be modified in the callback).
1285                 var urlBreakpoints = [];
1286                 urlBreakpoints.push.apply(urlBreakpoints, urlBreakpointsTemp);
1287 
1288                 for (var i = 0; i < urlBreakpoints.length; ++i)
1289                 {
1290                     var bp = urlBreakpoints[i];
1291                     if (bp.type & BP_NORMAL && !(bp.type & BP_ERROR) )
1292                     {
1293                         if (bp.scriptsWithBreakpoint && bp.scriptsWithBreakpoint.length > 0)
1294                         {
1295                             var rc = cb.call.apply(bp, [url, bp.lineNo, bp, bp.scriptsWithBreakpoint]);
1296                             if (rc)
1297                                 return [bp];
1298                         }
1299                         else
1300                         {
1301                             var rc = cb.call.apply(bp, [url, bp.lineNo, bp]);
1302                             if (rc)
1303                                 return [bp];
1304                         }
1305                     }
1306                 }
1307             }
1308         }
1309         else
1310         {
1311             var bps = [];
1312             var urls = fbs.getBreakpointURLs();
1313             for (var i = 0; i < urls.length; i++)
1314                 bps.push(this.enumerateBreakpoints(urls[i], cb));
1315 
1316             return bps;
1317         }
1318     },
1319 
1320     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1321     // error breakpoints are a way of selecting breakpoint from the Console
1322 
1323     setErrorBreakpoint: function(sourceFile, lineNo, debuggr)
1324     {
1325         var url = sourceFile.href;
1326         var index = this.findErrorBreakpoint(url, lineNo);
1327         if (index == -1)
1328         {
1329             try
1330             {
1331                 var bp = this.addBreakpoint(BP_NORMAL | BP_ERROR, sourceFile, lineNo, null, debuggr);
1332                 if (bp)
1333                 {
1334                     errorBreakpoints.push({href: url, lineNo: lineNo, type: BP_ERROR });
1335                     dispatch(debuggers, "onToggleErrorBreakpoint", [url, lineNo, true, debuggr]);
1336                     fbs.saveBreakpoints(sourceFile.href);  // after every call to onToggleBreakpoint
1337                 }
1338             }
1339             catch(exc)
1340             {
1341                 FBTrace.sysout("fbs.setErrorBreakpoint ERROR "+exc, exc);
1342             }
1343         }
1344     },
1345 
1346     clearErrorBreakpoint: function(url, lineNo, debuggr)
1347     {
1348         var index = this.findErrorBreakpoint(url, lineNo);
1349         if (index != -1)
1350         {
1351             var bp = this.removeBreakpoint(BP_NORMAL | BP_ERROR, url, lineNo);
1352 
1353             errorBreakpoints.splice(index, 1);
1354             dispatch(debuggers, "onToggleErrorBreakpoint", [url, lineNo, false, debuggr]);
1355 
1356             // after every call to onToggleBreakpoint
1357             fbs.saveBreakpoints(url);
1358         }
1359     },
1360 
1361     clearErrorBreakpoints: function(sourceFiles, debuggr)
1362     {
1363         for (var i=0; i<sourceFiles.length; ++i)
1364         {
1365             var url = sourceFiles[i].href;
1366             if (!url)
1367                 continue;
1368 
1369             fbs.enumerateErrorBreakpoints(url,
1370             {
1371                 call: function(url, lineNo)
1372                 {
1373                     fbs.clearErrorBreakpoint(url, lineNo, debuggr);
1374                 }
1375             });
1376         }
1377     },
1378 
1379     hasErrorBreakpoint: function(url, lineNo)
1380     {
1381         return this.findErrorBreakpoint(url, lineNo) != -1;
1382     },
1383 
1384     enumerateErrorBreakpoints: function(url, cb)
1385     {
1386         // Clone breakpoints array before iteration. The callback could modify it.
1387         var copyBreakpoints = [];
1388         copyBreakpoints.push.apply(copyBreakpoints, errorBreakpoints);
1389 
1390         if (url)
1391         {
1392             for (var i=0; i<copyBreakpoints.length; ++i)
1393             {
1394                 var bp = copyBreakpoints[i];
1395                 if (bp.href == url)
1396                     cb.call(bp.href, bp.lineNo, bp);
1397             }
1398         }
1399         else
1400         {
1401             for (var i=0; i<copyBreakpoints.length; ++i)
1402             {
1403                 var bp = copyBreakpoints[i];
1404                 cb.call(bp.href, bp.lineNo, bp);
1405             }
1406         }
1407     },
1408 
1409     findErrorBreakpoint: function(url, lineNo)
1410     {
1411         for (var i = 0; i < errorBreakpoints.length; ++i)
1412         {
1413             var bp = errorBreakpoints[i];
1414             if (bp.lineNo === lineNo && bp.href == url)
1415                 return i;
1416         }
1417 
1418         return -1;
1419     },
1420 
1421     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1422     // JSD Handlers
1423 
1424     addHandler: function(handler)
1425     {
1426         jsdHandlers.add(handler);
1427         jsdHandlers.hook();
1428     },
1429 
1430     removeHandler: function(handler)
1431     {
1432         // First remove the hook and then call unhook. The 'unhook' function
1433         // checks for registered handlers and removes various JSD hooks only, if there
1434         // are no corresponding handlers.
1435         jsdHandlers.remove(handler);
1436         jsdHandlers.unhook();
1437     },
1438 
1439     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1440 
1441     traceAll: function(urls, debuggr)
1442     {
1443         this.hookCalls(debuggr.onFunctionCall, false);  // call on all passed urls
1444     },
1445 
1446     untraceAll: function(debuggr)
1447     {
1448         this.unhookFunctions(); // undo hookCalls()
1449     },
1450 
1451     traceCalls: function(sourceFile, lineNo, debuggr)
1452     {
1453         // set a breakpoint on the starting point
1454         var bp = this.monitor(sourceFile, lineNo, debuggr);
1455         bp.type |= BP_TRACE;
1456 
1457         // when we hit the bp in onBreakPoint we being tracing.
1458     },
1459 
1460     untraceCalls: function(sourceFile, lineNo, debuggr)
1461     {
1462         var bp = lineNo != -1 ? this.findBreakpoint(url, lineNo) : null;
1463         if (bp)
1464         {
1465             bp.type &= ~BP_TRACE;
1466             this.unmonitor(sourceFile.href, lineNo);
1467         }
1468     },
1469 
1470     monitor: function(sourceFile, lineNo, debuggr)
1471     {
1472         if (lineNo == -1)
1473             return null;
1474 
1475         var bp = this.addBreakpoint(BP_MONITOR, sourceFile, lineNo, null, debuggr);
1476         if (bp)
1477         {
1478             ++monitorCount;
1479             dispatch(debuggers, "onToggleMonitor", [sourceFile.href, lineNo, true]);
1480         }
1481 
1482         return bp;
1483     },
1484 
1485     unmonitor: function(href, lineNo)
1486     {
1487         if (lineNo != -1 && this.removeBreakpoint(BP_MONITOR, href, lineNo))
1488         {
1489             --monitorCount;
1490             dispatch(debuggers, "onToggleMonitor", [ href, lineNo, false]);
1491         }
1492     },
1493 
1494     isMonitored: function(url, lineNo)
1495     {
1496         var bp = lineNo != -1 ? this.findBreakpoint(url, lineNo) : null;
1497         return bp && bp.type & BP_MONITOR;
1498     },
1499 
1500     enumerateMonitors: function(url, cb)
1501     {
1502         if (url)
1503         {
1504             var urlBreakpoints = fbs.getBreakpoints(url);
1505             if (urlBreakpoints)
1506             {
1507                 for (var i = 0; i < urlBreakpoints.length; ++i)
1508                 {
1509                     var bp = urlBreakpoints[i];
1510                     if (bp.type & BP_MONITOR)
1511                         cb.call(url, bp.lineNo, bp);
1512                 }
1513             }
1514         }
1515         else
1516         {
1517             for (var url in breakpoints)
1518                 this.enumerateBreakpoints(url, cb);
1519         }
1520     },
1521 
1522     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1523 
1524     enumerateScripts: function(length)
1525     {
1526         var scripts = [];
1527         jsd.enumerateScripts( {
1528             enumerateScript: function(script) {
1529                 var fileName = script.fileName;
1530                 if ( !isFilteredURL(fileName) ) {
1531                     scripts.push(script);
1532                 }
1533             }
1534         });
1535 
1536         length.value = scripts.length;
1537         return scripts;
1538     },
1539 
1540     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1541 
1542     startProfiling: function()
1543     {
1544         if (!this.profiling)
1545         {
1546             this.profiling = true;
1547             profileStart = new Date();
1548 
1549             jsd.flags |= COLLECT_PROFILE_DATA;
1550         }
1551 
1552         ++profileCount;
1553     },
1554 
1555     stopProfiling: function()
1556     {
1557         if (--profileCount == 0)
1558         {
1559             jsd.flags &= ~COLLECT_PROFILE_DATA;
1560 
1561             var t = profileStart.getTime();
1562 
1563             this.profiling = false;
1564             profileStart = null;
1565 
1566             return new Date().getTime() - t;
1567         }
1568         else
1569             return -1;
1570     },
1571 
1572     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1573 
1574     enableDebugger: function()
1575     {
1576         if (waitingForTimer)
1577         {
1578             timer.cancel();
1579             waitingForTimer = false;
1580         }
1581 
1582         if (enabledDebugger)
1583             return;
1584 
1585         enabledDebugger = true;
1586 
1587         this.obeyPrefs();
1588 
1589         if (!jsd)
1590         {
1591             jsd = DebuggerService.getService(jsdIDebuggerService);
1592 
1593             if ( FBTrace.DBG_FBS_ERRORS )
1594                 FBTrace.sysout("fbs.enableDebugger gets jsd service, isOn:" + jsd.isOn +
1595                     " initAtStartup:" + jsd.initAtStartup + " now have " + debuggers.length +
1596                     " debuggers in " + clients.length + " clients");
1597 
1598             // This property has been removed from Fx40
1599             if (jsd.initAtStartup)
1600                 jsd.initAtStartup = false;
1601         }
1602 
1603         if (jsd.asyncOn) // then FF 4.0+
1604         {
1605             if (!jsd.isOn)
1606             {
1607                 if (FBTrace.DBG_ACTIVATION)
1608                 {
1609                     var startAsyncOn = new Date().getTime();
1610                     FBTrace.sysout("fbs.activation begin jsd.asyncOn " + startAsyncOn);
1611                 }
1612 
1613                 jsd.asyncOn(  // turn on jsd for the next event
1614                 {
1615                     onDebuggerActivated: function doDebuggerActivated()
1616                     {
1617                         // now we are in the next event and jsd is on.
1618                         if (FBTrace.DBG_ACTIVATION)
1619                         {
1620                             var nowAsyncOn = new Date().getTime();
1621                             FBTrace.sysout("fbs.activation now we are in the " +
1622                                 "next event and JSD is ON " + nowAsyncOn + " delta: " +
1623                                 (nowAsyncOn - startAsyncOn) + "ms");
1624                         }
1625 
1626                         fbs.onDebuggerActivated();
1627                         fbs.onJSDebuggingActive();
1628                     }
1629                 });
1630             }
1631             else
1632             {
1633                 fbs.onJSDebuggingActive();
1634             }
1635         }
1636         else // FF 3.6-
1637         {
1638             if (!jsd.isOn)
1639             {
1640                 if (FBTrace.DBG_FBS_ERRORS)
1641                     FBTrace.sysout("fbs.Firefox 3.6 or earlier");
1642 
1643                 jsd.on(); // this should be the only call to jsd.on().
1644                 fbs.onDebuggerActivated();
1645             }
1646             fbs.onJSDebuggingActive();
1647         }
1648     },
1649 
1650     onDebuggerActivated: function()
1651     {
1652         jsd.flags |= DISABLE_OBJECT_TRACE;
1653 
1654         if (FBTrace.DBG_ACTIVATION)
1655             FBTrace.sysout("fbs.onDebuggerActivated");
1656 
1657         if (jsd.pauseDepth && FBTrace.DBG_FBS_ERRORS)
1658             FBTrace.sysout("fbs.enableDebugger found non-zero jsd.pauseDepth !! " +
1659                 jsd.pauseDepth);
1660     },
1661 
1662     onJSDebuggingActive: function()
1663     {
1664         if (!this.filterChrome)
1665             this.createChromeBlockingFilters();
1666 
1667         var active = fbs.isJSDActive();
1668 
1669         dispatch(clients, "onJSDActivate", [active, "fbs enableDebugger"]);
1670         this.hookScripts();
1671 
1672         if (FBTrace.DBG_ACTIVATION)
1673             FBTrace.sysout("fbs.enableDebugger with active " + active);
1674     },
1675 
1676     obeyPrefs: function()
1677     {
1678         fbs.showStackTrace = getPref("showStackTrace");
1679         fbs.breakOnErrors = getPref("breakOnErrors");
1680         fbs.trackThrowCatch = getPref("trackThrowCatch");
1681 
1682         var pref = fbs.scriptsFilter;
1683         fbs.scriptsFilter = getPref("scriptsFilter");
1684         var mustReset = (pref !== fbs.scriptsFilter);
1685 
1686         if (FBTrace.DBG_OPTIONS)
1687             FBTrace.sysout("fbs.obeyPrefs mustReset = " + mustReset + " pref: " + pref +
1688                 " fbs.scriptsFilter: " + fbs.scriptsFilter, fbs);
1689 
1690         pref = fbs.filterSystemURLs;
1691 
1692         // may not be exposed to users
1693         fbs.filterSystemURLs = getPref("filterSystemURLs");
1694         mustReset = mustReset || (pref !== fbs.filterSystemURLs);
1695 
1696         if (FBTrace.DBG_OPTIONS)
1697             FBTrace.sysout("fbs.obeyPrefs mustReset = " + mustReset + " pref: " + pref +
1698                 " fbs.filterSystemURLs: " + fbs.filterSystemURLs);
1699 
1700         if (mustReset && jsd && jsd.scriptHook)
1701         {
1702             fbs.unhookScripts();
1703             fbs.hookScripts();
1704         }
1705 
1706         if (FBTrace.DBG_FBS_FUNCTION)
1707         {
1708             fbs.loggingFunctionCalls = new LogFunctionStepper();
1709             jsdHandlers.add(fbs.loggingFunctionCalls);
1710             jsdHandlers.hook(); // no frame argument
1711         }
1712         else if (fbs.loggingFunctionCalls)
1713         {
1714             jsdHandlers.unhook("no frame argument");
1715             fbs.jsdHandler.remove(fbs.loggingFunctionCalls);
1716             delete fbs.loggingFunctionCalls;
1717         }
1718 
1719         FirebugPrefsObserver.syncFilter();
1720 
1721         try
1722         {
1723             if (FBTrace.DBG_OPTIONS)
1724                 FBTrace.sysout("fbs.obeyPrefs showStackTrace:"+fbs.showStackTrace+
1725                     " breakOnErrors:"+fbs.breakOnErrors+" trackThrowCatch:"+fbs.trackThrowCatch+
1726                     " scriptFilter:"+fbs.scriptsFilter+" filterSystemURLs:"+fbs.filterSystemURLs);
1727         }
1728         catch (exc)
1729         {
1730             FBTrace.sysout("fbs.constructor getBoolPrefs FAILED with exception=", exc);
1731         }
1732     },
1733 
1734     disableDebugger: function()
1735     {
1736         if (!enabledDebugger)
1737             return;
1738 
1739         if (!timer)  // then we probably shutdown
1740             return;
1741 
1742         enabledDebugger = false;
1743 
1744         if (jsd.isOn)
1745         {
1746             jsd.pause();
1747             fbs.unhookScripts();
1748 
1749             while (jsd.pauseDepth > 0)  // unwind completely
1750                 jsd.unPause();
1751 
1752             jsd.off();
1753         }
1754 
1755         var active = fbs.isJSDActive();
1756         dispatch(clients, "onJSDDeactivate", [active, "fbs disableDebugger"]);
1757 
1758         fbs.onXScriptCreatedByTag = {};  // clear any uncleared top level scripts
1759 
1760         if (FBTrace.DBG_FBS_FINDDEBUGGER || FBTrace.DBG_ACTIVATION)
1761             FBTrace.sysout("fbs.disableDebugger jsd.isOn:"+jsd.isOn+" for enabledDebugger: "+
1762                 enabledDebugger);
1763     },
1764 
1765     // must support multiple calls
1766     pause: function(debuggerName)
1767     {
1768         if (!enabledDebugger || !jsd || !jsd.isOn)
1769             return "not enabled";
1770 
1771         var rejection = [];
1772         dispatch(clients, "onPauseJSDRequested", [rejection, debuggerName]);
1773 
1774         // Number of rejections:
1775         // 0 - then everyone wants to pause
1776         // 1 - then everyone wants to pause (including the current active tab)
1777         if (rejection.length < 1)
1778         {
1779             if (jsd.pauseDepth == 0)  // don't pause if we are paused.
1780             {
1781                 jsd.pause();
1782                 fbs.unhookScripts();
1783             }
1784 
1785             var active = fbs.isJSDActive();
1786             dispatch(clients, "onJSDDeactivate", [active, "pause depth "+jsd.pauseDepth]);
1787         }
1788         else // we don't want to pause
1789         {
1790             fbs.unPause(true);
1791         }
1792 
1793         if (FBTrace.DBG_FBS_FINDDEBUGGER || FBTrace.DBG_ACTIVATION)
1794         {
1795             FBTrace.sysout("fbs.pause depth "+(jsd.isOn?jsd.pauseDepth:"jsd OFF")+" rejection "+
1796                 rejection.length+" from "+clients.length+" clients ", rejection);
1797 
1798             // The next line gives NS_ERROR_NOT_AVAILABLE
1799             // FBTrace.sysout("fbs.pause depth "+(jsd.isOn?jsd.pauseDepth:"jsd OFF")+
1800             //    " rejection "+rejection.length+" from clients "+clients, rejection);
1801         }
1802         return jsd.pauseDepth;
1803     },
1804 
1805     unPause: function(force)
1806     {
1807         if (!jsd)
1808             return;
1809 
1810         if (jsd.pauseDepth > 0 || force)
1811         {
1812             if (FBTrace.DBG_ACTIVATION && (!jsd.isOn || jsd.pauseDepth == 0) )
1813                 FBTrace.sysout("fbs.unpause while jsd.isOn is "+jsd.isOn+
1814                     " and hooked scripts pauseDepth:"+jsd.pauseDepth);
1815 
1816             fbs.hookScripts();
1817 
1818             if(jsd.pauseDepth)
1819                 var depth = jsd.unPause();
1820 
1821             var active = fbs.isJSDActive();
1822 
1823             if (FBTrace.DBG_ACTIVATION)
1824                 FBTrace.sysout("fbs.unPause hooked scripts and unPaused, active:" + active +
1825                     " depth " + depth + " jsd.isOn: " + jsd.isOn);
1826 
1827             dispatch(clients, "onJSDActivate", [active, "unpause depth"+jsd.pauseDepth]);
1828         }
1829         else  // we were not paused.
1830         {
1831             if (FBTrace.DBG_ACTIVATION)
1832             {
1833                 var noAction = "("+jsd.pauseDepth+" || "+ !jsd.isOn+")";
1834                 FBTrace.sysout("fbs.unPause no action: (jsd.pauseDepth || !jsd.isOn) = " +
1835                     noAction);
1836             }
1837         }
1838 
1839         return jsd.pauseDepth;
1840     },
1841 
1842     isJSDActive: function()
1843     {
1844         return (jsd && jsd.isOn && (jsd.pauseDepth === 0) );
1845     },
1846 
1847     // TODO delete once Chromebug works on BTI
1848     // re-transmit the message (string) with args [objs] to XUL windows.
1849     broadcast: function(message, args)
1850     {
1851         dispatch(clients, message, args);
1852         if (FBTrace.DBG_ACTIVATION)
1853             FBTrace.sysout("fbs.broadcast "+message+" to "+clients.length+" clients", clients);
1854     },
1855 
1856     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1857 
1858     normalizeURL: function(url)
1859     {
1860         // For some reason, JSD reports file URLs like "file:/" instead of "file:///", so they
1861         // don't match up with the URLs we get back from the DOM
1862         return url ? url.replace(/file:\/([^/])/, "file:///$1") : "";
1863     },
1864 
1865     denormalizeURL: function(url)
1866     {
1867         // This should not be called.
1868         return url ? url.replace(/file:\/\/\//, "file:/") : "";
1869     },
1870 
1871     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1872     // jsd Hooks
1873 
1874     // When engine encounters debugger keyword (only)
1875     onDebugger: function(frame, type, rv)
1876     {
1877         if (FBTrace.DBG_FBS_BP)
1878             FBTrace.sysout("fbs.onDebugger with haltDebugger="+
1879                 (haltObject?haltObject.haltDebugger:"null")+" in "+frame.script.fileName,
1880                 frame.script);
1881 
1882         try
1883         {
1884             if (FBTrace.DBG_FBS_SRCUNITS && fbs.isTopLevelScript(frame, type, rv))
1885                 FBTrace.sysout("fbs.onDebugger found topLevelScript "+ frame.script.tag);
1886 
1887             if (FBTrace.DBG_FBS_SRCUNITS && fbs.isNestedScript(frame, type, rv))
1888                 FBTrace.sysout("fbs.onDebugger found nestedScript "+ frame.script.tag);
1889 
1890             if (haltObject)
1891             {
1892                 var peelOurselvesOff = frame;
1893                 if (peelOurselvesOff.script.fileName.indexOf("debuggerHalter.js") > 0)
1894                     peelOurselvesOff = frame.callingFrame;  // remove debuggerHalter()
1895 
1896                 while (peelOurselvesOff && (peelOurselvesOff.script.fileName.indexOf("firebug-service.js") > 0 ))
1897                     peelOurselvesOff = peelOurselvesOff.callingFrame;
1898 
1899                 while (peelOurselvesOff && (peelOurselvesOff.script.fileName.indexOf("/debugger.js") > 0 ))
1900                     peelOurselvesOff = peelOurselvesOff.callingFrame;
1901 
1902                 if (peelOurselvesOff)
1903                 {
1904                     if (FBTrace.DBG_FBS_BP)
1905                         FBTrace.sysout("fbs.onDebugger, " +
1906                             (haltObject.haltCallBack ? "with" : "without") +
1907                             " callback, adjusted newest frame: " + peelOurselvesOff.line +
1908                             "@" + peelOurselvesOff.script.fileName + " frames: ",
1909                             framesToString(frame));
1910 
1911                     var debuggr = haltObject.haltDebugger;
1912                     var callback = haltObject.haltCallBack;
1913                     fbs.haltReturnValue = callback.apply(debuggr,[peelOurselvesOff]);
1914                 }
1915                 else
1916                 {
1917                     FBTrace.sysout("fbs.halt ERROR "+framesToString(frame));
1918                     fbs.haltReturnValue = "firebug-service.halt ERROR, no stack frames left ";
1919                 }
1920 
1921                 return RETURN_CONTINUE;
1922             }
1923             else
1924             {
1925                 var peelOurselvesOff = frame;
1926                 if (peelOurselvesOff.script.fileName.indexOf("consoleExposed.js") > 0)
1927                     peelOurselvesOff = frame.callingFrame;
1928 
1929                 var bp = this.findBreakpointByScript(peelOurselvesOff.script, peelOurselvesOff.pc);
1930 
1931                 // then breakpoints override debugger statements (to allow conditional
1932                 // debugger statements);
1933                 if (bp)
1934                     return this.onBreakpoint(peelOurselvesOff, type, rv);
1935                 else
1936                     return fbs.routeBreakToDebuggr(peelOurselvesOff, type, rv);
1937             }
1938         }
1939         catch(exc)
1940         {
1941             if (FBTrace.DBG_FBS_ERRORS)
1942                 FBTrace.sysout("fbs.onDebugger failed: "+exc,exc);
1943 
1944             ERROR("onDebugger failed: "+exc, exc);
1945             return RETURN_CONTINUE;
1946         }
1947         finally
1948         {
1949             haltObject = null;
1950         }
1951     },
1952 
1953     // when the onError handler returns false
1954     onDebug: function(frame, type, rv)
1955     {
1956         if (FBTrace.DBG_FBS_ERRORS)
1957         {
1958             fbs.onDebugRequests--;
1959             FBTrace.sysout("fbs.onDebug (" + fbs.onDebugRequests + ") fileName=" +
1960                 frame.script.fileName + " reportNextError=" + reportNextError +
1961                 " breakOnErrors:" + this.breakOnErrors + " fbs.breakOnDebugCall: " +
1962                 fbs.breakOnDebugCall);
1963         }
1964 
1965         if (isFilteredURL(frame.script.fileName))
1966         {
1967             reportNextError = false;
1968             return RETURN_CONTINUE;
1969         }
1970 
1971         try
1972         {
1973             var breakOnNextError = this.needToBreakForError(reportNextError);
1974             var debuggr = (reportNextError || breakOnNextError) ? this.findDebugger(frame) : null;
1975 
1976             if (reportNextError)
1977             {
1978                 reportNextError = false;
1979                 if (debuggr)
1980                 {
1981                     var hookReturn = debuggr.onError(frame, errorInfo, fbs.breakOnDebugCall);
1982                     if (hookReturn >=0)
1983                         return hookReturn;
1984                     else if (hookReturn==-1)
1985                         breakOnNextError = true;
1986 
1987                     if (breakOnNextError)
1988                         debuggr = this.reFindDebugger(frame, debuggr);
1989                 }
1990             }
1991 
1992             if (breakOnNextError)
1993             {
1994                 if (fbs.isTopLevelScript(frame, type, rv) && FBTrace.DBG_FBS_SRCUNITS)
1995                     FBTrace.sysout("fbs.onDebug found topLevelScript "+ frame.script.tag);
1996 
1997                 if (fbs.isNestedScript(frame, type, rv) && FBTrace.DBG_FBS_SRCUNITS)
1998                     FBTrace.sysout("fbs.onDebug found nestedScript "+ frame.script.tag);
1999 
2000                 breakOnNextError = false;
2001                 delete fbs.breakOnDebugCall;
2002 
2003                 if (debuggr)
2004                     return this.breakIntoDebugger(debuggr, frame, type);
2005             }
2006         }
2007         catch (exc)
2008         {
2009             ERROR("onDebug failed: "+exc);
2010         }
2011 
2012         return RETURN_CONTINUE;
2013     },
2014 
2015     onBreakpoint: function(frame, type, val)
2016     {
2017         if (fbs.isTopLevelScript(frame, type, val))
2018         {
2019             if (FBTrace.DBG_FBS_BP)
2020                 FBTrace.sysout("fbs.onBreakpoint isTopLevel returning " + RETURN_CONTINUE);
2021 
2022             return RETURN_CONTINUE;
2023         }
2024 
2025         var bp = this.findBreakpointByScript(frame.script, frame.pc);
2026         if (bp)
2027         {
2028             var theDebugger = fbs.getDebuggerByName(bp.debuggerName);
2029             if (!theDebugger)
2030                 theDebugger = this.findDebugger(frame);  // sets debuggr.breakContext
2031 
2032             var currFrameId = frameId(frame, 0);
2033 
2034             // See issue 1179, should not break if we resumed from a single
2035             // step and have not advanced.
2036             // Only break on a breakpoint if a single-step didn't start on
2037             // the current line (issue 1098)
2038             for (var i=0; i<jsdHandlers.hooks.length; i++)
2039             {
2040                 var handler = jsdHandlers.hooks[i];
2041                 if (handler.startFrameId == currFrameId)
2042                     return RETURN_CONTINUE;
2043             }
2044 
2045             if (disabledCount || monitorCount || conditionCount || runningUntil)
2046             {
2047                 if (FBTrace.DBG_FBS_BP)
2048                 {
2049                     FBTrace.sysout("fbs.onBreakpoint("+getExecutionStopNameFromType(type)+
2050                         ") disabledCount:"+disabledCount+" monitorCount:"+monitorCount+
2051                         " conditionCount:"+conditionCount+" runningUntil:"+runningUntil, bp);
2052                 }
2053 
2054                 if (bp.type & BP_ERROR)
2055                     return RETURN_CONTINUE; // if onError gets called, then we will break
2056 
2057                 if (bp.type & BP_MONITOR && !(bp.disabled & BP_MONITOR))
2058                 {
2059                     if (bp.type & BP_TRACE && !(bp.disabled & BP_TRACE) )
2060                         this.hookCalls(theDebugger.onFunctionCall, true);  // TODO
2061                     else
2062                         theDebugger.onMonitorScript(frame);
2063                 }
2064 
2065                 if (bp.type & BP_UNTIL)  // then we hit the runningUntil breakpoint
2066                 {
2067                     if (runningUntil)
2068                     {
2069                         this.removeBreakpoint(BP_UNTIL, runningUntil.href, runningUntil.lineNo);
2070                         runningUntil = null;
2071                     }
2072                     else
2073                     {
2074                         if (FBTrace.DBG_FBS_ERRORS)
2075                             FBTrace.sysout("fbs.BP_UNTIL but not runningUntil!", bp);
2076                     }
2077 
2078                     if (theDebugger)
2079                         return this.breakIntoDebugger(theDebugger, frame, type);
2080                 }
2081                 else if (!(bp.type & BP_NORMAL) || bp.disabled & BP_NORMAL)
2082                 {
2083                     return  RETURN_CONTINUE;
2084                 }
2085                 else if (bp.type & BP_NORMAL)
2086                 {
2087                     var passed = testBreakpoint(frame, bp);
2088                     if (!passed)
2089                         return RETURN_CONTINUE;
2090                 }
2091                 // type was normal, but passed test
2092             }
2093             else
2094             {
2095                 // not special, just break for sure
2096                 return this.breakIntoDebugger(theDebugger, frame, type);
2097             }
2098         }
2099         else
2100         {
2101             if (FBTrace.DBG_FBS_BP)
2102                 FBTrace.sysout("fbs.onBreakpoint(" + getExecutionStopNameFromType(type) +
2103                     ") NO bp match with frame.script.tag=" + frame.script.tag +
2104                     " clearing and continuing");
2105 
2106             // We did not find a logical breakpoint to match the one set into JSD, so stop trying.
2107             frame.script.clearBreakpoint(frame.pc);
2108             return RETURN_CONTINUE;
2109         }
2110 
2111         if (runningUntil)
2112             return RETURN_CONTINUE;
2113         else
2114             return fbs.routeBreakToDebuggr(frame, type, val);
2115     },
2116 
2117     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2118 
2119     onThrow: function(frame, type, rv)
2120     {
2121         if (isFilteredURL(frame.script.fileName))
2122             return RETURN_CONTINUE_THROW;
2123 
2124         if (rv && rv.value && rv.value.isValid)
2125         {
2126             var value = rv.value;
2127             if (value.jsClassName == "Error" && value.stringValue.indexOf("too much recursion") !== -1)
2128             {
2129                 if (fbs._lastErrorCaller)
2130                 {
2131                     // then are unwinding recursion
2132                     if (fbs._lastErrorCaller == frame.script.tag)
2133                     {
2134                         fbs._lastErrorCaller =
2135                             frame.callingFrame ? frame.callingFrame.script.tag : null;
2136 
2137                         return RETURN_CONTINUE_THROW;
2138                     }
2139                 }
2140                 else
2141                 {
2142                     fbs._lastErrorCaller = frame.callingFrame.script.tag;
2143                     frame = fbs.discardRecursionFrames(frame);
2144                     // go on to process the throw.
2145                 }
2146             }
2147             else
2148             {
2149                 delete fbs._lastErrorCaller; // throw is not recursion
2150             }
2151         }
2152         else
2153         {
2154             delete fbs._lastErrorCaller; // throw is not recursion either
2155         }
2156 
2157         if (fbs.showStackTrace)
2158         {
2159             if (FBTrace.DBG_FBS_ERRORS)
2160                 FBTrace.sysout("fbs.onThrow from tag:" + frame.script.tag + ":" +
2161                     frame.script.fileName + "@" + frame.line + ": " + frame.pc);
2162 
2163             var debuggr = this.findDebugger(frame);
2164             if (debuggr)
2165                 return debuggr.onThrow(frame, rv);
2166         }
2167 
2168         return RETURN_CONTINUE_THROW;
2169     },
2170 
2171     onError: function(message, fileName, lineNo, pos, flags, errnum, exc)
2172     {
2173         if (FBTrace.DBG_FBS_ERRORS)
2174         {
2175             var messageKind;
2176             if (flags & jsdIErrorHook.REPORT_ERROR)
2177                 messageKind = "Error";
2178             if (flags & jsdIErrorHook.REPORT_WARNING)
2179                 messageKind = "Warning";
2180             if (flags & jsdIErrorHook.REPORT_EXCEPTION)
2181                 messageKind = "Uncaught-Exception";
2182             if (flags & jsdIErrorHook.REPORT_STRICT)
2183                 messageKind += "-Strict";
2184 
2185             FBTrace.sysout("fbs.onError ("+fbs.onDebugRequests+") with this.showStackTrace="+
2186                 this.showStackTrace+" and this.breakOnErrors="+this.breakOnErrors+" kind="+
2187                 messageKind+" msg="+message+"@"+fileName+":"+lineNo+"."+pos,
2188                 (exc ? exc.getWrappedValue() : "No exc object"));
2189         }
2190 
2191         delete fbs.breakOnDebugCall;
2192 
2193         if (exc)
2194         {
2195             var exception = exc.getWrappedValue();
2196             fbs.enumerateErrorBreakpoints(exception.fileName, {call: function breakIfMatch(url, lineNo, bp)
2197             {
2198                 // An error breakpoint is in this file
2199                 if (exception.lineNumber == bp.lineNo)
2200                 {
2201                     fbs.breakOnDebugCall = true;
2202 
2203                     if (FBTrace.DBG_FBS_ERRORS)
2204                         FBTrace.sysout("fbs.onError setting breakOnDebugCall for " + url + "@" +
2205                             exception.lineNumber);
2206                 }
2207             }});
2208         }
2209 
2210         // Global to pass info to onDebug. Some duplicate values to support different apis
2211         // Do not store the exception object itself |exc|, errofInfo is a global variable
2212         // and it would keep the page (that is producing the error) in the memory
2213         // (see bug 669730)
2214         errorInfo = { errorMessage: message, sourceName: fileName, lineNumber: lineNo,
2215                 message: message, fileName: fileName, lineNo: lineNo,
2216                 columnNumber: pos, flags: flags, category: "js", errnum: errnum };
2217 
2218         if (message == "out of memory")  // bail
2219         {
2220             if (FBTrace.DBG_FBS_ERRORS)
2221                 fbs.osOut("fbs.onError sees out of memory "+fileName+":"+lineNo+"\n");
2222             return true;
2223         }
2224 
2225         reportNextError = { fileName: fileName, lineNo: lineNo };
2226 
2227         if (FBTrace.DBG_FBS_ERRORS)
2228             fbs.onDebugRequests++;
2229 
2230         return false; // Drop into onDebug, sometimes only
2231     },
2232 
2233     onTopLevel: function(frame, type)
2234     {
2235         if (FBTrace.DBG_TOPLEVEL)
2236             FBTrace.sysout("fbs.onTopLevel " + getCallFromType(type) + " with delegate " +
2237                 fbs.onTopLevelDelegate + " " + frame.script.tag + " " + frame.script.fileName);
2238 
2239         if (fbs.onTopLevelDelegate)
2240             fbs.onTopLevelDelegate(frame, type)
2241     },
2242 
2243     isTopLevelScript: function(frame, type, val)
2244     {
2245         var scriptTag = frame.script.tag;
2246 
2247         if (FBTrace.DBG_FBS_SRCUNITS)
2248             FBTrace.sysout("fbs.isTopLevelScript frame.script.tag="+frame.script.tag );
2249 
2250         if (scriptTag in this.onXScriptCreatedByTag)
2251         {
2252             if (FBTrace.DBG_FBS_TRACKFILES)
2253                 trackFiles.def(frame);
2254 
2255             var onXScriptCreated = this.onXScriptCreatedByTag[scriptTag];
2256 
2257             if (FBTrace.DBG_FBS_BP)
2258                 FBTrace.sysout("fbs.isTopLevelScript(" + getExecutionStopNameFromType(type) +
2259                     ") with frame.script.tag=" + frame.script.tag + " onXScriptCreated:" +
2260                     onXScriptCreated.kind);
2261 
2262             delete this.onXScriptCreatedByTag[scriptTag];
2263             frame.script.clearBreakpoint(0);
2264 
2265             try
2266             {
2267                 var sourceFile = onXScriptCreated(frame, type, val);
2268             }
2269             catch (e)
2270             {
2271                 FBTrace.sysout("fbs.isTopLevelScript called onXScriptCreated and " +
2272                     "it didn't end well:", e);
2273             }
2274 
2275             if (FBTrace.DBG_FBS_SRCUNITS)
2276             {
2277                 var msg = "Top Scripts Uncleared:";
2278                 for (var p in this.onXScriptCreatedByTag)
2279                     msg += (p + "|");
2280                 FBTrace.sysout(msg);
2281             }
2282 
2283             if (!sourceFile || !sourceFile.breakOnZero || sourceFile.breakOnZero != scriptTag)
2284             {
2285                 return true;
2286             }
2287             else
2288             {
2289                // sourceFile.breakOnZero matches the script we have halted.
2290                if (FBTrace.DBG_FBS_BP)
2291                    FBTrace.sysout("fbs.isTopLevelScript breakOnZero, continuing for " +
2292                     "user breakpoint");
2293             }
2294         }
2295 
2296         return false;
2297     },
2298 
2299     /**
2300      * If true, emergency bailout: a frame is running a script which has not been
2301      * processed as source
2302      */
2303     isNestedScript: function(frame, type, val)
2304     {
2305         if (fbs.nestedScriptStack.length === 0 ||
2306             fbs.nestedScriptStack.indexOf(frame.script) === -1)
2307         {
2308             return false;
2309         }
2310 
2311         try
2312         {
2313             var sourceFile = fbs.onTopLevelScriptCreated(frame, type, val);
2314         }
2315         catch (e)
2316         {
2317             FBTrace.sysout("fbs.isNestedScript called onXScriptCreated and it didn't end well:", e);
2318         }
2319 
2320         return true;
2321     },
2322 
2323     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2324 
2325     onXULScriptCreated: function(frame, type, val, noNestTest)
2326     {
2327         // A XUL script hit a breakpoint
2328         try
2329         {
2330             var outerScript = frame.script;
2331             var innerScripts = [];
2332             for (var i=0; i<fbs.pendingXULScripts.length; i++)
2333             {
2334                 // Take all the pending script from the same file as part of this sourcefile
2335                 if (fbs.pendingXULScripts[i].fileName === outerScript.fileName)
2336                 {
2337                     var innerScript = fbs.pendingXULScripts[i];
2338                     innerScripts.push(innerScript);
2339                     if (innerScript.isValid)
2340                         innerScript.clearBreakpoint(0);
2341 
2342                     fbs.pendingXULScripts.splice(i,1);
2343                 }
2344             }
2345 
2346             var debuggr = fbs.findDebugger(frame);  // sets debuggr.breakContext
2347             if (debuggr)
2348             {
2349                 innerScripts.push(outerScript);
2350                 var innerScriptEnumerator =
2351                 {
2352                      index: 0,
2353                      max: innerScripts.length,
2354                      hasMoreElements: function() { return this.index < this.max;},
2355                      getNext: function() { return innerScripts[this.index++]; },
2356                 };
2357 
2358                 var sourceFile = debuggr.onXULScriptCreated(frame, outerScript,
2359                     innerScriptEnumerator);
2360                 fbs.resetBreakpoints(sourceFile, debuggr);
2361             }
2362             else
2363             {
2364                 if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2365                     FBTrace.sysout("fbs.onEventScriptCreated no debuggr for " + frame.script.tag +
2366                         ":" + frame.script.fileName);
2367             }
2368         }
2369         catch(exc)
2370         {
2371             if (FBTrace.DBG_FBS_ERRORS)
2372                 FBTrace.sysout("fbs.onXULScriptCreated ERROR " + exc, exc);
2373         }
2374     },
2375 
2376     onEventScriptCreated: function(frame, type, val, noNestTest)
2377     {
2378         if (fbs.showEvents)
2379         {
2380             try
2381             {
2382                if (!noNestTest)
2383                 {
2384                     // In onScriptCreated we saw a script with baseLineNumber = 1.
2385                     // We marked it as event and nested.
2386                     // Now we know its event, not nested.
2387                     if (fbs.nestedScriptStack.length > 0)
2388                     {
2389                         fbs.nestedScriptStack.splice(0,1);
2390                     }
2391                     else
2392                     {
2393                         if (FBTrace.DBG_FBS_SRCUNITS)  // these seem to be harmless, but...
2394                         {
2395                             var script = frame.script;
2396                             FBTrace.sysout("fbs.onEventScriptCreated no nestedScriptStack: " +
2397                                 script.tag + "@(" + script.baseLineNumber + "-" +
2398                                 (script.baseLineNumber+script.lineExtent) + ")" +
2399                                 script.fileName);
2400 
2401                             FBTrace.sysout("fbs.onEventScriptCreated name: \'" +
2402                                 script.functionName + "\'");
2403 
2404                             try
2405                             {
2406                                 FBTrace.sysout(script.functionSource);
2407                             }
2408                             catch (exc)
2409                             {
2410                                 /*Bug 426692 */
2411                             }
2412                         }
2413                     }
2414                 }
2415 
2416                 var debuggr = fbs.findDebugger(frame);  // sets debuggr.breakContext
2417                 if (debuggr)
2418                 {
2419                     var sourceFile = debuggr.onEventScriptCreated(frame, frame.script,
2420                         fbs.getNestedScriptEnumerator());
2421                     fbs.resetBreakpoints(sourceFile, debuggr);
2422                 }
2423                 else
2424                 {
2425                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2426                         FBTrace.sysout("fbs.onEventScriptCreated no debuggr for " +
2427                             frame.script.tag + ":" + frame.script.fileName);
2428                 }
2429             }
2430             catch(exc)
2431             {
2432                 if (FBTrace.DBG_ERRORS)
2433                     FBTrace.sysout("fbs.onEventScriptCreated failed: " + exc, exc);
2434             }
2435 
2436             if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2437                 FBTrace.sysout("fbs.onEventScriptCreated frame.script.tag:" + frame.script.tag +
2438                     " href: " + (sourceFile ? sourceFile.href : "no sourceFile"), sourceFile);
2439         }
2440 
2441         fbs.clearNestedScripts();
2442         return sourceFile;
2443     },
2444 
2445     onEvalScriptCreated: function(frame, type, val)
2446     {
2447         if (fbs.showEvals)
2448         {
2449             try
2450             {
2451                 if (!frame.callingFrame)
2452                 {
2453                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2454                         FBTrace.sysout("fbs.No calling Frame for eval frame.script.fileName:" +
2455                             frame.script.fileName);
2456 
2457                     // These are eval-like things called by native code. They come from .xml files
2458                     // They should be marked as evals but we'll treat them like event handlers for now.
2459                     return fbs.onEventScriptCreated(frame, type, val, true);
2460                 }
2461 
2462                 // In onScriptCreated we found a no-name script, set a bp in PC=0, and a flag.
2463                 // onBreakpoint saw the flag, cleared the flag, and sent us here.
2464                 // Start by undoing our damage
2465                 var outerScript = frame.script;
2466 
2467                 // sets debuggr.breakContext
2468                 var debuggr = fbs.findDebugger(frame);
2469                 if (debuggr)
2470                 {
2471                     var sourceFile = debuggr.onEvalScriptCreated(frame, outerScript,
2472                         fbs.getNestedScriptEnumerator());
2473                     fbs.resetBreakpoints(sourceFile, debuggr);
2474                 }
2475                 else
2476                 {
2477                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2478                         FBTrace.sysout("fbs.onEvalScriptCreated no debuggr for " +
2479                             outerScript.tag + ":" + outerScript.fileName);
2480                 }
2481             }
2482             catch (exc)
2483             {
2484                 ERROR("onEvalScriptCreated failed: "+exc);
2485 
2486                 if (FBTrace.DBG_FBS_ERRORS)
2487                     FBTrace.sysout("fbs.onEvalScriptCreated failed:", exc);
2488             }
2489         }
2490 
2491         fbs.clearNestedScripts();
2492 
2493         if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2494             FBTrace.sysout("fbs.onEvalScriptCreated outerScript.tag:" + outerScript.tag +
2495                 " href: " + (sourceFile ? sourceFile.href : "no sourceFile"));
2496 
2497         return sourceFile;
2498     },
2499 
2500     onTopLevelScriptCreated: function(frame, type, val)
2501     {
2502         try
2503         {
2504             // In onScriptCreated we may have found a script at baseLineNumber=1
2505             // Now we know its not an event
2506             if (fbs.nestedScriptStack.length > 0)
2507             {
2508                 var firstScript = fbs.nestedScriptStack[0];
2509                 if (firstScript.tag in fbs.onXScriptCreatedByTag)
2510                 {
2511                     delete  fbs.onXScriptCreatedByTag[firstScript.tag];
2512                     if (firstScript.isValid)
2513                         firstScript.clearBreakpoint(0);
2514 
2515                     if (FBTrace.DBG_FBS_SRCUNITS)
2516                         FBTrace.sysout("fbs.onTopLevelScriptCreated clear bp@0 for " +
2517                             "firstScript.tag: " + firstScript.tag);
2518                 }
2519             }
2520 
2521             // On compilation of a top-level (global-appending) function.
2522             // After this top-level script executes we lose the jsdIScript so we can't
2523             // build its line table. Therefore we need to build it here.
2524 
2525             // sets debuggr.breakContext
2526             var debuggr = fbs.findDebugger(frame);
2527             if (debuggr)
2528             {
2529                 var sourceFile = debuggr.onTopLevelScriptCreated(frame, frame.script,
2530                     fbs.getNestedScriptEnumerator());
2531 
2532                 if (FBTrace.DBG_FBS_SRCUNITS)
2533                     FBTrace.sysout("fbs.onTopLevelScriptCreated got sourceFile:" + sourceFile +
2534                         " using " + fbs.nestedScriptStack.length + " nestedScripts");
2535 
2536                 fbs.resetBreakpoints(sourceFile, debuggr);
2537             }
2538             else
2539             {
2540                 // modules end up here?
2541                 if (FBTrace.DBG_FBS_SRCUNITS)
2542                     FBTrace.sysout("fbs.onTopLevelScriptCreated no debuggr for " + frame.script.tag);
2543             }
2544         }
2545         catch (exc)
2546         {
2547             FBTrace.sysout("fbs.onTopLevelScriptCreated FAILED: " + exc, exc);
2548             ERROR("onTopLevelScriptCreated ERROR: " + exc);
2549         }
2550 
2551         fbs.clearNestedScripts();
2552 
2553         if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2554             FBTrace.sysout("fbs.onTopLevelScriptCreated script.tag:" + frame.script.tag +
2555                 " href: " + (sourceFile ? sourceFile.href : "no sourceFile"));
2556 
2557         return sourceFile;
2558     },
2559 
2560     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2561 
2562     getNestedScriptEnumerator: function()
2563     {
2564         var enumer =
2565         {
2566             index: 0,
2567             hasMoreElements: function()
2568             {
2569                 return (this.index < fbs.nestedScriptStack.length);
2570             },
2571             getNext: function()
2572             {
2573                 var rv = fbs.nestedScriptStack[this.index];
2574                 this.index++;
2575                 return rv;
2576             }
2577         };
2578         return enumer;
2579     },
2580 
2581     clearNestedScripts: function()
2582     {
2583         var innerScripts = fbs.nestedScriptStack;
2584         for (var i=0; i<innerScripts.length; i++)
2585         {
2586             var script = innerScripts[i];
2587             if (script.isValid && script.baseLineNumber == 1)
2588             {
2589                 // Clear helper breakpoints that are set automatically when a new script
2590                 // is created. But avoid cases where the user has a breakpoint on the
2591                 // first line in a function (issue 3985).
2592                 if (!fbs.findBreakpointByScript(script, 0))
2593                     script.clearBreakpoint(0);
2594 
2595                 if (this.onXScriptCreatedByTag[script.tag])
2596                     delete this.onXScriptCreatedByTag[script.tag];
2597             }
2598         }
2599 
2600         fbs.nestedScriptStack = [];
2601     },
2602 
2603     onScriptCreated: function(script)
2604     {
2605         if (!fbs)
2606         {
2607             if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS || FBTrace.DBG_FBS_TRACKFILES)
2608                 FBTrace.sysout("fbs.onScriptCreated " + script.tag +
2609                     ", but no fbs for script.fileName=" + script.fileName);
2610              return;
2611         }
2612 
2613         try
2614         {
2615             var fileName = script.fileName;
2616 
2617             if (FBTrace.DBG_FBS_TRACKFILES)
2618                 trackFiles.add(fileName);
2619 
2620             if (isFilteredURL(fileName) || fbs.isChromebug(fileName))
2621             {
2622                 try
2623                 {
2624                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2625                         FBTrace.sysout("fbs.onScriptCreated " + script.tag +
2626                             ": filename filtered:\'" +
2627                             fileName + "\'" + (fbs.filterConsoleInjections ?
2628                             " console injection" : ""));
2629                 }
2630                 catch (exc)
2631                 {
2632                     FBTrace.sysout("fbs.onScriptCreated " + script.tag + " filtered msg ERROR \'" +
2633                         script.fileName+"\'"); /*? Bug 426692 */
2634                 }
2635 
2636                 if (FBTrace.DBG_FBS_TRACKFILES)
2637                     trackFiles.drop(fileName);
2638 
2639                 return;
2640             }
2641 
2642             if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
2643                 FBTrace.sysout("fbs.onScriptCreated: " + script.tag + "@(" + script.baseLineNumber +
2644                     "-" + (script.baseLineNumber+script.lineExtent) + ")" + script.fileName);
2645 
2646             if (script.lineExtent > 80000 && FBTrace.DBG_FBS_SRCUNITS)
2647                 FBTrace.sysout("fbs.BOGUS line extent (" + script.lineExtent +
2648                     ") for " + script.fileName);
2649 
2650             if (FBTrace.DBG_FBS_CREATION)
2651             {
2652                 try
2653                 {
2654                     FBTrace.sysout("fbs.onScriptCreated: \'"+script.functionName+"\'",
2655                         script.functionSource);
2656                 }
2657                 catch (exc)
2658                 {
2659                     FBTrace.sysout("fbs.onScriptCreated " + script.tag + " ERROR \'" +
2660                         script.fileName + "\'"); /*? Bug 426692 */
2661                 }
2662             }
2663 
2664             if (reXUL.test(script.fileName))
2665             {
2666                 fbs.onXScriptCreatedByTag[script.tag] = fbs.onXULScriptCreated;
2667                 fbs.pendingXULScripts.push(script);
2668 
2669                 // Stop in the first one called and assign all with this fileName to sourceFile.
2670                 script.setBreakpoint(0);
2671             }
2672             else if (!script.functionName) // top or eval-level
2673             {
2674                 // We need to detect eval() and grab its source.
2675                 var hasCaller = fbs.createdScriptHasCaller();
2676                 if (FBTrace.DBG_FBS_SRCUNITS)
2677                     FBTrace.sysout("fbs.top or eval case createdScriptHasCaller " + hasCaller);
2678 
2679                 if (hasCaller)
2680                 {
2681                     // components end up here
2682                     fbs.onXScriptCreatedByTag[script.tag] = this.onEvalScriptCreated;
2683                 }
2684                 else
2685                 {
2686                     fbs.onXScriptCreatedByTag[script.tag] = this.onTopLevelScriptCreated;
2687                 }
2688 
2689                 script.setBreakpoint(0);
2690 
2691                 if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS || FBTrace.DBG_FBS_BP)
2692                 {
2693                     FBTrace.sysout("fbs.onScriptCreated: set BP at PC 0 in " +
2694                         (hasCaller ? "eval" : "top") + " level tag=" + script.tag + ":" +
2695                         script.fileName + " jsd depth:" + (jsd.isOn ? jsd.pauseDepth +
2696                         "" : "OFF"));
2697                 }
2698             }
2699             else if (script.baseLineNumber == 1)
2700             {
2701                 // could be a 1) Browser-generated event handler or
2702                 // 2) a nested script at the top of a file
2703                 // One way to tell is assume both then wait to see which we hit first:
2704                 // 1) bp at pc=0 for this script or 2) for a top-level on at the same filename
2705 
2706                 if (FBTrace.DBG_FBS_SRCUNITS)
2707                 {
2708                     var hasCaller = fbs.createdScriptHasCaller();
2709                     FBTrace.sysout("fbs.browser generated createdScriptHasCaller " + hasCaller);
2710                 }
2711 
2712                 fbs.onXScriptCreatedByTag[script.tag] = this.onEventScriptCreated; // for case 1
2713                 script.setBreakpoint(0);
2714 
2715                 fbs.nestedScriptStack.push(script);  // for case 2
2716 
2717                 if (FBTrace.DBG_FBS_CREATION)
2718                     FBTrace.sysout("fbs.onScriptCreated: set BP at PC 0 in event level tag=" +
2719                         script.tag);
2720             }
2721             else
2722             {
2723                 fbs.nestedScriptStack.push(script);
2724 
2725                 if (FBTrace.DBG_FBS_CREATION)
2726                     FBTrace.sysout("fbs.onScriptCreated: nested function named: " +
2727                         script.functionName);
2728 
2729                 dispatch(scriptListeners, "onScriptCreated", [script, fileName, script.baseLineNumber]);
2730             }
2731         }
2732         catch(exc)
2733         {
2734             ERROR("onScriptCreated failed: " + exc);
2735             FBTrace.sysout("fbs.onScriptCreated failed:", exc);
2736         }
2737     },
2738 
2739     createdScriptHasCaller: function()
2740     {
2741         if (FBTrace.DBG_FBS_SRCUNITS)
2742             dumpComponentsStack("createdScriptHasCaller ");
2743 
2744         // createdScriptHasCaller
2745         var frame = Components.stack;
2746 
2747         // onScriptCreated
2748         frame = frame.caller;
2749         if (!frame)
2750             return frame;
2751 
2752         // hook apply
2753         frame = frame.caller;
2754         if (!frame)
2755             return frame;
2756 
2757         // native interpret?
2758         frame = frame.caller;
2759         if (!frame)
2760             return frame;
2761 
2762         // our creator ... or null if we are top level
2763         frame = frame.caller;
2764         return frame;
2765     },
2766 
2767     onScriptDestroyed: function(script)
2768     {
2769         if (!fbs)
2770              return;
2771 
2772         if (script.tag in fbs.onXScriptCreatedByTag)
2773             delete  fbs.onXScriptCreatedByTag[script.tag];
2774 
2775         try
2776         {
2777             var fileName = script.fileName;
2778             if (isFilteredURL(fileName))
2779                 return;
2780 
2781             if (FBTrace.DBG_FBS_CREATION)
2782                 FBTrace.sysout('fbs.onScriptDestroyed '+script.tag);
2783 
2784             dispatch(scriptListeners,"onScriptDestroyed",[script]);
2785         }
2786         catch(exc)
2787         {
2788             ERROR("onScriptDestroyed failed: "+exc);
2789             FBTrace.sysout("fbs.onScriptDestroyed failed: ", exc);
2790         }
2791     },
2792 
2793     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2794 
2795     createFilter: function(pattern, pass)
2796     {
2797         var filter = {
2798             globalObject: null,
2799             flags: pass ? (jsdIFilter.FLAG_ENABLED | jsdIFilter.FLAG_PASS) : jsdIFilter.FLAG_ENABLED,
2800             urlPattern: pattern,
2801             startLine: 0,
2802             endLine: 0
2803         };
2804         return filter;
2805     },
2806 
2807     setChromeBlockingFilters: function()
2808     {
2809         if (!fbs.isChromeBlocked)
2810         {
2811 
2812             jsd.appendFilter(this.noFilterHalter);  // must be first
2813             jsd.appendFilter(this.noFilterTrace);  // must be second
2814             jsd.appendFilter(this.filterChrome);
2815             jsd.appendFilter(this.filterComponents);
2816             jsd.appendFilter(this.filterFirebugComponents);
2817             jsd.appendFilter(this.filterModules);
2818             jsd.appendFilter(this.filterStringBundle);
2819             jsd.appendFilter(this.filterPrettyPrint);
2820             jsd.appendFilter(this.filterWrapper);
2821 
2822             for (var i = 0; i < this.componentFilters.length; i++)
2823                 jsd.appendFilter(this.componentFilters[i]);
2824 
2825             fbs.isChromeBlocked = true;
2826 
2827             if (FBTrace.DBG_FBS_BP)
2828                 this.traceFilters("setChromeBlockingFilters with " +
2829                     this.componentFilters.length + " component filters");
2830         }
2831     },
2832 
2833     removeChromeBlockingFilters: function()
2834     {
2835         try
2836         {
2837             if (fbs.isChromeBlocked)
2838             {
2839                 if (!this.filterChrome)
2840                     FBTrace.sysout("fbs.removeChromeBlockingFilters is confused ", this);
2841 
2842                 jsd.removeFilter(this.filterChrome);
2843                 jsd.removeFilter(this.filterComponents);
2844                 jsd.removeFilter(this.filterFirebugComponents);
2845                 jsd.removeFilter(this.filterModules);
2846                 jsd.removeFilter(this.filterStringBundle);
2847                 jsd.removeFilter(this.filterPrettyPrint);
2848                 jsd.removeFilter(this.filterWrapper);
2849                 jsd.removeFilter(this.noFilterHalter);
2850                 jsd.removeFilter(this.noFilterTrace);
2851 
2852                 for (var i = 0; i < this.componentFilters.length; i++)
2853                     jsd.removeFilter(this.componentFilters[i]);
2854 
2855                 fbs.isChromeBlocked = false;
2856             }
2857         }
2858         catch (err)
2859         {
2860             FBTrace.sysout("fbs.removeChromeBlockingFilters; EXCEPTION " + err, err);
2861         }
2862 
2863         if (FBTrace.DBG_FBS_BP)
2864             this.traceFilters("removeChromeBlockingFilters");
2865     },
2866 
2867     // call after components are loaded.
2868     createChromeBlockingFilters: function()
2869     {
2870         try
2871         {
2872             this.filterModules = this.createFilter("*/firefox/modules/*");
2873             this.filterComponents = this.createFilter("*/firefox/components/*");
2874             this.filterFirebugComponents = this.createFilter("*/modules/firebug-*");
2875             this.filterStringBundle = this.createFilter("XStringBundle");
2876             this.filterChrome = this.createFilter("chrome://*");
2877             this.filterPrettyPrint = this.createFilter("x-jsd:ppbuffer*");
2878             this.filterWrapper = this.createFilter("XPCSafeJSObjectWrapper.cpp");
2879             this.noFilterHalter = this.createFilter("resource://firebug/debuggerHalter.js", true);
2880             this.noFilterTrace = this.createFilter("chrome://firebug/content/console/consoleExposed.js", true);
2881 
2882             // jsdIFilter does not allow full regexp matching.
2883             // So to filter components, we filter their directory names, which we obtain
2884             // by looking for scripts that match regexps
2885 
2886             var componentsUnfound = [];
2887             for (var i=0; i<COMPONENTS_FILTERS.length; ++i)
2888                 componentsUnfound.push(COMPONENTS_FILTERS[i]);
2889 
2890             this.componentFilters = [];
2891 
2892             jsd.enumerateScripts( {
2893                 enumerateScript: function(script)
2894                 {
2895                     var fileName = script.fileName;
2896                     for (var i=0; i<componentsUnfound.length; ++i)
2897                     {
2898                         if (componentsUnfound[i].test(fileName))
2899                         {
2900                             var match = componentsUnfound[i].exec(fileName);
2901                             fbs.componentFilters.push(fbs.createFilter(match[1]));
2902                             componentsUnfound.splice(i, 1);
2903                             return;
2904                         }
2905                     }
2906                 }
2907             });
2908         }
2909         catch (exc)
2910         {
2911             FBTrace.sysout("fbs.createChromeblockingFilters ERROR >>>>>>>>>>>>>>>>> "+exc, exc);
2912         }
2913 
2914         if (FBTrace.DBG_FBS_BP)
2915         {
2916             FBTrace.sysout("fbs.createChromeBlockingFilters considered "+COMPONENTS_FILTERS.length+
2917                     " regexps and created "+this.componentFilters.length+
2918                     " filters with unfound: "+componentsUnfound.length, componentsUnfound);
2919             fbs.traceFilters("createChromeBlockingFilters");
2920         }
2921     },
2922 
2923     traceFilters: function(from)
2924     {
2925         FBTrace.sysout("fbs.traceFilters from "+from);
2926 
2927         jsd.enumerateFilters({ enumerateFilter: function(filter)
2928         {
2929             FBTrace.sysout("fbs.jsdIFilter "+filter.urlPattern, filter);
2930         }});
2931     },
2932 
2933     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2934 
2935     eachJSContext: function(callback)
2936     {
2937         var enumeratedContexts = [];
2938         jsd.enumerateContexts({ enumerateContext: function(jscontext)
2939         {
2940             try
2941             {
2942                 if (!jscontext.isValid)
2943                     return;
2944 
2945                 var wrappedGlobal = jscontext.globalObject;
2946                 if (!wrappedGlobal)
2947                     return;
2948 
2949                 var unwrappedGlobal = wrappedGlobal.getWrappedValue();
2950                 if (!unwrappedGlobal)
2951                     return;
2952 
2953                 if (unwrappedGlobal instanceof Ci.nsISupports)
2954                     var global = new XPCNativeWrapper(unwrappedGlobal);
2955                 else
2956                     var global = unwrappedGlobal;
2957 
2958                 if (FBTrace.DBG_FBS_JSCONTEXTS)
2959                     FBTrace.sysout("fbs.getJSContexts jsIContext tag:"+jscontext.tag+
2960                         (jscontext.isValid?" - isValid\n":" - NOT valid\n"));
2961 
2962                 if (global)
2963                 {
2964                     callback(global, jscontext.tag);
2965                 }
2966                 else
2967                 {
2968                     if (FBTrace.DBG_FBS_JSCONTEXTS)
2969                         FBTrace.sysout("fbs.getJSContexts no global object tag:"+jscontext.tag);
2970                     return; // skip this
2971                 }
2972 
2973                 enumeratedContexts.push(jscontext);
2974             }
2975             catch(e)
2976             {
2977                 FBTrace.sysout("fbs.jscontext dump FAILED "+e, e);
2978             }
2979         }});
2980 
2981         return enumeratedContexts;
2982     },
2983 
2984     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2985 
2986     getOutermostScope: function(frame)
2987     {
2988         var scope = frame.scope;
2989         if (scope)
2990         {
2991             while(scope.jsParent)
2992                 scope = scope.jsParent;
2993 
2994             // These are just determined by trial and error.
2995             if (scope.jsClassName == "Window" || scope.jsClassName == "ChromeWindow" ||
2996                 scope.jsClassName == "ModalContentWindow")
2997             {
2998                 lastWindowScope = wrapIfNative(scope.getWrappedValue());
2999                 return  lastWindowScope;
3000             }
3001 
3002     /*        if (scope.jsClassName == "DedicatedWorkerGlobalScope")
3003             {
3004                 //var workerScope = new XPCNativeWrapper(scope.getWrappedValue());
3005 
3006                 //if (FBTrace.DBG_FBS_FINDDEBUGGER)
3007                 //        FBTrace.sysout("fbs.getOutermostScope found WorkerGlobalScope: "+scope.jsClassName, workerScope);
3008                 // https://bugzilla.mozilla.org/show_bug.cgi?id=507930 if (FBTrace.DBG_FBS_FINDDEBUGGER)
3009                 //        FBTrace.sysout("fbs.getOutermostScope found WorkerGlobalScope.location: "+workerScope.location, workerScope.location);
3010                 return null; // https://bugzilla.mozilla.org/show_bug.cgi?id=507783
3011             }
3012     */
3013             if (scope.jsClassName == "Sandbox")
3014             {
3015                 // Drop one frame see attachConsoleInjector
3016                 var fileName = this.safeGetUrlFromFrame(frame);
3017                 if (fileName && fileName.indexOf("console/consoleInjector.js") > 0)
3018                 {
3019                     if (frame.callingFrame)
3020                         return fbs.getOutermostScope(frame.callingFrame);
3021                 }
3022 
3023                 var proto = scope.jsPrototype;
3024 
3025                 // this is the path if we have web page in a sandbox
3026                 if (proto.jsClassName == "XPCNativeWrapper")
3027                 {
3028                     proto = proto.jsParent;
3029                     if (proto.jsClassName == "Window")
3030                         return wrapIfNative(proto.getWrappedValue());
3031                 }
3032                 else
3033                 {
3034                     return wrapIfNative(scope.getWrappedValue());
3035                 }
3036             }
3037 
3038             if (FBTrace.DBG_FBS_FINDDEBUGGER)
3039                 FBTrace.sysout("fbs.getOutermostScope found scope chain bottom, not Window: " +
3040                     scope.jsClassName, scope);
3041 
3042             return wrapIfNative(scope.getWrappedValue());  // not a window or a sandbox
3043         }
3044         else
3045         {
3046             return null;
3047         }
3048     },
3049 
3050     findDebugger: function(frame)
3051     {
3052         if (debuggers.length < 1)
3053             return;
3054 
3055         var checkFrame = frame;
3056         while (checkFrame) // We may stop in a component, but want the callers Window
3057         {
3058             // the outermost lexical scope of the function running the frame
3059             var frameScopeRoot = this.getOutermostScope(checkFrame);
3060             if (frameScopeRoot)
3061                 break;
3062 
3063             if (FBTrace.DBG_FBS_FINDDEBUGGER)
3064                 FBTrace.sysout("fbs.findDebugger no frame Window, looking to older stackframes",
3065                     checkFrame);
3066 
3067             checkFrame = checkFrame.callingFrame;
3068         }
3069 
3070         if (!checkFrame && FBTrace.DBG_FBS_FINDDEBUGGER)
3071             FBTrace.sysout("fbs.findDebugger fell thru bottom of stack", frame);
3072 
3073         // frameScopeRoot should be the top window for the scope of the frame function
3074         // or null
3075         var the_debuggr = fbs.askDebuggersForSupport(frameScopeRoot, frame);
3076         if (the_debuggr)
3077              return the_debuggr;
3078 
3079         if (FBTrace.DBG_FBS_FINDDEBUGGER)
3080             FBTrace.sysout("fbs.findDebugger no debuggr on bottom frame", frame);
3081 
3082         return null;
3083     },
3084 
3085     isChromebug: function(location)
3086     {
3087         // TODO this is a kludge: isFilteredURL stops users from seeing firebug but
3088         // chromebug has to disable the filter
3089 
3090         if (location)
3091         {
3092             if (location.indexOf("chrome://chromebug/") >= 0 ||
3093                 location.indexOf("chrome://fb4cb/") >= 0)
3094             {
3095                 return true;
3096             }
3097         }
3098         return false;
3099     },
3100 
3101     getLocationSafe: function(global)
3102     {
3103         try
3104         {
3105             // then we have a window, it will be an nsIDOMWindow, right?
3106             if (global && global.location)
3107                 return global.location.toString();
3108             else if (global && global.tag)
3109                 return "global_tag_"+global.tag;
3110         }
3111         catch (exc)
3112         {
3113             // FF3 gives (NS_ERROR_INVALID_POINTER) [nsIDOMLocation.toString]
3114         }
3115 
3116         return null;
3117     },
3118 
3119     safeGetUrlFromFrame: function(frame)
3120     {
3121         try
3122         {
3123             if (frame)
3124                 return frame.script.fileName;
3125         }
3126         catch (err)
3127         {
3128         }
3129         return "";
3130     },
3131 
3132     askDebuggersForSupport: function(global, frame)
3133     {
3134         if (FBTrace.DBG_FBS_FINDDEBUGGER)
3135             FBTrace.sysout("fbs.askDebuggersForSupport using global " + global + " for " +
3136                 frame.script.fileName);
3137 
3138         if (global && fbs.isChromebug(fbs.getLocationSafe(global)))
3139             return false;
3140 
3141         if (FBTrace.DBG_FBS_FINDDEBUGGER)
3142             FBTrace.sysout("fbs.askDebuggersForSupport " + debuggers.length +
3143                 " debuggers to check for " + frame.script.fileName, debuggers);
3144 
3145         for (var i=debuggers.length-1; i>=0; i--)
3146         {
3147             try
3148             {
3149                 var debuggr = debuggers[i];
3150                 if (debuggr.supportsGlobal(global, frame))
3151                 {
3152                     if (!debuggr.breakContext)
3153                         FBTrace.sysout("fbs.Debugger with no breakContext:",debuggr.supportsGlobal);
3154 
3155                     if (FBTrace.DBG_FBS_FINDDEBUGGER)
3156                         FBTrace.sysout("fbs.findDebugger found debuggr (" + debuggr.debuggerName +
3157                             ") at " + i + " with breakContext " + debuggr.breakContext.getName() +
3158                             " for global " + fbs.getLocationSafe(global) + " while processing " +
3159                             frame.script.fileName);
3160 
3161                     return debuggr;
3162                 }
3163             }
3164             catch (exc)
3165             {
3166                 FBTrace.sysout("fbs.askDebuggersForSupport ERROR: " + exc,exc);
3167             }
3168         }
3169         return null;
3170     },
3171 
3172     dumpIValue: function(value)
3173     {
3174         var listValue = {value: null}, lengthValue = {value: 0};
3175         value.getProperties(listValue, lengthValue);
3176 
3177         for (var i=0; i<lengthValue.value; ++i)
3178         {
3179             var prop = listValue.value[i];
3180             try
3181             {
3182                 var name = unwrapIValue(prop.name);
3183                 FBTrace.sysout("fbs." + i + "]" + name + "=" + unwrapIValue(prop.value));
3184             }
3185             catch (e)
3186             {
3187                 FBTrace.sysout("fbs." + i + "]" + e);
3188             }
3189         }
3190     },
3191 
3192     reFindDebugger: function(frame, debuggr)
3193     {
3194         var frameScopeRoot = this.getOutermostScope(frame);
3195         if (frameScopeRoot && debuggr.supportsGlobal(frameScopeRoot, frame))
3196             return debuggr;
3197 
3198         if (FBTrace.DBG_FBS_FINDDEBUGGER)
3199             FBTrace.sysout("fbs.reFindDebugger debuggr " + debuggr.debuggerName +
3200                 " does not support frameScopeRoot " + frameScopeRoot, frameScopeRoot);
3201 
3202         return null;
3203     },
3204 
3205     discardRecursionFrames: function(frame)
3206     {
3207         var i = 0;
3208         var rest = 0;
3209         var mark = frame;  // a in abcabcabcdef
3210         var point = frame;
3211 
3212         while (point = point.callingFrame)
3213         {
3214             i++;
3215 
3216             // then we found a repeating caller abcabcdef
3217             if (point.script.tag == mark.script.tag)
3218             {
3219                 mark = point;
3220                 rest = i;
3221             }
3222         }
3223 
3224         // here point is null and mark is the last repeater, abcdef
3225         if (FBTrace.DBG_FBS_ERRORS)
3226             FBTrace.sysout("fbs.discardRecursionFrames dropped " + rest + " of " + i, mark);
3227 
3228         return mark;
3229     },
3230 
3231     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
3232     // Breakpoints
3233 
3234     // jsd breakpoints are on a PC in a jsdIScript
3235     // Users breakpoint on a line of source
3236     // Because test.js can be included multiple times, the URL+line number from the UI is not unique.
3237     // sourcefile.href != script.fileName, generally script.fileName cannot be used.
3238     // If the source is compiled, then we have zero, one, or more jsdIScripts on a line.
3239     //    If zero, cannot break at that line
3240     //    If one, set a jsd breakpoint
3241     //    If more than one, set jsd breakpoint on each script
3242     // Else we know that the source will be compiled before it is run.
3243     //    Save the sourceFile.href+line and set the jsd breakpoint when we compile
3244     //    Venkman called these "future" breakpoints
3245     //    We cannot prevent future breakpoints on lines that have no script.
3246     //    Break onCreate with error?
3247     addBreakpoint: function(type, sourceFile, lineNo, props, debuggr)
3248     {
3249         var url = sourceFile.href;
3250         var bp = this.findBreakpoint(url, lineNo);
3251         if (bp && bp.type & type)
3252             return null;
3253 
3254         if (bp)
3255         {
3256             bp.type |= type;
3257 
3258             if (debuggr)
3259             {
3260                 bp.debuggerName = debuggr.debuggerName;
3261             }
3262             else
3263             {
3264                 if (FBTrace.DBG_FBS_BP)
3265                     FBTrace.sysout("fbs.addBreakpoint with no debuggr:");
3266             }
3267         }
3268         else
3269         {
3270             bp = this.recordBreakpoint(type, url, lineNo, debuggr, props, sourceFile);
3271         }
3272 
3273         if (FBTrace.DBG_FBS_BP)
3274             FBTrace.sysout("fbs.addBreakpoint for "+url, [bp, sourceFile]);
3275 
3276         return bp;
3277     },
3278 
3279     recordBreakpoint: function(type, url, lineNo, debuggr, props, sourceFile)
3280     {
3281         var urlBreakpoints = fbs.getBreakpoints(url);
3282         if (!urlBreakpoints)
3283             urlBreakpoints = [];
3284 
3285         if (typeof(lineNo) !== 'number')
3286             throw new Error("firebug-service line numbers must be numbers "+lineNo+"@"+url);
3287 
3288         var bp = {type: type, href: url, lineNo: lineNo, disabled: 0,
3289             debuggerName: debuggr.debuggerName,
3290             condition: "", onTrue: true, hitCount: -1, hit: 0};
3291 
3292         if (props)
3293         {
3294             bp.condition = props.condition;
3295             bp.onTrue = props.onTrue;
3296             bp.hitCount = props.hitCount;
3297             if (bp.condition || bp.hitCount > 0)
3298                 ++conditionCount;
3299             if(props.disabled)
3300             {
3301                 bp.disabled |= BP_NORMAL;
3302                 ++disabledCount;
3303             }
3304         }
3305 
3306         urlBreakpoints.push(bp);
3307         fbs.setJSDBreakpoint(sourceFile, bp);
3308         fbs.setBreakpoints(url, urlBreakpoints);
3309         ++breakpointCount;
3310 
3311         return bp;
3312     },
3313 
3314     removeBreakpoint: function(type, url, lineNo)
3315     {
3316         var urlBreakpoints = fbs.getBreakpoints(url);
3317 
3318         if (FBTrace.DBG_FBS_BP)
3319             FBTrace.sysout("fbs.removeBreakpoint for "+url+", need to check bps="+
3320                 (urlBreakpoints?urlBreakpoints.length:"none"));
3321 
3322         if (!urlBreakpoints)
3323             return false;
3324 
3325         for (var i=0; i<urlBreakpoints.length; ++i)
3326         {
3327             var bp = urlBreakpoints[i];
3328             if (FBTrace.DBG_FBS_BP)
3329                 FBTrace.sysout("fbs.removeBreakpoint checking bp.lineNo vs lineNo=" + bp.lineNo +
3330                     " vs " + lineNo);
3331 
3332             if (bp.lineNo === lineNo)
3333             {
3334                 bp.type &= ~type;
3335                 if (!bp.type)
3336                 {
3337                     if (bp.scriptsWithBreakpoint)
3338                     {
3339                         for (var j=0; j<bp.scriptsWithBreakpoint.length; j++)
3340                         {
3341                             var script = bp.scriptsWithBreakpoint[j];
3342                             if (script && script.isValid)
3343                             {
3344                                 try
3345                                 {
3346                                     script.clearBreakpoint(bp.pc[j]);
3347                                     if (FBTrace.DBG_FBS_BP)
3348                                         FBTrace.sysout("fbs.removeBreakpoint in tag=" + script.tag +
3349                                             " at " + lineNo + "@" + url);
3350                                 }
3351                                 catch (exc)
3352                                 {
3353                                     FBTrace.sysout("fbs.Firebug service failed to remove breakpoint in " +
3354                                         script.tag + " at lineNo=" + lineNo + " pcmap:" + bp.pcmap);
3355                                 }
3356                             }
3357                         }
3358                     }
3359 
3360                     // else this was a future breakpoint that never hit or a script that was GCed
3361 
3362                     urlBreakpoints.splice(i, 1);
3363                     --breakpointCount;
3364 
3365                     if (bp.disabled)
3366                         --disabledCount;
3367 
3368                     if (bp.condition || bp.hitCount > 0)
3369                         --conditionCount;
3370 
3371                     fbs.setBreakpoints(url, urlBreakpoints);
3372                 }
3373 
3374                 return bp;
3375             }
3376         }
3377 
3378         if (FBTrace.DBG_FBS_BP)
3379             FBTrace.sysout("fbs.removeBreakpoint no find for " + lineNo + "@" + url + " in " +
3380                 urlBreakpoints.length, urlBreakpoints);
3381 
3382         return false;
3383     },
3384 
3385     findBreakpoint: function(url, lineNo)
3386     {
3387         var urlBreakpoints = fbs.getBreakpoints(url);
3388         if (urlBreakpoints)
3389         {
3390             for (var i = 0; i < urlBreakpoints.length; ++i)
3391             {
3392                 var bp = urlBreakpoints[i];
3393                 if (bp.lineNo === lineNo)
3394                     return bp;
3395             }
3396         }
3397 
3398         if (FBTrace.DBG_FBS_BP)
3399             FBTrace.sysout("fbs.findBreakpoint no find for "+lineNo+"@"+url, urlBreakpoints);
3400 
3401         return null;
3402     },
3403 
3404     // When we are called, scripts have been compiled so all relevant breakpoints are not "future"
3405     findBreakpointByScript: function(script, pc)
3406     {
3407         var urlsWithBreakpoints = fbs.getBreakpointURLs();
3408         for (var iURL = 0; iURL < urlsWithBreakpoints.length; iURL++)
3409         {
3410             var url = urlsWithBreakpoints[iURL];
3411             var urlBreakpoints = fbs.getBreakpoints(url);
3412             if (urlBreakpoints)
3413             {
3414                 for (var iBreakpoint = 0; iBreakpoint < urlBreakpoints.length; ++iBreakpoint)
3415                 {
3416                     var bp = urlBreakpoints[iBreakpoint];
3417                     if (bp.scriptsWithBreakpoint)
3418                     {
3419                         for (var iScript = 0; iScript < bp.scriptsWithBreakpoint.length; iScript++)
3420                         {
3421                             if (FBTrace.DBG_FBS_BP)
3422                             {
3423                                 var vs = (bp.scriptsWithBreakpoint[iScript] ?
3424                                     bp.scriptsWithBreakpoint[iScript].tag + "@" + bp.pc[iScript] :
3425                                     "future") + " on " + url;
3426 
3427                                 FBTrace.sysout("fbs.findBreakpointByScript[" + iURL + "," + iBreakpoint +
3428                                     "," + iScript + "]" + " looking for " + script.tag + "@" + pc +
3429                                     " vs " + vs);
3430                             }
3431 
3432                             if (bp.scriptsWithBreakpoint[iScript] &&
3433                                 (bp.scriptsWithBreakpoint[iScript].tag == script.tag) &&
3434                                 (bp.pc[iScript] == pc))
3435                             {
3436                                 return bp;
3437                             }
3438                         }
3439                     }
3440                 }
3441             }
3442         }
3443 
3444         return null;
3445     },
3446 
3447     // the sourcefile has just been created after compile
3448     resetBreakpoints: function(sourceFile, debuggr)
3449     {
3450         // If the new script is replacing an old script with a breakpoint still
3451         var url = sourceFile.href;
3452         var urlBreakpoints = fbs.getBreakpoints(url);
3453 
3454         if (FBTrace.DBG_FBS_BP)
3455         {
3456             try
3457             {
3458                 var msg = "resetBreakpoints: breakpoints["+sourceFile.href;
3459                 msg += "]="+(urlBreakpoints?urlBreakpoints.length:"NONE")+"\n";
3460                 FBTrace.sysout(msg);
3461             }
3462             catch (exc)
3463             {
3464                 FBTrace.sysout("fbs.Failed to give resetBreakpoints trace in url: " + url +
3465                     " because " + exc + " for urlBreakpoints=", urlBreakpoints);
3466             }
3467         }
3468 
3469         if (urlBreakpoints)
3470         {
3471             if (FBTrace.DBG_FBS_BP)
3472                 FBTrace.sysout("fbs.resetBreakpoints total bp=" + urlBreakpoints.length +
3473                     " for url=" + url);
3474 
3475             fbs.deleteBreakpoints(url);
3476 
3477             for (var i=0; i<urlBreakpoints.length; ++i)
3478             {
3479                 var bp = urlBreakpoints[i];
3480                 bp = fbs.recordBreakpoint(bp.type, url, bp.lineNo, debuggr, bp, sourceFile);
3481 
3482                 if (bp.type & BP_ERROR)
3483                 {
3484                     var existingBP = null;
3485                     fbs.enumerateErrorBreakpoints(url, {call: function checkExisting(url, lineNo, bp)
3486                     {
3487                         // An error breakpoint is in this file
3488                         if (lineNo == bp.lineNo)
3489                             existingBP = true;
3490                     }});
3491 
3492                     if (!existingBP)
3493                         errorBreakpoints.push(bp);  // TODO implement as hashtable errorBreakpoints[url@lineNo]
3494                 }
3495 
3496                 if (bp.disabled & BP_NORMAL)
3497                 {
3498                     if (FBTrace.DBG_FBS_BP)
3499                         FBTrace.sysout("fbs.resetBreakpoints:  mark breakpoint disabled: " +
3500                             bp.lineNo + "@" + sourceFile);
3501 
3502                     fbs.disableBreakpoint(url, bp.lineNo);
3503                 }
3504                 else
3505                 {
3506                     if (FBTrace.DBG_FBS_BP)
3507                         FBTrace.sysout("fbs.resetBreakpoints: "+bp.lineNo+"@"+sourceFile);
3508                 }
3509             }
3510         }
3511         else
3512         {
3513             if (FBTrace.DBG_FBS_BP)
3514                 FBTrace.sysout("fbs.resetBreakpoints no breakpoints for "+url);
3515         }
3516     },
3517 
3518     setJSDBreakpoint: function(sourceFile, bp)
3519     {
3520         var scripts = sourceFile.getScriptsAtLineNumber(bp.lineNo);
3521         if (!scripts)
3522         {
3523             if (FBTrace.DBG_FBS_BP)
3524                 FBTrace.sysout("fbs.setJSDBreakpoint:  NO inner scripts: "+bp.lineNo+"@"+sourceFile);
3525 
3526             if (!sourceFile.outerScript || !sourceFile.outerScript.isValid)
3527             {
3528                 if (FBTrace.DBG_FBS_BP)
3529                     FBTrace.sysout("fbs.setJSDBreakpoint:  NO valid outerScript\n");
3530                 return;
3531             }
3532 
3533             scripts = [sourceFile.outerScript];
3534         }
3535 
3536         if (!bp.scriptsWithBreakpoint)
3537         {
3538             bp.scriptsWithBreakpoint = [];
3539             bp.pc = [];
3540         }
3541 
3542         for (var i=0; i<scripts.length; i++)
3543         {
3544             var script = scripts[i];
3545             if (!script.isValid)
3546             {
3547                 if (FBTrace.DBG_FBS_BP)
3548                     FBTrace.sysout("fbs.setJSDBreakpoint:  tag " + script.tag + ", " + i + "/" +
3549                         scripts.length + " is invalid");
3550                 continue;
3551             }
3552 
3553             var haveScript = false;
3554             for (var j=0; j<bp.scriptsWithBreakpoint.length; j++)
3555             {
3556                 if (bp.scriptsWithBreakpoint[j].tag === script.tag)
3557                 {
3558                     haveScript = true;
3559                     break;
3560                 }
3561             }
3562 
3563             if (haveScript)
3564                 continue;
3565 
3566             var pcmap = sourceFile.pcmap_type;
3567             if (!pcmap)
3568             {
3569                 if (FBTrace.DBG_FBS_ERRORS)
3570                     FBTrace.sysout("fbs.setJSDBreakpoint pcmap undefined " +
3571                         sourceFile, sourceFile);
3572 
3573                 pcmap = PCMAP_SOURCETEXT;
3574             }
3575 
3576             // we subtraced this offset when we showed the user so lineNo is a user line
3577             // number; now we need to talk
3578             // to jsd its line world
3579             var jsdLine = bp.lineNo + sourceFile.getBaseLineOffset();
3580             // test script.isLineExecutable(jsdLineNo, pcmap) ??
3581 
3582             var isExecutable = false;
3583             try
3584             {
3585                  isExecutable = script.isLineExecutable(jsdLine, pcmap);
3586             }
3587             catch(e)
3588             {
3589                 // guess not then...
3590             }
3591 
3592             if (isExecutable)
3593             {
3594                 var pc = script.lineToPc(jsdLine, pcmap);
3595                 var pcToLine = script.pcToLine(pc, pcmap);  // avoid calling this unless we have to...
3596 
3597                 if (pcToLine == jsdLine)
3598                 {
3599                     script.setBreakpoint(pc);
3600 
3601                     bp.scriptsWithBreakpoint.push(script);
3602                     bp.pc.push(pc);
3603                     bp.pcmap = pcmap;
3604                     bp.jsdLine = jsdLine;
3605 
3606                     if (pc == 0)  // signal the breakpoint handler to break for user
3607                         sourceFile.breakOnZero = script.tag;
3608 
3609                     if (FBTrace.DBG_FBS_BP)
3610                         FBTrace.sysout("fbs.setJSDBreakpoint tag: " + script.tag + " line.pc@url=" +
3611                             bp.lineNo + "." + pc + "@" + sourceFile.href + " using offset:" +
3612                             sourceFile.getBaseLineOffset() + " jsdLine: " + jsdLine +
3613                             " pcToLine: " + pcToLine +
3614                             (isExecutable ? " isExecuable" : " notExecutable"),
3615                             {sourceFile: sourceFile, script: script});
3616                 }
3617                 else
3618                 {
3619                     if (FBTrace.DBG_FBS_BP)
3620                         FBTrace.sysout("fbs.setJSDBreakpoint LINE MISMATCH for tag: " +
3621                             script.tag + " line.pc@url=" + bp.lineNo + "." + pc + "@" +
3622                             sourceFile.href + " using offset:" + sourceFile.getBaseLineOffset() +
3623                             " jsdLine: " + jsdLine + " pcToLine: " + pcToLine +
3624                             (isExecutable ? " isExecuable" : " notExecutable"), sourceFile);
3625                 }
3626             }
3627             else
3628             {
3629                 if (FBTrace.DBG_FBS_BP)
3630                     FBTrace.sysout("fbs.setJSDBreakpoint NOT isExecutable tag: " + script.tag +
3631                         " jsdLine@url=" + jsdLine + "@" + sourceFile.href + " pcmap:" +
3632                         pcmap + " baselineOffset:" + sourceFile.getBaseLineOffset(), script);
3633             }
3634          }
3635     },
3636 
3637     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
3638 
3639     saveBreakpoints: function(url)
3640     {
3641         // Do not call fbs.setBreakpoints() it calls us.
3642         try
3643         {
3644             var urlBreakpoints = fbs.getBreakpoints(url);
3645 
3646             if (!urlBreakpoints || !urlBreakpoints.length)
3647             {
3648                 fbs.breakpointStore.removeItem(url);
3649                 fbs.deleteBreakpoints(url);
3650                 return;
3651             }
3652 
3653             var cleanBPs = [];
3654             for(var i = 0; i < urlBreakpoints.length; i++)
3655             {
3656                 var bp = urlBreakpoints[i];
3657 
3658                 // Do not store breakpoins for "Run until this line". These are not
3659                 // visible in Firebug UI and so, it is not possible to remove them.
3660                 // Note that there can be cases where such breakpoint is not removed
3661                 // by RunUntil (e.g. crash).
3662                 if (bp.type == BP_UNTIL)
3663                     continue;
3664 
3665                 var cleanBP = {};
3666                 for (var p in bp)
3667                     cleanBP[p] = bp[p];
3668 
3669                 delete cleanBP.scriptsWithBreakpoint; // not JSON-able
3670                 delete cleanBP.pc; // co-indexed with scriptsWithBreakpoint
3671                 delete cleanBP.debuggerName;
3672                 cleanBPs.push(cleanBP);
3673             }
3674 
3675             fbs.breakpointStore.setItem(url, cleanBPs);
3676         }
3677         catch (exc)
3678         {
3679             FBTrace.sysout("fbs.saveBreakpoints ERROR " + exc, exc);
3680         }
3681     },
3682 
3683     setBreakpoints: function(url, urlBreakpoints)
3684     {
3685         fbs.breakpoints[url] = urlBreakpoints;
3686         fbs.saveBreakpoints(url);
3687     },
3688 
3689     getBreakpoints: function(url)
3690     {
3691         return fbs.breakpoints[url];
3692     },
3693 
3694     deleteBreakpoints: function(url)
3695     {
3696         delete fbs.breakpoints[url];
3697     },
3698 
3699     getBreakpointURLs: function()
3700     {
3701         var breakpointStore = this.getBreakpointStore();
3702         if (!breakpointStore)
3703             return [];
3704 
3705         return breakpointStore.getKeys();
3706     },
3707 
3708     getBreakpointStore: function()
3709     {
3710         if (this.breakpointStore)
3711             return this.breakpointStore;
3712 
3713         try
3714         {
3715             Components.utils.import("resource://firebug/storageService.js");
3716 
3717             if (typeof(StorageService) != "undefined")
3718             {
3719                 this.breakpointStore = StorageService.getStorage("breakpoints.json");
3720             }
3721             else
3722             {
3723                 ERROR("firebug-service breakpoint StorageService ERROR");
3724 
3725                 this.breakpointStore =
3726                 {
3727                     setItem: function(){},
3728                     removeItem: function(){},
3729                     getKeys: function(){return [];},
3730                     clear: function(){},
3731                 };
3732             }
3733 
3734             return this.breakpointStore;
3735         }
3736         catch(exc)
3737         {
3738             // Throws another exception since fbs is null.
3739             //ERROR("firebug-service restoreBreakpoints ERROR "+exc);
3740 
3741             // xxxHonza: why I can't see this log in the Tracing Console?
3742             FBTrace.sysout("fbs.restoreBreakpoints ERROR " + exc, exc);
3743         }
3744     },
3745 
3746     restoreBreakpoints: function()
3747     {
3748         this.breakpoints = {};
3749 
3750         var breakpointStore = fbs.getBreakpointStore();
3751         if (!breakpointStore)
3752             return;
3753 
3754         var urls = fbs.getBreakpointURLs();
3755         for (var i=0; i<urls.length; i++)
3756         {
3757             var url = urls[i];
3758             var bps = breakpointStore.getItem(url);
3759 
3760             // Do not restore "Run unit this line" breakpoints. This should solve complaints
3761             // about Firebug breaking in the source even if there are no breakpoints in
3762             // Firebug UI.
3763             if (bps.type == BP_UNTIL)
3764                 continue;
3765 
3766             this.breakpoints[url] = bps;
3767 
3768             for (var j=0; j<bps.length; j++)
3769             {
3770                 var bp = bps[j];
3771                 if (bp.condition)
3772                     ++conditionCount;
3773                 if (bp.disabled)
3774                     ++disabledCount;
3775                 if (bp.type & BP_MONITOR)
3776                     ++monitorCount;
3777             }
3778         }
3779 
3780         if (FBTrace.DBG_FBS_BP)
3781         {
3782             FBTrace.sysout("fbs.restoreBreakpoints "+urls.length+", disabledCount:"+disabledCount+
3783                 " monitorCount:"+monitorCount+" conditionCount:"+conditionCount+", restored ",
3784                 this.breakpoints);
3785 
3786             for (var p in this.breakpoints)
3787                 FBTrace.sysout("fbs.restoreBreakpoints restored "+p+" condition "+
3788                     this.breakpoints[p].condition);
3789         }
3790     },
3791 
3792     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
3793     // When (debugger keyword and not halt)||(bp and BP_UNTIL) || (onBreakPoint && no conditions)
3794     // || interuptHook (single stepping).
3795     // rv is ignored
3796     routeBreakToDebuggr: function(frame, type, rv, stepStayOnDebuggr)
3797     {
3798         try
3799         {
3800             // avoid step_out from web page to chrome
3801             if (stepStayOnDebuggr)
3802             {
3803                 var debuggr = this.reFindDebugger(frame, stepStayOnDebuggr);
3804                 if (FBTrace.DBG_FBS_STEP)
3805                     FBTrace.sysout("fbs.routeBreakToDebuggr type="+getExecutionStopNameFromType(type)+
3806                         " stepStayOnDebuggr "+stepStayOnDebuggr+" debuggr:"+(debuggr?debuggr:"null"));
3807 
3808                 if (!debuggr) // then the frame is not for our debugger
3809                     return RETURN_CONTINUE;  // This means that we will continue to take interrupts until  when?
3810             }
3811             else
3812             {
3813                 var debuggr = this.findDebugger(frame);
3814 
3815                 if (FBTrace.DBG_FBS_STEP)
3816                     FBTrace.sysout("fbs.routeBreakToDebuggr type="+getExecutionStopNameFromType(type)+
3817                         " debuggr:"+(debuggr?debuggr:"null"));
3818             }
3819 
3820             if (debuggr)
3821                 return this.breakIntoDebugger(debuggr, frame, type);
3822         }
3823         catch(exc)
3824         {
3825             if (FBTrace.DBG_FBS_ERRORS)
3826                 FBTrace.sysout("fbs.routeBreakToDebuggr failed: "+exc,exc);
3827             ERROR("routeBreakToDebuggr failed: "+exc, exc);
3828         }
3829 
3830         return RETURN_CONTINUE;
3831     },
3832 
3833     breakIntoDebugger: function(debuggr, frame, type)
3834     {
3835         if (FBTrace.DBG_FBS_STEP || FBTrace.DBG_FBS_BP)
3836             FBTrace.sysout("fbs.breakIntoDebugger called "+debuggr.debuggerName+
3837                 " fbs.isChromeBlocked:"+fbs.isChromeBlocked);
3838 
3839         // Before we break, clear information about previous stepping session
3840         this.stopStepping(frame);
3841 
3842         // Break into the debugger - execution will stop here until the user resumes
3843         var returned;
3844         try
3845         {
3846             var debuggr = this.reFindDebugger(frame, debuggr);
3847             returned = debuggr.onBreak(frame, type);
3848         }
3849         catch (exc)
3850         {
3851             ERROR("breakIntoDebugger ERROR "+exc, exc);
3852             returned = RETURN_CONTINUE;
3853         }
3854 
3855         // Execution resumes now. Check if the user requested stepping and if so
3856         // install the necessary hooks
3857         this.startStepping(frame);
3858 
3859         if (FBTrace.DBG_FBS_STEP || FBTrace.DBG_FBS_BP)
3860             FBTrace.sysout("fbs.breakIntoDebugger returning "+returned+" from "+
3861                 debuggr.debuggerName+" with jsd.pauseDepth "+jsd.pauseDepth+" and functionHook "+
3862                 jsd.functionHook);
3863 
3864         return returned;
3865     },
3866 
3867     needToBreakForError: function(reportNextError)
3868     {
3869         return this.breakOnErrors || fbs.breakOnDebugCall;
3870     },
3871 
3872     // debuggr calls us to stage stepping
3873     step: function(mode, context, debuggr)
3874     {
3875         var stepper;
3876 
3877         if (mode === STEP_INTO)
3878             stepper = new IntoStepper(debuggr, context);
3879         else if (mode === STEP_OVER)
3880             stepper = new LineStepper(debuggr, context);
3881         else if (mode === STEP_OUT)
3882             stepper = new OutStepper(debuggr, context);
3883 
3884         if (stepper)
3885             jsdHandlers.add(stepper);
3886         else
3887             ERROR("fbs.step ERROR unknown mode "+mode);
3888 
3889         // The actual stepping starts after we resume the debuggger. Stepping is always
3890         // done when the execution/debugger is paused, so we need to resume and break e.g.
3891         // on the next line.
3892     },
3893 
3894     startStepping: function(frame) // if needed
3895     {
3896         jsdHandlers.hook(frame);
3897     },
3898 
3899     stopStepping: function(frame, context)
3900     {
3901         jsdHandlers.unhook(frame);
3902     },
3903 
3904     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
3905     // Hook Interupts
3906 
3907     hookInterrupts: function(frame)
3908     {
3909         // TODO move the try code in hook() to dispatch
3910         jsd.interruptHook = { onExecute: hook(this.onInterrupt, RETURN_CONTINUE)};
3911 
3912         if (frame)
3913             ScriptInterrupter.enable(frame.script);
3914 
3915         if (FBTrace.DBG_FBS_STEP)
3916             FBTrace.sysout("fbs.set InterruptHook frame.script.tag: "+
3917                 (frame?frame.script.tag:"<no frame>"), ScriptInterrupter);
3918     },
3919 
3920     onInterrupt: function(frame, type, rv)
3921     {
3922         return jsdHandlers.dispatch("onInterrupt", frame, type);
3923     },
3924 
3925     unhookInterrupts: function(frame)
3926     {
3927         jsd.interruptHook = null;
3928 
3929         ScriptInterrupter.disableAll();
3930     },
3931 
3932     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
3933     // Hook Functions
3934 
3935     hookFunctions: function()
3936     {
3937         if (FBTrace.DBG_FBS_STEP)
3938             FBTrace.sysout("fbs.set functionHook");
3939 
3940         jsd.functionHook = { onCall: hook(this.onFunction, true) };
3941         jsd.topLevelHook = { onCall: hook(this.onFunction, true) };
3942     },
3943 
3944     onFunction: function(frame, type) // called in try/catch block with this === fbs
3945     {
3946         switch (type)
3947         {
3948             case TYPE_TOPLEVEL_START: // fall through
3949             case TYPE_FUNCTION_CALL:  // the frame will be running the called script
3950             {
3951                 jsdHandlers.dispatch("onFunctionCall", frame, shiftCallType(type));
3952                 break;
3953             }
3954             case TYPE_TOPLEVEL_END: // fall through
3955             case TYPE_FUNCTION_RETURN:  // the frame will be running the called script
3956             {
3957                 jsdHandlers.dispatch("onFunctionReturn", frame, shiftCallType(type));
3958                 break;
3959             }
3960         }
3961     },
3962 
3963     unhookFunctions: function()
3964     {
3965         jsd.functionHook = null;
3966         jsd.topLevelHook = null;
3967     },
3968 
3969     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
3970     // Hook Scripts
3971 
3972     hookScripts: function()
3973     {
3974         if (FBTrace.DBG_FBS_STEP || FBTrace.DBG_FBS_TRACKFILES)
3975             FBTrace.sysout("fbs.set scriptHook\n");
3976 
3977         jsd.scriptHook = {
3978             onScriptCreated: hook(this.onScriptCreated),
3979             onScriptDestroyed: hook(this.onScriptDestroyed)
3980         };
3981 
3982         if (fbs.filterSystemURLs)
3983             fbs.removeChromeBlockingFilters();
3984 
3985         jsd.clearFilters();
3986 
3987         if (fbs.filterSystemURLs)
3988             fbs.setChromeBlockingFilters();
3989 
3990         jsd.debuggerHook = { onExecute: hook(this.onDebugger, RETURN_CONTINUE) };
3991         jsd.debugHook = { onExecute: hook(this.onDebug, RETURN_CONTINUE) };
3992         jsd.breakpointHook = { onExecute: hook(this.onBreakpoint, RETURN_CONTINUE) };
3993         jsd.throwHook = { onExecute: hook(this.onThrow, RETURN_CONTINUE_THROW) };
3994         jsd.errorHook = { onError: hook(this.onError, true) };
3995     },
3996 
3997     unhookScripts: function()
3998     {
3999         jsd.scriptHook = null;
4000         fbs.removeChromeBlockingFilters();
4001 
4002         if (FBTrace.DBG_FBS_STEP || FBTrace.DBG_FBS_TRACKFILES)
4003             FBTrace.sysout("fbs.unset scriptHook\n");
4004     },
4005 
4006     // TODO rewrite as a Stepper
4007     // xxxJJB: perhaps xxxHonza could implement the stepper, but what the code is responsible for?
4008     // xxxhonza: traceAll and trace a function. I don't think these work anyway, a good start is to remove the old code.
4009     hookCalls: function(callBack, unhookAtBottom)
4010     {
4011         var contextCached = null;
4012 
4013         function callHook(frame, type)
4014         {
4015             switch (type)
4016             {
4017                 case TYPE_FUNCTION_CALL:
4018                 {
4019                     ++hookFrameCount;
4020 
4021                     if (FBTrace.DBG_FBS_STEP)
4022                         FBTrace.sysout("fbs.callHook TYPE_FUNCTION_CALL "+frame.script.fileName);
4023 
4024                     contextCached = callBack(contextCached, frame, hookFrameCount, true);
4025 
4026                     break;
4027                 }
4028                 case TYPE_FUNCTION_RETURN:
4029                 {
4030                     if (hookFrameCount <= 0)  // ignore returns until we have started back in
4031                         return;
4032 
4033                     --hookFrameCount;
4034 
4035                     if (FBTrace.DBG_FBS_STEP)
4036                         FBTrace.sysout("fbs.functionHook TYPE_FUNCTION_RETURN " +
4037                             frame.script.fileName);
4038 
4039                     // stack empty
4040                     if (unhookAtBottom && hookFrameCount == 0)
4041                        fbs.unhookFunctions();
4042 
4043                     contextCached = callBack(contextCached, frame, hookFrameCount, false);
4044                     break;
4045                 }
4046             }
4047         }
4048 
4049         if (jsd.functionHook)
4050         {
4051             if (FBTrace.DBG_FBS_ERRORS)
4052                 FBTrace.sysout("fbs.hookCalls cannot set functionHook, one is already set");
4053             return;
4054         }
4055 
4056         if (FBTrace.DBG_FBS_STEP)
4057             FBTrace.sysout("fbs.set callHook");
4058 
4059         hookFrameCount = 0;
4060         jsd.functionHook = { onCall: callHook };
4061     },
4062 
4063     getJSD: function()
4064     {
4065         return jsd; // for debugging fbs
4066     },
4067 
4068     dumpFileTrack: function(moreFiles)
4069     {
4070         if (moreFiles)
4071             trackFiles.merge(moreFiles);
4072         trackFiles.dump();
4073     },
4074 };
4075 
4076 // ********************************************************************************************* //
4077 // Script Interrupt Manager
4078 
4079 var ScriptInterrupter =
4080 {
4081     entries: {},
4082 
4083     enable: function(script)
4084     {
4085         if (!script.enableSingleStepInterrupts)
4086             return;
4087 
4088         if (this.entries[script.tag])
4089             return;
4090 
4091         try
4092         {
4093             script.enableSingleStepInterrupts(true);
4094         }
4095         catch (e)
4096         {
4097             if (FBTrace.DBG_ERRORS)
4098                 FBTrace.sysout("fbs.ScriptInterrupter.enable; EXCEPTION");
4099         }
4100 
4101         this.entries[script.tag] = {
4102             script: script
4103         }
4104     },
4105 
4106     disable: function(script)
4107     {
4108         if (!script.enableSingleStepInterrupts)
4109             return;
4110 
4111         var entry = this.entries[script.tag];
4112         if (!entry)
4113             return;
4114 
4115         try
4116         {
4117             script.enableSingleStepInterrupts(false);
4118         }
4119         catch (e)
4120         {
4121             if (FBTrace.DBG_ERRORS)
4122                 FBTrace.sysout("fbs.ScriptInterrupter.disable; EXCEPTION");
4123         }
4124 
4125         delete this.entries[script.tag];
4126     },
4127 
4128     disableAll: function()
4129     {
4130         for (var tag in this.entries)
4131         {
4132             var entry = this.entries[tag];
4133             if (!entry.script.enableSingleStepInterrupts)
4134                 return;
4135 
4136             try
4137             {
4138                 entry.script.enableSingleStepInterrupts(false);
4139             }
4140             catch (e)
4141             {
4142                 if (FBTrace.DBG_ERRORS)
4143                     FBTrace.sysout("fbs.ScriptInterrupter.disable; EXCEPTION");
4144             }
4145        }
4146 
4147        this.entries = {};
4148     }
4149 };
4150 
4151 // ********************************************************************************************* //
4152 // Local Helpers
4153 
4154 function getStepName(mode)
4155 {
4156     if (mode == STEP_OVER)
4157         return "STEP_OVER";
4158 
4159     if (mode == STEP_INTO)
4160         return "STEP_INTO";
4161 
4162     if (mode == STEP_OUT)
4163         return "STEP_OUT";
4164 
4165     if (mode == STEP_SUSPEND)
4166         return "STEP_SUSPEND";
4167 
4168     return "(not a step mode)";
4169 }
4170 
4171 // called by enumerateScripts, onThrow, onDebug, onScriptCreated/Destroyed.
4172 function isFilteredURL(rawJSD_script_filename)
4173 {
4174     if (!rawJSD_script_filename)
4175         return true;
4176     if (fbs.filterConsoleInjections)
4177         return true;
4178     if (rawJSD_script_filename[0] == 'h')
4179         return false;
4180     if (rawJSD_script_filename == "XPCSafeJSObjectWrapper.cpp")
4181         return true;
4182     if (fbs.filterSystemURLs)
4183         return systemURLStem(rawJSD_script_filename);
4184 
4185     for (var i=0; i<fbs.alwayFilterURLsStarting.length; i++)
4186     {
4187         if (rawJSD_script_filename.indexOf(fbs.alwayFilterURLsStarting[i]) != -1)
4188             return true;
4189     }
4190 
4191     return false;
4192 }
4193 
4194 function systemURLStem(rawJSD_script_filename)
4195 {
4196     // attempt to optimize stream of similar urls
4197     if (this.url_class)
4198     {
4199         if (rawJSD_script_filename.substr(0, this.url_class.length) == this.url_class)
4200             return this.url_class;
4201     }
4202 
4203     this.url_class = deepSystemURLStem(rawJSD_script_filename);
4204     return this.url_class;
4205 }
4206 
4207 function deepSystemURLStem(rawJSD_script_filename)
4208 {
4209     for (var i=0; i<urlFilters.length; ++i)
4210     {
4211         var filter = urlFilters[i];
4212         if ( rawJSD_script_filename.substr(0,filter.length) == filter )
4213             return filter;
4214     }
4215 
4216     for (var i=0; i<COMPONENTS_FILTERS.length; ++i)
4217     {
4218         if (COMPONENTS_FILTERS[i].test(rawJSD_script_filename))
4219         {
4220             var match = COMPONENTS_FILTERS[i].exec(rawJSD_script_filename);
4221             urlFilters.push(match[1]);  // cache this for future calls
4222             return match[1];
4223         }
4224     }
4225 
4226     return false;
4227 }
4228 
4229 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
4230 
4231 function dispatch(listeners, name, args)
4232 {
4233     var totalListeners = listeners.length;
4234     for (var i=0; i<totalListeners; ++i)
4235     {
4236         var listener = listeners[i];
4237         if (listener.hasOwnProperty(name) && listener[name])
4238             listener[name].apply(listener, args);
4239     }
4240 
4241     //if (FBTrace.DBG_FBS_ERRORS)
4242     //    FBTrace.sysout("fbs.dispatch "+name+" to "+listeners.length+" listeners");
4243 }
4244 
4245 function hook(fn, rv)
4246 {
4247     return function()
4248     {
4249         try
4250         {
4251             return fn.apply(fbs, arguments);
4252         }
4253         catch (exc)
4254         {
4255             var msg =  "Error in hook: "+ exc;
4256             ERROR(msg, exc);
4257             return rv;
4258         }
4259     }
4260 }
4261 
4262 var lastWindowScope = null;
4263 function wrapIfNative(obj)
4264 {
4265     try
4266     {
4267         if (obj instanceof Ci.nsISupports)
4268             return XPCNativeWrapper(obj);
4269         else
4270             return obj;
4271     }
4272     catch(exc)
4273     {
4274         if (FBTrace.DBG_FBS_ERRORS)
4275             FBTrace.sysout("fbs.wrapIfNative FAILED: "+exc, obj);
4276     }
4277 }
4278 
4279 function getRootWindow(win)
4280 {
4281     for (; win; win = win.parent)
4282     {
4283         if (!win.parent || win == win.parent || !(win.parent instanceof Window))
4284             return win;
4285     }
4286     return null;
4287 }
4288 
4289 function countFrames(frame)
4290 {
4291     var frameCount = 0;
4292     try
4293     {
4294         for (; frame; frame = frame.callingFrame)
4295             ++frameCount;
4296     }
4297     catch(exc)
4298     {
4299     }
4300 
4301     return frameCount;
4302 }
4303 
4304 function framesToString(frame)
4305 {
4306     var str = "";
4307     while (frame)
4308     {
4309         str += frameToString(frame)+"\n";
4310         frame = frame.callingFrame;
4311     }
4312 
4313     return str;
4314 }
4315 
4316 function frameToString(frame)
4317 {
4318     if (!frame)
4319         return "< no frame >";
4320 
4321     if (!frame.script)
4322     {
4323         ERROR("frameToString bad frame "+typeof(frame), frame);
4324         return "<bad frame>";
4325     }
4326 
4327     return frame.script.tag+" in "+frame.script.fileName+"@"+frame.line+"(pc="+frame.pc+")";
4328 }
4329 
4330 function dumpComponentsStack(from)
4331 {
4332     var msg = [];
4333     for (var frame = Components.stack; frame; frame = frame.caller)
4334         msg.push( {desc:frame.filename + "@" + frame.lineNumber +": "+
4335             frame.sourceLine,frame:frame}  );
4336 
4337     FBTrace.sysout("fbs." + from + " has stack size:" + msg.length, msg);
4338 }
4339 
4340 function testBreakpoint(frame, bp)
4341 {
4342     if (FBTrace.DBG_FBS_BP)
4343         FBTrace.sysout("fbs.testBreakpoint "+bp.condition, bp);
4344 
4345     if (bp.condition && bp.condition != "")
4346     {
4347         var result = {};
4348         frame.scope.refresh();
4349 
4350         if (frame.eval(bp.condition, "", 1, result))
4351         {
4352             if (bp.onTrue)
4353             {
4354                 if (!result.value.booleanValue)
4355                     return false;
4356             }
4357             else
4358             {
4359                 var value = unwrapIValue(result.value);
4360                 if (typeof bp.lastValue == "undefined")
4361                 {
4362                     bp.lastValue = value;
4363                     return false;
4364                 }
4365                 else
4366                 {
4367                     if (bp.lastValue == value)
4368                         return false;
4369                     bp.lastValue = value;
4370                 }
4371             }
4372         }
4373     }
4374 
4375     ++bp.hit;
4376 
4377     if (bp.hitCount > 0)
4378     {
4379         if (bp.hit < bp.hitCount)
4380             return false;
4381     }
4382 
4383     return true;
4384 }
4385 
4386 function remove(list, item)
4387 {
4388     var index = list.indexOf(item);
4389     if (index != -1)
4390         list.splice(index, 1);
4391 }
4392 
4393 function unwrapIValue(object)
4394 {
4395     var unwrapped = object.getWrappedValue();
4396     try
4397     {
4398         if (unwrapped)
4399             return XPCSafeJSObjectWrapper(unwrapped);
4400     }
4401     catch (exc)
4402     {
4403         if (FBTrace.DBG_ERRORS)
4404             FBTrace.sysout("fbs.unwrapIValue ERROR for "+object,
4405                 {exc: exc, object: object, unwrapped: unwrapped});
4406     }
4407 }
4408 
4409 // ********************************************************************************************* //
4410 // Preferences
4411 
4412 var FirebugPrefsObserver =
4413 {
4414     syncFilter: function()
4415     {
4416         var filter = fbs.scriptsFilter;
4417         fbs.showEvents = (filter == "all" || filter == "events");
4418         fbs.showEvals = (filter == "all" || filter == "evals");
4419 
4420         if (FBTrace.DBG_OPTIONS)
4421             FBTrace.sysout("fbs.showEvents "+fbs.showEvents+" fbs.showEvals "+fbs.showEvals);
4422     }
4423 };
4424 
4425 // ********************************************************************************************* //
4426 // Application Observers
4427 
4428 var QuitApplicationGrantedObserver =
4429 {
4430     observe: function(subject, topic, data)
4431     {
4432         if (FBTrace.DBG_FBS_ERRORS)
4433             FBTrace.sysout("fbs.QuitApplicationGrantedObserver " + topic + " start");
4434     }
4435 };
4436 
4437 var QuitApplicationRequestedObserver =
4438 {
4439     observe: function(subject, topic, data)
4440     {
4441         if (FBTrace.DBG_FBS_ERRORS)
4442             FBTrace.sysout("fbs.QuitApplicationRequestedObserver " + topic);
4443     }
4444 };
4445 
4446 var QuitApplicationObserver =
4447 {
4448     observe: function(subject, topic, data)
4449     {
4450         if (FBTrace.DBG_FBS_ERRORS)
4451             FBTrace.sysout("fbs.QuitApplicationObserver " + topic);
4452 
4453         fbs.disableDebugger();
4454         fbs.shutdown();
4455         fbs = null;
4456 
4457         if (FBTrace.DBG_FBS_ERRORS)
4458             FBTrace.sysout("fbs.QuitApplicationObserver " + topic + " end");
4459     }
4460 };
4461 
4462 // ********************************************************************************************* //
4463 // Console Service
4464 
4465 // xxxJJB: Support for console logging could be moved into separate module, correct?
4466 
4467 var consoleService = null;
4468 
4469 function ERROR(text, exc)
4470 {
4471     if (!consoleService)
4472         consoleService = ConsoleService.getService(nsIConsoleService);
4473 
4474     try
4475     {
4476         fbs.unhookInterrupts(); // Stop and clear everything
4477         fbs.unhookFunctions();
4478         fbs.disableDebugger();
4479         jsdHandlers.list = [];
4480         consoleService.logStringMessage("ERROR: "+text);
4481 
4482         var frame = Components.stack;
4483         frame = frame.caller; // drop this frame we are in now.
4484         for ( ; frame; frame = frame.caller)
4485             consoleService.logStringMessage(frame.filename + "@" + frame.lineNumber + ";");
4486 
4487         FBTrace.sysout(text, exc);
4488     }
4489     catch(exc)
4490     {
4491         var msg = exc.toString() +" "+(exc.fileName || exc.sourceName) + "@" + exc.lineNumber;
4492         Cu.reportError("firebug-service ERROR in ERROR: "+msg);
4493     }
4494     finally
4495     {
4496         fbs.enableDebugger(); // we were enabled to get ERROR, so we hope all is cleared up now.
4497     }
4498 }
4499 
4500 function getExecutionStopNameFromType(type)
4501 {
4502     switch (type)
4503     {
4504         case jsdIExecutionHook.TYPE_INTERRUPTED: return "interrupted";
4505         case jsdIExecutionHook.TYPE_BREAKPOINT: return "breakpoint";
4506         case jsdIExecutionHook.TYPE_DEBUG_REQUESTED: return "debug requested";
4507         case jsdIExecutionHook.TYPE_DEBUGGER_KEYWORD: return "debugger_keyword";
4508         case jsdIExecutionHook.TYPE_THROW: return "interrupted";
4509         default: return "unknown("+type+")";
4510     }
4511 }
4512 
4513 function getCallFromType(type)
4514 {
4515     var typeName = type;
4516     switch(type)
4517     {
4518         case TYPE_FUNCTION_RETURN: { typeName = "FUNCTION_RETURN"; break; }
4519         case TYPE_FUNCTION_CALL:   { typeName = "FUNCTION_CALL"; break; }
4520         case TYPE_TOPLEVEL_START: { typeName = "TOPLEVEL_START"; break; }
4521         case TYPE_TOPLEVEL_END:   { typeName = "TOPLEVEL_END"; break; }
4522     }
4523     return typeName;
4524 }
4525 
4526 function shiftCallType(type)
4527 {
4528     return type + 10;
4529 }
4530 
4531 // ********************************************************************************************* //
4532 // Chromebug Tracing
4533 
4534 // xxxJJB, shouldn't the following code be part of Chromebug (could be done as part of splitting
4535 // this file into more modules?)
4536 // xxxhonza, yes
4537 
4538 function getTmpFile()
4539 {
4540     var file = Components.classes["@mozilla.org/file/directory_service;1"].
4541         getService(Components.interfaces.nsIProperties).
4542         get("TmpD", Components.interfaces.nsIFile);
4543     file.append("fbs.tmp");
4544     file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
4545 
4546     FBTrace.sysout("fbs.opened tmp file "+file.path);
4547 
4548     return file;
4549 }
4550 
4551 function getTmpStream(file)
4552 {
4553     // file is nsIFile, data is a string
4554     var foStream = Cc["@mozilla.org/network/file-output-stream;1"].
4555         createInstance(Ci.nsIFileOutputStream);
4556 
4557     // use 0x02 | 0x10 to open file for appending.
4558     foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
4559     // write, create, truncate
4560     // In a c file operation, we have no need to set file mode with or operation,
4561     // directly using "r" or "w" usually.
4562 
4563     return foStream;
4564 }
4565 
4566 var trackFiles =
4567 {
4568     allFiles: {},
4569 
4570     add: function(fileName)
4571     {
4572         var name = new String(fileName);
4573         this.allFiles[name] = [];
4574     },
4575 
4576     drop: function(fileName)
4577     {
4578         var name = new String(fileName);
4579         this.allFiles[name].push("dropped");
4580     },
4581 
4582     def: function(frame)
4583     {
4584         var frameGlobal = fbs.getOutermostScope(frame);
4585         var scope = frame.scope;
4586         if (scope)
4587         {
4588             while(scope.jsParent)
4589                 scope = scope.jsParent;
4590         }
4591 
4592         var scopeName = fbs.getLocationSafe(frameGlobal);
4593         if (!scopeName)
4594             scopeName = frameGlobal + "";
4595 
4596         scopeName = scope.jsClassName + ": "+scopeName;
4597 
4598         var name = new String(frame.script.fileName);
4599         if (!(name in this.allFiles))
4600             this.allFiles[name]=["not added"];
4601 
4602         this.allFiles[name].push(scopeName);
4603     },
4604 
4605     merge: function(moreFiles)
4606     {
4607         for (var p in moreFiles)
4608         {
4609             if (p in this.allFiles)
4610                 this.allFiles[p] = this.allFiles[p].concat(moreFiles[p]);
4611             else
4612                 this.allFiles[p] = moreFiles[p];
4613         }
4614     },
4615 
4616     dump: function()
4617     {
4618         var n = 0;
4619         for (var p in this.allFiles)
4620         {
4621             tmpout( (++n) + ") "+p);
4622             var where = this.allFiles[p];
4623             if (where.length > 0)
4624             {
4625                 for (var i = 0; i < where.length; i++)
4626                     tmpout(", "+where[i]);
4627                 tmpout("\n");
4628             }
4629             else
4630             {
4631                 tmpout("     none\n");
4632             }
4633         }
4634     },
4635 };
4636 
4637 function tmpout(text)
4638 {
4639     if (!fbs.foStream)
4640         fbs.foStream = getTmpStream(getTmpFile());
4641 
4642     fbs.foStream.write(text, text.length);
4643 }
4644 
4645 // ********************************************************************************************* //
4646 // Initialization
4647 
4648 fbs.initialize();
4649 
4650 // ********************************************************************************************* //
4651