1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/chrome/firefox",
  7     "arch/compilationunit",
  8     "firebug/lib/xpcom",
  9     "firebug/chrome/reps",
 10     "firebug/lib/locale",
 11     "firebug/lib/wrapper",
 12     "firebug/lib/url",
 13     "firebug/js/sourceLink",
 14     "firebug/js/stackFrame",
 15     "firebug/lib/css",
 16     "firebug/chrome/window",
 17     "firebug/lib/string",
 18     "firebug/lib/array",
 19     "firebug/trace/debug",
 20     "firebug/js/fbs",
 21     "firebug/lib/events",
 22     "firebug/console/errors",
 23 ],
 24 function(Obj, Firebug, Firefox, CompilationUnit, Xpcom, FirebugReps, Locale,
 25     Wrapper, Url, SourceLink, StackFrame, Css, Win, Str, Arr, Debug, FBS, Events) {
 26 
 27 // ********************************************************************************************* //
 28 // Constants
 29 
 30 const Cc = Components.classes;
 31 const Ci = Components.interfaces;
 32 const jsdIScript = Ci.jsdIScript;
 33 const jsdIStackFrame = Ci.jsdIStackFrame;
 34 const jsdIExecutionHook = Ci.jsdIExecutionHook;
 35 const nsISupports = Ci.nsISupports;
 36 const nsICryptoHash = Ci.nsICryptoHash;
 37 const nsIURI = Ci.nsIURI;
 38 
 39 const PCMAP_SOURCETEXT = jsdIScript.PCMAP_SOURCETEXT;
 40 const PCMAP_PRETTYPRINT = jsdIScript.PCMAP_PRETTYPRINT;
 41 
 42 const RETURN_VALUE = jsdIExecutionHook.RETURN_RET_WITH_VAL;
 43 const RETURN_THROW_WITH_VAL = jsdIExecutionHook.RETURN_THROW_WITH_VAL;
 44 const RETURN_CONTINUE = jsdIExecutionHook.RETURN_CONTINUE;
 45 const RETURN_CONTINUE_THROW = jsdIExecutionHook.RETURN_CONTINUE_THROW;
 46 const RETURN_ABORT = jsdIExecutionHook.RETURN_ABORT;
 47 const RETURN_HOOK_ERROR = jsdIExecutionHook.RETURN_HOOK_ERROR;
 48 
 49 const TYPE_THROW = jsdIExecutionHook.TYPE_THROW;
 50 const TYPE_DEBUGGER_KEYWORD = jsdIExecutionHook.TYPE_DEBUGGER_KEYWORD;
 51 
 52 const STEP_OVER = 1;
 53 const STEP_INTO = 2;
 54 const STEP_OUT = 3;
 55 
 56 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 57 
 58 const tooltipTimeout = 300;
 59 
 60 const reEval =  /\s*eval\s*\(([^)]*)\)/m;        // eval ( $1 )
 61 const reHTM = /\.[hH][tT][mM]/;
 62 const reFunction = /\s*Function\s*\(([^)]*)\)/m;
 63 const reTooMuchRecursion = /too\smuch\srecursion/;
 64 
 65 var jsd = Cc["@mozilla.org/js/jsd/debugger-service;1"].getService(Ci.jsdIDebuggerService);
 66 
 67 // ************************************************************************************************
 68 
 69 Firebug.Debugger = Obj.extend(Firebug.ActivableModule,
 70 {
 71     dispatchName: "debugger",
 72     fbs: FBS, // access to firebug-service in chromebug under browser.xul.Dom.Firebug.Debugger.fbs
 73 
 74     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 75     // Debugging
 76 
 77     // moz
 78     hasValidStack: function(context)
 79     {
 80         return context.stopped && context.currentFrame.isValid;
 81     },
 82 
 83     // on bti, method of stack
 84     evaluate: function(js, context, scope)  // TODO remote: move to backend, proxy to front
 85     {
 86         var frame = context.currentFrame;
 87         if (!frame)
 88             return;
 89 
 90         frame.scope.refresh(); // XXX what's this do?
 91 
 92         var result = {};
 93         var scriptToEval = js;
 94 
 95         // This seem to be safe; eval'ing a getter property in content that tries to
 96         // be evil and get Components.classes results in a permission denied error.
 97         var ok = frame.eval(scriptToEval, "", 1, result);
 98 
 99         var value = Wrapper.unwrapIValue(result.value, Firebug.viewChrome);
100         if (ok)
101             return value;
102         else
103             throw value;
104     },
105 
106     // on bti (not called in firebug source)
107     evaluateInCallingFrame: function(js, fileName, lineNo)
108     {
109         return this.halt(function evalInFrame(frame)
110         {
111             FBTrace.sysout("evaluateInCallingFrame " + js + " fileName: " +
112                 frame.script.fileName + " stack: " + StackFrame.getJSDStackDump(frame));
113 
114             var result = {};
115             var ok = frame.eval(js, fileName, lineNo, result);
116             var value = Wrapper.unwrapIValue(result.value, Firebug.viewChrome);
117 
118             if (ok)
119                 return value;
120             else
121                 throw value;
122         });
123     },
124 
125     /**
126      * Used by autocomplete in commandLine
127      * @return array of locally visible property names for each scope we are in
128      */
129     getCurrentFrameKeys: function(context)  // TODO remote, on bti
130     {
131         // return is safe
132         var globals = Arr.keys(Wrapper.getContentView(context.getGlobalScope()));
133         if (context.currentFrame)
134             return this.getFrameKeys(context.currentFrame, globals);
135 
136         return globals;
137     },
138 
139     /**
140      * private to Debugger, returns list of strings
141      */
142     getFrameKeys: function(frame, names) // moz
143     {
144         var scope = frame.scope;
145         while (scope)
146         {
147             var listValue = {value: null}, lengthValue = {value: 0};
148             scope.getProperties(listValue, lengthValue);
149 
150             for (var i=0; i<lengthValue.value; ++i)
151             {
152                 var prop = listValue.value[i];
153                 var name = Wrapper.unwrapIValue(prop.name);
154                 names.push(name);
155             }
156 
157             scope = scope.jsParent;
158         }
159 
160         return names;
161     },
162 
163     // @Deprecated  see chrome.js
164     focusWatch: function(context)  // TODO moved
165     {
166         return Firebug.chrome.focusWatch(context);
167     },
168 
169     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
170     // Private to Debugger
171 
172     // moz
173     beginInternalOperation: function() // stop debugger operations like breakOnErrors
174     {
175         var state = {breakOnErrors: Firebug.breakOnErrors};
176         Firebug.breakOnErrors = false;
177         return state;
178     },
179 
180     // moz
181     endInternalOperation: function(state)  // pass back the object given by beginInternalOperation
182     {
183         Firebug.breakOnErrors = state.breakOnErrors;
184         return true;
185     },
186 
187     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
188 
189     // moz
190     halt: function(fnOfFrame)
191     {
192         if(FBTrace.DBG_BP)
193             FBTrace.sysout('debugger.halt '+fnOfFrame);
194 
195         return FBS.halt(this, fnOfFrame);
196     },
197 
198     // on bti
199     getCurrentStackTrace: function(context)
200     {
201         var trace = null;
202 
203         Firebug.Debugger.halt(function(frame)
204         {
205             if (FBTrace.DBG_STACK)
206                 FBTrace.sysout("lib.getCurrentStackTrace frame:", frame);
207 
208             trace = StackFrame.getCorrectedStackTrace(frame, context);
209 
210             if (FBTrace.DBG_STACK)
211                 FBTrace.sysout("lib.getCurrentStackTrace trace:", trace.toString().split('\n'));
212         });
213 
214         return trace;
215     },
216 
217     // Used by FBTest
218     breakAsIfDebugger: function(frame)
219     {
220         // should return 'this' but also sets this.breakContext
221         var debuggr = FBS.findDebugger(frame);
222         FBS.breakIntoDebugger(debuggr, frame, 3);
223     },
224 
225     // This URL prefix is used to skip frames from chrome URLs. Note that sometimes chrome URLs
226     // are used even in web pages, but only in rare cases so don't worry about it.
227     // Don't be specific like: chrome://firebug/ since frames coming from extensions e.g.
228     // chrome://firecookie/ wouldn't be skipped then.
229     breakNowURLPrefix: "chrome://",
230 
231     // on bti
232     breakNow: function(context)
233     {
234         Firebug.Debugger.halt(function haltAnalysis(frame)
235         {
236             if (FBTrace.DBG_UI_LOOP)
237                 FBTrace.sysout("debugger.breakNow: frame "+frame.script.fileName+" context "+
238                     context.getName(), StackFrame.getJSDStackDump(frame) );
239 
240             for (; frame && frame.isValid; frame = frame.callingFrame)
241             {
242                 var fileName = frame.script.fileName;
243                 if (!fileName)
244                     continue;
245                 else if (Str.hasPrefix(fileName, Firebug.Debugger.breakNowURLPrefix))
246                     continue;
247                 else if (fileName.indexOf("/modules/firebug-") != -1)
248                     continue;
249                 else
250                     break;
251             }
252 
253             if (frame)
254             {
255                 Firebug.Debugger.breakContext = context;
256 
257                 // I just made up a type that won't match TYPE_DEBUGGER_KEYWORD
258                 Firebug.Debugger.onBreak(frame, "halt");
259             }
260             else
261             {
262                 if (FBTrace.DBG_UI_LOOP)
263                     FBTrace.sysout("debugger.breakNow: no frame that not starting with "+
264                         Firebug.Debugger.breakNowURLPrefix);
265             }
266         });
267     },
268 
269     // moz, called by back end
270     stop: function(context, frame, type, rv)
271     {
272         if (context.stopped)
273             return RETURN_CONTINUE;
274 
275         if (!this.isAlwaysEnabled())
276             return RETURN_CONTINUE;
277 
278         if (FBTrace.DBG_UI_LOOP)
279             FBTrace.sysout("debugger.stop "+context.getName()+" frame",frame);
280 
281         // Do not break if the user is on another tab
282         if (Firefox.getCurrentURI().spec !== context.window.location.toString())
283         {
284             if (FBTrace.DBG_UI_LOOP)
285             {
286                 var current = Firefox.getCurrentURI().spec;
287                 var prev = context.window.location.toString();
288                 var locations = {current: current, context: prev};
289 
290                 FBTrace.sysout("debugger.stop ERROR break is not in current window ", locations);
291             }
292             return RETURN_CONTINUE;
293         }
294 
295         context.stoppedFrame = frame;  // the frame we stopped in, don't change this elsewhere.
296         context.currentFrame = frame;  // the frame we show to user, depends on selection
297         context.stopped = true;
298 
299         var hookReturn = Firebug.connection.dispatch("onStop",[context,frame, type,rv]);
300         if ( hookReturn && hookReturn >= 0 )
301         {
302             delete context.stopped;
303             delete context.stoppedFrame;
304             delete context.currentFrame;
305 
306             if (FBTrace.DBG_UI_LOOP)
307             {
308                 FBTrace.sysout("debugger.stop extension vetoed stop with hookReturn " +
309                     hookReturn);
310             }
311 
312             return hookReturn;
313         }
314 
315         try
316         {
317             this.freeze(context);
318 
319             // If firebug hits a breakpoint in an event handler, which used setCapture
320             // the entire browser window is unclickable (see issue 5064)
321             context.window.document.releaseCapture();
322 
323             // We will pause here until resume is called
324             var depth = FBS.enterNestedEventLoop({
325                 onNest: Obj.bindFixed(this.startDebugging, this, context)
326             });
327 
328             // For some reason we don't always end up here
329 
330             if (FBTrace.DBG_UI_LOOP)
331                 FBTrace.sysout("debugger.stop, nesting depth:"+depth+" jsd.pauseDepth: "+
332                     jsd.pauseDepth+" context:"+context.getName());
333         }
334         catch (exc)
335         {
336             // Just ignore exceptions that happened while in the nested loop
337             if (FBTrace.DBG_ERRORS)
338                 FBTrace.sysout("debugger exception in nested event loop: "+exc, exc);
339             else
340                 Debug.ERROR("debugger exception in nested event loop: "+exc+"\n");
341         }
342         finally
343         {
344             this.thaw(context);
345         }
346 
347         this.stopDebugging(context);
348 
349         Firebug.connection.dispatch("onResume",[context]);
350 
351         if (context.aborted)
352         {
353             delete context.aborted;
354             return RETURN_ABORT;
355         }
356         else if (Firebug.rerun)
357         {
358             setTimeout(function reExecute()
359             {
360                 var rerun = context.savedRerun = Firebug.rerun;
361                 delete Firebug.rerun;
362 
363                 if (FBTrace.DBG_UI_LOOP)
364                     FBTrace.sysout("Firebug.debugger.reExecute ", {rerun: rerun});
365 
366                 // fire the prestored script
367                 function successConsoleFunction(result, context)
368                 {
369                     if (FBTrace.DBG_UI_LOOP)
370                         FBTrace.sysout("Firebug.debugger.reExecute success", result);
371                     Firebug.connection.dispatch( "onRerunComplete", [true, result]);
372                 }
373 
374                 function exceptionFunction(result, context)
375                 {
376                     if (FBTrace.DBG_ERRORS)
377                         FBTrace.sysout("Firebug.debugger.reExecute FAILED "+result, result);
378                     Firebug.connection.dispatch( "onRerunComplete", [false, result]);
379                 }
380 
381                 Firebug.CommandLine.evaluate("window._firebug.rerunFunction()", context, null,
382                     context.window, successConsoleFunction, exceptionFunction);
383             });
384 
385             if (FBTrace.DBG_UI_LOOP)
386                 FBTrace.sysout("Firebug.debugger.reExecute return "+RETURN_HOOK_ERROR);
387 
388             return RETURN_HOOK_ERROR;
389         }
390         else
391         {
392             return RETURN_CONTINUE;
393         }
394     },
395 
396     // on bti
397     rerun: function(context)
398     {
399         if(!context.stopped)
400         {
401             FBTrace.sysout("debugger.rerun FAILS: not stopped");
402             return;
403         }
404 
405         if (Firebug.rerun)
406         {
407             FBTrace.sysout("debugger.rerun FAILS: Firebug.rerun in progress");
408             return;
409         }
410 
411         Firebug.rerun = this.getRerun(context);
412 
413         // now continue but abort the current call stack.
414         this.resume(context);  // the Firebug.rerun will signal abort stack
415     },
416 
417     // moz
418     getRerun: function(context)
419     {
420         if (FBTrace.DBG_UI_LOOP)
421                 FBTrace.sysout("debugger.rerun for "+context.getName());
422         try
423         {
424             // walk back to the oldest frame, but not top level
425             var frame = context.stoppedFrame;
426             while (frame.callingFrame && frame.callingFrame.script.functionName)
427             {
428                 frame = frame.callingFrame;
429 
430                 if (frame.script.functionName == "_firebugRerun") // re-reRun
431                 {
432                     if (FBTrace.DBG_UI_LOOP)
433                         FBTrace.sysout("getRerun re-rerun ", context.savedRerun);
434                     return context.savedRerun;
435                 }
436             }
437 
438             // In this oldest frame we have element.onclick(event) or window.foo()
439             // We want to cause the page to run this again after we abort this call stack.
440             function getStoreRerunInfoScript(fnName)
441             {
442                 var str = "if (!window._firebug)window._firebug={};\n";
443                 str += "window._firebug.rerunThis = this;\n";
444                 str += "window._firebug.rerunArgs = [];\n"
445                 str += "if (arguments && arguments.length) for (var i = 0; i < arguments.length; i++) window._firebug.rerunArgs.push(arguments[i]);\n"
446                 str += "window._firebug.rerunFunctionName = "+fnName+";\n"
447                 str +="window._firebug.rerunFunction = function _firebugRerun() { "+fnName+".apply(window._firebug.rerunThis, window._firebug.rerunArgs); }"
448                 return str;
449             }
450 
451             var rerun = {};
452 
453             var fnName = StackFrame.getFunctionName(frame.script, context, frame, true);
454             rerun.script = getStoreRerunInfoScript(fnName);
455             var jsdFunctionName = frame.script.functionName;
456 
457             // now run the script that stores the rerun info in the page
458             var result = {};
459             var ok = frame.eval(rerun.script, context.window.location + "/RerunScript", 1, result);
460 
461             // If the eval goes off somewhere wacky, the frame may be invalid by this point.
462             if (FBTrace.DBG_UI_LOOP)
463                 FBTrace.sysout("debugger.rerun "+ok+" and result: "+result+" for "+context.getName(),
464                     {result: result, rerun: rerun, functionName: jsdFunctionName});
465         }
466         catch(exc)
467         {
468             if (FBTrace.DBG_ERRORS)
469                 FBTrace.sysout("debugger.rerun FAILS for "+context.getName()+" because "+exc,
470                     {exc:exc, rerun: rerun});
471         }
472 
473         return rerun;
474     },
475 
476     // bti
477     resume: function(context)
478     {
479         if (FBTrace.DBG_UI_LOOP)
480             FBTrace.sysout("debugger.resume, context.stopped:"+context.stopped+"\n");
481 
482         // this will cause us to return to just after the enterNestedEventLoop call
483         var depth = FBS.exitNestedEventLoop();
484 
485 
486         if (FBTrace.DBG_UI_LOOP)
487             FBTrace.sysout("debugger.resume, depth:"+depth+"\n");
488     },
489 
490     // bti
491     abort: function(context)
492     {
493         if (context.stopped)
494         {
495             context.aborted = true;
496             this.thaw(context);
497             this.resume(context);
498             FBS.unPause(true);
499         }
500     },
501 
502     // bti
503     stepOver: function(context)
504     {
505         if (!context.stoppedFrame || !context.stoppedFrame.isValid)
506             return;
507 
508         FBS.step(STEP_OVER, context, this);
509         this.resume(context);
510     },
511 
512     stepInto: function(context)
513     {
514         if (!context.stoppedFrame || !context.stoppedFrame.isValid)
515             return;
516 
517         FBS.step(STEP_INTO, context, this);
518         this.resume(context);
519     },
520 
521     stepOut: function(context)
522     {
523         if (!context.stoppedFrame || !context.stoppedFrame.isValid)
524             return;
525 
526         FBS.step(STEP_OUT, context, this);
527         this.resume(context);
528     },
529 
530     suspend: function(context)
531     {
532         if (context.stopped)
533             return;
534 
535         FBS.suspend(this, context);
536     },
537 
538     unSuspend: function(context)
539     {
540         FBS.stopStepping(null, context);  // TODO per context
541         FBS.cancelBreakOnNextCall(this, context)
542     },
543 
544     runUntil: function(context, compilationUnit, lineNo)
545     {
546         if (FBTrace.DBG_UI_LOOP)
547             FBTrace.sysout("runUntil "+lineNo+" @"+compilationUnit);
548 
549         if (!context.stoppedFrame || !context.stoppedFrame.isValid)
550             return;
551 
552         var sourceFile = compilationUnit.sourceFile;
553         FBS.runUntil(compilationUnit.sourceFile, lineNo, context.stoppedFrame, this);
554         this.resume(context);
555     },
556 
557     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
558     // moz
559 
560     freeze: function(context)
561     {
562         var executionContext = context.stoppedFrame.executionContext;
563         try {
564             executionContext.scriptsEnabled = false;
565             this.suppressEventHandling(context);
566             context.isFrozen = true;
567 
568             // https://developer.mozilla.org/en/XUL_Tutorial/Focus_and_Selection#Getting_the_currently_focused_element
569             if (context.window && context.window.document.commandDispatcher)
570             {
571                 context.saveFocus = context.window.document.commandDispatcher.focusedElement;
572                 if (context.saveFocus && !context.discardBlurEvents)
573                 {
574                     context.discardBlurEvents = function blurDiscarder(event)
575                     {
576                         if (!context.saveFocus)
577                         {
578                             Events.removeEventListener(context.window, "blur",
579                                 context.discardBlurEvents, true);
580                             delete context.discardBlurEvents;
581                         }
582 
583                         if (FBTrace.DBG_UI_LOOP)
584                         {
585                             FBTrace.sysout("debugger.freeze discard blur event " +
586                                 context.saveFocus + " while focus is " +
587                                 context.window.document.commandDispatcher.focusedElement,
588                                 event);
589                         }
590 
591                         event.preventDefault();
592                         event.stopPropagation();
593                     },
594 
595                     Events.addEventListener(context.window, "blur",
596                         context.discardBlurEvents, true);
597                 }
598             }
599 
600             if (FBTrace.DBG_UI_LOOP)
601             {
602                 FBTrace.sysout("debugger.freeze context.saveFocus "+context.saveFocus,
603                     context.saveFocus);
604 
605                 FBTrace.sysout("debugger.freeze try to disable scripts "+
606                     (context.eventSuppressor?"and events":"but not events")+" in "+
607                     context.getName()+" executionContext.tag "+executionContext.tag+
608                     ".scriptsEnabled: "+executionContext.scriptsEnabled);
609             }
610         }
611         catch (exc)
612         {
613             // This attribute is only valid for contexts which implement nsIScriptContext.
614             if (FBTrace.DBG_UI_LOOP)
615                 FBTrace.sysout("debugger.freeze, freeze exception " + exc + " in " +
616                     context.getName(), exc);
617         }
618     },
619 
620     suppressEventHandling: function(context)
621     {
622         if (context.window instanceof Ci.nsIInterfaceRequestor)
623         {
624             context.eventSuppressor = context.window.getInterface(Ci.nsIDOMWindowUtils);
625             if (context.eventSuppressor)
626                 context.eventSuppressor.suppressEventHandling(true);
627         }
628     },
629 
630     thaw: function(context)
631     {
632         try {
633             if (context.isFrozen)
634                 delete context.isFrozen;
635             else
636                 return; // bail, we did not freeze this context
637 
638                 var executionContext = context.stoppedFrame.executionContext;
639             if (executionContext.isValid)
640             {
641                 this.unsuppressEventHandling(context);
642 
643                 // Before we release JS, put the focus back
644                 if (context.saveFocus)
645                 {
646                     context.window.focus();
647                     context.saveFocus.focus();
648                     delete context.saveFocus;
649 
650                     if (FBTrace.DBG_UI_LOOP)
651                     {
652                         var nowFocused = context.window.document.commandDispatcher ?
653                             context.window.document.commandDispatcher.focusedElement : null;
654                         FBTrace.sysout("debugger.thaw context.saveFocus "+context.saveFocus+
655                             " vs "+nowFocused, context.saveFocus);
656                     }
657                 }
658 
659                 executionContext.scriptsEnabled = true;
660             }
661             else
662             {
663                 if (FBTrace.DBG_UI_LOOP)
664                     FBTrace.sysout("debugger.thaw "+executionContext.tag+
665                         " executionContext is not valid");
666             }
667 
668             if (FBTrace.DBG_UI_LOOP)
669                 FBTrace.sysout("debugger.thaw try to enable scripts " +
670                     (context.eventSuppressor?"with events suppressed":"events enabled")+
671                     " in "+context.getName()+" executionContext.tag "+executionContext.tag+
672                     ".scriptsEnabled: "+executionContext.scriptsEnabled);
673         }
674         catch (exc)
675         {
676             if (FBTrace.DBG_UI_LOOP)
677                 FBTrace.sysout("debugger.stop, scriptsEnabled = true exception:", exc);
678         }
679     },
680 
681     unsuppressEventHandling: function(context)
682     {
683         if (context.eventSuppressor)
684         {
685             context.eventSuppressor.suppressEventHandling(false);
686             delete context.eventSuppressor;
687         }
688     },
689 
690     // on bti
691     toggleFreezeWindow: function(context)
692     {
693         // then we need to break into debugger to get the executionContext
694         if (!context.stopped)
695         {
696             Firebug.Debugger.halt(function grabContext(frame)
697             {
698                 context.stoppedFrame = frame;
699                 Firebug.Debugger.doToggleFreezeWindow(context);
700                 delete context.stoppedFrame;
701             });
702 
703             Firebug.Debugger.suspend(context);
704         }
705         else
706         {
707             Firebug.Debugger.doToggleFreezeWindow(context);
708         }
709     },
710 
711     // moz
712     doToggleFreezeWindow: function(context)
713     {
714         if (context.isFrozen)
715             Firebug.Debugger.unsuppressEventHandling(context);
716         else
717             Firebug.Debugger.suppressEventHandling(context);
718     },
719 
720     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
721     // Breakpoints
722 
723     setBreakpoint: function(sourceFile, lineNo)  // TODO: arg should be url
724     {
725         if (sourceFile instanceof CompilationUnit)
726             sourceFile = sourceFile.sourceFile;  // see HACK in tabContext
727         FBS.setBreakpoint(sourceFile, lineNo, null, Firebug.Debugger);
728     },
729 
730     clearBreakpoint: function(sourceFile, lineNo)
731     {
732         if (sourceFile instanceof CompilationUnit)
733             sourceFile = sourceFile.sourceFile;  // see HACK in tabContext
734         FBS.clearBreakpoint(sourceFile.href, lineNo);
735     },
736 
737     setErrorBreakpoint: function(compilationUnit, line)
738     {
739         FBS.setErrorBreakpoint(compilationUnit.sourceFile, line, Firebug.Debugger);
740     },
741 
742     clearErrorBreakpoint: function(compilationUnit, line)
743     {
744         FBS.clearErrorBreakpoint(compilationUnit.getURL(), line, Firebug.Debugger);
745     },
746 
747     // Called by bti browser.clearAllBreakpoints
748     clearAllBreakpoints: function(context)
749     {
750         if (context)
751         {
752             var units = context.getAllCompilationUnits();
753             FBS.clearAllBreakpoints(units, Firebug.Debugger);
754             FBS.clearErrorBreakpoints(units, Firebug.Debugger);
755         }
756         else
757         {
758             // null means all urls
759             FBS.enumerateBreakpoints(null, {call: function(url, lineNo, bp)
760             {
761                 // skip breakpoints of other debuggers.
762                 if (bp.debuggerName !== Firebug.Debugger.debuggerName)
763                     return;
764 
765                 FBS.clearBreakpoint(url, lineNo);
766             }});
767 
768             // and also error breakpoints
769             FBS.enumerateErrorBreakpoints(null, {call: function(url, lineNo)
770             {
771                 FBS.clearErrorBreakpoint(url, lineNo, Firebug.Debugger);
772             }});
773         }
774     },
775 
776     enableAllBreakpoints: function(context)
777     {
778         if (FBTrace.DBG_BP)
779             FBTrace.sysout("enableAllBreakpoints sourceFileMap:", context.sourceFileMap);
780 
781         for (var url in context.sourceFileMap)
782         {
783             FBS.enumerateBreakpoints(url, {call: function(url, lineNo)
784             {
785                 FBS.enableBreakpoint(url, lineNo);
786             }});
787         }
788     },
789 
790     disableAllBreakpoints: function(context)
791     {
792         for (var url in context.sourceFileMap)
793         {
794             FBS.enumerateBreakpoints(url, {call: function(url, lineNo)
795             {
796                 FBS.disableBreakpoint(url, lineNo);
797             }});
798         }
799     },
800 
801     getBreakpointCount: function(context)
802     {
803         var count = 0;
804         for (var url in context.sourceFileMap)
805         {
806             FBS.enumerateBreakpoints(url,
807             {
808                 call: function(url, lineNo)
809                 {
810                     ++count;
811                 }
812             });
813 
814             FBS.enumerateErrorBreakpoints(url,
815             {
816                 call: function(url, lineNo)
817                 {
818                     ++count;
819                 }
820             });
821         }
822         return count;
823     },
824 
825     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
826     // Debugging and monitoring
827 
828     traceAll: function(context)
829     {
830         FBS.traceAll(Firebug.SourceFile.sourceURLsAsArray(context), this);
831     },
832 
833     untraceAll: function(context)
834     {
835         FBS.untraceAll(this);
836     },
837 
838     monitorFunction: function(fn, mode)
839     {
840         if (typeof(fn) == "function" || fn instanceof Function)
841         {
842             var script = Firebug.SourceFile.findScriptForFunctionInContext(
843                 Firebug.currentContext, fn);
844 
845             if (script)
846             {
847                 this.monitorScript(fn, script, mode);
848             }
849             else
850             {
851                 Firebug.Console.logFormatted(
852                     ["Firebug unable to locate jsdIScript for function", fn],
853                     Firebug.currentContext, "info");
854             }
855         }
856         else
857         {
858             Firebug.Console.logFormatted(
859                 ["Firebug.Debugger.monitorFunction requires a function", fn],
860                 Firebug.currentContext, "info");
861         }
862     },
863 
864     unmonitorFunction: function(fn, mode)
865     {
866         if (typeof(fn) == "function" || fn instanceof Function)
867         {
868             var script = Firebug.SourceFile.findScriptForFunctionInContext(
869                 Firebug.currentContext, fn);
870 
871             if (script)
872                 this.unmonitorScript(fn, script, mode);
873         }
874     },
875 
876     monitorScript: function(fn, script, mode)
877     {
878         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(
879             Firebug.currentContext, script);
880 
881         if (scriptInfo)
882         {
883             if (mode == "debug")
884                 Firebug.Debugger.setBreakpoint(scriptInfo.sourceFile, scriptInfo.lineNo);
885             else if (mode == "monitor")
886                 FBS.monitor(scriptInfo.sourceFile, scriptInfo.lineNo, Firebug.Debugger);
887         }
888     },
889 
890     unmonitorScript: function(fn, script, mode)
891     {
892         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(
893             Firebug.currentContext, script);
894 
895         if (scriptInfo)
896         {
897             if (mode == "debug")
898                 this.clearBreakpoint(scriptInfo.sourceFile, scriptInfo.lineNo);
899             else if (mode == "monitor")
900                 FBS.unmonitor(scriptInfo.sourceFile.href, scriptInfo.lineNo);
901         }
902     },
903 
904     traceCalls: function(context, fn)
905     {
906         if (typeof(fn) == "function" || fn instanceof Function)
907         {
908             var script = Firebug.SourceFile.findScriptForFunctionInContext(context, fn);
909             if (script)
910                 this.traceScriptCalls(context, script);
911             else
912             {
913                 if (FBTrace.DBG_ERRORS)
914                     FBTrace.sysout("debugger.traceCalls no script found for "+fn, fn);
915             }
916         }
917     },
918 
919     untraceCalls: function(context, fn)
920     {
921         if (typeof(fn) == "function" || fn instanceof Function)
922         {
923             var script = Firebug.SourceFile.findScriptForFunctionInContext(context, fn);
924             if (script)
925                 this.untraceScriptCalls(context, script);
926         }
927     },
928 
929     traceScriptCalls: function(context, script)
930     {
931         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(context, script);
932         if (scriptInfo)
933             FBS.traceCalls(scriptInfo.sourceFile, scriptInfo.lineNo, Firebug.Debugger);
934     },
935 
936     untraceScriptCalls: function(context, script)
937     {
938         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(context, script);
939         if (scriptInfo)
940             FBS.untraceCalls(scriptInfo.sourceFile, scriptInfo.lineNo, Firebug.Debugger);
941     },
942 
943     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
944     // UI Stuff
945 
946     /*
947      * Called when a nestedEventLoop begins
948      */
949     startDebugging: function(context)
950     {
951         if (FBTrace.DBG_UI_LOOP)
952             FBTrace.sysout("Firebug.Debugger startDebugging enter context.stopped:" +
953                 context.stopped + " for context: " + context.getName());
954 
955         try
956         {
957             FBS.lockDebugger();
958 
959             context.executingSourceFile =
960                 Firebug.SourceFile.getSourceFileByScript(context, context.stoppedFrame.script);
961 
962             // bail out, we don't want the user stuck in debug with out source.
963             if (!context.executingSourceFile)
964             {
965                 if (FBTrace.DBG_UI_LOOP)
966                     FBTrace.sysout("startDebugging resuming, no sourceFile for "+
967                         context.stoppedFrame.script.fileName,
968                         context.stoppedFrame.script.functionSource);
969 
970                 this.resume(context);
971                 return;
972             }
973 
974             // Make Firebug.currentContext = context and sync the UI
975             if (context != Firebug.currentContext)
976                 Firebug.selectContext(context);
977 
978         }
979         catch(exc)
980         {
981             if (FBTrace.DBG_ERRORS)
982                 FBTrace.sysout("Resuming debugger: error during debugging loop: "+exc, exc);
983 
984             Firebug.Console.log("Resuming debugger: error during debugging loop: "+exc);
985 
986             this.resume(context);
987         }
988 
989         var frame = StackFrame.getStackFrame(context.stoppedFrame, context);
990         Firebug.connection.dispatch( "onStartDebugging", [context, frame]);
991 
992         if (FBTrace.DBG_UI_LOOP)
993             FBTrace.sysout("startDebugging exit context.stopped:" + context.stopped +
994                 " for context: " + context.getName());
995     },
996 
997     /**
998      * Called in the main event loop, from jsd, after we have exited the nested event loop
999      */
1000     stopDebugging: function(context)
1001     {
1002         if (FBTrace.DBG_UI_LOOP)
1003             FBTrace.sysout("stopDebugging enter context: " + context.getName());
1004 
1005         try
1006         {
1007             FBS.unlockDebugger();
1008 
1009             // If the user reloads the page while the debugger is stopped, then
1010             // the current context will be destroyed just before
1011             if (context && !context.aborted)
1012             {
1013                 delete context.stopped;
1014                 delete context.stoppedFrame;
1015                 delete context.currentFrame;
1016                 context.executingSourceFile = null;
1017                 delete context.breakLineNumber;
1018 
1019                 Firebug.connection.dispatch( "onStopDebugging", [context]);
1020 
1021             }
1022             else
1023             {
1024                 if (FBTrace.DBG_UI_LOOP)
1025                     FBTrace.sysout("debugger.stopDebugging else "+context.getName()+" "+
1026                         Win.safeGetWindowLocation(context.window));
1027             }
1028         }
1029         catch (exc)
1030         {
1031             if (FBTrace.DBG_UI_LOOP)
1032                 FBTrace.sysout("debugger.stopDebugging FAILS", exc);
1033 
1034             // If the window is closed while the debugger is stopped,
1035             // then all hell will break loose here
1036             Debug.ERROR(exc);
1037         }
1038     },
1039 
1040     suspendFirebug: function()
1041     {
1042         Firebug.suspendFirebug();
1043     },
1044 
1045     resumeFirebug: function()
1046     {
1047         Firebug.resumeFirebug();
1048     },
1049 
1050     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1051 
1052     supportsWindow: function(win)
1053     {
1054         if (!this.isAlwaysEnabled())
1055             return false;
1056 
1057         var context = ((win && Firebug.TabWatcher) ?
1058             Firebug.TabWatcher.getContextByWindow(win) : null);
1059 
1060         this.breakContext = context;
1061         return !!context;
1062     },
1063 
1064     // This is called from fbs for almost all fbs operations
1065     supportsGlobal: function(frameWin)
1066     {
1067         var context = ( (frameWin && Firebug.TabWatcher) ?
1068             Firebug.TabWatcher.getContextByWindow(frameWin) : null);
1069 
1070         if (!context)
1071             return false;
1072 
1073         // otherwise we cannot be called.
1074         context.jsDebuggerCalledUs = true;
1075 
1076         if (!Firebug.Console.injector.isAttached(context, frameWin))
1077         {
1078             this.injectConsole(context, frameWin);
1079         }
1080         else
1081         {
1082             if (FBTrace.DBG_CONSOLE)
1083                 FBTrace.sysout("debugger.supportsGlobal console isAttached to "+
1084                     Win.safeGetWindowLocation(frameWin)+" in  "+context.getName());
1085         }
1086 
1087         this.breakContext = context;
1088         //FBTrace.sysout("debugger.js this.breakContext "+this.breakContext.getName());
1089         return true;
1090     },
1091 
1092     injectConsole: function(context, frameWin)
1093     {
1094         if (Firebug.Console.isAlwaysEnabled())
1095         {
1096             // This is how the console is injected ahead of JS running on the page
1097             FBS.filterConsoleInjections = true;
1098             try
1099             {
1100                 var consoleReady = Firebug.Console.isReadyElsePreparing(context, frameWin);
1101             }
1102             catch(exc)
1103             {
1104                 if (FBTrace.DBG_ERRORS)
1105                     FBTrace.sysout("debugger.supportsGlobal injectConsole FAILS: "+exc, exc);
1106             }
1107             finally
1108             {
1109                 FBS.filterConsoleInjections = false;
1110             }
1111 
1112             if (FBTrace.DBG_CONSOLE)
1113                 FBTrace.sysout("debugger.supportsGlobal injectConsole consoleReady:"+consoleReady+
1114                     " jsDebuggerCalledUs: "+context.jsDebuggerCalledUs, frameWin);
1115         }
1116         else
1117         {
1118             if (FBTrace.DBG_CONSOLE)
1119                 FBTrace.sysout("debugger.supportsGlobal injectConsole console NOT enabled ",
1120                     frameWin);
1121         }
1122     },
1123 
1124     onLock: function(state)
1125     {
1126         // XXXjoe For now, trying to see if it's ok to have multiple contexts
1127         // debugging simultaneously - otherwise we need this
1128         //if (this.context != this.debugContext)
1129         {
1130             // XXXjoe Disable step/continue buttons
1131         }
1132     },
1133 
1134     onBreak: function(frame, type)
1135     {
1136         try
1137         {
1138             var context = this.breakContext;
1139 
1140             // If the script panel is disabled, Firebug can't break (issue 4290).
1141             if (!Firebug.PanelActivation.isPanelEnabled("script"))
1142                 return RETURN_CONTINUE;
1143 
1144             if (FBTrace.DBG_BP || (!context && FBTrace.DBG_FBS_ERRORS))
1145                 FBTrace.sysout("debugger.onBreak breakContext: " +
1146                     (context ? context.getName() : " none!"), StackFrame.getJSDStackDump(frame));
1147 
1148             delete this.breakContext;
1149 
1150             if (!context)
1151                 return RETURN_CONTINUE;
1152 
1153             if (type == TYPE_DEBUGGER_KEYWORD)
1154             {
1155                 var trace = Wrapper.getContentView(context.window)._firebugStackTrace;
1156                 if (trace == "console-tracer")
1157                     return this.debuggerTracer(context, frame);
1158                 else
1159                     this.setDebuggerKeywordCause(context, frame);
1160             }
1161 
1162             return this.stop(context, frame, type);
1163         }
1164         catch (exc)
1165         {
1166             if (FBTrace.DBG_ERRORS && FBTrace.DBG_BP)
1167                 FBTrace.sysout("debugger.onBreak FAILS", exc);
1168             throw exc;
1169         }
1170     },
1171 
1172     debuggerTracer: function(context, frame)
1173     {
1174         var trace = StackFrame.getCorrectedStackTrace(frame, context);
1175         if (FBTrace.DBG_ERRORLOG)
1176             FBTrace.sysout("debugger.firebugDebuggerTracer corrected trace.frames "+
1177                 trace.frames.length, trace.frames);
1178 
1179         if (trace)
1180         {
1181             // drop the firebugDebuggerTracer and reorder
1182             //trace.frames = trace.frames.slice(1);
1183 
1184             if (FBTrace.DBG_ERRORLOG)
1185                 FBTrace.sysout("debugger.firebugDebuggerTracer dropped tracer trace.frames "+
1186                     trace.frames.length, trace.frames);
1187 
1188             // drop one frame see attachConsoleInjector
1189             //trace.frames = trace.frames.slice(1);
1190             Firebug.Console.log(trace, context, "stackTrace");
1191         }
1192 
1193         if (FBTrace.DBG_BP)
1194             FBTrace.sysout("debugger.onBreak "+(trace?"debugger trace":" debugger no trace!"));
1195 
1196         return RETURN_CONTINUE;
1197     },
1198 
1199     /**
1200      * for |debugger;| keyword offer the skip/continue dialog (optionally?)
1201      */
1202     setDebuggerKeywordCause: function(context, frame)
1203     {
1204         var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, frame.script);
1205         if (!sourceFile)
1206         {
1207             if (FBTrace.DBG_ERRORS)
1208                 FBTrace.sysout("debugger.setDebuggerKeywordCause FAILS, no sourceFile for "+
1209                     frame.script.tag+"@"+frame.script.fileName+" in "+context.getName());
1210             return;
1211         }
1212 
1213         var analyzer = sourceFile.getScriptAnalyzer(frame.script);
1214         var lineNo = analyzer.getSourceLineFromFrame(context, frame);
1215 
1216         context.breakingCause =
1217         {
1218             title: Locale.$STR("debugger keyword"),
1219             skipActionTooltip: Locale.$STR("firebug.bon.tooltip.disableDebuggerKeyword2"),
1220             message: Locale.$STR("firebug.bon.cause.disableDebuggerKeyword2"),
1221             skipAction: function addSkipperAndGo()
1222             {
1223                 // a breakpoint that never hits, but prevents debugger keyword
1224                 // (see FBS.onDebugger as well)
1225                 var bp = Firebug.Debugger.setBreakpoint(sourceFile, lineNo);
1226                 FBS.disableBreakpoint(sourceFile.href, lineNo);
1227 
1228                 if (FBTrace.DBG_BP)
1229                     FBTrace.sysout("debugger.onBreak converted to disabled bp "+sourceFile.href+
1230                         "@"+lineNo+" tag: "+frame.script.tag, bp);
1231 
1232                 Firebug.Debugger.resume(context);
1233             },
1234         };
1235     },
1236 
1237     onThrow: function(frame, rv)
1238     {
1239         // onThrow is called for throw, for catches that do not succeed,
1240         // and for functions that exceptions pass through.
1241         var context = this.breakContext;
1242         delete this.breakContext;
1243 
1244         if (!context)
1245         {
1246             if (FBTrace.DBG_BP)
1247                 FBTrace.sysout("debugger.onThrow, no context, try to get from frame\n");
1248             context = this.getContextByFrame(frame);
1249         }
1250 
1251         if (FBTrace.DBG_ERRORLOG)
1252         {
1253             var lines = [];
1254             var frames = StackFrame.getCorrectedStackTrace(frame, context).frames;
1255             for (var i=0; i<frames.length; i++)
1256                 lines.push(frames[i].line + ", " + frames[i].fn);
1257 
1258             FBTrace.sysout("debugger.onThrow context:" + (context ? context.getName() :
1259                 "undefined") + ", " + lines.join("; "), frames);
1260         }
1261 
1262         if (!context)
1263             return RETURN_CONTINUE_THROW;
1264 
1265         if (!FBS.showStackTrace)
1266             return RETURN_CONTINUE_THROW;
1267 
1268         try
1269         {
1270             var realThrow = this.isRealThrow(frame, context);
1271             if (realThrow)
1272             {
1273                 context.thrownStackTrace = StackFrame.getCorrectedStackTrace(frame, context);
1274 
1275                 if (FBTrace.DBG_BP)
1276                     FBTrace.sysout("debugger.onThrow reset context.thrownStackTrace",
1277                         context.thrownStackTrace.frames);
1278 
1279                 // xxxHonza: this could fix Issue 3276: Track Throw/Catch is not working
1280                 /*if (FBS.trackThrowCatch)
1281                 {
1282                     var object = {
1283                         errorMessage: errorObject.value.stringValue,
1284                         message: errorObject.value.stringValue,
1285                         sourceName: "",
1286                         lineNumber: -1,
1287                         sourceLine: "",
1288                         category: "javascript",
1289                         flags: 2,
1290                         exceptionFlag: 2,
1291                     };
1292 
1293                     Firebug.Errors.logScriptError(context, object, false);
1294 
1295                     context.thrownStackTrace = StackFrame.getCorrectedStackTrace(frame, context);
1296                 }*/
1297             }
1298             else
1299             {
1300                 if (FBTrace.DBG_BP)
1301                     FBTrace.sysout("debugger.onThrow not a real throw");
1302             }
1303         }
1304         catch (exc)
1305         {
1306             if (FBTrace.DBG_ERRORS)
1307                 FBTrace.sysout("onThrow FAILS: " + exc, exc);
1308         }
1309 
1310         if (Firebug.connection.dispatch("onThrow",[context, frame, rv]))
1311             return this.stop(context, frame, TYPE_THROW, rv);
1312 
1313         return RETURN_CONTINUE_THROW;
1314     },
1315 
1316     isRealThrow: function(mozFrame, context)
1317     {
1318         // Determine whether the throw was a real one, or just a rethrow of the
1319         // last exception (probably automatically inserted - which it seems
1320         // happens for every function an exception passes through - but it
1321         // could also be manual because there is no simple way to tell them
1322         // apart). A rethrow is detected when the current stack exists at the
1323         // end of the previous exception's, except that the current top-most
1324         // stack frame only has to be in the same function to match.
1325         if (!context.thrownStackTrace)
1326             return true;
1327 
1328         var trace = context.thrownStackTrace.frames;
1329         var findMozFrame = mozFrame.callingFrame, againstFrame = null;
1330         if (findMozFrame)
1331         {
1332             // Verify that the previous exception includes this frame's call
1333             // site somewhere.
1334             var findFrameSig = findMozFrame.script.tag + "." + findMozFrame.pc;
1335             for (var i=1; i<trace.length; i++)
1336             {
1337                 var preFrameSig = trace[i].signature();
1338 
1339                 if (FBTrace.DBG_ERRORS && FBTrace.DBG_STACK)
1340                 {
1341                     FBTrace.sysout("debugger.isRealThrow " + findFrameSig + "==" +
1342                         preFrameSig);
1343                 }
1344 
1345                 if (findFrameSig === preFrameSig)
1346                 {
1347                     againstFrame = trace[i-1];
1348                     break;
1349                 }
1350             }
1351 
1352             if (!againstFrame)
1353                 return true;
1354         }
1355         else
1356         {
1357             againstFrame = trace[trace.length-1];
1358         }
1359 
1360         // Verify that the current frame's function location matches what the
1361         // exception has above the matched frame.
1362         if (mozFrame.script !== againstFrame.script)
1363             return true;
1364 
1365         return false;
1366     },
1367 
1368     onMonitorScript: function(frame)
1369     {
1370         var context = this.breakContext;
1371         delete this.breakContext;
1372 
1373         if (!context)
1374             context = this.getContextByFrame(frame);
1375         if (!context)
1376             return RETURN_CONTINUE;
1377 
1378         frame = StackFrame.getStackFrame(frame, context);
1379 
1380         Firebug.connection.dispatch("onMonitorScript",[context, frame]);
1381     },
1382 
1383     onFunctionCall: function(context, frame, depth, calling)
1384     {
1385         if (!context)
1386             context = this.getContextByFrame(frame);
1387 
1388         if (!context)
1389             return RETURN_CONTINUE;
1390 
1391         frame = StackFrame.getStackFrame(frame, context);
1392 
1393         Firebug.connection.dispatch("onFunctionCall",[context, frame, depth, calling]);
1394 
1395         return context;  // returned as first arg on next call from same trace
1396     },
1397 
1398     onError: function(frame, error, hitErrorBreakpoint)
1399     {
1400         var context = this.breakContext;
1401         delete this.breakContext;
1402 
1403         // If the script panel is disabled, Firebug can't break on error.
1404         if (!Firebug.PanelActivation.isPanelEnabled("script"))
1405             return 0;
1406 
1407         try
1408         {
1409             if (FBTrace.DBG_ERRORLOG)
1410                 FBTrace.sysout("debugger.onError: "+error.errorMessage+" in "+
1411                     (context?context.getName():"no context"), error);
1412 
1413             if (reTooMuchRecursion.test(error.errorMessage))
1414                 frame = FBS.discardRecursionFrames(frame);
1415 
1416             Firebug.errorStackTrace = StackFrame.getCorrectedStackTrace(frame, context);
1417 
1418             if (FBTrace.DBG_ERRORLOG)
1419                 FBTrace.sysout("debugger.onError; break=" + Firebug.breakOnErrors +
1420                     ", errorStackTrace:", Firebug.errorStackTrace);
1421 
1422             delete context.breakingCause;
1423 
1424             if (Firebug.breakOnErrors || hitErrorBreakpoint)
1425             {
1426                 var eventOrigin = Wrapper.unwrapIValue(frame.executionContext.globalObject);
1427                 if (!eventOrigin)
1428                     return 0;
1429 
1430                 // Check if the eventOrigin (window) comes from this context.
1431                 var eventOriginIndex = -1;
1432                 for (var i=0; i<context.windows.length; i++)
1433                 {
1434                     if (Wrapper.getContentView(context.windows[i]) == eventOrigin) {
1435                         eventOriginIndex = i;
1436                         break;
1437                     }
1438                 }
1439 
1440                 // Bail out if the event that lead the error is not cause by code in this context.
1441                 if (eventOriginIndex < 0)
1442                 {
1443                     if (FBTrace.DBG_ERRORS)
1444                         FBTrace.sysout("debugger.onError; error is not from this context: (" +
1445                             eventOriginIndex + ") " + frame.script.tag+"@"+frame.script.fileName);
1446                     return 0;
1447                 }
1448 
1449                 var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, frame.script);
1450                 if (!sourceFile)
1451                 {
1452                     if (FBTrace.DBG_ERRORS)
1453                         FBTrace.sysout("debugger.breakon Errors no sourceFile for "+
1454                             frame.script.tag+"@"+frame.script.fileName);
1455                     return;
1456                 }
1457 
1458                 var analyzer = sourceFile.getScriptAnalyzer(frame.script);
1459                 var lineNo = analyzer.getSourceLineFromFrame(context, frame);
1460 
1461                 var doBreak = true;
1462                 FBS.enumerateBreakpoints(sourceFile.href, {call: function(url, line, props, scripts)
1463                 {
1464                     if (FBTrace.DBG_FBS_BP)
1465                         FBTrace.sysout("debugger.breakon Errors bp "+url+"@"+line+" scripts "+
1466                             (scripts?scripts.length:"none"));
1467 
1468                     if (line === lineNo)
1469                         doBreak = false;
1470                 }});
1471 
1472                 if (FBTrace.DBG_BP)
1473                     FBTrace.sysout("debugger.breakon Errors " + doBreak + " for " +
1474                         sourceFile.href + "@" + lineNo);
1475 
1476                 if (doBreak)
1477                 {
1478                     context.breakingCause =
1479                     {
1480                         title: Locale.$STR("Break on Error"),
1481                         message: error.message,
1482                         copyAction: Obj.bindFixed(FirebugReps.ErrorMessage.copyError,
1483                             FirebugReps.ErrorMessage, error),
1484 
1485                         skipAction: function addSkipperAndGo()
1486                         {
1487                             // a breakpoint that never hits, but prevents BON for errors
1488                             var bp = Firebug.Debugger.setBreakpoint(sourceFile, lineNo);
1489                             FBS.disableBreakpoint(sourceFile.href, lineNo);
1490 
1491                             if (FBTrace.DBG_BP)
1492                                 FBTrace.sysout("debugger.breakon Errors set "+sourceFile.href+"@"+
1493                                     lineNo+" tag: "+frame.script.tag, bp);
1494 
1495                             Firebug.Debugger.resume(context);
1496                         },
1497                     };
1498                 }
1499             }
1500         }
1501         catch (exc)
1502         {
1503             if (FBTrace.DBG_ERRORS)
1504                 FBTrace.sysout("debugger.onError getCorrectedStackTrace FAILED: "+exc, exc);
1505         }
1506 
1507         var hookReturn = Firebug.connection.dispatch("onError",[context, frame, error]);
1508 
1509         if (!context.breakingCause)
1510             return 0;
1511 
1512         if (Firebug.breakOnErrors)
1513         {
1514             // Switch of Break on Next tab lightning.
1515             var panel = context.getPanel("console", true);
1516             //Firebug.Breakpoint.updatePanelTab(panel, false);
1517 
1518             return -1;  // break
1519         }
1520 
1521         if (hookReturn)
1522             return hookReturn;
1523 
1524         return -2; /* let firebug service decide to break or not */
1525     },
1526 
1527     onUncaughtException: function(errorInfo)
1528     {
1529         var context = this.breakContext;
1530         delete this.breakContext;
1531 
1532         Firebug.Errors.logScriptError(context, errorInfo, false);
1533         return -2;
1534     },
1535 
1536     onXULScriptCreated: function(frame, outerScript, innerScriptEnumerator)
1537     {
1538         try
1539         {
1540             var context = this.breakContext;
1541             delete this.breakContext;
1542 
1543             var sourceFile = context.sourceFileMap[outerScript.fileName];
1544             if (sourceFile)
1545             {
1546                 if (FBTrace.DBG_SOURCEFILES)
1547                     FBTrace.sysout("debugger.onXULScriptCreated reuse sourcefile="+
1548                         sourceFile.toString()+" -> "+context.getName()+" ("+context.uid+")");
1549 
1550                 Firebug.SourceFile.addScriptsToSourceFile(sourceFile, null, innerScriptEnumerator);
1551             }
1552             else
1553             {
1554                 sourceFile = new Firebug.XULSourceFile(outerScript.fileName, outerScript,
1555                     innerScriptEnumerator);
1556             }
1557 
1558             this.watchSourceFile(context, sourceFile);
1559 
1560             if (FBTrace.DBG_SOURCEFILES)
1561                 FBTrace.sysout("debugger.onXULScriptCreated script.fileName="+outerScript.fileName+
1562                     " in "+context.getName()+" "+sourceFile);
1563 
1564             Firebug.connection.dispatch("onXULScriptCreated",[context, frame, sourceFile.href]);
1565             return sourceFile;
1566         }
1567         catch (e)
1568         {
1569             if (FBTrace.DBG_TOPLEVEL || FBTrace.DBG_ERRORS)
1570                 FBTrace.sysout("onXULScriptCreated FaILS "+e, e);
1571         }
1572     },
1573 
1574     onEvalScriptCreated: function(frame, outerScript, innerScripts)
1575     {
1576         try
1577         {
1578             if (FBTrace.DBG_EVAL)
1579                 FBTrace.sysout("debugger.onEvalLevelScript script.fileName=" +
1580                     outerScript.fileName);
1581 
1582             var context = this.breakContext;
1583             delete this.breakContext;
1584 
1585             var sourceFile = this.getEvalLevelSourceFile(frame, context, innerScripts);
1586 
1587             if (FBTrace.DBG_EVAL)
1588                 FBTrace.sysout("debugger.onEvalScriptCreated url="+sourceFile.href,
1589                     StackFrame.getCorrectedStackTrace(frame, context));
1590 
1591             Firebug.connection.dispatch("onEvalScriptCreated",[context, frame, sourceFile.href]);
1592             return sourceFile;
1593         }
1594         catch (e)
1595         {
1596             if (FBTrace.DBG_EVAL || FBTrace.DBG_ERRORS)
1597                 FBTrace.sysout("onEvalScriptCreated FaILS "+e, e);
1598         }
1599     },
1600 
1601     onEventScriptCreated: function(frame, outerScript, innerScripts)
1602     {
1603         if (FBTrace.DBG_EVENTS)
1604             FBTrace.sysout("debugger.onEventScriptCreated script.fileName=" +
1605                 outerScript.fileName, {outerScript: outerScript, script: frame.script});
1606 
1607         var context = this.breakContext;
1608         delete this.breakContext;
1609 
1610         var script = frame.script;
1611         var creatorURL = Url.normalizeURL(frame.script.fileName);
1612         var innerScriptArray = [];
1613 
1614         try
1615         {
1616             var source = script.functionSource;
1617 
1618             while (innerScripts.hasMoreElements())
1619             {
1620                 var inner = innerScripts.getNext();
1621                 source += "\n"+inner.functionSource;
1622                 innerScriptArray.push(inner);
1623             }
1624         }
1625         catch (exc)
1626         {
1627             /*Bug 426692 */
1628             var source = creatorURL + "/"+Obj.getUniqueId();
1629         }
1630 
1631         var lines = Str.splitLines(source);
1632 
1633         var urlDescribed = this.getDynamicURL(context,
1634             Url.normalizeURL(frame.script.fileName), lines, "event");
1635 
1636         var handlerName = outerScript.functionName;
1637         if (handlerName)
1638             var url = urlDescribed.href + '/' + handlerName;
1639         else
1640             var url = urlDescribed.href;
1641 
1642         context.sourceCache.invalidate(url);
1643         context.sourceCache.storeSplitLines(url, lines);
1644 
1645         var sourceFile = new Firebug.EventSourceFile(url, frame.script, "event:"+
1646             script.functionName+"."+script.tag, lines, new ArrayEnumerator(innerScriptArray));
1647 
1648         this.watchSourceFile(context, sourceFile);
1649 
1650         if (FBTrace.DBG_EVENTS)
1651             FBTrace.sysout("debugger.onEventScriptCreated url="+sourceFile.href+"\n");
1652 
1653         if (FBTrace.DBG_EVENTS)
1654              FBTrace.sysout("debugger.onEventScriptCreated sourceFileMap:", context.sourceFileMap);
1655 
1656         if (FBTrace.DBG_SOURCEFILES)
1657             FBTrace.sysout("debugger.onEventScriptCreated sourcefile="+sourceFile.toString()+
1658                 " -> "+context.getName()+"\n");
1659 
1660         Firebug.connection.dispatch("onEventScriptCreated",[context, frame, url]);
1661         return sourceFile;
1662     },
1663 
1664     // We just compiled a bunch of JS, eg a script tag in HTML.  We are about to run the outerScript.
1665     onTopLevelScriptCreated: function(frame, outerScript, innerScripts)
1666     {
1667         if (FBTrace.DBG_TOPLEVEL)
1668             FBTrace.sysout("debugger("+this.debuggerName+").onTopLevelScriptCreated script.fileName="+
1669                 outerScript.fileName+"\n");
1670 
1671         var context = this.breakContext;
1672         delete this.breakContext;
1673 
1674         // This is our only chance to get the linetable for the outerScript
1675         // since it will run and be GC next.
1676         var script = frame.script;
1677         var url = Url.normalizeURL(script.fileName);
1678 
1679         if (FBTrace.DBG_TOPLEVEL)
1680             FBTrace.sysout("debugger.onTopLevelScriptCreated frame.script.tag="+frame.script.tag+
1681                 " has url="+url);
1682 
1683         var isInline = false;
1684 
1685         /* The primary purpose here was to deal with http://code.google.com/p/fbug/issues/detail?id=2912
1686          * This approach could be applied to inline scripts, so I'll leave the code here until we decide.
1687         Win.iterateWindows(context.window, function isInlineScriptTag(win)
1688         {
1689             var location = Win.safeGetWindowLocation(win);
1690             if (location === url)
1691             {
1692                 isInline = true;
1693                 return isInline;
1694             }
1695         });
1696         */
1697 
1698         if (FBTrace.DBG_TOPLEVEL)
1699             FBTrace.sysout("debugger.onTopLevelScriptCreated has inLine:"+isInline+" url="+url);
1700 
1701         if (isInline) // never true see above
1702         {
1703             var href = url +"/"+context.dynamicURLIndex++;
1704             sourceFile = new Firebug.ScriptTagAppendSourceFile(href, script,
1705                 script.lineExtent, innerScripts);
1706             this.watchSourceFile(context, sourceFile);
1707             context.pendingScriptTagSourceFile = sourceFile;
1708         }
1709         else
1710         {
1711             var sourceFile = context.sourceFileMap[url];
1712 
1713             // Multiple script tags in HTML or duplicate .js file names.
1714             if (sourceFile && (sourceFile instanceof Firebug.TopLevelSourceFile))
1715             {
1716                 if (FBTrace.DBG_SOURCEFILES)
1717                     FBTrace.sysout("debugger.onTopLevelScriptCreated reuse sourcefile="+
1718                         sourceFile.toString()+" -> "+context.getName()+" ("+context.uid+")");
1719 
1720                 if (!sourceFile.outerScript || !sourceFile.outerScript.isValid)
1721                     sourceFile.outerScript = outerScript;
1722 
1723                 Firebug.SourceFile.addScriptsToSourceFile(sourceFile, outerScript,
1724                     innerScripts);
1725             }
1726             else
1727             {
1728                 sourceFile = new Firebug.TopLevelSourceFile(url, script, script.lineExtent,
1729                     innerScripts);
1730 
1731                 if (FBTrace.DBG_SOURCEFILES)
1732                     FBTrace.sysout("debugger.onTopLevelScriptCreated create sourcefile="+
1733                         sourceFile.toString()+" -> "+context.getName()+" ("+context.uid+")");
1734             }
1735 
1736             // If a script is inserted multiple times in HTML, we still need to make
1737             // sure that meta info is updated (e.g. sourceFileByTag in the context)
1738             // (see issue 4880)
1739             this.watchSourceFile(context, sourceFile);
1740         }
1741 
1742         Firebug.connection.dispatch("onTopLevelScriptCreated",[context, frame, sourceFile.href]);
1743         return sourceFile;
1744     },
1745 
1746     getContextByFrame: function(frame)
1747     {
1748         if (FBTrace.DBG_BP)
1749             FBTrace.sysout("debugger.getContextByFrame");
1750 
1751         var win = FBS.getOutermostScope(frame);
1752         return win ? Firebug.TabWatcher.getContextByWindow(win) : null;
1753     },
1754 
1755     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1756 
1757     watchSourceFile: function(context, sourceFile)
1758     {
1759         context.addSourceFile(sourceFile);  // store in the context and notify listeners
1760         //FBS.watchSourceFile(sourceFile);    // tell the service to watch this file
1761 
1762         // Update the Script panel, this script could have been loaded asynchronously
1763         // and perhaps is the only one that should be displayed (otherwise the panel
1764         // would show: No Javascript on this page). See issue 4932
1765         var panel = context.getPanel("script", true);
1766         if (panel)
1767             panel.context.invalidatePanels("script");
1768     },
1769 
1770     unwatchSourceFile: function(context, sourceFile)
1771     {
1772         //FBS.unwatchSourceFile(sourceFile);
1773         context.removeSourceFile(sourceFile);
1774     },
1775 
1776     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1777 
1778     onToggleBreakpoint: function(url, lineNo, isSet, props)
1779     {
1780         if (props.debuggerName != this.debuggerName) // then not for us
1781         {
1782             if (FBTrace.DBG_BP)
1783                 FBTrace.sysout("debugger(" + this.debuggerName +
1784                     ").onToggleBreakpoint ignoring toggle for " +
1785                     props.debuggerName + " target " + lineNo + "@" + url);
1786             return;
1787         }
1788 
1789         var found = false;
1790         for (var i = 0; i < Firebug.TabWatcher.contexts.length; ++i)
1791         {
1792             var context = Firebug.TabWatcher.contexts[i];
1793             var sourceFile = context.sourceFileMap[url];
1794             if (sourceFile)
1795             {
1796                 if (FBTrace.DBG_BP)
1797                     FBTrace.sysout("debugger(" + this.debuggerName +
1798                         ").onToggleBreakpoint found context " +
1799                         context.getName());
1800 
1801                 if (!isSet && context.dynamicURLhasBP)
1802                     this.checkDynamicURLhasBP(context);
1803 
1804                 var panel = context.getPanel("script", true);
1805                 if (!panel)
1806                     continue;
1807 
1808                 panel.context.invalidatePanels("breakpoints");
1809 
1810                 var sourceBox = panel.getSourceBoxByURL(url);
1811                 if (!sourceBox)
1812                 {
1813                     if (FBTrace.DBG_BP)
1814                         FBTrace.sysout("debugger("+this.debuggerName+").onToggleBreakpoint context "+
1815                             i+" script panel no sourcebox for url: "+url, panel.sourceBoxes);
1816                     continue;
1817                 }
1818 
1819                 var row = sourceBox.getLineNode(lineNo);
1820                 if (FBTrace.DBG_BP)
1821                     FBTrace.sysout(i+") onToggleBreakpoint getLineNode="+row+" lineNo="+lineNo+
1822                         " context:"+context.getName());
1823 
1824                 if (!row)
1825                     continue;  // we *should* only be called for lines in the viewport...
1826 
1827                 row.setAttribute("breakpoint", isSet);
1828                 if (isSet && props)
1829                 {
1830                     row.setAttribute("condition", props.condition ? "true" : "false");
1831                     row.breakpointCondition = props.condition ? props.condition : null;
1832 
1833                     if (props.condition)  // issue 1371
1834                     {
1835                         var watchPanel = this.ableWatchSidePanel(context);
1836 
1837                         if (watchPanel)
1838                         {
1839                             watchPanel.addWatch(props.condition);
1840                         }
1841                         else
1842                         {
1843                             if (FBTrace.DBG_ERRORS)
1844                                 FBTrace.sysout("onToggleBreakpoint no watch panel in context "+
1845                                     context.getName());
1846                         }
1847                     }
1848                     row.setAttribute("disabledBreakpoint", new Boolean(props.disabled).toString());
1849                 }
1850                 else
1851                 {
1852                     row.removeAttribute("condition");
1853                     if (props.condition)
1854                     {
1855                         var watchPanel = this.ableWatchSidePanel(context);
1856                         watchPanel.removeWatch(props.condition);
1857                         watchPanel.rebuild();
1858                     }
1859                     row.removeAttribute("disabledBreakpoint");
1860                 }
1861                 Firebug.connection.dispatch( "onToggleBreakpoint", [context, url, lineNo, isSet]);
1862                 found = true;
1863                 continue;
1864             }
1865         }
1866 
1867         if (FBTrace.DBG_BP && !found)
1868             FBTrace.sysout("debugger("+this.debuggerName+").onToggleBreakpoint no find context");
1869     },
1870 
1871     // xxxHonza, xxxjjb: duplicated in script.js, does it belong here?
1872     // But onToggleBreakpoint needs it.
1873     ableWatchSidePanel: function(context)
1874     {
1875         // TODO if (commandline is not active, then we should not show the new watch feature)
1876         var watchPanel = context.getPanel("watches", true);
1877         if (watchPanel)
1878             return watchPanel;
1879     },
1880 
1881     onToggleErrorBreakpoint: function(url, lineNo, isSet)
1882     {
1883         for (var i = 0; i < Firebug.TabWatcher.contexts.length; ++i)
1884         {
1885             var context = Firebug.TabWatcher.contexts[i];
1886             var panel = context.getPanel("console", true);
1887             if (panel)
1888             {
1889                 panel.context.invalidatePanels("breakpoints");
1890 
1891                 for (var row = panel.panelNode.firstChild; row; row = row.nextSibling)
1892                 {
1893                     var error = row.firstChild.repObject;
1894                     if (error instanceof FirebugReps.ErrorMessageObj && error.href == url &&
1895                         error.lineNo == lineNo)
1896                     {
1897                         if (isSet)
1898                             Css.setClass(row.firstChild, "breakForError");
1899                         else
1900                             Css.removeClass(row.firstChild, "breakForError");
1901 
1902                         Firebug.connection.dispatch( "onToggleErrorBreakpoint",
1903                             [context, url, lineNo, isSet]);
1904                     }
1905                 }
1906             }
1907         }
1908     },
1909 
1910     onToggleMonitor: function(url, lineNo, isSet)
1911     {
1912         for (var i = 0; i < Firebug.TabWatcher.contexts.length; ++i)
1913         {
1914             var panel = Firebug.TabWatcher.contexts[i].getPanel("console", true);
1915             if (panel)
1916                 panel.context.invalidatePanels("breakpoints");
1917         }
1918     },
1919 
1920     checkDynamicURLhasBP: function (context)
1921     {
1922         context.dynamicURLhasBP = false;
1923         for (var url in context.sourceFileMap)
1924         {
1925              var sourceFile = context.sourceFileMap[url];
1926                if (sourceFile.isEval() || sourceFile.isEvent())
1927                {
1928                    FBS.enumerateBreakpoints(url, {call: function setDynamicIfSet(url, lineNo)
1929                    {
1930                        context.dynamicURLhasBP = true;
1931                    }});
1932                }
1933                if (context.dynamicURLhasBP)
1934                    break;
1935         }
1936         if (FBTrace.DBG_SOURCEFILES || FBTrace.DBG_BP)
1937             FBTrace.sysout("debugger.checkDynamicURLhasBP "+context.dynamicURLhasBP);
1938     },
1939 
1940     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1941     // XXXjjb this code is not called, because I found the scheme for detecting Function
1942     // too complex. I'm leaving it here to remind us that we need to support new Function().
1943     onFunctionConstructor: function(frame, ctor_script)
1944     {
1945        try
1946         {
1947             var context = this.breakContext;
1948             delete this.breakContext;
1949 
1950             var sourceFile = this.createSourceFileForFunctionConstructor(frame, ctor_script, context);
1951 
1952             if (FBTrace.DBG_EVAL)
1953             {
1954                 FBTrace.sysout("debugger.onFunctionConstructor tag=" + ctor_script.tag +
1955                     " url=" + sourceFile.href);
1956 
1957                 FBTrace.sysout(StackFrame.traceToString(
1958                     StackFrame.getCorrectedStackTrace(frame, context)));
1959             }
1960 
1961             Firebug.connection.dispatch("onFunctionConstructor",
1962                 [context, frame, ctor_script, sourceFile.href]);
1963 
1964             return sourceFile.href;
1965         }
1966         catch(exc)
1967         {
1968             Debug.ERROR("debugger.onFunctionConstructor failed: "+exc);
1969 
1970             if (FBTrace.DBG_EVAL)
1971                 FBTrace.sysout("debugger.onFunctionConstructor failed: ",exc);
1972 
1973             return null;
1974         }
1975 
1976     },
1977 
1978     createSourceFileForFunctionConstructor: function(caller_frame, ctor_script, context)
1979     {
1980         var ctor_expr = null; // this.getConstructorExpression(caller_frame, context);
1981         if (FBTrace.DBG_EVAL)
1982             FBTrace.sysout("createSourceFileForFunctionConstructor ctor_expr:"+ctor_expr+"\n");
1983 
1984         var source;
1985         if (ctor_expr)
1986         {
1987             source = this.getEvalBody(caller_frame,
1988                 "lib.createSourceFileForFunctionConstructor ctor_expr", 1, ctor_expr);
1989         }
1990         else
1991         {
1992             source = " bah createSourceFileForFunctionConstructor"; //ctor_script.functionSource;
1993         }
1994 
1995         if (FBTrace.DBG_EVAL)
1996             FBTrace.sysout("createSourceFileForFunctionConstructor source:"+source);
1997 
1998         var url = this.getDynamicURL(context, Url.normalizeURL(caller_frame.script.fileName),
1999             source, "Function");
2000 
2001         var lines = context.sourceCache.store(url.href, source);
2002         var sourceFile = new Firebug.FunctionConstructorSourceFile(url, caller_frame.script,
2003             ctor_expr, lines.length);
2004 
2005         this.watchSourceFile(context, sourceFile);
2006 
2007         if (FBTrace.DBG_SOURCEFILES)
2008             FBTrace.sysout("debugger.onNewFunction sourcefile="+sourceFile.toString()+" -> "+
2009                 context.getName()+"\n");
2010 
2011         return sourceFile;
2012     },
2013 
2014     getConstructorExpression: function(caller_frame, context)
2015     {
2016         // We believe we are just after the ctor call.
2017         var decompiled_lineno = getLineAtPC(caller_frame, context);
2018         if (FBTrace.DBG_EVAL)
2019             FBTrace.sysout("debugger.getConstructoreExpression decompiled_lineno:"+
2020                 decompiled_lineno+"\n");
2021 
2022         // TODO place in sourceCache?
2023         var decompiled_lines = Str.splitLines(caller_frame.script.functionSource);
2024         if (FBTrace.DBG_EVAL)
2025             FBTrace.sysout("debugger.getConstructoreExpression decompiled_lines:",decompiled_lines);
2026 
2027         var candidate_line = decompiled_lines[decompiled_lineno - 1]; // zero origin
2028         if (FBTrace.DBG_EVAL)
2029             FBTrace.sysout("debugger.getConstructoreExpression candidate_line:" + candidate_line);
2030 
2031         if (candidate_line && candidate_line != null)
2032         {
2033             var m = reFunction.exec(candidate_line);
2034             if (m)
2035                 var arguments =  m[1];     // TODO Lame: need to count parens, with escapes and quotes
2036         }
2037 
2038         if (FBTrace.DBG_EVAL)
2039             FBTrace.sysout("debugger.getConstructoreExpression arguments:"+arguments+"\n");
2040 
2041         if (arguments) // need to break down commas and get last arg.
2042         {
2043             var lastComma = arguments.lastIndexOf(',');
2044             return arguments.substring(lastComma+1);  // if -1 then 0
2045         }
2046 
2047         return null;
2048     },
2049 
2050     // end of guilt trip
2051     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2052 
2053     // Called by debugger.onEval() to store eval() source.
2054     // The frame has the blank-function-name script and it is not the top frame.
2055     // The frame.script.fileName is given by spidermonkey as file of the first eval().
2056     // The frame.script.baseLineNumber is given by spidermonkey as the line of the first eval() call
2057     // The source that contains the eval() call is the source of our caller.
2058     // If our caller is a file, the source of our caller is at frame.script.baseLineNumber
2059     // If our caller is an eval, the source of our caller is TODO Check Test Case
2060     getEvalLevelSourceFile: function(frame, context, innerScripts)
2061     {
2062         var eval_expr = this.getEvalExpression(frame, context);
2063 
2064         if (FBTrace.DBG_EVAL)
2065             FBTrace.sysout("getEvalLevelSourceFile eval_expr:"+eval_expr);
2066 
2067         if (eval_expr)
2068         {
2069             var source  = this.getEvalBody(frame, "lib.getEvalLevelSourceFile.getEvalBody",
2070                 1, eval_expr);
2071             var mapType = PCMAP_SOURCETEXT;
2072         }
2073         else
2074         {
2075             var source = frame.script.functionSource; // XXXms - possible crash on OSX FF2
2076             var mapType = PCMAP_PRETTYPRINT;
2077         }
2078 
2079         var lines = Str.splitLines(source);
2080 
2081         if (FBTrace.DBG_EVAL)
2082             FBTrace.sysout("getEvalLevelSourceFile "+lines.length+ "lines, mapType:"+
2083                 ((mapType==PCMAP_SOURCETEXT)?"SOURCE":"PRETTY")+" source:"+source);
2084 
2085         var url = this.getDynamicURL(context, Url.normalizeURL(frame.script.fileName),
2086             lines, "eval");
2087 
2088         context.sourceCache.invalidate(url.href);
2089         context.sourceCache.storeSplitLines(url.href, lines);
2090 
2091         var sourceFile = new Firebug.EvalLevelSourceFile(url, frame.script, eval_expr, lines,
2092             mapType, innerScripts);
2093 
2094         this.watchSourceFile(context, sourceFile);
2095 
2096         if (FBTrace.DBG_SOURCEFILES)
2097             FBTrace.sysout("debugger.getEvalLevelSourceFile sourcefile="+sourceFile.toString()+
2098                 " -> "+context.getName()+"\n");
2099 
2100         return sourceFile;
2101     },
2102 
2103     getDynamicURL: function(context, callerURL, lines, kind)
2104     {
2105         var url = this.getURLFromLastLine(context, lines);
2106         if (url)
2107             return url;
2108 
2109         var url = this.getSequentialURL(context, callerURL, kind);
2110         if (url)
2111             return url;
2112 
2113         var url = this.getURLFromMD5(callerURL, lines, kind);
2114         if (url)
2115             return url;
2116 
2117         var url = this.getDataURLForScript(callerURL, lines);
2118         if (url)
2119             return url;
2120 
2121         return url;
2122     },
2123 
2124     getURLFromLastLine: function(context, lines)
2125     {
2126         var url = null;
2127         // Ignores any trailing whitespace in |source|
2128         const reURIinComment = /\/\/@\ssourceURL=\s*(\S*?)\s*$/m;
2129         var m = reURIinComment.exec(lines[lines.length - 1]);
2130 
2131         if (m)
2132         {
2133             // add context info to the sourceURL so eval'd sources are grouped
2134             // correctly in the source file list
2135             if (m[1] && m[1].indexOf('://') == -1)
2136             {
2137                 var loc = context.window.location;
2138                 if (m[1].charAt(0) != '/') m[1] = '/'+m[1]; // prepend leading slash if necessary
2139                 m[1] = loc.protocol + '//' + loc.host + m[1]; // prepend protocol and host
2140             }
2141 
2142             var href = new String(m[1]);
2143             href = Url.normalizeURL(href);
2144 
2145             url = {href: href, kind: "source"};
2146             if (FBTrace.DBG_SOURCEFILES)
2147                 FBTrace.sysout("debugger.getURLFromLastLine "+url.href, url);
2148         }
2149         else
2150         {
2151             if (FBTrace.DBG_SOURCEFILES)
2152                 FBTrace.sysout("debugger.getURLFromLastLine no match"+lines[lines.length - 1]);
2153         }
2154 
2155         return url;
2156     },
2157 
2158     getSequentialURL: function(context, callerURL, kind)
2159     {
2160         var url = null;
2161         if (!context.dynamicURLhasBP)
2162         {
2163             // If no breakpoints live in dynamic code then we don't need to compare
2164             // the previous and reloaded source. In that case let's use a cheap Url.
2165             var href = new String(callerURL + (kind ? "/"+kind+"/" : "/nokind/")+"seq/"
2166                 +(context.dynamicURLIndex++));
2167             url = {href: href, kind: "seq"};
2168 
2169             if (FBTrace.DBG_SOURCEFILES || isNaN(context.dynamicURLIndex))
2170                 FBTrace.sysout("debugger.getSequentialURL context:"+context.getName()+
2171                     " url:"+url.href+" index: "+context.dynamicURLIndex, url);
2172         }
2173         return url;
2174     },
2175 
2176     getURLFromMD5: function(callerURL, lines, kind)
2177     {
2178         this.hash_service.init(this.nsICryptoHash.MD5);
2179         var source = lines.join('\n'); // we could double loop, would that be any faster?
2180         byteArray = [];
2181         for (var j = 0; j < source.length; j++)
2182         {
2183             byteArray.push( source.charCodeAt(j) );
2184         }
2185         this.hash_service.update(byteArray, byteArray.length);
2186         var hash = this.hash_service.finish(true);
2187 
2188         // encoding the hash should be ok, it should be information-preserving?
2189         // Or at least reversable?
2190         var href= new String(callerURL + (kind ? "/"+kind+"/" : "/nokind/")+"MD5/" +
2191             encodeURIComponent(hash));
2192         url = {href: href, kind: "MD5"};
2193 
2194         if (FBTrace.DBG_SOURCEFILES)
2195             FBTrace.sysout("debugger.getURLFromMD5 "+url.href, url);
2196 
2197         return url;
2198     },
2199 
2200     getDataURLForScript: function(callerURL, lines)
2201     {
2202         var url = null;
2203         var href = null;
2204         if (!source)
2205         {
2206             href = "eval."+script.tag;
2207         }
2208         else
2209         {
2210             // data:text/javascript;fileName=x%2Cy.js;baseLineNumber=10,<the-url-encoded-data>
2211             href = new String("data:text/javascript;");
2212             href += "fileName="+encodeURIComponent(callerURL);
2213             var source = lines.join('\n');
2214             //url +=  ";"+ "baseLineNumber="+encodeURIComponent(script.baseLineNumber) +
2215             href +="," + encodeURIComponent(source);
2216         }
2217 
2218         url = {href:href, kind:"data"};
2219         if (FBTrace.DBG_SOURCEFILES)
2220             FBTrace.sysout("debugger.getDataURLForScript "+url.href, url);
2221 
2222         return url;
2223     },
2224 
2225     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2226 
2227     getEvalExpression: function(frame, context)
2228     {
2229         var expr = this.getEvalExpressionFromEval(frame, context);  // eval in eval
2230 
2231         return (expr) ? expr : this.getEvalExpressionFromFile(Url.normalizeURL(frame.script.fileName),
2232             frame.script.baseLineNumber, context);
2233     },
2234 
2235     getEvalExpressionFromFile: function(url, lineNo, context)
2236     {
2237         if (context && context.sourceCache)
2238         {
2239             var in_url = Url.reJavascript.exec(url);
2240             if (in_url)
2241             {
2242                 var m = reEval.exec(in_url[1]);
2243                 if (m)
2244                     return m[1];
2245                 else
2246                     return null;
2247             }
2248 
2249             var htm = reHTM.exec(url);
2250             if (htm)
2251                 lineNo = lineNo + 1; // embedded scripts seem to be off by one?  XXXjjb heuristic
2252 
2253             // Walk backwards from the first line in the function until we find the line which
2254             // matches the pattern above, which is the eval call
2255             var line = "";
2256             for (var i = 0; i < 3; ++i)
2257             {
2258                 line = context.sourceCache.getLine(url, lineNo-i) + line;
2259                 if (line && line != null)
2260                 {
2261                     var m = reEval.exec(line);
2262                     if (m)
2263                         return m[1];
2264                 }
2265             }
2266         }
2267         return null;
2268     },
2269 
2270     getEvalExpressionFromEval: function(frame, context)
2271     {
2272         var callingFrame = frame.callingFrame;
2273         var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, callingFrame.script);
2274         if (sourceFile)
2275         {
2276             if (FBTrace.DBG_EVAL)
2277             {
2278                 FBTrace.sysout("debugger.getEvalExpressionFromEval sourceFile.href=" +
2279                     sourceFile.href);
2280 
2281                 FBTrace.sysout("debugger.getEvalExpressionFromEval callingFrame.pc=" +
2282                     callingFrame.pc + " callingFrame.script.baseLineNumber=" +
2283                     callingFrame.script.baseLineNumber);
2284             }
2285 
2286             var lineNo = callingFrame.script.pcToLine(callingFrame.pc, PCMAP_SOURCETEXT);
2287             lineNo = lineNo - callingFrame.script.baseLineNumber + 1;
2288             var url  = sourceFile.href;
2289 
2290             if (FBTrace.DBG_EVAL && !context.sourceCache)
2291                 FBTrace.sysout("debugger.getEvalExpressionFromEval context.sourceCache null??\n");
2292 
2293             // Walk backwards from the first line in the function until we find the line which
2294             // matches the pattern above, which is the eval call
2295             var line = "";
2296             for (var i = 0; i < 3; ++i)
2297             {
2298                 line = context.sourceCache.getLine(url, lineNo-i) + line;
2299                 if (FBTrace.DBG_EVAL)
2300                     FBTrace.sysout("debugger.getEvalExpressionFromEval lineNo-i="+lineNo+"-"+i+"="+
2301                         (lineNo-i)+" line:"+line+"\n");
2302 
2303                 if (line && line != null)
2304                 {
2305                     var m = reEval.exec(line);
2306                     if (m)
2307                         return m[1];     // TODO Lame: need to count parens, with escapes and quotes
2308                 }
2309             }
2310         }
2311         return null;
2312     },
2313 
2314     getEvalBody: function(frame, asName, asLine, evalExpr)
2315     {
2316         if (evalExpr)
2317         {
2318             var result_src = {};
2319             var evalThis = "new String("+evalExpr+");";
2320             var evaled = frame.eval(evalThis, asName, asLine, result_src);
2321 
2322             if (evaled)
2323             {
2324                 var src = Wrapper.unwrapIValue(result_src.value);
2325                 return src+"";
2326             }
2327             else
2328             {
2329                 // Either we failed, or this was a window.eval() call
2330                 var source;
2331 
2332                 if (frame.callingFrame && !this.avoidRecursing)
2333                 {
2334                     // Try the caller, in case we are in window.eval() where the scope is global
2335                     this.avoidRecursing = true;
2336                     source = this.getEvalBody(frame.callingFrame, asName, asLine, evalExpr);
2337                     delete this.avoidRecursing;
2338 
2339                     if (FBTrace.DBG_EVAL)
2340                         FBTrace.sysout("callingFrame used to get source ", source);
2341 
2342                     return source;
2343                 }
2344 
2345                 if(evalExpr == "function(p,a,c,k,e,r")
2346                     source = "/packer/ JS compressor detected";
2347                 else
2348                     source = frame.script.functionSource;
2349 
2350                 return source+" /* !eval("+evalThis+")) */";
2351             }
2352         }
2353         else
2354         {
2355             return frame.script.functionSource; // XXXms - possible crash on OSX FF2
2356         }
2357     },
2358 
2359     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2360     // extends Module
2361 
2362     initialize: function()
2363     {
2364         Firebug.clientID = this.registerClient(Firebug.JSDebugClient);
2365         this.nsICryptoHash = Components.interfaces["nsICryptoHash"];
2366 
2367         this.debuggerName =  window.location.href +"-@-"+Obj.getUniqueId();
2368         this.toString = function() { return this.debuggerName; }
2369 
2370         if (FBTrace.DBG_INITIALIZE)
2371             FBTrace.sysout("debugger.initialize "+ this.debuggerName+" Firebug.clientID "+
2372                 Firebug.clientID);
2373 
2374         this.hash_service = Xpcom.CCSV("@mozilla.org/security/hash;1", "nsICryptoHash");
2375 
2376 
2377         this.wrappedJSObject = this;  // how we communicate with fbs
2378 
2379         this.onFunctionCall = Obj.bind(this.onFunctionCall, this);
2380 
2381         Firebug.ActivableModule.initialize.apply(this, arguments);
2382     },
2383 
2384     shutdown: function()
2385     {
2386         //Firebug.connection.unregisterTool(this.asTool);
2387 
2388         Firebug.ActivableModule.destroy.apply(this, arguments);
2389     },
2390 
2391     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2392     // BTI
2393 
2394     toolName: "script",
2395 
2396     addListener: function(listener)
2397     {
2398         Firebug.connection.addListener(listener);
2399     },
2400 
2401     removeListener: function(listener)
2402     {
2403         Firebug.connection.removeListener(listener);
2404     },
2405 
2406     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2407 
2408     /**
2409      * per-XUL window registration; this method just allows us to keep fbs in this file.
2410      * @param clientAPI an object that implements functions called by fbs for clients.
2411      */
2412     registerClient: function(clientAPI)
2413     {
2414         return FBS.registerClient(clientAPI);
2415     },
2416 
2417     unregisterClient: function(clientAPI)
2418     {
2419         FBS.unregisterClient(clientAPI);
2420     },
2421 
2422     enable: function()
2423     {
2424         if (FBTrace.DBG_ACTIVATION)
2425             FBTrace.sysout("debugger.Firebug.Debugger.enable; " + this.enabled);
2426     },
2427 
2428     disable: function()
2429     {
2430         if (FBTrace.DBG_ACTIVATION)
2431             FBTrace.sysout("debugger.Firebug.Debugger.disable; " + this.enabled);
2432     },
2433 
2434     selfObserver: {}, // empty listener, registered as observer while Script panel is enabled.
2435 
2436     initializeUI: function()
2437     {
2438         Firebug.ActivableModule.initializeUI.apply(this, arguments);
2439         this.obeyPrefs();
2440         this.filterButton = Firebug.chrome.$("fbScriptFilterMenu");  // TODO move to script.js
2441         this.filterMenuUpdate();
2442         if (FBS.isJSDActive())  // notify frontend of current state
2443             Firebug.JSDebugClient.onJSDActivate(true, 'Firebug.Debugger.initializeUI');
2444     },
2445 
2446     obeyPrefs: function()
2447     {
2448         var name = "script.enableSites";
2449         var value = Firebug.Options.get("script.enableSites");
2450         this.updateOption(name, value);
2451     },
2452 
2453     initContext: function(context, persistedState)
2454     {
2455         if (persistedState)
2456             context.dynamicURLhasBP = persistedState.dynamicURLhasBP;
2457 
2458         context.dynamicURLIndex = 1; // any dynamic urls need to be unique to the context.
2459 
2460         context.jsDebuggerCalledUs = false;
2461 
2462         Firebug.ActivableModule.initContext.apply(this, arguments);
2463     },
2464 
2465     showContext: function(browser, context)
2466     {
2467         // then context was not active during load
2468         if (context && context.loaded && !context.onLoadWindowContent)
2469             this.updateScriptFiles(context);
2470     },
2471 
2472     // scan windows for 'script' tags (only if debugger is not enabled)
2473     updateScriptFiles: function(context)
2474     {
2475         function addFile(url, scriptTagNumber, dependentURL)
2476         {
2477             var sourceFile = new Firebug.ScriptTagSourceFile(context, url, scriptTagNumber);
2478             sourceFile.dependentURL = dependentURL;
2479             context.addSourceFile(sourceFile);
2480             return true;
2481         }
2482 
2483         Win.iterateWindows(context.window, function updateEachWin(win)
2484         {
2485             if (FBTrace.DBG_SOURCEFILES)
2486                 FBTrace.sysout("updateScriptFiles Win.iterateWindows: "+win.location.href+
2487                     " documentElement: "+win.document.documentElement);
2488 
2489             if (!win.document.documentElement)
2490                 return;
2491 
2492             var url = Url.normalizeURL(win.location.href);
2493 
2494             if (url)
2495             {
2496                 if (!context.sourceFileMap.hasOwnProperty(url))
2497                 {
2498                     var URLOnly = new Firebug.NoScriptSourceFile(context, url);
2499                     context.addSourceFile(URLOnly);
2500 
2501                     if (FBTrace.DBG_SOURCEFILES)
2502                         FBTrace.sysout("updateScriptFiles created NoScriptSourceFile for URL:" +
2503                             url, URLOnly);
2504                 }
2505             }
2506 
2507             var baseUrl = win.location.href;
2508             var bases = win.document.documentElement.getElementsByTagName("base");
2509             if (bases && bases[0])
2510             {
2511                 baseUrl = bases[0].href;
2512             }
2513 
2514             var scripts = win.document.documentElement.getElementsByTagName("script");
2515             for (var i = 0; i < scripts.length; ++i)
2516             {
2517                 var scriptSrc = scripts[i].getAttribute('src'); // for XUL use attribute
2518                 var url = scriptSrc ? Url.absoluteURL(scriptSrc, baseUrl) : win.location.href;
2519                 url = Url.normalizeURL(url ? url : win.location.href);
2520                 var added = addFile(url, i, (scriptSrc?win.location.href:null));
2521 
2522                 if (FBTrace.DBG_SOURCEFILES)
2523                     FBTrace.sysout("updateScriptFiles "+(scriptSrc?"inclusion":"inline")+
2524                         " script #"+i+"/"+scripts.length+(added?" adding ":" readded ")+url+
2525                         " to context="+context.getName()+"\n");
2526             }
2527         });
2528 
2529         if (FBTrace.DBG_SOURCEFILES)
2530         {
2531             FBTrace.sysout("updateScriptFiles sourcefiles:",
2532                 Firebug.SourceFile.sourceFilesAsArray(context.sourceFileMap));
2533         }
2534     },
2535 
2536     loadedContext: function(context)
2537     {
2538         if (FBTrace.DBG_ACTIVATION)
2539             FBTrace.sysout("loadedContext needs to trigger watchpanel updates");
2540 
2541         /*
2542         var watchPanel = this.ableWatchSidePanel(context);
2543         var needNow = watchPanel && watchPanel.watches;
2544         var watchPanelState = Firebug.getPanelState({name: "watches", context: context});
2545         var needPersistent = watchPanelState && watchPanelState.watches;
2546         if (needNow || needPersistent)
2547         {
2548             Firebug.CommandLine.isReadyElsePreparing(context);
2549             if (watchPanel)
2550             {
2551                 context.setTimeout(function refreshWatchesAfterCommandLineReady()
2552                 {
2553                     watchPanel.refresh();
2554                 });
2555             }
2556         }
2557         */
2558 
2559         if (FBTrace.DBG_SOURCEFILES)
2560             FBTrace.sysout("debugger("+this.debuggerName+").loadedContext enabled on load: "+
2561                 context.onLoadWindowContent+" context.sourceFileMap", context.sourceFileMap);
2562     },
2563 
2564     // clean up the source file map in case the frame is being reloaded.
2565     unwatchWindow: function(context, win)
2566     {
2567         var scriptTags = win.document.getElementsByTagName("script");
2568         for (var i = 0; i < scriptTags.length; i++)
2569         {
2570             var src = scriptTags[i].getAttribute("src");
2571             src = src ? src : Win.safeGetWindowLocation(win);
2572 
2573             // If the src is not in the source map, try to use absolute url.
2574             if (!context.sourceFileMap[src])
2575                 src = Url.absoluteURL(src, win.location.href);
2576 
2577             delete context.sourceFileMap[src];
2578 
2579             if (FBTrace.DBG_SOURCEFILES)
2580                 FBTrace.sysout("debugger.unWatchWindow; delete sourceFileMap entry for " + src);
2581         }
2582         if (scriptTags.length > 0)
2583             context.invalidatePanels('script');
2584     },
2585 
2586     destroyContext: function(context, persistedState)
2587     {
2588         Firebug.ActivableModule.destroyContext.apply(this, arguments);
2589 
2590         if (context.stopped)
2591         {
2592             // the abort will call resume, but the nestedEventLoop would continue the load...
2593             this.abort(context);
2594         }
2595 
2596         if (persistedState)
2597         {
2598             if (context.dynamicURLhasBP)
2599                 persistedState.dynamicURLhasBP = context.dynamicURLhasBP;
2600             else
2601                 delete persistedState.dynamicURLhasBP;
2602         }
2603     },
2604 
2605     updateOption: function(name, value)
2606     {
2607         if (name == "breakOnErrors")
2608             Firebug.chrome.getElementById("cmd_firebug_breakOnErrors").setAttribute("checked", value);
2609     },
2610 
2611     getObjectByURL: function(context, url)
2612     {
2613         var sourceFile = Firebug.SourceFile.getSourceFileByHref(url, context);
2614         if (sourceFile)
2615             return new SourceLink.SourceLink(sourceFile.href, 0, "js");
2616     },
2617 
2618     shutdown: function()
2619     {
2620         this.unregisterClient(Firebug.JSDebugClient);
2621         FBS.unregisterDebugger(this);
2622     },
2623 
2624     /**
2625      * 1.3.1 safe for multiple calls
2626      */
2627     registerDebugger: function()
2628     {
2629         // Do not activate JSD on shutdown.
2630         // https://bugzilla.mozilla.org/show_bug.cgi?id=756267#c12
2631         if (Firebug.isShutdown)
2632             return;
2633 
2634         if (FBTrace.DBG_ACTIVATION)
2635             FBTrace.sysout("registerDebugger");
2636 
2637         // this will eventually set 'jsd' on the statusIcon
2638         var check = FBS.registerDebugger(this);
2639 
2640         if (FBTrace.DBG_ACTIVATION)
2641             FBTrace.sysout("debugger.registerDebugger "+check+" debuggers");
2642     },
2643 
2644     unregisterDebugger: function() // 1.3.1 safe for multiple calls
2645     {
2646         if (FBTrace.DBG_ACTIVATION)
2647             FBTrace.sysout("debugger.unregisterDebugger");
2648 
2649         // stay registered if we are profiling across a reload.
2650         if (Firebug.Profiler && Firebug.Profiler.isProfiling())
2651             return;
2652 
2653         var check = FBS.unregisterDebugger(this);
2654 
2655         if (FBTrace.DBG_ACTIVATION)
2656             FBTrace.sysout("debugger.unregisterDebugger: "+check+" debuggers");
2657     },
2658 
2659     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2660     // extends ActivableModule
2661 
2662     onObserverChange: function(observer)
2663     {
2664         if (FBTrace.DBG_ACTIVATION)
2665         {
2666             var names = [];
2667             this.observers.forEach(function(ob){names.push(ob.name || ob.dispatchName || ob.toolName);});
2668 
2669             FBTrace.sysout("debugger.onObserverChange "+this.hasObservers()+" "+
2670                 this.observers.length+": "+names.join(','), this.observers);
2671         }
2672 
2673         if (this.hasObservers())
2674         {
2675             this.activateDebugger();
2676             if (Firebug.currentContext)
2677             {
2678                 var name = observer.name || observer.dispatchName || observer.toolName;
2679                 Firebug.Console.log("enabling javascript debugger "+(name?"to support "+name:""));
2680             }
2681         }
2682         else
2683         {
2684             this.deactivateDebugger();
2685         }
2686     },
2687 
2688     activateDebugger: function()
2689     {
2690         this.registerDebugger();
2691 
2692         // If jsd is already active, we'll notify true; else we'll get another event
2693         var isActive = FBS.isJSDActive();
2694         if (isActive)
2695             Firebug.JSDebugClient.onJSDActivate(true, 'activated already');
2696 
2697         if (FBTrace.DBG_PANELS || FBTrace.DBG_ACTIVATION)
2698             FBTrace.sysout("debugger.activateDebugger requested; activated already? "+isActive);
2699     },
2700 
2701     deactivateDebugger: function()
2702     {
2703         this.unregisterDebugger();
2704 
2705         var isActive = FBS.isJSDActive();
2706         if (!isActive)
2707             Firebug.JSDebugClient.onJSDDeactivate(false, 'deactivate');
2708 
2709         if (FBTrace.DBG_PANELS || FBTrace.DBG_ACTIVATION)
2710             FBTrace.sysout("debugger.deactivate");
2711     },
2712 
2713     onSuspendingFirebug: function()
2714     {
2715         var anyStopped = Firebug.TabWatcher.iterateContexts(function isAnyStopped(context)
2716         {
2717             return context.stopped;
2718         });
2719 
2720         return anyStopped;
2721     },
2722 
2723     onSuspendFirebug: function()
2724     {
2725         if (!Firebug.Debugger.isAlwaysEnabled())
2726             return;
2727 
2728         // can be called multiple times.
2729         var paused = FBS.pause(this.debuggerName);
2730 
2731         if (FBTrace.DBG_ACTIVATION)
2732             FBTrace.sysout("debugger.onSuspendFirebug paused: "+paused+" isAlwaysEnabled " +
2733                 Firebug.Debugger.isAlwaysEnabled());
2734 
2735         // JSD activation is not per browser-tab, so FBS.pause can return 'not-paused' when
2736         // Firebug is activated on another tab.
2737         // The start-button should somehow reflect that JSD can be still active (even if
2738         // Firebug is suspended for the current tab).
2739         if (!paused)  // then we failed to suspend, undo
2740             return true;
2741 
2742         return false;
2743     },
2744 
2745     onResumeFirebug: function()
2746     {
2747         if (!Firebug.Debugger.isAlwaysEnabled())
2748             return;
2749 
2750         var unpaused = FBS.unPause();
2751 
2752         if (FBTrace.DBG_ACTIVATION)
2753             FBTrace.sysout("debugger.onResumeFirebug unpaused: "+unpaused+" isAlwaysEnabled " +
2754                 Firebug.Debugger.isAlwaysEnabled());
2755     },
2756 
2757     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2758     // Menu in toolbar.
2759 
2760     onScriptFilterMenuTooltipShowing: function(tooltip, context)
2761     {
2762         if (FBTrace.DBG_OPTIONS)
2763             FBTrace.sysout("onScriptFilterMenuTooltipShowing not implemented");
2764     },
2765 
2766     onScriptFilterMenuCommand: function(event, context)
2767     {
2768         var menu = event.target;
2769         Firebug.Options.set("scriptsFilter", menu.value);
2770         Firebug.Debugger.filterMenuUpdate();
2771     },
2772 
2773     menuFullLabel:
2774     {
2775         "static": Locale.$STR("ScriptsFilterStatic"),
2776         evals: Locale.$STR("ScriptsFilterEval"),
2777         events: Locale.$STR("ScriptsFilterEvent"),
2778         all: Locale.$STR("ScriptsFilterAll"),
2779     },
2780 
2781     menuShortLabel:
2782     {
2783         "static": Locale.$STR("ScriptsFilterStaticShort"),
2784         evals: Locale.$STR("ScriptsFilterEvalShort"),
2785         events: Locale.$STR("ScriptsFilterEventShort"),
2786         all: Locale.$STR("ScriptsFilterAllShort"),
2787     },
2788 
2789     onScriptFilterMenuPopupShowing: function(menu, context)
2790     {
2791         if (this.menuTooltip)
2792             this.menuTooltip.fbEnabled = false;
2793 
2794         var items = menu.getElementsByTagName("menuitem");
2795         var value = this.filterButton.value;
2796 
2797         for (var i=0; i<items.length; i++)
2798         {
2799             var option = items[i].value;
2800             if (!option)
2801                 continue;
2802 
2803             if (option == value)
2804                 items[i].setAttribute("checked", "true");
2805 
2806             items[i].label = Firebug.Debugger.menuFullLabel[option];
2807         }
2808 
2809         return true;
2810     },
2811 
2812     onScriptFilterMenuPopupHiding: function(tooltip, context)
2813     {
2814         if (this.menuTooltip)
2815             this.menuTooltip.fbEnabled = true;
2816 
2817         return true;
2818     },
2819 
2820     filterMenuUpdate: function()
2821     {
2822         var value = Firebug.Options.get("scriptsFilter");
2823         this.filterButton.value = value;
2824         this.filterButton.label = this.menuShortLabel[value];
2825         this.filterButton.removeAttribute("disabled");
2826         this.filterButton.setAttribute("value", value);
2827         if (FBTrace.DBG_OPTIONS)
2828             FBTrace.sysout("debugger.filterMenuUpdate value: "+value+" label:"+
2829                 this.filterButton.label+'\n');
2830     },
2831 
2832     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
2833     // Options
2834 
2835     resetAllOptions: function()
2836     {
2837         Firebug.TabWatcher.iterateContexts( function clearBPs(context)
2838         {
2839             Firebug.Debugger.clearAllBreakpoints(context);
2840         });
2841 
2842         var breakpointStore = FBS.getBreakpointStore();
2843         breakpointStore.clear(true);
2844     }
2845 });
2846 
2847 // ********************************************************************************************* //
2848 
2849 Firebug.Debugger.Breakpoint = function(name, href, lineNumber, checked, sourceLine, isFuture)
2850 {
2851     this.name = name;
2852     this.href = href;
2853     this.lineNumber = lineNumber;
2854     this.checked = checked;
2855     this.sourceLine = sourceLine;
2856     this.isFuture = isFuture;
2857 }
2858 
2859 // ********************************************************************************************* //
2860 
2861 Firebug.DebuggerListener =
2862 {
2863     onStop: function(context, frame, type, rv)
2864     {
2865     },
2866 
2867     onResume: function(context)
2868     {
2869     },
2870 
2871     onThrow: function(context, frame, rv)
2872     {
2873         return false; /* continue throw */
2874     },
2875 
2876     onError: function(context, frame, error)
2877     {
2878     },
2879 
2880     onEventScriptCreated: function(context, frame, url, sourceFile)
2881     {
2882     },
2883 
2884     onTopLevelScriptCreated: function(context, frame, url, sourceFile)
2885     {
2886     },
2887 
2888     onEvalScriptCreated: function(context, frame, url, sourceFile)
2889     {
2890     },
2891 
2892     onFunctionConstructor: function(context, frame, ctor_script, url, sourceFile)
2893     {
2894     },
2895 };
2896 
2897 // ********************************************************************************************* //
2898 // Signals from fbs, passed along to our listeners
2899 
2900 Firebug.JSDebugClient =
2901 {
2902     onJSDActivate: function(active, fromMsg)
2903     {
2904         if (FBTrace.DBG_ACTIVATION)
2905             FBTrace.sysout("Firebug.JSDebugClient onJSDActivate "+active+" "+fromMsg);
2906         Firebug.connection.dispatch("onActivateTool", ["script", active]);
2907     },
2908 
2909     onJSDDeactivate: function(active, fromMsg)
2910     {
2911         if (FBTrace.DBG_ACTIVATION)
2912             FBTrace.sysout("Firebug.JSDebugClient onJSDDeactivate "+active+" "+fromMsg);
2913         Firebug.connection.dispatch("onActivateTool", ["script", active]);
2914     },
2915 
2916     onPauseJSDRequested: function(rejection, debuggerName)
2917     {
2918         // A debugger client (an instance of Firebug.JSDebugClient) asked to pause JSD.
2919         // Note that there is one instance of Firebug.JSDebugClient per browser (XUL) window.
2920         // If the debugger is 'on' in this browser window JSD and the request comes from
2921         // another window (debugger) JSD should *not* be paused (see issue 4609).
2922         // So, reject only if the request comes from another browser window and Firebug
2923         // is resumed in this window.
2924         if (debuggerName != Firebug.Debugger.debuggerName && !Firebug.getSuspended())
2925         {
2926             rejection.push(true);
2927 
2928             if (FBTrace.DBG_ERRORS)
2929                 FBTrace.sysout("Firebug.JSDebugClient onPauseJSDRequested rejected to suspend; " +
2930                     "Current debugger: " + Firebug.Debugger.debuggerName + ", requestor debugger: " +
2931                     debuggerName);
2932         }
2933 
2934         //Firebug.connection.dispatch("onPauseJSDRequested", arguments);
2935 
2936         if (FBTrace.DBG_ACTIVATION)
2937             FBTrace.sysout("Firebug.JSDebugClient onPauseJSDRequested rejection: " +
2938                 rejection.length + ", jsDebuggerOn: " + Firebug.jsDebuggerOn);
2939     },
2940 }
2941 
2942 // Recursively look for obj in container using array of visited objects
2943 function findObjectPropertyPath(containerName, container, obj, visited)
2944 {
2945     if (!container || !obj || !visited)
2946         return false;
2947 
2948     var referents = [];
2949     visited.push(container);
2950     for (var p in container)
2951     {
2952         if (container.hasOwnProperty(p))
2953         {
2954             var candidate = null;
2955             try
2956             {
2957                 candidate = container[p];
2958             }
2959             catch(exc)
2960             {
2961                 // eg sessionStorage
2962             }
2963 
2964             if (candidate === obj) // then we found a property pointing to our obj
2965             {
2966                 referents.push(new Referent(containerName, container, p, obj));
2967             }
2968             else // recurse
2969             {
2970                 var candidateType = typeof (candidate);
2971                 if (candidateType === 'object' || candidateType === 'function')
2972                 {
2973                     if (visited.indexOf(candidate) === -1)
2974                     {
2975                         var refsInChildren = findObjectPropertyPath(p, candidate, obj, visited);
2976                         if (refsInChildren.length)
2977                         {
2978                             // As we unwind the recursion we tack on layers of the path.
2979                             for (var i = 0; i < refsInChildren.length; i++)
2980                             {
2981                                 var refInChildren = refsInChildren[i];
2982                                 refInChildren.prependPath(containerName, container);
2983                                 referents.push(refInChildren);
2984 
2985                                 FBTrace.sysout(" Did prependPath with p "+p+" gave "+
2986                                     referents[referents.length - 1].getObjectPathExpression(),
2987                                     referents[referents.length - 1]);
2988                             }
2989                         }
2990                     }
2991                     //else we already looked at that object.
2992                 } // else the object has no properties
2993             }
2994         }
2995     }
2996 
2997     FBTrace.sysout(" Returning "+referents.length+ " referents", referents);
2998 
2999     return referents;
3000 }
3001 
3002 // ********************************************************************************************* //
3003 
3004 function getFrameWindow(frame)
3005 {
3006     var result = {};
3007     if (frame.eval("window", "", 1, result))
3008     {
3009         var win = Wrapper.unwrapIValue(result.value, Firebug.viewChrome);
3010         return Win.getRootWindow(win);
3011     }
3012 }
3013 
3014 function ArrayEnumerator(array)
3015 {
3016     this.index = 0;
3017     this.array = array;
3018     this.hasMoreElements = function()
3019     {
3020         return (this.index < array.length);
3021     }
3022     this.getNext = function()
3023     {
3024         return this.array[++this.index];
3025     }
3026 }
3027 
3028 // ********************************************************************************************* //
3029 // Registration
3030 
3031 Firebug.registerActivableModule(Firebug.Debugger);
3032 
3033 return Firebug.Debugger;
3034 
3035 // ********************************************************************************************* //
3036 });
3037