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 CLASS_ID = Components.ID("{a380e9c0-cb39-11da-a94d-0800200c9a66}");
 13 const CLASS_NAME = "Firebug Service";
 14 const CONTRACT_ID = "@joehewitt.com/firebug;1";
 15 const Cc = Components.classes;
 16 const Ci = Components.interfaces;
 17 
 18 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 19 
 20 const PrefService = Cc["@mozilla.org/preferences-service;1"];
 21 const DebuggerService = Cc["@mozilla.org/js/jsd/debugger-service;1"];
 22 const ConsoleService = Cc["@mozilla.org/consoleservice;1"];
 23 const Timer = Cc["@mozilla.org/timer;1"];
 24 const ObserverServiceFactory = Cc["@mozilla.org/observer-service;1"];
 25 
 26 const jsdIDebuggerService = Ci.jsdIDebuggerService;
 27 const jsdIScript = Ci.jsdIScript;
 28 const jsdIStackFrame = Ci.jsdIStackFrame;
 29 const jsdICallHook = Ci.jsdICallHook;
 30 const jsdIExecutionHook = Ci.jsdIExecutionHook;
 31 const jsdIErrorHook = Ci.jsdIErrorHook;
 32 const jsdIFilter = Components.interfaces.jsdIFilter;
 33 const nsISupports = Ci.nsISupports;
 34 const nsIPrefBranch = Ci.nsIPrefBranch;
 35 const nsIPrefBranch2 = Ci.nsIPrefBranch2;
 36 const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
 37 const nsIFactory = Ci.nsIFactory;
 38 const nsIConsoleService = Ci.nsIConsoleService;
 39 const nsITimer = Ci.nsITimer;
 40 const nsITimerCallback = Ci.nsITimerCallback;
 41 
 42 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 43 
 44 const NS_ERROR_NO_INTERFACE = Components.results.NS_ERROR_NO_INTERFACE;
 45 const NS_ERROR_NOT_IMPLEMENTED = Components.results.NS_ERROR_NOT_IMPLEMENTED;
 46 const NS_ERROR_NO_AGGREGATION = Components.results.NS_ERROR_NO_AGGREGATION;
 47 
 48 const PCMAP_SOURCETEXT = jsdIScript.PCMAP_SOURCETEXT;
 49 const PCMAP_PRETTYPRINT = jsdIScript.PCMAP_PRETTYPRINT;
 50 
 51 const COLLECT_PROFILE_DATA = jsdIDebuggerService.COLLECT_PROFILE_DATA;
 52 const DISABLE_OBJECT_TRACE = jsdIDebuggerService.DISABLE_OBJECT_TRACE;
 53 const HIDE_DISABLED_FRAMES = jsdIDebuggerService.HIDE_DISABLED_FRAMES;
 54 const DEBUG_WHEN_SET = jsdIDebuggerService.DEBUG_WHEN_SET;
 55 const MASK_TOP_FRAME_ONLY = jsdIDebuggerService.MASK_TOP_FRAME_ONLY;
 56 
 57 const TYPE_FUNCTION_CALL = jsdICallHook.TYPE_FUNCTION_CALL;
 58 const TYPE_FUNCTION_RETURN = jsdICallHook.TYPE_FUNCTION_RETURN;
 59 const TYPE_TOPLEVEL_START = jsdICallHook.TYPE_TOPLEVEL_START;
 60 const TYPE_TOPLEVEL_END = jsdICallHook.TYPE_TOPLEVEL_END;
 61 
 62 const RETURN_CONTINUE = jsdIExecutionHook.RETURN_CONTINUE;
 63 const RETURN_VALUE = jsdIExecutionHook.RETURN_RET_WITH_VAL;
 64 const RETURN_THROW_WITH_VAL = jsdIExecutionHook.RETURN_THROW_WITH_VAL;
 65 const RETURN_CONTINUE_THROW = jsdIExecutionHook.RETURN_CONTINUE_THROW;
 66 
 67 const NS_OS_TEMP_DIR = "TmpD"
 68 
 69 const STEP_OVER = 1;
 70 const STEP_INTO = 2;
 71 const STEP_OUT = 3;
 72 const STEP_SUSPEND = 4;
 73 
 74 const TYPE_ONE_SHOT = nsITimer.TYPE_ONE_SHOT;
 75 
 76 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 77 
 78 const BP_NORMAL = 1;
 79 const BP_MONITOR = 2;
 80 const BP_UNTIL = 4;
 81 const BP_ONRELOAD = 8;  // XXXjjb: This is a mark for the UI to test
 82 const BP_ERROR = 16;
 83 const BP_TRACE = 32; // BP used to initiate traceCalls
 84 
 85 const LEVEL_TOP = 1;
 86 const LEVEL_EVAL = 2;
 87 const LEVEL_EVENT = 3;
 88 
 89 const COMPONENTS_FILTERS = [
 90     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$"),
 91     new RegExp("^(file:/.*/)extensions/firebug@software\\.joehewitt\\.com/components/.*\\.js$"),
 92     new RegExp("^(file:/.*/extensions/)\\w+@mozilla\\.org/components/.*\\.js$"),
 93     new RegExp("^(file:/.*/components/)ns[A-Z].*\\.js$"),
 94     new RegExp("^(file:/.*/components/)firebug-[^\\.]*\\.js$"),
 95     new RegExp("^(file:/.*/Contents/MacOS/extensions/.*/components/).*\\.js$"),
 96     new RegExp("^(file:/.*/modules/).*\\.jsm$"),
 97     ];
 98 
 99 const reDBG = /DBG_(.*)/;
100 const reXUL = /\.xul$|\.xml$/;
101 const reTooMuchRecursion = /too\smuch\srecursion/;
102 
103 // ************************************************************************************************
104 // Globals
105 
106 var jsd, fbs, prefs;
107 var consoleService;
108 var observerService;
109 
110 var contextCount = 0;
111 
112 var urlFilters = [
113     'chrome://',
114     'XStringBundle',
115     'x-jsd:ppbuffer?type=function', // internal script for pretty printing
116     ];
117 
118 
119 var clients = [];
120 var debuggers = [];
121 var netDebuggers = [];
122 var scriptListeners = [];
123 
124 var stepMode = 0;
125 var stepFrame;
126 var stepFrameLineId;
127 var stepStayOnDebuggr; // if set, the debuggr we want to stay within
128 var stepFrameCount;
129 var hookFrameCount = 0;
130 
131 var haltDebugger = null;
132 
133 var breakpoints = {};
134 var breakpointCount = 0;
135 var disabledCount = 0;
136 var monitorCount = 0;
137 var conditionCount = 0;
138 var runningUntil = null;
139 
140 var errorBreakpoints = [];
141 
142 var profileCount = 0;
143 var profileStart;
144 
145 var enabledDebugger = false;
146 var reportNextError = false;
147 var breakOnNextError = false;
148 var errorInfo = null;
149 
150 var timer = Timer.createInstance(nsITimer);
151 var waitingForTimer = false;
152 
153 var FBTrace = null;
154 
155 // ************************************************************************************************
156 
157 function FirebugService()
158 {
159 
160     FBTrace = Cc["@joehewitt.com/firebug-trace-service;1"]
161                  .getService(Ci.nsISupports).wrappedJSObject.getTracer("extensions.firebug");
162 
163     if (FBTrace.DBG_FBS_ERRORS)
164         FBTrace.sysout("FirebugService Starting");
165 
166     fbs = this;
167 
168     this.wrappedJSObject = this;
169     this.timeStamp = new Date();  /* explore */
170     this.breakpoints = breakpoints; // so chromebug can see it /* explore */
171     this.onDebugRequests = 0;  // the number of times we called onError but did not call onDebug
172     fbs._lastErrorDebuggr = null;
173 
174 
175     if(FBTrace.DBG_FBS_ERRORS)
176         this.osOut("FirebugService Starting, FBTrace should be up\n");
177 
178     this.profiling = false;
179     this.pauseDepth = 0;
180 
181     prefs = PrefService.getService(nsIPrefBranch2);
182     fbs.prefDomain = "extensions.firebug.service."
183     prefs.addObserver(fbs.prefDomain, fbs, false);
184 
185     observerService = ObserverServiceFactory.getService(Ci.nsIObserverService);
186     observerService.addObserver(QuitApplicationGrantedObserver, "quit-application-granted", false);
187     observerService.addObserver(QuitApplicationRequestedObserver, "quit-application-requested", false);
188     observerService.addObserver(QuitApplicationObserver, "quit-application", false);
189 
190     this.scriptsFilter = "all";
191     // XXXjj For some reason the command line will not function if we allow chromebug to see it.?
192     this.alwayFilterURLsStarting = ["chrome://chromebug", "x-jsd:ppbuffer", "chrome://firebug/content/commandLine.js"];  // TODO allow override
193     this.onEvalScriptCreated.kind = "eval";
194     this.onTopLevelScriptCreated.kind = "top-level";
195     this.onEventScriptCreated.kind = "event";
196 
197     this.onXScriptCreatedByTag = {}; // fbs functions by script tag
198     this.nestedScriptStack = Components.classes["@mozilla.org/array;1"]
199                         .createInstance(Components.interfaces.nsIMutableArray);  // scripts contained in leveledScript that have not been drained
200 }
201 
202 FirebugService.prototype =
203 {
204     osOut: function(str)
205     {
206         if (!this.outChannel)
207         {
208             try
209             {
210                 var appShellService = Components.classes["@mozilla.org/appshell/appShellService;1"].
211                     getService(Components.interfaces.nsIAppShellService);
212                 this.hiddenWindow = appShellService.hiddenDOMWindow;
213                 this.outChannel = "hidden";
214             }
215             catch(exc)
216             {
217                 consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
218                 consoleService.logStringMessage("Using consoleService because nsIAppShellService.hiddenDOMWindow not available "+exc);
219                 this.outChannel = "service";
220             }
221         }
222         if (this.outChannel === "hidden")  // apparently can't call via JS function
223             this.hiddenWindow.dump(str);
224         else
225             consoleService.logStringMessage(str);
226     },
227 
228     shutdown: function()  // call disableDebugger first
229     {
230         timer = null;
231 
232         if (!jsd)
233             return;
234 
235         try
236         {
237             do
238             {
239                 var depth = jsd.exitNestedEventLoop();
240             }
241             while(depth > 0);
242         }
243         catch (exc)
244         {
245             // Seems to be the normal path...FBTrace.sysout("FirebugService, attempt to exitNestedEventLoop fails "+exc);
246         }
247 
248 
249         try
250         {
251             prefs.removeObserver(fbs.prefDomain, fbs);
252         }
253         catch (exc)
254         {
255             FBTrace.sysout("fbs prefs.removeObserver fails "+exc, exc);
256         }
257 
258         try
259         {
260             observerService.removeObserver(QuitApplicationGrantedObserver, "quit-application-granted");
261             observerService.removeObserver(QuitApplicationRequestedObserver, "quit-application-requested");
262             observerService.removeObserver(QuitApplicationObserver, "quit-application");
263         }
264         catch (exc)
265         {
266             FBTrace.sysout("fbs quit-application-observers removeObserver fails "+exc, exc);
267         }
268 
269         jsd = null;
270         if (!jsd)
271             FBTrace.sysout("*********************** SHUTDOWN JSD NULL ");
272     },
273 
274     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
275     // nsISupports
276 
277     QueryInterface: function(iid)
278     {
279         if (!iid.equals(nsISupports))
280             throw NS_ERROR_NO_INTERFACE;
281 
282         return this;
283     },
284 
285     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
286     // nsIObserver
287     observe: function(subject, topic, data)
288     {
289         fbs.obeyPrefs();
290     },
291     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
292 
293     get lastErrorWindow()
294     {
295         var win = this._lastErrorWindow;
296         this._lastErrorWindow = null; // Release to avoid leaks
297         return win;
298     },
299 
300     registerClient: function(client)  // clients are essentially XUL windows
301     {
302         clients.push(client);
303         return clients.length;
304     },
305 
306     unregisterClient: function(client)
307     {
308         for (var i = 0; i < clients.length; ++i)
309         {
310             if (clients[i] == client)
311             {
312                 clients.splice(i, 1);
313                 break;
314             }
315         }
316     },
317 
318     registerDebugger: function(debuggrWrapper)  // first one in will be last one called. Returns state enabledDebugger
319     {
320         var debuggr = debuggrWrapper.wrappedJSObject;
321 
322         if (debuggr)
323         {
324             debuggers.push(debuggr);
325             if (debuggers.length == 1)
326                 this.enableDebugger();
327             if (FBTrace.DBG_FBS_FINDDEBUGGER  || FBTrace.DBG_ACTIVATION)
328                 FBTrace.sysout("fbs.registerDebugger have "+debuggers.length+" after reg debuggr.debuggerName: "+debuggr.debuggerName+" we are "+(enabledDebugger?"enabled":"not enabled")+" " +
329                         "On:"+(jsd?jsd.isOn:"no jsd")+" jsd.pauseDepth:"+(jsd?jsd.pauseDepth:"off")+" fbs.pauseDepth:"+fbs.pauseDepth);
330         }
331         else
332             throw "firebug-service debuggers must have wrappedJSObject";
333 
334         try {
335             if (debuggr.suspendActivity)
336                 netDebuggers.push(debuggr);
337         } catch(exc) {
338         }
339         try {
340             if (debuggr.onScriptCreated)
341                 scriptListeners.push(debuggr);
342         } catch(exc) {
343         }
344         return  debuggers.length;  // 1.3.1 return to allow Debugger to check progress
345     },
346 
347     unregisterDebugger: function(debuggrWrapper)
348     {
349         var debuggr = debuggrWrapper.wrappedJSObject;
350 
351         for (var i = 0; i < debuggers.length; ++i)
352         {
353             if (debuggers[i] == debuggr)
354             {
355                 debuggers.splice(i, 1);
356                 break;
357             }
358         }
359 
360         for (var i = 0; i < netDebuggers.length; ++i)
361         {
362             if (netDebuggers[i] == debuggr)
363             {
364                 netDebuggers.splice(i, 1);
365                 break;
366             }
367         }
368         for (var i = 0; i < scriptListeners.length; ++i)
369         {
370             if (scriptListeners[i] == debuggr)
371             {
372                 scriptListeners.splice(i, 1);
373                 break;
374             }
375         }
376 
377         if (debuggers.length == 0)
378             this.disableDebugger();
379 
380         if (FBTrace.DBG_FBS_FINDDEBUGGER || FBTrace.DBG_ACTIVATION)
381             FBTrace.sysout("fbs.unregisterDebugger have "+debuggers.length+" after unreg debuggr.debuggerName: "+debuggr.debuggerName+" we are "+(enabledDebugger?"enabled":"not enabled")+" jsd.isOn:"+(jsd?jsd.isOn:"no jsd"));
382 
383         return debuggers.length;
384     },
385 
386     lockDebugger: function()
387     {
388         if (this.locked)
389             return;
390 
391         this.locked = true;
392 
393         dispatch(debuggers, "onLock", [true]);
394     },
395 
396     unlockDebugger: function()
397     {
398         if (!this.locked)
399             return;
400 
401         this.locked = false;
402 
403         dispatch(debuggers, "onLock", [false]);
404     },
405 
406     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
407     forceGarbageCollection: function()
408     {
409         jsd.GC(); // Force the engine to perform garbage collection.
410     },
411     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
412 
413     enterNestedEventLoop: function(callback)
414     {
415         dispatch(netDebuggers, "suspendActivity");
416         fbs.nestedEventLoopDepth = jsd.enterNestedEventLoop({
417             onNest: function()
418             {
419                 dispatch(netDebuggers, "resumeActivity");
420                 callback.onNest();
421             }
422         });
423         dispatch(netDebuggers, "resumeActivity");
424         return fbs.nestedEventLoopDepth;
425     },
426 
427     exitNestedEventLoop: function()
428     {
429         dispatch(netDebuggers, "suspendActivity");
430         try
431         {
432             return jsd.exitNestedEventLoop();
433         }
434         catch (exc)
435         {
436             if (FBTrace.DBG_FBS_ERRORS)
437                 FBTrace.sysout("fbs: jsd.exitNestedEventLoop FAILS "+exc);
438         }
439     },
440 
441     halt: function(debuggr)
442     {
443         haltDebugger = debuggr;
444     },
445 
446     step: function(mode, startFrame, stayOnDebuggr)
447     {
448         stepMode = mode;
449         stepFrame = startFrame;
450         stepFrameCount = countFrames(startFrame);
451         stepFrameLineId = stepFrameCount + startFrame.script.fileName + startFrame.line;
452         stepStayOnDebuggr = stayOnDebuggr;
453 
454         if (FBTrace.DBG_FBS_STEP)
455             FBTrace.sysout("step stepMode = "+getStepName(stepMode) +" stepFrameLineId="+stepFrameLineId+" stepFrameCount="+stepFrameCount+" stepStayOnDebuggr:"+(stepStayOnDebuggr?stepStayOnDebuggr:"null"));
456     },
457 
458     suspend: function(stayOnDebuggr, context)
459     {
460         stepMode = STEP_SUSPEND;
461         stepFrameLineId = null;
462         stepStayOnDebuggr = stayOnDebuggr;
463 
464         if (FBTrace.DBG_FBS_STEP)
465             FBTrace.sysout("step stepMode = "+getStepName(stepMode) +" stepFrameLineId="+stepFrameLineId+" stepFrameCount="+stepFrameCount+" stepStayOnDebuggr:"+(stepStayOnDebuggr?stepStayOnDebuggr:"null"));
466 
467         dispatch(debuggers, "onBreakingNext", [stayOnDebuggr, context]);
468 
469         this.hookInterrupts();
470     },
471 
472     runUntil: function(sourceFile, lineNo, startFrame, debuggr)
473     {
474         runningUntil = this.addBreakpoint(BP_UNTIL, sourceFile, lineNo, null, debuggr);
475         stepFrameCount = countFrames(startFrame);
476         stepFrameLineId = stepFrameCount + startFrame.script.fileName + startFrame.line;
477     },
478 
479     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
480 
481     setBreakpoint: function(sourceFile, lineNo, props, debuggr)
482     {
483         var bp = this.addBreakpoint(BP_NORMAL, sourceFile, lineNo, props, debuggr);
484         if (bp)
485         {
486             dispatch(debuggers, "onToggleBreakpoint", [sourceFile.href, lineNo, true, bp]);
487             return true;
488         }
489         return false;
490     },
491 
492     clearBreakpoint: function(url, lineNo)
493     {
494         var bp = this.removeBreakpoint(BP_NORMAL, url, lineNo);
495         if (bp)
496             dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, false, bp]);
497         else
498         {
499             if (FBTrace.DBG_FBS_BP)
500                 FBTrace.sysout("fbs.clearBreakpoint no find for "+lineNo+"@"+url);
501         }
502     },
503 
504     enableBreakpoint: function(url, lineNo)
505     {
506         var bp = this.findBreakpoint(url, lineNo);
507         if (bp && bp.type & BP_NORMAL)
508         {
509             bp.disabled &= ~BP_NORMAL;
510             dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, true, bp]);
511             --disabledCount;
512         }
513         else {
514             if (FBTrace.DBG_FBS_BP)
515                 FBTrace.sysout("fbs.enableBreakpoint no find for "+lineNo+"@"+url);
516         }
517     },
518 
519     disableBreakpoint: function(url, lineNo)
520     {
521         var bp = this.findBreakpoint(url, lineNo);
522         if (bp && bp.type & BP_NORMAL)
523         {
524             bp.disabled |= BP_NORMAL;
525             ++disabledCount;
526             dispatch(debuggers, "onToggleBreakpoint", [url, lineNo, true, bp]);
527         }
528         else
529         {
530             if (FBTrace.DBG_FBS_BP)
531                 FBTrace.sysout("fbs.disableBreakpoint no find for "+lineNo+"@"+url);
532         }
533 
534     },
535 
536     isBreakpointDisabled: function(url, lineNo)
537     {
538         var bp = this.findBreakpoint(url, lineNo);
539         if (bp && bp.type & BP_NORMAL)
540             return bp.disabled & BP_NORMAL;
541         else
542             return false;
543     },
544 
545     setBreakpointCondition: function(sourceFile, lineNo, condition, debuggr)
546     {
547         var bp = this.findBreakpoint(sourceFile.href, lineNo);
548         if (!bp)
549         {
550             bp = this.addBreakpoint(BP_NORMAL, sourceFile, lineNo, null, debuggr);
551         }
552 
553         if (!bp)
554             return;
555 
556         if (bp.hitCount <= 0 )
557         {
558             if (bp.condition && !condition)
559             {
560                 --conditionCount;
561             }
562             else if (condition && !bp.condition)
563             {
564                 ++conditionCount;
565             }
566         }
567         bp.condition = condition;
568 
569         dispatch(debuggers, "onToggleBreakpoint", [sourceFile.href, lineNo, true, bp]);
570     },
571 
572     getBreakpointCondition: function(url, lineNo)
573     {
574         var bp = this.findBreakpoint(url, lineNo);
575         return bp ? bp.condition : "";
576     },
577 
578     clearAllBreakpoints: function(sourceFiles)
579     {
580         for (var i = 0; i < sourceFiles.length; ++i)
581         {
582             var url = sourceFiles[i].href;
583             if (!url)
584                 continue;
585 
586             var urlBreakpoints = breakpoints[url];
587             if (!urlBreakpoints)
588                 continue;
589 
590             var removals = urlBreakpoints.length;
591             for (var j = 0; j < removals; ++j)
592             {
593                 var bp = urlBreakpoints[0];  // this one will be spliced out each time
594                 this.clearBreakpoint(url, bp.lineNo);
595             }
596          }
597     },
598 
599     enumerateBreakpoints: function(url, cb)  // url is sourceFile.href, not jsd script.fileName
600     {
601         if (url)
602         {
603             var urlBreakpoints = breakpoints[url];
604             if (urlBreakpoints)
605             {
606                 for (var i = 0; i < urlBreakpoints.length; ++i)
607                 {
608                     var bp = urlBreakpoints[i];
609                     if (bp.type & BP_NORMAL)
610                     {
611                         if (bp.scriptsWithBreakpoint && bp.scriptsWithBreakpoint.length > 0)
612                         {
613                             for (var j = 0; j < bp.scriptsWithBreakpoint.length; j++)
614                             {
615                                 var rc = cb.call(url, bp.lineNo, bp, bp.scriptsWithBreakpoint[j]);
616                                 if (rc)
617                                     return [bp];
618                             }
619                         } else {
620                             var rc = cb.call(url, bp.lineNo, bp);
621                             if (rc)
622                                 return [bp];
623                         }
624                     }
625                 }
626             }
627         }
628         else
629         {
630             var bps = [];
631             for (var url in breakpoints)
632                 bps.push(this.enumerateBreakpoints(url, cb));
633             return bps;
634         }
635     },
636 
637     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
638     // error breakpoints are a way of selecting breakpoint from the Console
639     //
640     setErrorBreakpoint: function(sourceFile, lineNo, debuggr)
641     {
642         var url = sourceFile.href;
643         var index = this.findErrorBreakpoint(url, lineNo);
644         if (index == -1)
645         {
646             this.setBreakpoint(sourceFile, lineNo, null, debuggr);
647             errorBreakpoints.push({href: url, lineNo: lineNo, type: BP_ERROR });
648             dispatch(debuggers, "onToggleErrorBreakpoint", [url, lineNo, true, debuggr]);
649         }
650     },
651 
652     clearErrorBreakpoint: function(sourceFile, lineNo, debuggr)
653     {
654         var url = sourceFile.href;
655         var index = this.findErrorBreakpoint(url, lineNo);
656         if (index != -1)
657         {
658             this.clearBreakpoint(url, lineNo);
659             errorBreakpoints.splice(index, 1);
660 
661             dispatch(debuggers, "onToggleErrorBreakpoint", [url, lineNo, false, debuggr]);
662         }
663     },
664 
665     hasErrorBreakpoint: function(url, lineNo)
666     {
667         return this.findErrorBreakpoint(url, lineNo) != -1;
668     },
669 
670     enumerateErrorBreakpoints: function(url, cb)
671     {
672         if (url)
673         {
674             for (var i = 0; i < errorBreakpoints.length; ++i)
675             {
676                 var bp = errorBreakpoints[i];
677                 if (bp.href == url)
678                     cb.call(bp.href, bp.lineNo, bp);
679             }
680         }
681         else
682         {
683             for (var i = 0; i < errorBreakpoints.length; ++i)
684             {
685                 var bp = errorBreakpoints[i];
686                 cb.call(bp.href, bp.lineNo, bp);
687             }
688         }
689     },
690 
691     findErrorBreakpoint: function(url, lineNo)
692     {
693         for (var i = 0; i < errorBreakpoints.length; ++i)
694         {
695             var bp = errorBreakpoints[i];
696             if (bp.lineNo == lineNo && bp.href == url)
697                 return i;
698         }
699 
700         return -1;
701     },
702 
703     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
704 
705     traceAll: function(urls, debuggr)
706     {
707         this.hookCalls(debuggr.onFunctionCall, false);  // call on all passed urls
708     },
709 
710     untraceAll: function(debuggr)
711     {
712         jsd.functionHook = null; // undo hookCalls()
713     },
714 
715     traceCalls: function(sourceFile, lineNo, debuggr)
716     {
717         var bp = this.monitor(sourceFile, lineNo, debuggr); // set a breakpoint on the starting point
718         bp.type |= BP_TRACE;
719         // when we hit the bp in onBreakPoint we being tracing.
720     },
721 
722     untraceCalls: function(sourceFile, lineNo, debuggr)
723     {
724         var bp = lineNo != -1 ? this.findBreakpoint(url, lineNo) : null;
725         if (bp)
726         {
727             bp.type &= ~BP_TRACE;
728             this.unmonitor(sourceFile, lineNo);
729         }
730     },
731 
732     monitor: function(sourceFile, lineNo, debuggr)
733     {
734         if (lineNo == -1)
735             return null;
736 
737         var bp = this.addBreakpoint(BP_MONITOR, sourceFile, lineNo, null, debuggr);
738         if (bp)
739         {
740             ++monitorCount;
741             dispatch(debuggers, "onToggleMonitor", [sourceFile.href, lineNo, true]);
742         }
743         return bp;
744     },
745 
746     unmonitor: function(sourceFile, lineNo)
747     {
748         if (lineNo != -1 && this.removeBreakpoint(BP_MONITOR, sourceFile.href, lineNo))
749         {
750             --monitorCount;
751             dispatch(debuggers, "onToggleMonitor", [sourceFile.href, lineNo, false]);
752         }
753     },
754 
755     isMonitored: function(url, lineNo)
756     {
757         var bp = lineNo != -1 ? this.findBreakpoint(url, lineNo) : null;
758         return bp && bp.type & BP_MONITOR;
759     },
760 
761     enumerateMonitors: function(url, cb)
762     {
763         if (url)
764         {
765             var urlBreakpoints = breakpoints[url];
766             if (urlBreakpoints)
767             {
768                 for (var i = 0; i < urlBreakpoints.length; ++i)
769                 {
770                     var bp = urlBreakpoints[i];
771                     if (bp.type & BP_MONITOR)
772                         cb.call(url, bp.lineNo, bp);
773                 }
774             }
775         }
776         else
777         {
778             for (var url in breakpoints)
779                 this.enumerateBreakpoints(url, cb);
780         }
781     },
782 
783     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
784 
785     enumerateScripts: function(length)
786     {
787         var scripts = [];
788         jsd.enumerateScripts( {
789             enumerateScript: function(script) {
790                 var fileName = script.fileName;
791                 if ( !isFilteredURL(fileName) ) {
792                     scripts.push(script);
793                 }
794             }
795         });
796         length.value = scripts.length;
797         return scripts;
798     },
799 
800     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
801 
802     startProfiling: function()
803     {
804         if (!this.profiling)
805         {
806             this.profiling = true;
807             profileStart = new Date();
808 
809             jsd.flags |= COLLECT_PROFILE_DATA;
810         }
811 
812         ++profileCount;
813     },
814 
815     stopProfiling: function()
816     {
817         if (--profileCount == 0)
818         {
819             jsd.flags &= ~COLLECT_PROFILE_DATA;
820 
821             var t = profileStart.getTime();
822 
823             this.profiling = false;
824             profileStart = null;
825 
826             return new Date().getTime() - t;
827         }
828         else
829             return -1;
830     },
831 
832     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
833 
834     enableDebugger: function()
835     {
836         if (waitingForTimer)
837         {
838             timer.cancel();
839             waitingForTimer = false;
840         }
841         if (enabledDebugger)
842             return;
843 
844         enabledDebugger = true;
845 
846         this.obeyPrefs();
847 
848         if (!jsd)
849         {
850             jsd = DebuggerService.getService(jsdIDebuggerService);
851 
852             if ( FBTrace.DBG_FBS_ERRORS )
853                 FBTrace.sysout("enableDebugger gets jsd service, isOn:"+jsd.isOn+" initAtStartup:"+jsd.initAtStartup+" now have "+debuggers.length+" debuggers"+" in "+clients.length+" clients");
854         }
855 
856         if (!jsd.isOn)
857         {
858             jsd.on(); // this should be the only call to jsd.on().
859             jsd.flags |= DISABLE_OBJECT_TRACE;
860 
861             if (jsd.pauseDepth && FBTrace.DBG_FBS_ERRORS)
862                 FBTrace.sysout("fbs.enableDebugger found non-zero jsd.pauseDepth !! "+jsd.pauseDepth)
863         }
864 
865         if (!this.filterChrome)
866             this.createChromeBlockingFilters();
867 
868         var active = fbs.isJSDActive();
869 
870         dispatch(clients, "onJSDActivate", [active, "fbs enableDebugger"]);
871         this.hookScripts();
872     },
873 
874     obeyPrefs: function()
875     {
876         this.showStackTrace = prefs.getBoolPref("extensions.firebug.service.showStackTrace");
877         this.breakOnErrors = prefs.getBoolPref("extensions.firebug.service.breakOnErrors");
878         this.trackThrowCatch = prefs.getBoolPref("extensions.firebug.service.trackThrowCatch");
879         this.scriptsFilter = prefs.getCharPref("extensions.firebug.service.scriptsFilter");
880         this.filterSystemURLs = prefs.getBoolPref("extensions.firebug.service.filterSystemURLs");  // may not be exposed to users
881 
882         FirebugPrefsObserver.syncFilter();
883 
884         try {
885             // CREATION and BP generate a huge trace
886             if (FBTrace.DBG_FF_START)
887             {
888                 FBTrace.DBG_BP = true;
889                 FBTrace.DBG_FBS_CREATION = true;
890             }
891 
892             if (FBTrace.DBG_FBS_ERRORS)
893                 FBTrace.sysout("fbs.obeyPrefs showStackTrace:"+this.showStackTrace+" breakOnErrors:"+this.breakOnErrors+" trackThrowCatch:"+this.trackThrowCatch+" scriptFilter:"+this.scriptsFilter+" filterSystemURLs:"+this.filterSystemURLs);
894         }
895         catch (exc)
896         {
897             FBTrace.sysout("firebug-service: constructor getBoolPrefs FAILED with exception=",exc);
898         }
899     },
900 
901     disableDebugger: function()
902     {
903         if (!enabledDebugger)
904             return;
905 
906         if (!timer)  // then we probably shutdown
907             return;
908 
909         enabledDebugger = false;
910 
911         if (jsd.isOn)
912         {
913             jsd.pause();
914             fbs.unhookScripts();
915 
916             while (jsd.pauseDepth > 0)  // unwind completely
917                 jsd.unPause();
918             fbs.pauseDepth = 0;
919 
920             jsd.off();
921         }
922 
923         var active = fbs.isJSDActive();
924         dispatch(clients, "onJSDDeactivate", [active, "fbs disableDebugger"]);
925 
926         fbs.onXScriptCreatedByTag = {};  // clear any uncleared top level scripts
927 
928         if (FBTrace.DBG_FBS_FINDDEBUGGER || FBTrace.DBG_ACTIVATION)
929             FBTrace.sysout("fbs.disableDebugger jsd.isOn:"+jsd.isOn+" for enabledDebugger: "+enabledDebugger);
930     },
931 
932     pause: function()  // must support multiple calls
933     {
934         if (!enabledDebugger)
935             return "not enabled";
936         var rejection = [];
937         dispatch(clients, "onPauseJSDRequested", [rejection]);
938 
939         if (rejection.length == 0)  // then everyone wants to pause
940         {
941             if (fbs.pauseDepth == 0)  // don't pause if we are paused.
942             {
943                 fbs.pauseDepth++;
944                 jsd.pause();
945                 fbs.unhookScripts();
946             }
947             var active = fbs.isJSDActive();
948             dispatch(clients, "onJSDDeactivate", [active, "pause depth "+jsd.pauseDepth]);
949         }
950         else // we don't want to pause
951         {
952             while (fbs.pauseDepth > 0)  // make sure we are not paused.
953                 fbs.unPause();
954             fbs.pauseDepth = 0;
955         }
956         if (FBTrace.DBG_FBS_FINDDEBUGGER || FBTrace.DBG_ACTIVATION)
957         {
958             FBTrace.sysout("fbs.pause depth "+(jsd.isOn?jsd.pauseDepth:"jsd OFF")+" fbs.pauseDepth: "+fbs.pauseDepth+" rejection "+rejection.length+" from "+clients.length+" clients ");
959             // The next line gives NS_ERROR_NOT_AVAILABLE
960             // FBTrace.sysout("fbs.pause depth "+(jsd.isOn?jsd.pauseDepth:"jsd OFF")+" rejection "+rejection.length+" from clients "+clients, rejection);
961         }
962         return fbs.pauseDepth;
963     },
964 
965     unPause: function()
966     {
967         if (fbs.pauseDepth > 0)
968         {
969             if (FBTrace.DBG_ACTIVATION && (!jsd.isOn || jsd.pauseDepth == 0) )
970                 FBTrace.sysout("fbs.unpause while jsd.isOn is "+jsd.isOn+" and hooked scripts pauseDepth:"+jsd.pauseDepth);
971 
972             fbs.pauseDepth--;
973             fbs.hookScripts();
974 
975             var depth = jsd.unPause();
976             var active = fbs.isJSDActive();
977 
978 
979             if (FBTrace.DBG_ACTIVATION)
980                 FBTrace.sysout("fbs.unPause hooked scripts and unPaused, active:"+active+" depth "+depth+" jsd.isOn: "+jsd.isOn+" fbs.pauseDepth "+fbs.pauseDepth);
981 
982             dispatch(clients, "onJSDActivate", [active, "unpause depth"+jsd.pauseDepth]);
983 
984         }
985         else  // we were not paused.
986         {
987             if (FBTrace.DBG_ACTIVATION)
988                 FBTrace.sysout("fbs.unPause no action: (jsd.pauseDepth || !jsd.isOn) = ("+ jsd.pauseDepth+" || "+ !jsd.isOn+")"+" fbs.pauseDepth "+fbs.pauseDepth);
989         }
990         return fbs.pauseDepth;
991     },
992 
993     isJSDActive: function()
994     {
995         return (jsd && jsd.isOn && (jsd.pauseDepth == 0) );
996     },
997 
998     broadcast: function(message, args)  // re-transmit the message (string) with args [objs] to XUL windows.
999     {
1000         dispatch(clients, message, args);
1001         if (FBTrace.DBG_FBS_ERRORS)
1002             FBTrace.sysout("fbs.broadcast "+message+" to "+clients.length+" windows\n");
1003     },
1004 
1005     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1006 
1007     normalizeURL: function(url)
1008     {
1009         // For some reason, JSD reports file URLs like "file:/" instead of "file:///", so they
1010         // don't match up with the URLs we get back from the DOM
1011         return url ? url.replace(/file:\/([^/])/, "file:///$1") : "";
1012     },
1013 
1014     denormalizeURL: function(url)
1015     {
1016         // This should not be called.
1017         return url ? url.replace(/file:\/\/\//, "file:/") : "";
1018     },
1019 
1020 
1021     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1022     // jsd Hooks
1023 
1024     // When (debugger keyword and not halt)||(bp and BP_UNTIL) || (onBreakPoint && no conditions)
1025     // || interuptHook.  rv is ignored
1026     onBreak: function(frame, type, rv)
1027     {
1028         try
1029         {
1030             // avoid step_out from web page to chrome
1031             if (type==jsdIExecutionHook.TYPE_INTERRUPTED && stepStayOnDebuggr)
1032             {
1033                 var debuggr = this.reFindDebugger(frame, stepStayOnDebuggr);
1034                 if (FBTrace.DBG_FBS_STEP && (stepMode != STEP_SUSPEND) )
1035                     FBTrace.sysout("fbs.onBreak type="+getExecutionStopNameFromType(type)+" hookFrameCount:"+hookFrameCount+" stepStayOnDebuggr "+stepStayOnDebuggr+" debuggr:"+(debuggr?debuggr:"null")+" last_debuggr="+(fbs.last_debuggr?fbs.last_debuggr.debuggerName:"null"));
1036 
1037                 if (!debuggr)
1038                 {
1039                     // This frame is not for the debugger we want
1040                     if (stepMode == STEP_OVER || stepMode == STEP_OUT)  // then we are in the debuggr we want and returned in to one we don't
1041                     {
1042                         this.stopStepping(); // run, you are free.
1043                     }
1044 
1045                     return RETURN_CONTINUE;  // This means that we will continue to take interrupts until  when?
1046                 }
1047                 else
1048                 {
1049                     if (stepMode == STEP_SUSPEND) // then we have interrupted the outerFunction
1050                     {
1051                         var scriptTag = frame.script.tag;
1052                         if (scriptTag in this.onXScriptCreatedByTag) // yes, we have to create the sourceFile
1053                             this.onBreakpoint(frame, type, rv);  // TODO refactor so we don't get mixed up
1054                     }
1055                 }
1056             }
1057             else
1058             {
1059                 var debuggr = this.findDebugger(frame);
1060 
1061                 if (FBTrace.DBG_FBS_STEP)
1062                     FBTrace.sysout("fbs.onBreak type="+getExecutionStopNameFromType(type)+" debuggr:"+(debuggr?debuggr:"null")+" last_debuggr="+(fbs.last_debuggr?fbs.last_debuggr.debuggerName:"null"));
1063             }
1064 
1065             if (debuggr)
1066                 return this.breakIntoDebugger(debuggr, frame, type);
1067 
1068         }
1069         catch(exc)
1070         {
1071             if (FBTrace.DBG_FBS_ERRORS)
1072                 FBTrace.sysout("onBreak failed: "+exc,exc);
1073             ERROR("onBreak failed: "+exc);
1074         }
1075         return RETURN_CONTINUE;
1076     },
1077 
1078     // When engine encounters debugger keyword (only)
1079     onDebugger: function(frame, type, rv)
1080     {
1081         if (FBTrace.DBG_FBS_BP)
1082             FBTrace.sysout("fbs.onDebugger with haltDebugger="+haltDebugger+" in "+frame.script.fileName, frame.script);
1083         try {
1084             if (haltDebugger)
1085             {
1086                 var debuggr = haltDebugger;
1087                 haltDebugger = null;
1088                 return debuggr.onHalt(frame);
1089             }
1090             else
1091                 return this.onBreak(frame, type, rv);
1092          }
1093          catch(exc)
1094          {
1095             if (FBTrace.DBG_FBS_ERRORS)
1096                 FBTrace.sysout("onDebugger failed: "+exc,exc);
1097 
1098             ERROR("onDebugger failed: "+exc);
1099             return RETURN_CONTINUE;
1100          }
1101     },
1102 
1103     // when the onError handler returns false
1104     onDebug: function(frame, type, rv)
1105     {
1106         if (FBTrace.DBG_FBS_ERRORS)
1107         {
1108             fbs.onDebugRequests--;
1109             FBTrace.sysout("fbs.onDebug ("+fbs.onDebugRequests+") fileName="+frame.script.fileName+ " reportNextError="+reportNextError+" breakOnNextError="+breakOnNextError+" breakOnNext:"+this.breakOnErrors);
1110         }
1111         if ( isFilteredURL(frame.script.fileName) )
1112             return RETURN_CONTINUE;
1113         try
1114         {
1115             var debuggr = (reportNextError || breakOnNextError) ? this.findDebugger(frame) : null;
1116 
1117             if (reportNextError)
1118             {
1119                 reportNextError = false;
1120                 if (debuggr)
1121                 {
1122                     var hookReturn = debuggr.onError(frame, errorInfo);
1123                     if (hookReturn >=0)
1124                         return hookReturn;
1125                     else if (hookReturn==-1)
1126                         breakOnNextError = true;
1127                     if (breakOnNextError)
1128                         debuggr = this.reFindDebugger(frame, debuggr);
1129                 }
1130             }
1131 
1132             if (breakOnNextError)
1133             {
1134                 breakOnNextError = false;
1135                 if (debuggr)
1136                     return this.breakIntoDebugger(debuggr, frame, type);
1137             }
1138         } catch (exc) {
1139             ERROR("onDebug failed: "+exc);
1140         }
1141         return RETURN_CONTINUE;
1142     },
1143 
1144     onBreakpoint: function(frame, type, val)
1145     {
1146         var scriptTag = frame.script.tag;
1147         if (FBTrace.DBG_FBS_SRCUNITS) FBTrace.sysout("onBreakpoint frame.script.tag="+frame.script.tag );
1148 
1149         if (scriptTag in this.onXScriptCreatedByTag)
1150         {
1151             if (FBTrace.DBG_FBS_TRACKFILES)
1152                 trackFiles.def(frame);
1153             var onXScriptCreated = this.onXScriptCreatedByTag[scriptTag];
1154             if (FBTrace.DBG_FBS_BP) FBTrace.sysout("onBreakpoint("+getExecutionStopNameFromType(type)+") with frame.script.tag="
1155                                       +frame.script.tag+" onXScriptCreated:"+onXScriptCreated.kind+"\n");
1156             delete this.onXScriptCreatedByTag[scriptTag];
1157             frame.script.clearBreakpoint(0);
1158             try {
1159                 var sourceFile = onXScriptCreated(frame, type, val);
1160             } catch (e) {
1161                 FBTrace.sysout("onBreakpoint called onXScriptCreated and it didn't end well:",e);
1162             }
1163 
1164             if (FBTrace.DBG_FBS_SRCUNITS)
1165             {
1166                 var msg = "Top Scripts Uncleared:";
1167                 for (p in this.onXScriptCreatedByTag) msg += (p+"|");
1168                 FBTrace.sysout(msg);
1169             }
1170             if (!sourceFile || !sourceFile.breakOnZero || sourceFile.breakOnZero != scriptTag)
1171                 return RETURN_CONTINUE;
1172             else  // sourceFile.breakOnZero matches the script we have halted.
1173             {
1174                if (FBTrace.DBG_FBS_BP) FBTrace.sysout("fbs.onBreakpoint breakOnZero, continuing for user breakpoint\n");
1175             }
1176         }
1177 
1178 
1179         var bp = this.findBreakpointByScript(frame.script, frame.pc);
1180         if (bp)
1181         {
1182             // See issue 1179, should not break if we resumed from a single step and have not advanced.
1183             if (disabledCount || monitorCount || conditionCount || runningUntil)
1184             {
1185                 if (FBTrace.DBG_FBS_BP)
1186                 {
1187                     FBTrace.sysout("onBreakpoint("+getExecutionStopNameFromType(type)+") disabledCount:"+disabledCount
1188                               +" monitorCount:"+monitorCount+" conditionCount:"+conditionCount+" runningUntil:"+runningUntil, bp);
1189                 }
1190 
1191                 if (bp.type & BP_MONITOR && !(bp.disabled & BP_MONITOR))
1192                 {
1193                     if (bp.type & BP_TRACE && !(bp.disabled & BP_TRACE) )
1194                         this.hookCalls(bp.debugger.onFunctionCall, true);
1195                     else
1196                         bp.debugger.onMonitorScript(frame);
1197                 }
1198 
1199                 if (bp.type & BP_UNTIL)
1200                 {
1201                     this.stopStepping();
1202                     if (bp.debugger)
1203                         return this.breakIntoDebugger(bp.debugger, frame, type);
1204                 }
1205                 else if (!(bp.type & BP_NORMAL) || bp.disabled & BP_NORMAL)
1206                 {
1207                     return  RETURN_CONTINUE;
1208                 }
1209                 else if (bp.type & BP_NORMAL)
1210                 {
1211                     var passed = testBreakpoint(frame, bp);
1212                     if (!passed)
1213                         return RETURN_CONTINUE;
1214                 }
1215                 // type was normal, but passed test
1216             }
1217             else  // not special, just break for sure
1218                 return this.breakIntoDebugger(bp.debugger, frame, type);
1219         }
1220         else
1221         {
1222             if (FBTrace.DBG_FBS_BP) FBTrace.sysout("onBreakpoint("+getExecutionStopNameFromType(type)+") NO bp match with frame.script.tag="
1223                 +frame.script.tag+"\n");
1224         }
1225 
1226         if (runningUntil)
1227             return RETURN_CONTINUE;
1228         else
1229             return this.onBreak(frame, type, val);
1230     },
1231 
1232     onThrow: function(frame, type, rv)
1233     {
1234         if ( isFilteredURL(frame.script.fileName) )
1235             return RETURN_CONTINUE_THROW;
1236 
1237         if (rv && rv.value && rv.value.isValid)
1238         {
1239             var value = rv.value;
1240             if (value.jsClassName == "Error" && reTooMuchRecursion.test(value.stringValue))
1241             {
1242                 if (fbs._lastErrorCaller)
1243                 {
1244                     if (fbs._lastErrorCaller == frame.script.tag) // then are unwinding recursion
1245                     {
1246                         fbs._lastErrorCaller = frame.callingFrame ? frame.callingFrame.script.tag : null;
1247                         return RETURN_CONTINUE_THROW;
1248                     }
1249                 }
1250                 else
1251                 {
1252                     fbs._lastErrorCaller = frame.callingFrame.script.tag;
1253                     frame = fbs.discardRecursionFrames(frame);
1254                     // go on to process the throw.
1255                 }
1256             }
1257             else
1258             {
1259                 delete fbs._lastErrorCaller; // throw is not recursion
1260             }
1261         }
1262         else
1263         {
1264             delete fbs._lastErrorCaller; // throw is not recursion either
1265         }
1266 
1267         // Remember the error where the last exception is thrown - this will
1268         // be used later when the console service reports the error, since
1269         // it doesn't currently report the window where the error occurred
1270 
1271         this._lastErrorWindow =  getFrameGlobal(frame);
1272 
1273         if (this.showStackTrace)  // store these in case the throw is not caught
1274         {
1275             var debuggr = this.findDebugger(frame);  // sets debuggr.breakContext
1276             if (debuggr)
1277             {
1278                 fbs._lastErrorScript = frame.script;
1279                 fbs._lastErrorLine = frame.line;
1280                 fbs._lastErrorDebuggr = debuggr;
1281                 fbs._lastErrorContext = debuggr.breakContext; // XXXjjb this is bad API
1282             }
1283             else
1284                 delete fbs._lastErrorDebuggr;
1285         }
1286 
1287         if (fbs.trackThrowCatch)
1288         {
1289             if (FBTrace.DBG_FBS_ERRORS)
1290                 FBTrace.sysout("onThrow from tag:"+frame.script.tag+":"+frame.script.fileName+"@"+frame.line+": "+frame.pc);
1291 
1292             var debuggr = this.findDebugger(frame);
1293             if (debuggr)
1294                 return debuggr.onThrow(frame, rv);
1295         }
1296 
1297         return RETURN_CONTINUE_THROW;
1298     },
1299 
1300     onError: function(message, fileName, lineNo, pos, flags, errnum, exc)
1301     {
1302         if (FBTrace.DBG_FBS_ERRORS)
1303         {
1304             var messageKind;
1305             if (flags & jsdIErrorHook.REPORT_ERROR)
1306                 messageKind = "Error";
1307             if (flags & jsdIErrorHook.REPORT_WARNING)
1308                 messageKind = "Warning";
1309             if (flags & jsdIErrorHook.REPORT_EXCEPTION)
1310                 messageKind = "Uncaught-Exception";
1311             if (flags & jsdIErrorHook.REPORT_STRICT)
1312                 messageKind += "-Strict";
1313             FBTrace.sysout("fbs.onError ("+fbs.onDebugRequests+") with this.showStackTrace="+this.showStackTrace+" and this.breakOnErrors="
1314                    +this.breakOnErrors+" kind="+messageKind+" msg="+message+"@"+fileName+":"+lineNo+"."+pos+"\n");
1315         }
1316 
1317         // global to pass info to onDebug. Some duplicate values to support different apis
1318         errorInfo = { errorMessage: message, sourceName: fileName, lineNumber: lineNo,
1319                 message: message, fileName: fileName, lineNo: lineNo,
1320                 columnNumber: pos, flags: flags, category: "js", errnum: errnum, exc: exc };
1321 
1322         if (message=="out of memory")  // bail
1323         {
1324             if (FBTrace.DBG_FBS_ERRORS)
1325                 fbs.osOut("fbs.onError sees out of memory "+fileName+":"+lineNo+"\n");
1326             return true;
1327         }
1328 
1329         if (this.showStackTrace)
1330         {
1331             reportNextError = true;
1332             if (FBTrace.DBG_FBS_ERRORS)
1333             {
1334                 FBTrace.sysout("fbs.onError debugs missed:("+fbs.onDebugRequests+") showStackTrace, we will try to drop into onDebug\n");
1335                 fbs.onDebugRequests++;
1336             }
1337             return false; // Drop into onDebug, sometimes only
1338         }
1339         else
1340         {
1341             return !this.needToBreakForError(fileName, lineNo);
1342         }
1343     },
1344 
1345     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1346 
1347     onEventScriptCreated: function(frame, type, val, noNestTest)
1348     {
1349         if (fbs.showEvents)
1350         {
1351             try
1352             {
1353                if (!noNestTest)
1354                 {
1355                     // In onScriptCreated we saw a script with baseLineNumber = 1. We marked it as event and nested.
1356                     // Now we know its event, not nested.
1357                     if (fbs.nestedScriptStack.length > 0)
1358                     {
1359                         fbs.nestedScriptStack.removeElementAt(0);
1360                     }
1361                     else
1362                     {
1363                         if (FBTrace.DBG_FBS_SRCUNITS)  // these seem to be harmless, but...
1364                         {
1365                             var script = frame.script;
1366                              FBTrace.sysout("onEventScriptCreated no nestedScriptStack: "+script.tag+"@("+script.baseLineNumber+"-"
1367                                 +(script.baseLineNumber+script.lineExtent)+")"+script.fileName+"\n");
1368                             FBTrace.sysout("onEventScriptCreated name: \'"+script.functionName+"\'\n");
1369                             try {
1370                             FBTrace.sysout(script.functionSource);
1371                             } catch (exc) { /*Bug 426692 */ }
1372 
1373                         }
1374                     }
1375                 }
1376 
1377                 var debuggr = fbs.findDebugger(frame);  // sets debuggr.breakContext
1378                 if (debuggr)
1379                 {
1380                     var sourceFile = debuggr.onEventScriptCreated(frame, frame.script, fbs.nestedScriptStack.enumerate());
1381                     fbs.resetBreakpoints(sourceFile);
1382                 }
1383                 else
1384                 {
1385                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
1386                         FBTrace.sysout("fbs.onEventScriptCreated no debuggr for "+frame.script.tag+":"+frame.script.fileName);
1387                 }
1388             } catch(exc) {
1389                 if (FBTrace.DBG_ERRORS)
1390                     FBTrace.sysout("onEventScriptCreated failed: "+exc, exc);
1391             }
1392             if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
1393                 FBTrace.sysout("onEventScriptCreated frame.script.tag:"+frame.script.tag+" href: "+(sourceFile?sourceFile.href:"no sourceFile"), sourceFile);
1394         }
1395 
1396         fbs.clearNestedScripts();
1397         return sourceFile;
1398     },
1399 
1400     onEvalScriptCreated: function(frame, type, val)
1401     {
1402         if (fbs.showEvals)
1403         {
1404             try
1405             {
1406                 if (!frame.callingFrame)
1407                 {
1408                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS) FBTrace.sysout("No calling Frame for eval frame.script.fileName:"+frame.script.fileName);
1409                     // These are eval-like things called by native code. They come from .xml files
1410                     // They should be marked as evals but we'll treat them like event handlers for now.
1411                     return fbs.onEventScriptCreated(frame, type, val, true);
1412                 }
1413                 // In onScriptCreated we found a no-name script, set a bp in PC=0, and a flag.
1414                 // onBreakpoint saw the flag, cleared the flag, and sent us here.
1415                 // Start by undoing our damage
1416                 var outerScript = frame.script;
1417 
1418                 var debuggr = fbs.findDebugger(frame);  // sets debuggr.breakContext
1419                 if (debuggr)
1420                 {
1421                     var sourceFile = debuggr.onEvalScriptCreated(frame, outerScript, fbs.nestedScriptStack.enumerate());
1422                     fbs.resetBreakpoints(sourceFile);
1423                 }
1424                 else
1425                 {
1426                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS) FBTrace.sysout("fbs.onEvalScriptCreated no debuggr for "+outerScript.tag+":"+outerScript.fileName);
1427                 }
1428             }
1429             catch (exc)
1430             {
1431                 ERROR("onEvalScriptCreated failed: "+exc);
1432                 if (FBTrace.DBG_FBS_ERRORS) FBTrace.sysout("onEvalScriptCreated failed:", exc);
1433             }
1434         }
1435 
1436         fbs.clearNestedScripts();
1437         if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS) FBTrace.sysout("onEvalScriptCreated outerScript.tag:"+outerScript.tag+" href: "+(sourceFile?sourceFile.href:"no sourceFile"));
1438         return sourceFile;
1439     },
1440 
1441     onTopLevelScriptCreated: function(frame, type, val)
1442     {
1443         try
1444         {
1445             // In onScriptCreated we may have found a script at baseLineNumber=1
1446             // Now we know its not an event
1447             if (fbs.nestedScriptStack.length > 0)
1448             {
1449                 var firstScript = fbs.nestedScriptStack.queryElementAt(0, jsdIScript);
1450                 if (firstScript.tag in fbs.onXScriptCreatedByTag)
1451                 {
1452                     delete  fbs.onXScriptCreatedByTag[firstScript.tag];
1453                     if (firstScript.isValid)
1454                         firstScript.clearBreakpoint(0);
1455                     if (FBTrace.DBG_FBS_SRCUNITS)
1456                         FBTrace.sysout("fbs.onTopLevelScriptCreated clear bp@0 for firstScript.tag: "+firstScript.tag+"\n");
1457                 }
1458             }
1459 
1460             // On compilation of a top-level (global-appending) function.
1461             // After this top-level script executes we lose the jsdIScript so we can't build its line table.
1462             // Therefore we need to build it here.
1463             var debuggr = fbs.findDebugger(frame);  // sets debuggr.breakContext
1464             if (debuggr)
1465             {
1466                 var sourceFile = debuggr.onTopLevelScriptCreated(frame, frame.script, fbs.nestedScriptStack.enumerate());
1467                 if (FBTrace.DBG_FBS_SRCUNITS) FBTrace.sysout("fbs.onTopLevelScriptCreated got sourceFile:"+sourceFile+" using "+fbs.nestedScriptStack.length+" nestedScripts\n");
1468                 fbs.resetBreakpoints(sourceFile, frame.script.baseLineNumber+frame.script.lineExtent);
1469             }
1470             else
1471             {
1472                 // modules end up here?
1473                 if (FBTrace.DBG_FBS_SRCUNITS)
1474                     FBTrace.sysout("FBS.onTopLevelScriptCreated no debuggr for "+frame.script.tag);
1475             }
1476         }
1477         catch (exc)
1478         {
1479             FBTrace.sysout("onTopLevelScriptCreated FAILED: ", exc);
1480             ERROR("onTopLevelScriptCreated Fails: "+exc);
1481         }
1482 
1483         fbs.clearNestedScripts();
1484         if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS) FBTrace.sysout("fbs.onTopLevelScriptCreated script.tag:"+frame.script.tag+" href: "+(sourceFile?sourceFile.href:"no sourceFile"));
1485 
1486         return sourceFile;
1487     },
1488 
1489     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1490 
1491     clearNestedScripts: function()
1492     {
1493         var innerScripts = fbs.nestedScriptStack.enumerate();
1494         while (innerScripts.hasMoreElements())
1495         {
1496             var script = innerScripts.getNext();
1497             if (script.isValid && script.baseLineNumber == 1)
1498             {
1499                 script.clearBreakpoint(0);
1500                 if (this.onXScriptCreatedByTag[script.tag])
1501                     delete this.onXScriptCreatedByTag[script.tag];
1502             }
1503         }
1504         fbs.nestedScriptStack.clear();
1505     },
1506 
1507     onScriptCreated: function(script)
1508     {
1509         if (!fbs)
1510         {
1511             if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
1512                 FBTrace.sysout("onScriptCreated, but no fbs for script.fileName="+script.fileName);
1513              return;
1514         }
1515 
1516         try
1517         {
1518             var fileName = script.fileName;
1519             if (FBTrace.DBG_FBS_TRACKFILES)
1520                 trackFiles.add(fileName);
1521             if (isFilteredURL(fileName))
1522             {
1523                 try {
1524                     if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS)
1525                         FBTrace.sysout("onScriptCreated: filename filtered:\'"+fileName+"\'"+(fbs.filterConsoleInjections?" console injection":""));
1526                 } catch (exc) { /*Bug 426692 */ }
1527                 if (FBTrace.DBG_FBS_TRACKFILES)
1528                     trackFiles.drop(fileName);
1529                 return;
1530             }
1531 
1532             // reset tracing flags on first unfiltered filename
1533             if (!FBTrace.DBG_FF_START && !fbs.firstUnfilteredFilename)
1534             {
1535                 fbs.firstUnfilteredFilename = true;
1536                 FBTrace.DBG_FBS_BP = fbs.resetBP ? true : false;
1537                 FBTrace.DBG_FBS_CREATION = fbs.resetCreation ? true : false;
1538             }
1539 
1540             if (FBTrace.DBG_FBS_CREATION) {
1541                 FBTrace.sysout("onScriptCreated: "+script.tag+"@("+script.baseLineNumber+"-"
1542                     +(script.baseLineNumber+script.lineExtent)+")"+script.fileName+"\n");
1543                 try {
1544                     FBTrace.sysout("onScriptCreated: \'"+script.functionName+"\'", script.functionSource);
1545                 } catch (exc) { /*Bug 426692 */ }
1546             }
1547 
1548             if (fbs.pendingXULFileName && fbs.pendingXULFileName != script.fileName)
1549                 fbs.flushXUL();
1550 
1551             if (!script.functionName) // top or eval-level
1552             {
1553                 // We need to detect eval() and grab its source.
1554                 var hasCaller = fbs.createdScriptHasCaller();
1555                 if (FBTrace.DBG_FBS_SRCUNITS) FBTrace.sysout("createdScriptHasCaller "+hasCaller);
1556 
1557                 if (hasCaller)
1558                 {
1559                     // components end up here
1560                     fbs.onXScriptCreatedByTag[script.tag] = this.onEvalScriptCreated;
1561                 }
1562                 else
1563                     fbs.onXScriptCreatedByTag[script.tag] = this.onTopLevelScriptCreated;
1564 
1565                 script.setBreakpoint(0);
1566                 if (FBTrace.DBG_FBS_CREATION || FBTrace.DBG_FBS_SRCUNITS || FBTrace.DBG_FBS_BP)
1567                 {
1568                     FBTrace.sysout("onScriptCreated: set BP at PC 0 in "+(hasCaller?"eval":"top")+" level tag="+script.tag+":"+script.fileName+" jsd depth:"+(jsd.isOn?jsd.pauseDepth+"":"OFF"));
1569                 }
1570             }
1571             else if (script.baseLineNumber == 1)
1572             {
1573                 // could be a 1) Browser-generated event handler or 2) a nested script at the top of a file
1574                 // One way to tell is assume both then wait to see which we hit first:
1575                 // 1) bp at pc=0 for this script or 2) for a top-level on at the same filename
1576 
1577                 fbs.onXScriptCreatedByTag[script.tag] = this.onEventScriptCreated; // for case 1
1578                 script.setBreakpoint(0);
1579 
1580                 fbs.nestedScriptStack.appendElement(script, false);  // for case 2
1581 
1582                 if (FBTrace.DBG_FBS_CREATION)
1583                     FBTrace.sysout("onScriptCreated: set BP at PC 0 in event level tag="+script.tag);
1584             }
1585             else if( reXUL.test(script.fileName) )
1586             {
1587                 fbs.pendingXULFileName = script.fileName;  // if these were different, we would already have called flushXUL()
1588                 fbs.nestedScriptStack.appendElement(script, false);
1589             }
1590             else
1591             {
1592                 fbs.nestedScriptStack.appendElement(script, false);
1593                 if (FBTrace.DBG_FBS_CREATION) FBTrace.sysout("onScriptCreated: nested function named: "+script.functionName);
1594                 dispatch(scriptListeners,"onScriptCreated",[script, fileName, script.baseLineNumber]);
1595             }
1596         }
1597         catch(exc)
1598         {
1599             ERROR("onScriptCreated failed: "+exc);
1600             FBTrace.sysout("onScriptCreated failed: ", exc);
1601         }
1602     },
1603 
1604     flushXUL: function()
1605     {
1606         for ( var i = debuggers.length - 1; i >= 0; i--)
1607         {
1608             try
1609             {
1610                 var debuggr = debuggers[i];
1611                 if (debuggr.onXULScriptCreated)
1612                     debuggr.onXULScriptCreated(fbs.pendingXULFileName, fbs.nestedScriptStack.enumerate());
1613             }
1614             catch (exc)
1615             {
1616                 FBTrace.sysout("firebug-service flushXUL FAILS: ",exc);
1617             }
1618         }
1619         delete fbs.pendingXULFileName;
1620         fbs.clearNestedScripts();
1621     },
1622 
1623     createdScriptHasCaller: function()
1624     {
1625         if (FBTrace.DBG_FBS_SRCUNITS)
1626         {
1627             var msg = [];
1628             for (var frame = Components.stack; frame; frame = frame.caller)
1629                 msg.push( frame.filename + "@" + frame.lineNumber +": "+frame.sourceLine  );
1630             FBTrace.sysout("createdScriptHasCaller "+msg.length, msg);
1631         }
1632 
1633         var frame = Components.stack; // createdScriptHasCaller
1634 
1635         frame = frame.caller;         // onScriptCreated
1636         if (!frame) return frame;
1637 
1638         frame = frame.caller;         // hook apply
1639         if (!frame) return frame;
1640         frame = frame.caller;         // native interpret?
1641         if (!frame) return frame;
1642         frame = frame.caller;         // our creator ... or null if we are top level
1643         return frame;
1644     },
1645 
1646     onScriptDestroyed: function(script)
1647     {
1648         if (!fbs)
1649              return;
1650         if (script.tag in fbs.onXScriptCreatedByTag)
1651             delete  fbs.onXScriptCreatedByTag[script.tag];
1652 
1653         try
1654         {
1655             var fileName = script.fileName;
1656             if (isFilteredURL(fileName))
1657                 return;
1658             if (FBTrace.DBG_FBS_CREATION)
1659                 FBTrace.sysout('fbs.onScriptDestroyed '+script.tag);
1660 
1661             dispatch(scriptListeners,"onScriptDestroyed",[script]);
1662         }
1663         catch(exc)
1664         {
1665             ERROR("onScriptDestroyed failed: "+exc);
1666             FBTrace.sysout("onScriptDestroyed failed: ", exc);
1667         }
1668     },
1669 
1670     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1671 
1672     createFilter: function(pattern, pass)
1673     {
1674         var filter = {
1675                 globalObject: null,
1676                 flags: pass ? (jsdIFilter.FLAG_ENABLED | jsdIFilter.FLAG_PASS) : jsdIFilter.FLAG_ENABLED,
1677                 urlPattern: pattern,
1678                 startLine: 0,
1679                 endLine: 0
1680             };
1681         return filter;
1682     },
1683 
1684     setChromeBlockingFilters: function()
1685     {
1686         if (!fbs.isChromeBlocked)
1687         {
1688             jsd.appendFilter(this.noFilterHalter);  // must be first
1689             jsd.appendFilter(this.filterChrome);
1690             jsd.appendFilter(this.filterComponents);
1691             jsd.appendFilter(this.filterFirebugComponents);
1692             jsd.appendFilter(this.filterModules);
1693             jsd.appendFilter(this.filterStringBundle);
1694             jsd.appendFilter(this.filterPrettyPrint);
1695             jsd.appendFilter(this.filterWrapper);
1696 
1697             for (var i = 0; i < this.componentFilters.length; i++)
1698                 jsd.appendFilter(this.componentFilters[i]);
1699 
1700             fbs.isChromeBlocked = true;
1701 
1702             if (FBTrace.DBG_FBS_BP)
1703                 this.traceFilters("setChromeBlockingFilters with "+this.componentFilters.length+" component filters");
1704         }
1705     },
1706 
1707     removeChromeBlockingFilters: function()
1708     {
1709         if (fbs.isChromeBlocked)
1710         {
1711             jsd.removeFilter(this.filterChrome);
1712             jsd.removeFilter(this.filterComponents);
1713             jsd.removeFilter(this.filterFirebugComponents);
1714             jsd.removeFilter(this.filterModules);
1715             jsd.removeFilter(this.filterStringBundle);
1716             jsd.removeFilter(this.filterPrettyPrint);
1717             jsd.removeFilter(this.filterWrapper);
1718             jsd.removeFilter(this.noFilterHalter);
1719             for (var i = 0; i < this.componentFilters.length; i++)
1720                 jsd.removeFilter(this.componentFilters[i]);
1721 
1722             fbs.isChromeBlocked = false;
1723         }
1724         if (FBTrace.DBG_FBS_BP)
1725             this.traceFilters("removeChromeBlockingFilters");
1726     },
1727 
1728     createChromeBlockingFilters: function() // call after components are loaded.
1729     {
1730         try
1731         {
1732             this.filterModules = this.createFilter("*/firefox/modules/*");
1733             this.filterComponents = this.createFilter("*/firefox/components/*");
1734             this.filterFirebugComponents = this.createFilter("*/components/firebug-*");
1735             this.filterStringBundle = this.createFilter("XStringBundle");
1736             this.filterChrome = this.createFilter("chrome://*");
1737             this.filterPrettyPrint = this.createFilter("x-jsd:ppbuffer*");
1738             this.filterWrapper = this.createFilter("XPCSafeJSObjectWrapper.cpp");
1739             this.noFilterHalter = this.createFilter("chrome://firebug/content/debuggerHalter.js", true);
1740 
1741             // jsdIFilter does not allow full regexp matching.
1742             // So to filter components, we filter their directory names, which we obtain by looking for
1743             // scripts that match regexps
1744 
1745             var componentsUnfound = [];
1746             for( var i = 0; i < COMPONENTS_FILTERS.length; ++i )
1747             {
1748                 componentsUnfound.push(COMPONENTS_FILTERS[i]);
1749             }
1750 
1751             this.componentFilters = [];
1752 
1753             jsd.enumerateScripts( {
1754                 enumerateScript: function(script) {
1755                     var fileName = script.fileName;
1756                 for( var i = 0; i < componentsUnfound.length; ++i )
1757                     {
1758                         if ( componentsUnfound[i].test(fileName) )
1759                         {
1760                             var match = componentsUnfound[i].exec(fileName);
1761                             fbs.componentFilters.push(fbs.createFilter(match[1]));
1762                             componentsUnfound.splice(i, 1);
1763                             return;
1764                         }
1765                     }
1766                 }
1767             });
1768         } catch (exc) {
1769             FBTrace.sysout("createChromeblockingFilters fails >>>>>>>>>>>>>>>>> "+exc, exc);
1770         }
1771 
1772         if (FBTrace.DBG_FBS_BP)
1773         {
1774             FBTrace.sysout("createChromeBlockingFilters considered "+COMPONENTS_FILTERS.length+
1775                     " regexps and created "+this.componentFilters.length+
1776                     " filters with unfound: "+componentsUnfound.length, componentsUnfound);
1777         }
1778     },
1779 
1780     traceFilters: function(from)
1781     {
1782         FBTrace.sysout("fbs.traceFilters from "+from);
1783         jsd.enumerateFilters({ enumerateFilter: function(filter)
1784             {
1785                 FBTrace.sysout("jsdIFilter "+filter.urlPattern, filter);
1786             }});
1787     },
1788     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1789 
1790     getJSContexts: function()
1791     {
1792         var enumeratedContexts = [];
1793         jsd.enumerateContexts( {enumerateContext: function(jscontext)
1794         {
1795                 try
1796                 {
1797                     if (!jscontext.isValid)
1798                         return;
1799 
1800                     var wrappedGlobal = jscontext.globalObject;
1801                     if (!wrappedGlobal)
1802                         return;
1803 
1804                     var unwrappedGlobal = wrappedGlobal.getWrappedValue();
1805                     if (!unwrappedGlobal)
1806                         return;
1807 
1808                     var global = new XPCNativeWrapper(unwrappedGlobal);
1809 
1810                     if (FBTrace.DBG_FBS_JSCONTEXTS)
1811                         FBTrace.sysout("getJSContexts jsIContext tag:"+jscontext.tag+(jscontext.isValid?" - isValid\n":" - NOT valid\n"));
1812 
1813                     if (global)
1814                     {
1815                         var document = global.document;
1816                         if (document)
1817                         {
1818                             if (FBTrace.DBG_FBS_JSCONTEXTS)
1819                                 FBTrace.sysout("getJSContexts global document.location: "+document.location);
1820                         }
1821                         else
1822                         {
1823                             if (FBTrace.DBG_FBS_JSCONTEXTS)
1824                             {
1825                                 var total = 0;
1826                                 for(var p in global)
1827                                     total++;
1828 
1829                                 FBTrace.sysout("getJSContexts global  without document type: "+typeof(global)+" with "+total+" properties and interfaces", global);
1830                             }
1831                             return; // skip these
1832                         }
1833                     }
1834                     else
1835                     {
1836                         if (FBTrace.DBG_FBS_JSCONTEXTS)
1837                             FBTrace.sysout("getJSContexts no global object tag:"+jscontext.tag);
1838                         return; // skip this
1839                     }
1840 
1841                     if (FBTrace.DBG_FBS_JSCONTEXTS)
1842                     {
1843                         if (jscontext.privateData)
1844                         {
1845                             var isTimer = (jscontext.privateData instanceof nsITimerCallback);
1846                             if (FBTrace.DBG_FBS_JSCONTEXTS)
1847                                 FBTrace.sysout("jscontext.privateData isTimer:"+isTimer, jscontext.privateData);
1848                         }
1849                     /*
1850                      * jsdIContext has jsdIEphemeral, nsISupports, jsdIContext
1851                      * jsdIContext.wrappedContext has nsISupports and nsITimerCallback, nothing interesting
1852                      * jsdIContext.JSContext is undefined
1853                      */
1854                         var wContext = jscontext.wrappedContext;
1855                         if (wContext instanceof nsITimerCallback)
1856                         {
1857                             var asTimer = wContext.QueryInterface(nsITimerCallback);
1858                             FBTrace.sysout("jsContext.wrappedContext ", asTimer);
1859                         }
1860                         var c = jscontext.JSContext;
1861                         FBTrace.sysout("jsContext.JSContext", c);
1862                     }
1863 
1864                     enumeratedContexts.push(jscontext);
1865                 }
1866                 catch(e)
1867                 {
1868                     FBTrace.sysout("jscontext dump FAILED "+e, e);
1869                 }
1870 
1871         }});
1872         return enumeratedContexts;
1873     },
1874 
1875     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1876 
1877     findDebugger: function(frame)
1878     {
1879         if (debuggers.length < 1)
1880             return;
1881 
1882         var checkFrame = frame;
1883         while (checkFrame) // We may stop in a component, but want the callers Window
1884         {
1885             var frameScopeRoot = getFrameScopeRoot(checkFrame);
1886             if (frameScopeRoot)
1887                 break;
1888 
1889             if (FBTrace.DBG_FBS_FINDDEBUGGER)
1890                 FBTrace.sysout("fbs.findDebugger no frame Window, looking down the stack", checkFrame);
1891 
1892             checkFrame = checkFrame.callingFrame;
1893         }
1894 
1895         if (!checkFrame && FBTrace.DBG_FBS_FINDDEBUGGER)
1896             FBTrace.sysout("fbs.findDebugger fell thru bottom of stack", frame);
1897 
1898         // frameScopeRoot should be the top window for the scope of the frame function
1899         // or null
1900         fbs.last_debuggr = fbs.askDebuggersForSupport(frameScopeRoot, frame);
1901         if (fbs.last_debuggr)
1902              return fbs.last_debuggr;
1903         else
1904             return null;
1905     },
1906 
1907     isChromebug: function(global)
1908     {
1909         // TODO this is a kludge: isFilteredURL stops users from seeing firebug but chromebug has to disable the filter
1910 
1911         var location = fbs.getLocationSafe(global);
1912         if (location)
1913         {
1914             if (location.indexOf("chrome://chromebug/") >= 0 || location.indexOf("chrome://fb4cb/") >= 0)
1915                 return true;
1916         }
1917         return false;
1918     },
1919 
1920     getLocationSafe: function(global)
1921     {
1922         try
1923         {
1924             if (global && global.location)  // then we have a window, it will be an nsIDOMWindow, right?
1925                 return global.location.toString();
1926             else if (global && global.tag)
1927                 return "global_tag_"+global.tag;
1928         }
1929         catch (exc)
1930         {
1931             // FF3 gives (NS_ERROR_INVALID_POINTER) [nsIDOMLocation.toString]
1932         }
1933         return null;
1934     },
1935 
1936     askDebuggersForSupport: function(global, frame)
1937     {
1938         if (FBTrace.DBG_FBS_FINDDEBUGGER)
1939             FBTrace.sysout("askDebuggersForSupport using global "+global+" for "+frame.script.fileName);
1940         if (global && fbs.isChromebug(global))
1941             return false;
1942 
1943         if (FBTrace.DBG_FBS_FINDDEBUGGER)
1944             FBTrace.sysout("askDebuggersForSupport "+debuggers.length+ " debuggers to check for "+frame.script.fileName, debuggers);
1945 
1946         for ( var i = debuggers.length - 1; i >= 0; i--)
1947         {
1948             try
1949             {
1950                 var debuggr = debuggers[i];
1951                 if (debuggr.supportsGlobal(global, frame))
1952                 {
1953                     if (!debuggr.breakContext)
1954                         FBTrace.sysout("Debugger with no breakContext:",debuggr.supportsGlobal);
1955                     if (FBTrace.DBG_FBS_FINDDEBUGGER)
1956                         FBTrace.sysout(" findDebugger found debuggr ("+debuggr.debuggerName+") at "+i+" with breakContext "+debuggr.breakContext.getName()+" for global "+fbs.getLocationSafe(global)+" while processing "+frame.script.fileName);
1957                     return debuggr;
1958                 }
1959             }
1960             catch (exc)
1961             {
1962                 FBTrace.sysout("firebug-service askDebuggersForSupport FAILS: "+exc,exc);
1963             }
1964         }
1965         return null;
1966     },
1967 
1968     dumpIValue: function(value)
1969     {
1970         var listValue = {value: null}, lengthValue = {value: 0};
1971         value.getProperties(listValue, lengthValue);
1972         for (var i = 0; i < lengthValue.value; ++i)
1973         {
1974             var prop = listValue.value[i];
1975             try {
1976                 var name = unwrapIValue(prop.name);
1977                 FBTrace.sysout(i+"]"+name+"="+unwrapIValue(prop.value));
1978             }
1979             catch (e)
1980             {
1981                 FBTrace.sysout(i+"]"+e);
1982             }
1983         }
1984     },
1985 
1986     reFindDebugger: function(frame, debuggr)
1987     {
1988         var frameScopeRoot = getFrameScopeRoot(frame);
1989         if (frameScopeRoot && debuggr.supportsGlobal(frameScopeRoot, frame)) return debuggr;
1990 
1991         if (FBTrace.DBG_FBS_FINDDEBUGGER)
1992             FBTrace.sysout("reFindDebugger debuggr "+debuggr.debuggerName+" does not support frameScopeRoot "+frameScopeRoot, frameScopeRoot);
1993         return null;
1994     },
1995 
1996     discardRecursionFrames: function(frame)
1997     {
1998         var i = 0;
1999         var rest = 0;
2000         var mark = frame;  // a in abcabcabcdef
2001         var point = frame;
2002         while (point = point.callingFrame)
2003         {
2004             i++;
2005             if (point.script.tag == mark.script.tag) // then we found a repeating caller abcabcdef
2006             {
2007                 mark = point;
2008                 rest = i;
2009             }
2010         }
2011         // here point is null and mark is the last repeater, abcdef
2012         if (FBTrace.DBG_FBS_ERRORS)
2013             FBTrace.sysout("fbs.discardRecursionFrames dropped "+rest+" of "+i, mark);
2014         return mark;
2015     },
2016 
2017     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2018     // jsd breakpoints are on a PC in a jsdIScript
2019     // Users breakpoint on a line of source
2020     // Because test.js can be included multiple times, the URL+line number from the UI is not unique.
2021     // sourcefile.href != script.fileName, generally script.fileName cannot be used.
2022     // If the source is compiled, then we have zero, one, or more jsdIScripts on a line.
2023     //    If zero, cannot break at that line
2024     //    If one, set a jsd breakpoint
2025     //    If more than one, set jsd breakpoint on each script
2026     // Else we know that the source will be compiled before it is run.
2027     //    Save the sourceFile.href+line and set the jsd breakpoint when we compile
2028     //    Venkman called these "future" breakpoints
2029     //    We cannot prevent future breakpoints on lines that have no script.  Break onCreate with error?
2030 
2031     addBreakpoint: function(type, sourceFile, lineNo, props, debuggr)
2032     {
2033         var url = sourceFile.href;
2034         var bp = this.findBreakpoint(url, lineNo);
2035         if (bp && bp.type & type)
2036             return null;
2037 
2038         if (bp)
2039         {
2040             bp.type |= type;
2041 
2042             if (debuggr)
2043                 bp.debugger = debuggr;
2044             else
2045             {
2046                 if (FBTrace.DBG_FBS_BP)
2047                     FBTrace.sysout("fbs.addBreakpoint with no debuggr:\n");
2048             }
2049         }
2050         else
2051         {
2052             bp = this.recordBreakpoint(type, url, lineNo, debuggr, props);
2053             fbs.setJSDBreakpoint(sourceFile, bp);
2054         }
2055         if (FBTrace.DBG_FBS_BP) FBTrace.sysout("addBreakpoint for "+url, [bp, sourceFile]);
2056         return bp;
2057     },
2058 
2059     recordBreakpoint: function(type, url, lineNo, debuggr, props)
2060     {
2061         var urlBreakpoints = breakpoints[url];
2062         if (!urlBreakpoints)
2063             breakpoints[url] = urlBreakpoints = [];
2064 
2065         var bp = {type: type, href: url, lineNo: lineNo, disabled: 0,
2066             debugger: debuggr,
2067             condition: "", onTrue: true, hitCount: -1, hit: 0};
2068         if (props)
2069         {
2070             bp.condition = props.condition;
2071             bp.onTrue = props.onTrue;
2072             bp.hitCount = props.hitCount;
2073             if (bp.condition || bp.hitCount > 0)
2074                 ++conditionCount;
2075             if(props.disabled)
2076             {
2077                 bp.disabled |= BP_NORMAL;
2078                 ++disabledCount;
2079             }
2080         }
2081         urlBreakpoints.push(bp);
2082         ++breakpointCount;
2083         return bp;
2084     },
2085 
2086     removeBreakpoint: function(type, url, lineNo)
2087     {
2088         if (FBTrace.DBG_FBS_BP) FBTrace.sysout("removeBreakpoint for url= "+url);
2089 
2090         var urlBreakpoints = breakpoints[url];
2091         if (!urlBreakpoints)
2092             return false;
2093 
2094         if (FBTrace.DBG_FBS_BP) FBTrace.sysout("removeBreakpoint need to check bps="+urlBreakpoints.length);
2095 
2096         for (var i = 0; i < urlBreakpoints.length; ++i)
2097         {
2098             var bp = urlBreakpoints[i];
2099             if (FBTrace.DBG_FBS_BP) FBTrace.sysout("removeBreakpoint checking bp.lineNo vs lineNo="+bp.lineNo+" vs "+lineNo);
2100 
2101             if (bp.lineNo == lineNo)
2102             {
2103                 bp.type &= ~type;
2104                 if (!bp.type)
2105                 {
2106                     if (bp.scriptsWithBreakpoint)
2107                     {
2108                         for (var j = 0; j < bp.scriptsWithBreakpoint.length; j++)
2109                         {
2110                             var script = bp.scriptsWithBreakpoint[j];
2111                             if (script && script.isValid)
2112                             {
2113                                 try
2114                                 {
2115                                     script.clearBreakpoint(bp.pc[j]);
2116                                     if (FBTrace.DBG_FBS_BP) FBTrace.sysout("removeBreakpoint in tag="+script.tag+" at "+lineNo+"@"+url);
2117                                 }
2118                                 catch (exc)
2119                                 {
2120                                     FBTrace.sysout("Firebug service failed to remove breakpoint in "+script.tag+" at lineNo="+lineNo+" pcmap:"+bp.pcmap);
2121                                 }
2122                             }
2123                         }
2124                     }
2125                     // else this was a future breakpoint that never hit or a script that was GCed
2126 
2127                     urlBreakpoints.splice(i, 1);
2128                     --breakpointCount;
2129 
2130                     if (bp.disabled)
2131                         --disabledCount;
2132 
2133                     if (bp.condition || bp.hitCount > 0)
2134                     {
2135                         --conditionCount;
2136                     }
2137 
2138 
2139                     if (!urlBreakpoints.length)
2140                         delete breakpoints[url];
2141 
2142                 }
2143                 return bp;
2144             }
2145         }
2146 
2147         return false;
2148     },
2149 
2150     findBreakpoint: function(url, lineNo)
2151     {
2152         var urlBreakpoints = breakpoints[url];
2153         if (urlBreakpoints)
2154         {
2155             for (var i =