1 /* See license.txt for terms of usage */
  2 
  3 FBL.ns(function() { with (FBL) {
  4 
  5 // ************************************************************************************************
  6 // Constants
  7 
  8 const Cc = Components.classes;
  9 const Ci = Components.interfaces;
 10 const jsdIScript = Ci.jsdIScript;
 11 const jsdIStackFrame = Ci.jsdIStackFrame;
 12 const jsdIExecutionHook = Ci.jsdIExecutionHook;
 13 const nsISupports = Ci.nsISupports;
 14 const nsICryptoHash = Ci.nsICryptoHash;
 15 const nsIURI = Ci.nsIURI;
 16 
 17 const PCMAP_SOURCETEXT = jsdIScript.PCMAP_SOURCETEXT;
 18 const PCMAP_PRETTYPRINT = jsdIScript.PCMAP_PRETTYPRINT;
 19 
 20 const RETURN_VALUE = jsdIExecutionHook.RETURN_RET_WITH_VAL;
 21 const RETURN_THROW_WITH_VAL = jsdIExecutionHook.RETURN_THROW_WITH_VAL;
 22 const RETURN_CONTINUE = jsdIExecutionHook.RETURN_CONTINUE;
 23 const RETURN_CONTINUE_THROW = jsdIExecutionHook.RETURN_CONTINUE_THROW;
 24 const RETURN_ABORT = jsdIExecutionHook.RETURN_ABORT;
 25 
 26 const TYPE_THROW = jsdIExecutionHook.TYPE_THROW;
 27 const TYPE_DEBUGGER_KEYWORD = jsdIExecutionHook.TYPE_DEBUGGER_KEYWORD;
 28 
 29 const STEP_OVER = 1;
 30 const STEP_INTO = 2;
 31 const STEP_OUT = 3;
 32 
 33 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 34 
 35 const tooltipTimeout = 300;
 36 
 37 const reLineNumber = /^[^\\]?#(\d*)$/;
 38 
 39 const reEval =  /\s*eval\s*\(([^)]*)\)/m;        // eval ( $1 )
 40 const reHTM = /\.[hH][tT][mM]/;
 41 const reFunction = /\s*Function\s*\(([^)]*)\)/m;
 42 const reTooMuchRecursion = /too\smuch\srecursion/;
 43 
 44 // ************************************************************************************************
 45 
 46 Firebug.Debugger = extend(Firebug.ActivableModule,
 47 {
 48     dispatchName: "debugger",
 49     fbs: fbs, // access to firebug-service in chromebug under browser.xul.DOM.Firebug.Debugger.fbs /*@explore*/
 50 
 51     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 52     // Debugging
 53 
 54     evaluate: function(js, context, scope)
 55     {
 56         var frame = context.currentFrame;
 57         if (!frame)
 58             return;
 59 
 60         frame.scope.refresh(); // XXX what's this do?
 61 
 62         var result = {};
 63         var scriptToEval = js;
 64 
 65         // This seem to be safe; eval'ing a getter property in content that tries to
 66         // be evil and get Components.classes results in a permission denied error.
 67         var ok = frame.eval(scriptToEval, "", 1, result);
 68 
 69         var value = unwrapIValue(result.value);
 70         if (ok)
 71             return value;
 72         else
 73             throw value;
 74     },
 75 
 76     getCurrentFrameKeys: function(context)
 77     {
 78         var globals = keys(context.getGlobalScope().wrappedJSObject);  // return is safe
 79 
 80         if (context.currentFrame)
 81             return this.getFrameKeys(context.currentFrame, globals);
 82 
 83         return globals;
 84     },
 85 
 86     getFrameKeys: function(frame, names)
 87     {
 88         var listValue = {value: null}, lengthValue = {value: 0};
 89         frame.scope.getProperties(listValue, lengthValue);
 90 
 91         for (var i = 0; i < lengthValue.value; ++i)
 92         {
 93             var prop = listValue.value[i];
 94             var name = unwrapIValue(prop.name);
 95             names.push(name);
 96         }
 97         return names;
 98     },
 99 
100     focusWatch: function(context)
101     {
102         if (Firebug.isDetached())
103             Firebug.chrome.focus();
104         else
105             Firebug.toggleBar(true);
106 
107         Firebug.chrome.selectPanel("script");
108 
109         var watchPanel = context.getPanel("watches", true);
110         if (watchPanel)
111         {
112             Firebug.CommandLine.isReadyElsePreparing(context);
113             watchPanel.editNewWatch();
114         }
115     },
116 
117     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
118 
119     beginInternalOperation: function() // stop debugger operations like breakOnErrors
120     {
121         var state = {breakOnErrors: Firebug.breakOnErrors};
122         Firebug.breakOnErrors = false;
123         return state;
124     },
125 
126     endInternalOperation: function(state)  // pass back the object given by beginInternalOperation
127     {
128         Firebug.breakOnErrors = state.breakOnErrors;
129         return true;
130     },
131 
132     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
133 
134     halt: function(fn)
135     {
136         this.haltCallback = fn; // called in this.onHalt as fn(frame);
137         fbs.halt(this);
138 
139         debuggerHalter(); // a function with a URL that passes jsdIFilter and says "debugger;"
140 
141         if (this.haltCallback) // so we have a second try
142         {
143             FBTrace.sysout("debugger did not halt jsd: ", jsd);
144             if (Firebug.CommandLine.isReadyElsePreparing(FirebugContext))
145                 Firebug.CommandLine.evaluate("debugger;", FirebugContext);
146         }
147 
148         if(FBTrace.DBG_BP)
149             FBTrace.sysout("debugger.halt, completed debugger stmt");
150     },
151 
152     breakNow: function()
153     {
154         Firebug.Debugger.halt(function(frame)
155         {
156             if (FBTrace.DBG_UI_LOOP)
157                 FBTrace.sysout("debugger.breakNow: frame "+frame.script.fileName, frame);
158 
159             for (; frame && frame.isValid; frame = frame.callingFrame)
160             {
161                 var fileName = frame.script.fileName;
162                 if (fileName && fileName.indexOf("chrome://firebug/") != 0 &&
163                     fileName.indexOf("/components/firebug-") == -1)
164                     break;
165             }
166 
167             if (frame)
168                 Firebug.Debugger.onBreak(frame, 3);
169             else
170             {
171                 // XXXrobc no-op, added for detrace
172                 if (FBTrace.DBG_UI_LOOP)
173                     FBTrace.sysout("debugger.breakNow: no frame that is not firebug");
174             }
175         });
176     },
177 
178     stop: function(context, frame, type, rv)
179     {
180         if (context.stopped)
181             return RETURN_CONTINUE;
182 
183         if (!this.isAlwaysEnabled())
184             return RETURN_CONTINUE;
185 
186         if (FBTrace.DBG_UI_LOOP)
187             FBTrace.sysout("debugger.stop "+context.getName()+" frame",frame);
188 
189         var executionContext;
190         try
191         {
192             executionContext = frame.executionContext;
193         }
194         catch (exc)
195         {
196             if (FBTrace.DBG_UI_LOOP)
197                 FBTrace.sysout("debugger.stop no executionContext, exit");
198 
199             // Can't proceed with an execution context - it happens sometimes.
200             return RETURN_CONTINUE;
201         }
202 
203         context.debugFrame = frame;
204         context.stopped = true;
205 
206         var hookReturn = dispatch2(this.fbListeners,"onStop",[context,frame, type,rv]);
207         if ( hookReturn && hookReturn >= 0 )
208         {
209             delete context.stopped;
210             delete context.debugFrame;
211             delete context;
212             if (FBTrace.DBG_UI_LOOP)
213                 FBTrace.sysout("debugger.stop extension vetoed stop with hookReturn "+hookReturn);
214 
215             return hookReturn;
216         }
217 
218         try
219         {
220             // We will pause here until resume is called
221             var depth = fbs.enterNestedEventLoop({onNest: bindFixed(this.startDebugging, this, context)});
222             // For some reason we don't always end up here
223             if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("debugger.stop, depth:"+depth+" context:"+context.getName());
224         }
225         catch (exc)
226         {
227             // Just ignore exceptions that happened while in the nested loop
228             if (FBTrace.DBG_ERRORS)
229                 FBTrace.sysout("debugger exception in nested event loop: ", exc);
230             else     // else /*@explore*/
231                 ERROR("debugger exception in nested event loop: "+exc+"\n");
232         }
233 
234         this.stopDebugging(context);
235 
236         dispatch(this.fbListeners,"onResume",[context]);
237 
238         if (context.aborted)
239         {
240             delete context.aborted;
241             return RETURN_ABORT;
242         }
243         else
244             return RETURN_CONTINUE;
245     },
246 
247     resume: function(context)
248     {
249         if (FBTrace.DBG_UI_LOOP)
250             FBTrace.sysout("debugger.resume, context.stopped:"+context.stopped+"\n");
251 
252         this.thaw(context);
253 
254         var depth = fbs.exitNestedEventLoop();
255         if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("debugger.resume, depth:"+depth+"\n");
256     },
257 
258     abort: function(context)
259     {
260         if (context.stopped)
261         {
262             context.aborted = true;
263             this.resume(context);
264         }
265     },
266 
267     stepOver: function(context)
268     {
269         if (!context.debugFrame || !context.debugFrame.isValid)
270             return;
271 
272         fbs.step(STEP_OVER, context.debugFrame, this);
273         this.resume(context);
274     },
275 
276     stepInto: function(context)
277     {
278         if (!context.debugFrame || !context.debugFrame.isValid)
279             return;
280 
281         fbs.step(STEP_INTO, context.debugFrame, this);
282         this.resume(context);
283     },
284 
285     stepOut: function(context)
286     {
287         if (!context.debugFrame || !context.debugFrame.isValid)
288             return;
289 
290         fbs.step(STEP_OUT, context.debugFrame);
291         this.resume(context);
292     },
293 
294     suspend: function(context)
295     {
296         if (context.stopped)
297             return;
298         fbs.suspend(this, context);
299     },
300 
301     unSuspend: function(context)
302     {
303         fbs.stopStepping();  // TODO per context
304     },
305 
306     runUntil: function(context, sourceFile, lineNo)
307     {
308         if (FBTrace.DBG_UI_LOOP)
309             FBTrace.sysout("runUntil "+lineNo+" @"+sourceFile);
310 
311         if (!context.debugFrame || !context.debugFrame.isValid)
312             return;
313 
314         fbs.runUntil(sourceFile, lineNo, context.debugFrame, this);
315         this.resume(context);
316     },
317 
318     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
319 
320     freeze: function(context)
321     {
322         var executionContext = context.debugFrame.executionContext;
323         try {
324             executionContext.scriptsEnabled = false;
325 
326             if (context.window instanceof Ci.nsIInterfaceRequestor)
327             {
328                 context.eventSuppressor = context.window.getInterface(Ci.nsIDOMWindowUtils);
329                 if (context.eventSuppressor)
330                     context.eventSuppressor.suppressEventHandling(true);
331             }
332 
333             if (FBTrace.DBG_UI_LOOP)
334                 FBTrace.sysout("debugger.stop try to disable scripts "+(context.eventSuppressor?"and events":"but not events")+" in "+context.getName()+" executionContext.tag "+executionContext.tag+".scriptsEnabled: "+executionContext.scriptsEnabled);
335 
336         } catch (exc) {
337             // This attribute is only valid for contexts which implement nsIScriptContext.
338             if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("debugger.stop, freeze exception in "+context.getName(), exc);
339         }
340     },
341 
342     thaw: function(context)
343     {
344         var executionContext = context.debugFrame.executionContext;
345         try {
346             if (executionContext.isValid)
347             {
348                 if (context.eventSuppressor)
349                 {
350                     context.eventSuppressor.suppressEventHandling(false);
351                     delete context.eventSuppressor;
352                 }
353 
354                 executionContext.scriptsEnabled = true;
355             }
356             else
357             {
358                 if (FBTrace.DBG_UI_LOOP)
359                     FBTrace.sysout("debugger.stop "+executionContext.tag+" executionContext is not valid");
360             }
361             if (FBTrace.DBG_UI_LOOP)
362                 FBTrace.sysout("debugger.stop try to ensable scripts "+(context.eventSuppressor?"with events suppressed":"events enabled")+" in "+context.getName()+" executionContext.tag "+executionContext.tag+".scriptsEnabled: "+executionContext.scriptsEnabled);
363         } catch (exc) {
364             if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("debugger.stop, scriptsEnabled = true exception:", exc);
365         }
366 
367     },
368 
369     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
370     // Breakpoints
371 
372     setBreakpoint: function(sourceFile, lineNo)
373     {
374         fbs.setBreakpoint(sourceFile, lineNo, null, Firebug.Debugger);
375     },
376 
377     clearBreakpoint: function(sourceFile, lineNo)
378     {
379         fbs.clearBreakpoint(sourceFile.href, lineNo);
380     },
381 
382     setErrorBreakpoint: function(sourceFile, line)
383     {
384         fbs.setErrorBreakpoint(sourceFile, line, Firebug.Debugger);
385     },
386 
387     clearErrorBreakpoint: function(sourceFile, line)
388     {
389         fbs.clearErrorBreakpoint(sourceFile, line, Firebug.Debugger);
390     },
391 
392     clearAllBreakpoints: function(context)
393     {
394         if (context)
395         {
396             var sourceFiles = sourceFilesAsArray(context.sourceFileMap);
397             fbs.clearAllBreakpoints(sourceFiles, Firebug.Debugger);
398         }
399         else
400         {
401             fbs.enumerateBreakpoints(null, {call: function(url, lineNo, bp) // null means all urls
402             {
403                 if (bp.debugger !== this) // skip breakpoints of other debuggers.
404                     return;
405 
406                 if (Firebug.filterSystemURLs) // then there are not system urls, clear all
407                     fbs.clearBreakpoint(url, lineNo);
408                 else
409                 {
410                     if (!isSystemURL(url))  // if there are system urls, leave them
411                         fbs.clearBreakpoint(url, lineNo);
412                 }
413             }});
414         }
415     },
416 
417     enableAllBreakpoints: function(context)
418     {
419         if (FBTrace.DBG_BP)
420             FBTrace.sysout("enableAllBreakpoints sourceFileMap:", context.sourceFileMap);
421         for (var url in context.sourceFileMap)
422         {
423             fbs.enumerateBreakpoints(url, {call: function(url, lineNo)
424             {
425                 fbs.enableBreakpoint(url, lineNo);
426             }});
427         }
428     },
429 
430     disableAllBreakpoints: function(context)
431     {
432         for (var url in context.sourceFileMap)
433         {
434             fbs.enumerateBreakpoints(url, {call: function(url, lineNo)
435             {
436                 fbs.disableBreakpoint(url, lineNo);
437             }});
438         }
439     },
440 
441     getBreakpointCount: function(context)
442     {
443         var count = 0;
444         for (var url in context.sourceFileMap)
445         {
446             fbs.enumerateBreakpoints(url,
447             {
448                 call: function(url, lineNo)
449                 {
450                     ++count;
451                 }
452             });
453 
454             fbs.enumerateErrorBreakpoints(url,
455             {
456                 call: function(url, lineNo)
457                 {
458                     ++count;
459                 }
460             });
461         }
462         return count;
463     },
464 
465     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
466     // Debugging and monitoring
467 
468     traceAll: function(context)
469     {
470         fbs.traceAll(sourceURLsAsArray(context), this);
471     },
472 
473     untraceAll: function(context)
474     {
475         fbs.untraceAll(this);
476     },
477 
478     monitorFunction: function(fn, mode)
479     {
480         if (typeof(fn) == "function" || fn instanceof Function)
481         {
482             var script = findScriptForFunctionInContext(FirebugContext, fn);
483             if (script)
484                 this.monitorScript(fn, script, mode);
485         }
486     },
487 
488     unmonitorFunction: function(fn, mode)
489     {
490         if (typeof(fn) == "function" || fn instanceof Function)
491         {
492             var script = findScriptForFunctionInContext(FirebugContext, fn);
493             if (script)
494                 this.unmonitorScript(fn, script, mode);
495         }
496     },
497 
498     monitorScript: function(fn, script, mode)
499     {
500         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(FirebugContext, script);
501         if (scriptInfo)
502         {
503             if (mode == "debug")
504                 this.setBreakpoint(scriptInfo.sourceFile, scriptInfo.lineNo, null, this);
505             else if (mode == "monitor")
506                 fbs.monitor(scriptInfo.sourceFile, scriptInfo.lineNo, Firebug.Debugger);
507         }
508     },
509 
510     unmonitorScript: function(fn, script, mode)
511     {
512         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(FirebugContext, script);
513         if (scriptInfo)
514         {
515             if (mode == "debug")
516                 this.clearBreakpoint(scriptInfo.sourceFile, scriptInfo.lineNo);
517             else if (mode == "monitor")
518                 fbs.unmonitor(scriptInfo.sourceFile, scriptInfo.lineNo);
519         }
520     },
521 
522     traceCalls: function(context, fn)
523     {
524         if (typeof(fn) == "function" || fn instanceof Function)
525         {
526             var script = findScriptForFunctionInContext(context, fn);
527             if (script)
528                 this.traceScriptCalls(context, script);
529             else
530             {
531                 if (FBTrace.DBG_ERRORS)
532                     FBTrace.sysout("debugger.traceCalls no script found for "+fn, fn);
533             }
534         }
535     },
536 
537     untraceCalls: function(context, fn)
538     {
539         if (typeof(fn) == "function" || fn instanceof Function)
540         {
541             var script = findScriptForFunctionInContext(context, fn);
542             if (script)
543                 this.untraceScriptCalls(context, script);
544         }
545     },
546 
547     traceScriptCalls: function(context, script)
548     {
549         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(context, script);
550         if (scriptInfo)
551             fbs.traceCalls(scriptInfo.sourceFile, scriptInfo.lineNo, Firebug.Debugger);
552     },
553 
554     untraceScriptCalls: function(context, script)
555     {
556         var scriptInfo = Firebug.SourceFile.getSourceFileAndLineByScript(context, script);
557         if (scriptInfo)
558             fbs.untraceCalls(scriptInfo.sourceFile, scriptInfo.lineNo, Firebug.Debugger);
559     },
560 
561     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
562     // UI Stuff
563 
564     /*
565      * Called when a nestedEventLoop begins
566      */
567     startDebugging: function(context)
568     {
569         if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("startDebugging enter context.stopped:"+context.stopped+" for context: "+context.getName()+"\n");
570         try {
571 
572             this.freeze(context);
573 
574             fbs.lockDebugger();
575 
576             context.currentFrame = context.debugFrame;
577 
578             context.executingSourceFile = Firebug.SourceFile.getSourceFileByScript(context, context.currentFrame.script);
579 
580             if (!context.executingSourceFile)  // bail out, we don't want the user stuck in debug with out source.
581             {
582                 if (FBTrace.DBG_UI_LOOP)
583                     FBTrace.sysout("startDebugging resuming, no sourceFile for "+context.debugFrame.script.fileName, context.debugFrame.script.functionSource);
584                 this.resume(context);
585                 return;
586             }
587 
588             var currentBreakable = Firebug.chrome.getGlobalAttribute("cmd_breakOnNext", "breakable");
589 
590             if (FBTrace.DBG_BP)
591                 FBTrace.sysout("debugger.startDebugging; currentBreakable "+currentBreakable+" in " + context.getName());
592 
593             if (currentBreakable == "false") // then we are armed but we broke
594                 Firebug.chrome.setGlobalAttribute("cmd_breakOnNext", "breakable", "true");
595 
596             if (context != FirebugContext || Firebug.isDetached())
597                 Firebug.showContext(context.browser, context);  // Make FirebugContext = context and sync the UI
598 
599             if (Firebug.isMinimized()) // then open the UI to show we are stopped
600                 Firebug.unMinimize();
601 
602             this.syncCommands(context);
603             this.syncListeners(context);
604 
605             // Update Break on Next lightning.
606             var panel = context.getPanel("script", true);
607             Firebug.Breakpoint.updatePanelTab(panel, false);
608 
609             Firebug.chrome.select(context.currentFrame, "script", null, true);
610             Firebug.chrome.focus();
611         }
612         catch(exc)
613         {
614             if (FBTrace.DBG_ERRORS)
615                 FBTrace.sysout("Resuming debugger: error during debugging loop: "+exc, exc);
616             Firebug.Console.log("Resuming debugger: error during debugging loop: "+exc);
617             this.resume(context);
618         }
619 
620         dispatch(this.fbListeners, "onStartDebugging", [context]);
621 
622         if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("startDebugging exit context.stopped:"+context.stopped+" for context: "+context.getName()+"\n");
623     },
624 
625     /*
626      * Called in the main event loop, from jsd, after we have exited the nested event loop
627      */
628 
629     stopDebugging: function(context)
630     {
631         if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("stopDebugging enter context: "+context.getName()+"\n");
632         try
633         {
634             fbs.unlockDebugger();
635 
636             // If the user reloads the page while the debugger is stopped, then
637             // the current context will be destroyed just before
638             if (context && context.window && !context.aborted)
639             {
640                 delete context.stopped;
641                 delete context.debugFrame;
642                 delete context.currentFrame;
643 
644                 var chrome = Firebug.chrome;
645 
646                 this.syncCommands(context);
647                 this.syncListeners(context);
648 
649                 chrome.syncSidePanels();
650 
651                 var panel = context.getPanel("script", true);
652                 if (panel && panel == Firebug.chrome.getSelectedPanel())
653                     panel.showNoStackFrame(); // unhighlight and remove toolbar-status line
654 
655                 if (panel)
656                     panel.highlight(false);
657 
658                 context.executingSourceFile = null;
659                 delete context.breakLineNumber;
660             }
661         }
662         catch (exc)
663         {
664             if (FBTrace.DBG_UI_LOOP) FBTrace.sysout("debugger.stopDebugging FAILS", exc);
665             // If the window is closed while the debugger is stopped,
666             // then all hell will break loose here
667             ERROR(exc);
668         }
669     },
670 
671     syncCommands: function(context)
672     {
673         var chrome = Firebug.chrome;
674         if (!chrome)
675         {
676             if (FBTrace.DBG_ERRORS)
677                 FBTrace.sysout("debugger.syncCommand, context with no chrome: "+context.getGlobalScope());
678             return;
679         }
680 
681         if (context.stopped)
682         {
683             chrome.setGlobalAttribute("fbDebuggerButtons", "stopped", "true");
684             chrome.setGlobalAttribute("cmd_resumeExecution", "disabled", "false");
685             chrome.setGlobalAttribute("cmd_stepOver", "disabled", "false");
686             chrome.setGlobalAttribute("cmd_stepInto", "disabled", "false");
687             chrome.setGlobalAttribute("cmd_stepOut", "disabled", "false");
688         }
689         else
690         {
691             chrome.setGlobalAttribute("fbDebuggerButtons", "stopped", "false");
692             chrome.setGlobalAttribute("cmd_stepOver", "disabled", "true");
693             chrome.setGlobalAttribute("cmd_stepInto", "disabled", "true");
694             chrome.setGlobalAttribute("cmd_stepOut", "disabled", "true");
695             chrome.setGlobalAttribute("cmd_resumeExecution", "disabled", "true");
696         }
697     },
698 
699     syncListeners: function(context)
700     {
701         var chrome = Firebug.chrome;
702 
703         if (context.stopped)
704             this.attachListeners(context, chrome);
705         else
706             this.detachListeners(context, chrome);
707     },
708 
709     attachListeners: function(context, chrome)
710     {
711         this.keyListeners =
712         [
713             chrome.keyCodeListen("F8", null, bind(this.resume, this, context), true),
714             chrome.keyListen("/", isControl, bind(this.resume, this, context)),
715             chrome.keyCodeListen("F10", null, bind(this.stepOver, this, context), true),
716             chrome.keyListen("'", isControl, bind(this.stepOver, this, context)),
717             chrome.keyCodeListen("F11", null, bind(this.stepInto, this, context)),
718             chrome.keyListen(";", isControl, bind(this.stepInto, this, context)),
719             chrome.keyCodeListen("F11", isShift, bind(this.stepOut, this, context)),
720             chrome.keyListen(",", isControlShift, bind(this.stepOut, this, context))
721         ];
722     },
723 
724     detachListeners: function(context, chrome)
725     {
726         if (this.keyListeners)
727         {
728             for (var i = 0; i < this.keyListeners.length; ++i)
729                 chrome.keyIgnore(this.keyListeners[i]);
730             delete this.keyListeners;
731         }
732     },
733 
734     showPanel: function(browser, panel)
735     {
736         if (panel && panel.name == "script")
737         {
738             this.syncCommands(panel.context);
739             this.ableWatchSidePanel(panel.context);
740             if (FBTrace.DBG_PANELS) FBTrace.sysout("debugger.showPanel this.location:"+this.location);
741         }
742     },
743 
744     suspendFirebug: function()
745     {
746         Firebug.suspendFirebug();
747     },
748 
749     resumeFirebug: function()
750     {
751         Firebug.resumeFirebug();
752     },
753 
754     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
755 
756     supportsWindow: function(win)
757     {
758         if (!this.isAlwaysEnabled())
759             return false;
760 
761         var context = ( (win && TabWatcher) ? TabWatcher.getContextByWindow(win) : null);
762 
763         this.breakContext = context;
764         return !!context;
765     },
766 
767     supportsGlobal: function(frameWin) // This is call from fbs for almost all fbs operations
768     {
769         var context = ( (frameWin && TabWatcher) ? TabWatcher.getContextByWindow(frameWin) : null);
770         if (!context)
771             return false;
772 
773         // Apparently the frameWin is a XPCSafeJSObjectWrapper that looks like a Window.
774         // Since this is method called a lot make a hacky fast check on _getFirebugConsoleElement
775         if (!frameWin._getFirebugConsoleElement && !context.stopped)
776         {
777             this.injectConsole(context, frameWin);
778         }
779         else
780         {
781             if (FBTrace.DBG_CONSOLE)
782                 FBTrace.sysout("debugger.supportsGlobal frameWin._getFirebugConsoleElement exists", frameWin);
783         }
784 
785         this.breakContext = context;
786         //FBTrace.sysout("debugger.js this.breakContext "+this.breakContext.getName());
787         return true;
788     },
789 
790     injectConsole: function(context, frameWin)
791     {
792         if (context.notificationSourceFile)
793         {
794             delete context.sourceFileMap[context.notificationSourceFile.href];
795             delete context.notificationSourceFile;
796         }
797         if (Firebug.Console.isAlwaysEnabled())
798         {
799             // This is how the console is injected ahead of JS running on the page
800             fbs.filterConsoleInjections = true;
801             try
802             {
803                 var consoleReady = Firebug.Console.isReadyElsePreparing(context, frameWin);
804             }
805             catch(exc)
806             {
807                 if (FBTrace.DBG_ERRORS)
808                     FBTrace.sysout("debugger.supportsGlobal !frameWin._getFirebugConsoleElement consoleReady FAILS: "+exc, exc);
809             }
810             finally
811             {
812                 fbs.filterConsoleInjections = false;
813             }
814             if (FBTrace.DBG_CONSOLE)
815                 FBTrace.sysout("debugger.supportsGlobal !frameWin._getFirebugConsoleElement consoleReady:"+consoleReady, frameWin);
816         }
817         else
818         {
819             if (FBTrace.DBG_CONSOLE)
820                 FBTrace.sysout("debugger.supportsGlobal !frameWin._getFirebugConsoleElement console NOT enabled ", frameWin);
821         }
822     },
823 
824     onLock: function(state)
825     {
826         // XXXjoe For now, trying to see if it's ok to have multiple contexts
827         // debugging simultaneously - otherwise we need this
828         //if (this.context != this.debugContext)
829         {
830             // XXXjoe Disable step/continue buttons
831         }
832     },
833 
834     onBreak: function(frame, type)
835     {
836         try {
837             var context = this.breakContext;
838 
839             if (!context)
840                 context = this.getContextByFrame(frame);
841 
842             if (FBTrace.DBG_BP)
843                 FBTrace.sysout("debugger.onBreak "+(this.breakContext?" no breakContext, tried getContextByFrame ":"breakContext: ") + (context ? context.getName() : " none!"), getJSDStackDump(frame) );
844 
845             delete this.breakContext;
846 
847             if (!context)
848                 return RETURN_CONTINUE;
849 
850             if (type == TYPE_DEBUGGER_KEYWORD && frame.functionName === 'firebugDebuggerTracer')
851             {
852                 var trace = FBL.getCorrectedStackTrace(frame, context);
853                 if (trace)
854                 {
855                     trace.frames = trace.frames.slice(1).reverse(); // drop the firebugDebuggerTracer and reorder
856                     Firebug.Console.log(trace, context, "stackTrace");
857                 }
858 
859                 if(FBTrace.DBG_BP)
860                     FBTrace.sysout("debugger.onBreak "+(trace?"debugger trace":" debugger no trace!"));
861 
862                 return RETURN_CONTINUE;
863             }
864 
865             return this.stop(context, frame, type);
866         }
867         catch (exc)
868         {
869             if (FBTrace.DBG_ERRORS || FBTrace.DBG_BP)
870                 FBTrace.sysout("debugger.onBreak FAILS", exc);
871             throw exc;
872         }
873     },
874 
875     onHalt: function(frame)
876     {
877         var callback = this.haltCallback;
878         delete this.haltCallback;
879 
880         if (callback)
881             callback(frame);
882 
883         return RETURN_CONTINUE;
884     },
885 
886     onThrow: function(frame, rv)
887     {
888         // onThrow is called for throw and for any catch that does not succeed.
889         var context = this.breakContext;
890         delete this.breakContext;
891 
892         if (!context)
893         {
894             FBTrace.sysout("debugger.onThrow, no context, try to get from frame\n");
895             context = this.getContextByFrame(frame);
896         }
897         if (FBTrace.DBG_BP) FBTrace.sysout("debugger.onThrow context:"+(context?context.getName():"undefined")+"\n");
898         if (!context)
899             return RETURN_CONTINUE_THROW;
900 
901         if (!fbs.trackThrowCatch)
902             return RETURN_CONTINUE_THROW;
903 
904         try
905         {
906             var isCatch = this.isCatchFromPreviousThrow(frame, context);
907             if (!isCatch)
908             {
909                 context.thrownStackTrace = getCorrectedStackTrace(frame, context);
910                 if (FBTrace.DBG_BP) FBTrace.sysout("debugger.onThrow reset context.thrownStackTrace", context.thrownStackTrace.frames);
911             }
912             else
913             {
914                 if (FBTrace.DBG_BP) FBTrace.sysout("debugger.onThrow isCatch\n");
915             }
916         }
917         catch  (exc)
918         {
919             FBTrace.sysout("onThrow FAILS: "+exc+"\n");
920         }
921 
922         if (dispatch2(this.fbListeners,"onThrow",[context, frame, rv]))
923             return this.stop(context, frame, TYPE_THROW, rv);
924         return RETURN_CONTINUE_THROW;
925     },
926 
927     isCatchFromPreviousThrow: function(frame, context)
928     {
929         if (context.thrownStackTrace)
930         {
931             var trace = context.thrownStackTrace.frames;
932             if (trace.length > 1)  // top of stack is [0]
933             {
934                 var curFrame = frame;
935                 var curFrameSig = curFrame.script.tag +"."+curFrame.pc;
936                 for (var i = 1; i < trace.length; i++)
937                 {
938                     var preFrameSig = trace[i].signature();
939                     if (FBTrace.DBG_ERRORS && FBTrace.DBG_STACK) FBTrace.sysout("debugger.isCatchFromPreviousThrow "+curFrameSig+"=="+preFrameSig+"\n");
940                     if (curFrameSig == preFrameSig)
941                     {
942                         return true;  // catch from previous throw (or do we need to compare whole stack?
943                     }
944                 }
945                 // We looked at the previous stack and did not match the current frame
946             }
947         }
948        return false;
949     },
950 
951     onMonitorScript: function(frame)
952     {
953         var context = this.breakContext;
954         delete this.breakContext;
955 
956         if (!context)
957             context = this.getContextByFrame(frame);
958         if (!context)
959             return RETURN_CONTINUE;
960 
961         frame = getStackFrame(frame, context);
962 
963         dispatch(this.fbListeners,"onMonitorScript",[context, frame]);
964     },
965 
966     onFunctionCall: function(context, frame, depth, calling)
967     {
968         if (!context)
969             context = this.getContextByFrame(frame);
970         if (!context)
971             return RETURN_CONTINUE;
972 
973         frame = getStackFrame(frame, context);
974 
975         dispatch(this.fbListeners,"onFunctionCall",[context, frame, depth, calling]);
976 
977         return context;  // returned as first arg on next call from same trace
978     },
979 
980     onError: function(frame, error)
981     {
982         var context = this.breakContext;
983         delete this.breakContext;
984 
985         try
986         {
987             if (FBTrace.DBG_ERRORS) FBTrace.sysout("debugger.onError: "+error.errorMessage+" in "+(context?context.getName():"no context"), error);
988 
989             if (reTooMuchRecursion.test(error.errorMessage))
990                 frame = fbs.discardRecursionFrames(frame);
991 
992             Firebug.errorStackTrace = getCorrectedStackTrace(frame, context);
993             if (FBTrace.DBG_ERRORS)
994                 FBTrace.sysout("debugger.onError errorStackTrace ", Firebug.errorStackTrace);
995 
996             if (Firebug.breakOnErrors)
997             {
998                 context.breakingCause = {
999                     title: $STR("Break on Error"),
1000                     message: error.message,
1001                     copyAction: bindFixed(FirebugReps.ErrorMessage.copyError,
1002                         FirebugReps.ErrorMessage, error)
1003                 };
1004             }
1005             else
1006             {
1007                 delete context.breakingCause;
1008             }
1009         }
1010         catch (exc)
1011         {
1012             if (FBTrace.DBG_ERRORS)
1013                 FBTrace.sysout("debugger.onError getCorrectedStackTrace FAILED:", exc);
1014         }
1015 
1016         var hookReturn = dispatch2(this.fbListeners,"onError",[context, frame, error]);
1017 
1018         if (Firebug.breakOnErrors)
1019         {
1020             //xxxHonza: for now, the BON options are disabled to keep things simple.
1021             // Deactivate "Break On All Errors" only if the other options says so.
1022             //if (!Firebug.persistBreakOnError)
1023             //    Firebug.setPref(Firebug.servicePrefDomain, "breakOnErrors", false);
1024 
1025             // Switch of Break on Next tab lightning.
1026             var panel = context.getPanel("console", true);
1027             //Firebug.Breakpoint.updatePanelTab(panel, false);
1028 
1029             return -1;  // break
1030         }
1031 
1032         if (hookReturn)
1033             return hookReturn;
1034 
1035         return -2; /* let firebug service decide to break or not */
1036     },
1037 
1038     onUncaughtException: function(errorInfo)
1039     {
1040         var context = this.breakContext;
1041         delete this.breakContext;
1042 
1043         Firebug.Errors.logScriptError(context, errorInfo, false);
1044         return -2;
1045     },
1046 
1047     onEvalScriptCreated: function(frame, outerScript, innerScripts)
1048     {
1049         try
1050         {
1051             if (FBTrace.DBG_EVAL) FBTrace.sysout("debugger.onEvalLevelScript script.fileName="+outerScript.fileName+"\n");
1052             var context = this.breakContext;
1053             delete this.breakContext;
1054 
1055             var sourceFile = this.getEvalLevelSourceFile(frame, context, innerScripts);
1056 
1057             if (FBTrace.DBG_EVAL)
1058                 FBTrace.sysout("debugger.onEvalScriptCreated url="+sourceFile.href, FBL.getCorrectedStackTrace(frame, context));
1059 
1060             dispatch(this.fbListeners,"onEvalScriptCreated",[context, frame, sourceFile.href]);
1061             return sourceFile;
1062         }
1063         catch (e)
1064         {
1065             if (FBTrace.DBG_EVAL || FBTrace.DBG_ERRORS)
1066                 FBTrace.sysout("onEvalScriptCreated FaILS ", e);
1067         }
1068     },
1069 
1070     onEventScriptCreated: function(frame, outerScript, innerScripts)
1071     {
1072         if (FBTrace.DBG_EVENTS) FBTrace.sysout("debugger.onEventScriptCreated script.fileName="+outerScript.fileName+"\n");
1073         var context = this.breakContext;
1074         delete this.breakContext;
1075 
1076         var script = frame.script;
1077         var creatorURL = normalizeURL(frame.script.fileName);
1078         var innerScriptArray = [];
1079         try {
1080             var source = script.functionSource;
1081 
1082             while (innerScripts.hasMoreElements())
1083             {
1084                 var inner = innerScripts.getNext();
1085                 source += "\n"+inner.functionSource;
1086                 innerScriptArray.push(inner);
1087             }
1088 
1089         } catch (exc) {
1090             /*Bug 426692 */
1091             var source = creatorURL + "/"+getUniqueId();
1092         }
1093 
1094         var lines = splitLines(source);
1095 
1096         var urlDescribed = this.getDynamicURL(context, normalizeURL(frame.script.fileName), source, "event");
1097         var url = urlDescribed.href;
1098 
1099         context.sourceCache.invalidate(url);
1100         context.sourceCache.storeSplitLines(url, lines);
1101 
1102         var sourceFile = new Firebug.EventSourceFile(url, frame.script, "event:"+script.functionName+"."+script.tag, lines, new ArrayEnumerator(innerScriptArray));
1103         this.watchSourceFile(context, sourceFile);
1104 
1105         if (FBTrace.DBG_EVENTS)
1106             FBTrace.sysout("debugger.onEventScriptCreated url="+sourceFile.href+"\n");
1107 
1108         if (FBTrace.DBG_EVENTS)
1109              FBTrace.sysout("debugger.onEventScriptCreated sourceFileMap:", context.sourceFileMap);
1110         if (FBTrace.DBG_SOURCEFILES)
1111             FBTrace.sysout("debugger.onEventScriptCreated sourcefile="+sourceFile.toString()+" -> "+context.getName()+"\n");
1112 
1113         dispatch(this.fbListeners,"onEventScriptCreated",[context, frame, url]);
1114         return sourceFile;
1115     },
1116 
1117     // We just compiled a bunch of JS, eg a script tag in HTML.  We are about to run the outerScript.
1118     onTopLevelScriptCreated: function(frame, outerScript, innerScripts)
1119     {
1120         if (FBTrace.DBG_TOPLEVEL) FBTrace.sysout("debugger("+this.debuggerName+").onTopLevelScriptCreated script.fileName="+outerScript.fileName+"\n");
1121         var context = this.breakContext;
1122         delete this.breakContext;
1123 
1124         // This is our only chance to get the linetable for the outerScript since it will run and be GC next.
1125         var script = frame.script;
1126         var url = normalizeURL(script.fileName);
1127 
1128         if (FBTrace.DBG_TOPLEVEL) FBTrace.sysout("debugger.onTopLevelScriptCreated outerScript.tag="+outerScript.tag+" has fileName="+outerScript.fileName+"\n");
1129 
1130         var sourceFile = context.sourceFileMap[url];
1131         if (sourceFile && (sourceFile instanceof Firebug.TopLevelSourceFile) )      // TODO test multiple script tags in one html file
1132         {
1133             if (FBTrace.DBG_SOURCEFILES) FBTrace.sysout("debugger.onTopLevelScriptCreated reuse sourcefile="+sourceFile.toString()+" -> "+context.getName()+" ("+context.uid+")"+"\n");
1134             if (!sourceFile.outerScript || !sourceFile.outerScript.isValid)
1135                 sourceFile.outerScript = outerScript;
1136             Firebug.SourceFile.addScriptsToSourceFile(sourceFile, outerScript, innerScripts);
1137         }
1138         else
1139         {
1140             sourceFile = new Firebug.TopLevelSourceFile(url, script, script.lineExtent, innerScripts);
1141             this.watchSourceFile(context, sourceFile);
1142             if (FBTrace.DBG_SOURCEFILES) FBTrace.sysout("debugger.onTopLevelScriptCreated create sourcefile="+sourceFile.toString()+" -> "+context.getName()+" ("+context.uid+")"+"\n");
1143         }
1144 
1145         dispatch(this.fbListeners,"onTopLevelScriptCreated",[context, frame, sourceFile.href]);
1146         return sourceFile;
1147     },
1148 
1149     getContextByFrame: function(frame)
1150     {
1151         var win = getFrameScopeWindowAncestor(frame);
1152         return win ? TabWatcher.getContextByWindow(win) : null;
1153     },
1154 
1155     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1156 
1157     watchSourceFile: function(context, sourceFile)
1158     {
1159         context.addSourceFile(sourceFile);  // store in the context and notify listeners
1160         //fbs.watchSourceFile(sourceFile);    // tell the service to watch this file
1161     },
1162 
1163     unwatchSourceFile: function(context, sourceFile)
1164     {
1165         //fbs.unwatchSourceFile(sourceFile);
1166         context.removeSourceFile(sourceFile);
1167     },
1168 
1169     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1170 
1171     onToggleBreakpoint: function(url, lineNo, isSet, props)
1172     {
1173         if (props.debugger != this) // then not for us
1174         {
1175             if (FBTrace.DBG_BP) FBTrace.sysout("debugger("+this.debuggerName+").onToggleBreakpoint ignoring toggle for "+(props.debugger?props.debugger.debuggerName:props.debugger)+" target "+lineNo+"@"+url+"\n");
1176             return;
1177         }
1178 
1179         for (var i = 0; i < TabWatcher.contexts.length; ++i)
1180         {
1181             var context = TabWatcher.contexts[i];
1182             var sourceFile = context.sourceFileMap[url];
1183             if (sourceFile) {
1184                 if (FBTrace.DBG_BP)
1185                     FBTrace.sysout("debugger("+this.debuggerName+").onToggleBreakpoint found context "+context.getName());
1186 
1187                 if (!isSet && context.dynamicURLhasBP)
1188                     this.checkDynamicURLhasBP(context);
1189 
1190                 var panel = context.getPanel("script", true);
1191                 if (!panel)
1192                 {
1193                     if (FBTrace.DBG_ERRORS)
1194                         FBTrace.sysout("onToggleBreakpoint no panel in context "+context.getName());
1195                     return;
1196                 }
1197 
1198                 panel.context.invalidatePanels("breakpoints");
1199 
1200                 var sourceBox = panel.getSourceBoxByURL(url);
1201                 if (!sourceBox)
1202                 {
1203                     if (FBTrace.DBG_BP)
1204                         FBTrace.sysout("debugger("+this.debuggerName+").onToggleBreakpoint context "+i+" script panel no sourcebox for url: "+url, panel.sourceBoxes);
1205                 }
1206 
1207                 var row = sourceBox.getLineNode(lineNo);
1208                 if (FBTrace.DBG_BP)
1209                     FBTrace.sysout(i+") onToggleBreakpoint getLineNode="+row+" lineNo="+lineNo+" context:"+context.getName()+"\n");
1210                 if (!row)
1211                     continue;  // we *should* only be called for lines in the viewport...
1212 
1213                 row.setAttribute("breakpoint", isSet);
1214                 if (isSet && props)
1215                 {
1216                     row.setAttribute("condition", props.condition ? "true" : "false");
1217                     if (props.condition)  // issue 1371
1218                     {
1219                         var watchPanel = this.ableWatchSidePanel(context);
1220                         watchPanel.addWatch(props.condition);
1221                     }
1222                     row.setAttribute("disabledBreakpoint", new Boolean(props.disabled).toString());
1223                 }
1224                 else
1225                 {
1226                     row.removeAttribute("condition");
1227                     if (props.condition)
1228                     {
1229                         var watchPanel = this.ableWatchSidePanel(context);
1230                         watchPanel.removeWatch(props.condition);
1231                         watchPanel.rebuild();
1232                     }
1233                     row.removeAttribute("disabledBreakpoint");
1234                 }
1235                 dispatch(this.fbListeners, "onToggleBreakpoint", [context, url, lineNo, isSet]);
1236                 return;
1237             }
1238         }
1239         if (FBTrace.DBG_BP)
1240             FBTrace.sysout("debugger("+this.debuggerName+").onToggleBreakpoint no find context");
1241     },
1242 
1243     onToggleErrorBreakpoint: function(url, lineNo, isSet)
1244     {
1245         for (var i = 0; i < TabWatcher.contexts.length; ++i)
1246         {
1247             var context = TabWatcher.contexts[i];
1248             var panel = context.getPanel("console", true);
1249             if (panel)
1250             {
1251                 panel.context.invalidatePanels("breakpoints");
1252 
1253                 for (var row = panel.panelNode.firstChild; row; row = row.nextSibling)
1254                 {
1255                     var error = row.firstChild.repObject;
1256                     if (error instanceof ErrorMessage && error.href == url && error.lineNo == lineNo)
1257                     {
1258                         if (isSet)
1259                             setClass(row.firstChild, "breakForError");
1260                         else
1261                             removeClass(row.firstChild, "breakForError");
1262 
1263                         dispatch(this.fbListeners, "onToggleErrorBreakpoint", [context, url, lineNo, isSet]);
1264                     }
1265                 }
1266             }
1267         }
1268     },
1269 
1270     onToggleMonitor: function(url, lineNo, isSet)
1271     {
1272         for (var i = 0; i < TabWatcher.contexts.length; ++i)
1273         {
1274             var panel = TabWatcher.contexts[i].getPanel("console", true);
1275             if (panel)
1276                 panel.context.invalidatePanels("breakpoints");
1277         }
1278     },
1279 
1280     checkDynamicURLhasBP: function (context)
1281     {
1282         context.dynamicURLhasBP = false;
1283         for (var url in context.sourceFileMap)
1284         {
1285              var sourceFile = context.sourceFileMap[url];
1286                if (sourceFile.isEval() || sourceFile.isEvent())
1287                {
1288                    fbs.enumerateBreakpoints(url, {call: function setDynamicIfSet(url, lineNo)
1289                    {
1290                        context.dynamicURLhasBP = true;
1291                    }});
1292                }
1293                if (context.dynamicURLhasBP)
1294                    break;
1295         }
1296         if (FBTrace.DBG_SOURCEFILES || FBTrace.DBG_BP)
1297             FBTrace.sysout("debugger.checkDynamicURLhasBP "+context.dynamicURLhasBP);
1298     },
1299 
1300     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1301     // XXXjjb this code is not called, because I found the scheme for detecting Function too complex.
1302     // I'm leaving it here to remind us that we need to support new Function().
1303     onFunctionConstructor: function(frame, ctor_script)
1304     {
1305        try
1306         {
1307             var context = this.breakContext;
1308             delete this.breakContext;
1309 
1310             var sourceFile = this.createSourceFileForFunctionConstructor(frame, ctor_script, context);
1311 
1312             if (FBTrace.DBG_EVAL)
1313             {
1314                 FBTrace.sysout("debugger.onFunctionConstructor tag="+ctor_script.tag+" url="+sourceFile.href+"\n");
1315                 FBTrace.sysout( traceToString(FBL.getCorrectedStackTrace(frame, context))+"\n" );
1316             }
1317 
1318             dispatch(this.fbListeners,"onFunctionConstructor",[context, frame, ctor_script, sourceFile.href]);
1319             return sourceFile.href;
1320         }
1321         catch(exc)
1322         {
1323             ERROR("debugger.onFunctionConstructor failed: "+exc);
1324             if (FBTrace.DBG_EVAL)
1325                 FBTrace.sysout("debugger.onFunctionConstructor failed: ",exc);
1326             return null;
1327         }
1328 
1329     },
1330 
1331     createSourceFileForFunctionConstructor: function(caller_frame, ctor_script, context)
1332     {
1333         var ctor_expr = null; // this.getConstructorExpression(caller_frame, context);
1334         if (FBTrace.DBG_EVAL) FBTrace.sysout("createSourceFileForFunctionConstructor ctor_expr:"+ctor_expr+"\n");
1335         if (ctor_expr)
1336             var source  = this.getEvalBody(caller_frame, "lib.createSourceFileForFunctionConstructor ctor_expr", 1, ctor_expr);
1337         else
1338             var source = " bah createSourceFileForFunctionConstructor"; //ctor_script.functionSource;
1339 
1340         if (FBTrace.DBG_EVAL) FBTrace.sysout("createSourceFileForFunctionConstructor source:"+source+"\n");
1341         var url = this.getDynamicURL(context, normalizeURL(caller_frame.script.fileName), source, "Function");
1342 
1343         var lines = context.sourceCache.store(url.href, source);
1344         var sourceFile = new Firebug.FunctionConstructorSourceFile(url, caller_frame.script, ctor_expr, lines.length);
1345         this.watchSourceFile(context, sourceFile);
1346 
1347         if (FBTrace.DBG_SOURCEFILES) FBTrace.sysout("debugger.onNewFunction sourcefile="+sourceFile.toString()+" -> "+context.getName()+"\n");
1348 
1349         return sourceFile;
1350     },
1351 
1352     getConstructorExpression: function(caller_frame, context)
1353     {
1354         // We believe we are just after the ctor call.
1355         var decompiled_lineno = getLineAtPC(caller_frame, context);
1356         if (FBTrace.DBG_EVAL) FBTrace.sysout("debugger.getConstructoreExpression decompiled_lineno:"+decompiled_lineno+"\n");
1357 
1358         var decompiled_lines = splitLines(caller_frame.script.functionSource);  // TODO place in sourceCache?
1359         if (FBTrace.DBG_EVAL) FBTrace.sysout("debugger.getConstructoreExpression decompiled_lines:",decompiled_lines);
1360 
1361         var candidate_line = decompiled_lines[decompiled_lineno - 1]; // zero origin
1362         if (FBTrace.DBG_EVAL) FBTrace.sysout("debugger.getConstructoreExpression candidate_line:"+candidate_line+"\n");
1363 
1364         if (candidate_line && candidate_line != null)
1365             {
1366                 var m = reFunction.exec(candidate_line);
1367                 if (m)
1368                     var arguments =  m[1];     // TODO Lame: need to count parens, with escapes and quotes
1369             }
1370         if (FBTrace.DBG_EVAL) FBTrace.sysout("debugger.getConstructoreExpression arguments:"+arguments+"\n");
1371         if (arguments) // need to break down commas and get last arg.
1372         {
1373                 var lastComma = arguments.lastIndexOf(',');
1374                 return arguments.substring(lastComma+1);  // if -1 then 0
1375         }
1376         return null;
1377     },
1378     // end of guilt trip
1379     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1380 
1381     // Called by debugger.onEval() to store eval() source.
1382     // The frame has the blank-function-name script and it is not the top frame.
1383     // The frame.script.fileName is given by spidermonkey as file of the first eval().
1384     // The frame.script.baseLineNumber is given by spidermonkey as the line of the first eval() call
1385     // The source that contains the eval() call is the source of our caller.
1386     // If our caller is a file, the source of our caller is at frame.script.baseLineNumber
1387     // If our caller is an eval, the source of our caller is TODO Check Test Case
1388     getEvalLevelSourceFile: function(frame, context, innerScripts)
1389     {
1390         var eval_expr = this.getEvalExpression(frame, context);
1391         if (FBTrace.DBG_EVAL) FBTrace.sysout("getEvalLevelSourceFile eval_expr:"+eval_expr+"\n");
1392 
1393         if (eval_expr && !Firebug.decompileEvals)
1394         {
1395             var source  = this.getEvalBody(frame, "lib.getEvalLevelSourceFile.getEvalBody", 1, eval_expr);
1396             var mapType = PCMAP_SOURCETEXT;
1397         }
1398         else
1399         {
1400             var source = frame.script.functionSource; // XXXms - possible crash on OSX FF2
1401             var mapType = PCMAP_PRETTYPRINT;
1402         }
1403 
1404         var lines = splitLines(source);
1405 
1406         if (FBTrace.DBG_EVAL)
1407             FBTrace.sysout("getEvalLevelSourceFile "+lines.length+ "lines, mapType:"+((mapType==PCMAP_SOURCETEXT)?"SOURCE":"PRETTY")+" source:"+source+"\n");
1408 
1409         var url = this.getDynamicURL(context, normalizeURL(frame.script.fileName), lines, "eval");
1410 
1411         context.sourceCache.invalidate(url.href);
1412         context.sourceCache.storeSplitLines(url.href, lines);
1413 
1414         var sourceFile = new Firebug.EvalLevelSourceFile(url, frame.script, eval_expr, lines, mapType, innerScripts);
1415         this.watchSourceFile(context, sourceFile);
1416 
1417         if (FBTrace.DBG_SOURCEFILES)
1418             FBTrace.sysout("debugger.getEvalLevelSourceFile sourcefile="+sourceFile.toString()+" -> "+context.getName()+"\n");
1419 
1420         return sourceFile;
1421     },
1422 
1423     getDynamicURL: function(context, callerURL, lines, kind)
1424     {
1425         var url = this.getURLFromLastLine(context, lines);
1426         if (url)
1427             return url;
1428 
1429         var url = this.getSequentialURL(context, callerURL, kind);
1430         if (url)
1431             return url;
1432 
1433         var url = this.getURLFromMD5(callerURL, lines, kind);
1434         if (url)
1435             return url;
1436 
1437         var url = this.getDataURLForScript(callerURL, lines);
1438         if (url)
1439             return url;
1440 
1441         return url;
1442     },
1443 
1444     getURLFromLastLine: function(context, lines)
1445     {
1446         var url = null;
1447         // Ignores any trailing whitespace in |source|
1448         const reURIinComment = /\/\/@\ssourceURL=\s*(\S*?)\s*$/m;
1449         var m = reURIinComment.exec(lines[lines.length - 1]);
1450         if (m)
1451         {
1452             // add context info to the sourceURL so eval'd sources are grouped correctly in the source file list
1453             if (m[1] && m[1].indexOf('://') == -1) {
1454                 var loc = context.window.location;
1455                 if (m[1].charAt(0) != '/') m[1] = '/'+m[1]; // prepend leading slash if necessary
1456                 m[1] = loc.protocol + '//' + loc.host + m[1]; // prepend protocol and host
1457             }
1458 
1459             var href = new String(m[1]);
1460 
1461             url = {href: href, kind: "source"};
1462             if (FBTrace.DBG_SOURCEFILES)
1463                 FBTrace.sysout("debugger.getURLFromLastLine "+url.href, url);
1464         }
1465         else
1466         {
1467             if (FBTrace.DBG_SOURCEFILES)
1468                 FBTrace.sysout("debugger.getURLFromLastLine no match"+lines[lines.length - 1]);
1469         }
1470         return url;
1471     },
1472 
1473     getSequentialURL: function(context, callerURL, kind)
1474     {
1475         var url = null;
1476         if (!context.dynamicURLhasBP)
1477         {
1478             // If no breakpoints live in dynamic code then we don't need to compare
1479             // the previous and reloaded source. In that case let's use a cheap URL.
1480             var href = new String(callerURL + (kind ? "/"+kind+"/" : "/nokind/")+"seq/" +(context.dynamicURLIndex++));
1481             url = {href: href, kind: "seq"};
1482             if (FBTrace.DBG_SOURCEFILES || isNaN(context.dynamicURLIndex) )
1483                 FBTrace.sysout("debugger.getSequentialURL context:"+context.getName()+" url:"+url.href+" index: "+context.dynamicURLIndex, url);
1484         }
1485         return url;
1486     },
1487 
1488     getURLFromMD5: function(callerURL, lines, kind)
1489     {
1490         this.hash_service.init(this.nsICryptoHash.MD5);
1491         var source = lines.join('\n'); // we could double loop, would that be any faster?
1492         byteArray = [];
1493         for (var j = 0; j < source.length; j++)
1494         {
1495             byteArray.push( source.charCodeAt(j) );
1496         }
1497         this.hash_service.update(byteArray, byteArray.length);
1498         var hash = this.hash_service.finish(true);
1499 
1500         // encoding the hash should be ok, it should be information-preserving? Or at least reversable?
1501         var href= new String(callerURL + (kind ? "/"+kind+"/" : "/nokind/")+"MD5/" + encodeURIComponent(hash));
1502         url = {href: href, kind: "MD5"};
1503         if (FBTrace.DBG_SOURCEFILES)
1504             FBTrace.sysout("debugger.getURLFromMD5 "+url.href, url);
1505         return url;
1506     },
1507 
1508     getDataURLForScript: function(callerURL, lines)
1509     {
1510         var url = null;
1511         var href = null;
1512         if (!source)
1513             href = "eval."+script.tag;
1514         else
1515         {
1516             // data:text/javascript;fileName=x%2Cy.js;baseLineNumber=10,<the-url-encoded-data>
1517             href = new String("data:text/javascript;");
1518             href += "fileName="+encodeURIComponent(callerURL);
1519             var source = lines.join('\n');
1520             //url +=  ";"+ "baseLineNumber="+encodeURIComponent(script.baseLineNumber) +
1521             href +="," + encodeURIComponent(source);
1522         }
1523         url = {href:href, kind:"data"};
1524         if (FBTrace.DBG_SOURCEFILES)
1525             FBTrace.sysout("debugger.getDataURLForScript "+url.href, url);
1526         return url;
1527     },
1528 
1529     // ********************************************************************************
1530     getEvalExpression: function(frame, context)
1531     {
1532         var expr = this.getEvalExpressionFromEval(frame, context);  // eval in eval
1533 
1534         return (expr) ? expr : this.getEvalExpressionFromFile(normalizeURL(frame.script.fileName), frame.script.baseLineNumber, context);
1535     },
1536 
1537     getEvalExpressionFromFile: function(url, lineNo, context)
1538     {
1539         if (context && context.sourceCache)
1540         {
1541             var in_url = FBL.reJavascript.exec(url);
1542             if (in_url)
1543             {
1544                 var m = reEval.exec(in_url[1]);
1545                 if (m)
1546                     return m[1];
1547                 else
1548                     return null;
1549             }
1550 
1551             var htm = reHTM.exec(url);
1552             if (htm) {
1553                 lineNo = lineNo + 1; // embedded scripts seem to be off by one?  XXXjjb heuristic
1554             }
1555             // Walk backwards from the first line in the function until we find the line which
1556             // matches the pattern above, which is the eval call
1557             var line = "";
1558             for (var i = 0; i < 3; ++i)
1559             {
1560                 line = context.sourceCache.getLine(url, lineNo-i) + line;
1561                 if (line && line != null)
1562                 {
1563                     var m = reEval.exec(line);
1564                     if (m)
1565                         return m[1];
1566                 }
1567             }
1568         }
1569         return null;
1570     },
1571 
1572     getEvalExpressionFromEval: function(frame, context)
1573     {
1574         var callingFrame = frame.callingFrame;
1575         var sourceFile = Firebug.SourceFile.getSourceFileByScript(context, callingFrame.script);
1576         if (sourceFile)
1577         {
1578             if (FBTrace.DBG_EVAL) {
1579                 FBTrace.sysout("debugger.getEvalExpressionFromEval sourceFile.href="+sourceFile.href+"\n");
1580                 FBTrace.sysout("debugger.getEvalExpressionFromEval callingFrame.pc="+callingFrame.pc
1581                     +" callingFrame.script.baseLineNumber="+callingFrame.script.baseLineNumber+"\n");
1582             }
1583             var lineNo = callingFrame.script.pcToLine(callingFrame.pc, PCMAP_SOURCETEXT);
1584             lineNo = lineNo - callingFrame.script.baseLineNumber + 1;
1585             var url  = sourceFile.href;
1586 
1587             if (FBTrace.DBG_EVAL && !context.sourceCache)
1588                 FBTrace.sysout("debugger.getEvalExpressionFromEval context.sourceCache null??\n");
1589 
1590             // Walk backwards from the first line in the function until we find the line which
1591             // matches the pattern above, which is the eval call
1592             var line = "";
1593             for (var i = 0; i < 3; ++i)
1594             {
1595                 line = context.sourceCache.getLine(url, lineNo-i) + line;
1596                 if (FBTrace.DBG_EVAL)
1597                     FBTrace.sysout("debugger.getEvalExpressionFromEval lineNo-i="+lineNo+"-"+i+"="+(lineNo-i)+" line:"+line+"\n");
1598                 if (line && line != null)
1599                 {
1600                     var m = reEval.exec(line);
1601                     if (m)
1602                         return m[1];     // TODO Lame: need to count parens, with escapes and quotes
1603                 }
1604             }
1605         }
1606         return null;
1607     },
1608 
1609     getEvalBody: function(frame, asName, asLine, evalExpr)
1610     {
1611         if (evalExpr  && !Firebug.decompileEvals)
1612         {
1613             var result_src = {};
1614             var evalThis = "new String("+evalExpr+");";
1615             var evaled = frame.eval(evalThis, asName, asLine, result_src);
1616 
1617             if (evaled)
1618             {
1619                 var src = unwrapIValue(result_src.value);
1620                 return src;
1621             }
1622             else
1623             {
1624                 var source;
1625                 if(evalExpr == "function(p,a,c,k,e,r")
1626                     source = "/packer/ JS compressor detected";
1627                 else
1628                     source = frame.script.functionSource;
1629                 return source+" /* !eval("+evalThis+")) */";
1630             }
1631         }
1632         else
1633         {
1634             return frame.script.functionSource; // XXXms - possible crash on OSX FF2
1635         }
1636     },
1637 
1638     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1639     // extends Module
1640 
1641     initialize: function()
1642     {
1643         this.nsICryptoHash = Components.interfaces["nsICryptoHash"];
1644 
1645         this.debuggerName =  window.location.href+"--"+FBL.getUniqueId(); /*@explore*/
1646         this.toString = function() { return this.debuggerName; } /*@explore*/
1647         if (FBTrace.DBG_INITIALIZE)
1648             FBTrace.sysout("debugger.initialize "+ this.debuggerName);
1649 
1650         this.hash_service = CCSV("@mozilla.org/security/hash;1", "nsICryptoHash");
1651 
1652         $("cmd_breakOnErrors").setAttribute("checked", Firebug.breakOnErrors);
1653         $("cmd_decompileEvals").setAttribute("checked", Firebug.decompileEvals);
1654 
1655         this.wrappedJSObject = this;  // how we communicate with fbs
1656         this.panelName = "script";
1657 
1658         // This is a service operation, a way of encapsulating fbs which is in turn implementing this
1659         // simple service. We could implment a whole component for this service, but it hardly makes sense.
1660         Firebug.broadcast = function encapsulateFBSBroadcast(message, args)
1661         {
1662             fbs.broadcast(message, args);
1663         }
1664 
1665         this.onFunctionCall = bind(this.onFunctionCall, this);
1666         Firebug.ActivableModule.initialize.apply(this, arguments);
1667     },
1668 
1669     /*
1670      * per-XUL window registration; this method just allows us to keep fbs in this file.
1671      * @param clientAPI an object that implements functions called by fbs for clients.
1672      */
1673     registerClient: function(clientAPI)
1674     {
1675         return fbs.registerClient(clientAPI);
1676     },
1677 
1678     unregisterClient: function(clientAPI)
1679     {
1680         fbs.unregisterClient(clientAPI);
1681     },
1682 
1683     enable: function()
1684     {
1685         if (this.isAlwaysEnabled())
1686             this.registerDebugger(); // allow callbacks for jsd
1687     },
1688 
1689     disable: function()
1690     {
1691         this.unregisterDebugger();
1692     },
1693 
1694     initializeUI: function()
1695     {
1696         Firebug.ActivableModule.initializeUI.apply(this, arguments);
1697         this.filterButton = $("fbScriptFilterMenu");
1698         this.filterMenuUpdate();
1699     },
1700 
1701     initContext: function(context, persistedState)
1702     {
1703         if (persistedState)
1704             context.dynamicURLhasBP = persistedState.dynamicURLhasBP;
1705 
1706         context.dynamicURLIndex = 1; // any dynamic urls need to be unique to the context.
1707 
1708         Firebug.ActivableModule.initContext.apply(this, arguments);
1709     },
1710 
1711     reattachContext: function(browser, context)
1712     {
1713         this.filterButton = Firebug.chrome.$("fbScriptFilterMenu");  // connect to the button in the new window, not 'window'
1714         this.filterMenuUpdate();
1715         Firebug.ActivableModule.reattachContext.apply(this, arguments);
1716     },
1717 
1718     loadedContext: function(context)
1719     {
1720         var watchPanel = this.ableWatchSidePanel(context);
1721         var needNow = watchPanel && watchPanel.watches;
1722         var watchPanelState = Firebug.getPanelState({name: "watches", context: context});
1723         var needPersistent = watchPanelState && watchPanelState.watches;
1724         if (needNow || needPersistent)
1725         {
1726             Firebug.CommandLine.isReadyElsePreparing(context);
1727             if (watchPanel)
1728             {
1729                 context.setTimeout(function refreshWatchesAfterCommandLineReady()
1730                 {
1731                     watchPanel.refresh();
1732                 });
1733             }
1734         }
1735 
1736         if (FBTrace.DBG_SOURCEFILES)
1737             FBTrace.sysout("debugger("+this.debuggerName+").loadedContext enabled on load: "+context.onLoadWindowContent+" context.sourceFileMap", context.sourceFileMap);
1738     },
1739 
1740     unwatchWindow: function(context, win)  // clean up the source file map in case the frame is being reloaded.
1741     {
1742         var scriptTags = win.document.getElementsByTagName("script");
1743         for (var i = 0; i < scriptTags.length; i++)
1744         {
1745             var src = scriptTags[i].getAttribute("src");
1746             src = src ? src : safeGetWindowLocation(win);
1747 
1748             // If the src is not in the source map, try to use absolute url.
1749             if (!context.sourceFileMap[src])
1750                 src = absoluteURL(src, win.location.href);
1751 
1752             delete context.sourceFileMap[src];
1753 
1754             if (FBTrace.DBG_SOURCEFILES)
1755                 FBTrace.sysout("debugger.unWatchWindow; delete sourceFileMap entry for " + src);
1756         }
1757         if (scriptTags.length > 0)
1758             context.invalidatePanels('script');
1759     },
1760 
1761     destroyContext: function(context, persistedState)
1762     {
1763         Firebug.ActivableModule.destroyContext.apply(this, arguments);
1764 
1765         if (context.stopped)
1766         {
1767             TabWatcher.cancelNextLoad = true;  // the abort will call resume, but the nestedEventLoop will continue the load.
1768             this.abort(context);
1769         }
1770 
1771         if(persistedState)
1772         {
1773             if (context.dynamicURLhasBP)
1774                 persistedState.dynamicURLhasBP = context.dynamicURLhasBP;
1775             else
1776                 delete persistedState.dynamicURLhasBP;
1777         }
1778     },
1779 
1780     updateOption: function(name, value)
1781     {
1782         if (name == "breakOnErrors")
1783             $("cmd_breakOnErrors").setAttribute("checked", value);
1784         else if (name == "decompileEvals")
1785             $("cmd_decompileEvals").setAttribute("checked", value);
1786     },
1787 
1788     getObjectByURL: function(context, url)
1789     {
1790         var sourceFile = getSourceFileByHref(url, context);
1791         if (sourceFile)
1792             return new SourceLink(sourceFile.href, 0, "js");
1793     },
1794 
1795     shutdown: function()
1796     {
1797         fbs.unregisterDebugger(this);
1798     },
1799 
1800     registerDebugger: function() // 1.3.1 safe for multiple calls
1801     {
1802         if (FBTrace.DBG_INITIALIZE)
1803             FBTrace.sysout("registerDebugger this.registered: "+this.registered);
1804 
1805         if (this.registered)
1806             return;
1807         this.registered = true;
1808 
1809         var check = fbs.registerDebugger(this);  //  this will eventually set 'jsd' on the statusIcon
1810 
1811         if (FBTrace.DBG_INITIALIZE)
1812             FBTrace.sysout("debugger.registerDebugger "+check+" debuggers");
1813     },
1814 
1815     unregisterDebugger: function() // 1.3.1 safe for multiple calls
1816     {
1817         if (FBTrace.DBG_INITIALIZE)
1818             FBTrace.sysout("debugger.unregisterDebugger this.registered: "+this.registered);
1819 
1820         if (!this.registered)
1821             return;
1822 
1823         if (Firebug.Profiler.isProfiling()) // stay registered if we are profiling across a reload.
1824             return;
1825 
1826         var check = fbs.unregisterDebugger(this);
1827 
1828         this.registered = false;
1829 
1830         if (FBTrace.DBG_ACTIVATION)
1831             FBTrace.sysout("debugger.unregisterDebugger: "+check+" debuggers");
1832     },
1833 
1834     onSourceFileCreated: function(context, sourceFile)
1835     {
1836         // This event can come at any time, eg by frame reloads or ajax, so we need to update the display.
1837         context.invalidatePanels("script", "breakpoints");
1838     },
1839 
1840     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1841     // extends ActivableModule
1842 
1843     onPanelEnable: function(panelName)
1844     {
1845         if (panelName != this.panelName)
1846             return;
1847 
1848         this.registerDebugger();
1849 
1850         if (FirebugContext && !fbs.isJSDActive())
1851             fbs.unPause();
1852 
1853         if (FBTrace.DBG_PANELS || FBTrace.DBG_ACTIVATION) FBTrace.sysout("debugger.onPanelEnable with panelName: "+panelName);
1854     },
1855 
1856     onPanelDisable: function(panelName)
1857     {
1858         if (panelName != this.panelName)
1859             return;
1860 
1861         if (this.dependents.length > 0)
1862         {
1863             for(var i = 0; i < this.dependents.length; i++)
1864             {
1865                 if (this.dependents[i].isAlwaysEnabled())
1866                 {
1867                     var name = this.dependents[0].dispatchName; // TODO getName() for modules required.
1868                     if (FirebugContext)
1869                         Firebug.Console.log("Cannot disable the script panel, "+name+" panel requires it", FirebugContext);
1870                     if (FBTrace.DBG_PANELS) FBTrace.sysout("debugger.onPanelDisable rejected: "+ name+" dependent, with panelName: "+panelName);
1871                     return;
1872                 }
1873             }
1874         }
1875         // else no dependents enabled:
1876         this.unregisterDebugger();
1877 
1878         if (FBTrace.DBG_PANELS || FBTrace.DBG_ACTIVATION) FBTrace.sysout("debugger.onPanelDisable with panelName: "+panelName);
1879         this.clearAllBreakpoints();
1880     },
1881 
1882     onDependentModuleChange: function(dependentAddedOrRemoved)
1883     {
1884         if (this.dependents.length > 0) // then we have dependents now
1885         {
1886             if (!this.isAlwaysEnabled()) // then we need to enable
1887             {
1888                 this.setDefaultState(true);
1889                 if (FirebugContext)
1890                     Firebug.Console.log("enabling javascript debugger to support "+dependentAddedOrRemoved.dispatchName, FirebugContext);
1891             }
1892         }
1893     },
1894 
1895     onSuspendFirebug: function()
1896     {
1897         if (!Firebug.Debugger.isAlwaysEnabled())
1898             return;
1899 
1900         var paused = fbs.pause();  // can be called multiple times.
1901 
1902         if (FBTrace.DBG_ACTIVATION)
1903             FBTrace.sysout("debugger.onSuspendFirebug paused: "+paused+" isAlwaysEnabled " +Firebug.Debugger.isAlwaysEnabled()+"\n");
1904 
1905         if (!paused)  // then we failed to suspend, undo
1906             return true;
1907 
1908         return false;
1909     },
1910 
1911     onResumeFirebug: function()
1912     {
1913         if (!Firebug.Debugger.isAlwaysEnabled())
1914             return;
1915 
1916         var unpaused = fbs.unPause();
1917 
1918         if (FBTrace.DBG_ACTIVATION)
1919             FBTrace.sysout("debugger.onResumeFirebug unpaused: "+unpaused+" isAlwaysEnabled " +Firebug.Debugger.isAlwaysEnabled());
1920         if (FBTrace.DBG_ERRORS && !this.registered)
1921             FBTrace.sysout("debugger.onResumeFirebug but debugger not registered! *** ");
1922     },
1923 
1924     ableWatchSidePanel: function(context)
1925     {
1926         if (Firebug.Console.isAlwaysEnabled())
1927         {
1928             var watchPanel = context.getPanel("watches", true);
1929             if (watchPanel)
1930                 return watchPanel;
1931         }
1932 
1933         return null;
1934     },
1935 
1936     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1937     // Menu in toolbar.
1938 
1939     onScriptFilterMenuTooltipShowing: function(tooltip, context)
1940     {
1941         if (FBTrace.DBG_OPTIONS)
1942             FBTrace.sysout("onScriptFilterMenuTooltipShowing not implemented");
1943     },
1944 
1945     onScriptFilterMenuCommand: function(event, context)
1946     {
1947         var menu = event.target;
1948         Firebug.setPref(Firebug.servicePrefDomain, "scriptsFilter", menu.value);
1949         Firebug.Debugger.filterMenuUpdate();
1950     },
1951 
1952     menuFullLabel:
1953     {
1954         static: $STR("ScriptsFilterStatic"),
1955         evals: $STR("ScriptsFilterEval"),
1956         events: $STR("ScriptsFilterEvent"),
1957         all: $STR("ScriptsFilterAll"),
1958     },
1959 
1960     menuShortLabel:
1961     {
1962         static: $STR("ScriptsFilterStaticShort"),
1963         evals: $STR("ScriptsFilterEvalShort"),
1964         events: $STR("ScriptsFilterEventShort"),
1965         all: $STR("ScriptsFilterAllShort"),
1966     },
1967 
1968     onScriptFilterMenuPopupShowing: function(menu, context)
1969     {
1970         if (this.menuTooltip)
1971             this.menuTooltip.fbEnabled = false;
1972 
1973         var items = menu.getElementsByTagName("menuitem");
1974         var value = this.filterButton.value;
1975 
1976         for (var i=0; i<items.length; i++)
1977         {
1978             var option = items[i].value;
1979             if (!option)
1980                 continue;
1981 
1982             if (option == value)
1983                 items[i].setAttribute("checked", "true");
1984 
1985             items[i].label = Firebug.Debugger.menuFullLabel[option];
1986         }
1987 
1988         return true;
1989     },
1990 
1991     onScriptFilterMenuPopupHiding: function(tooltip, context)
1992     {
1993         if (this.menuTooltip)
1994             this.menuTooltip.fbEnabled = true;
1995 
1996         return true;
1997     },
1998 
1999     filterMenuUpdate: function()
2000     {
2001         var value = Firebug.getPref(Firebug.servicePrefDomain, "scriptsFilter");
2002         this.filterButton.value = value;
2003         this.filterButton.label = this.menuShortLabel[value];
2004         this.filterButton.removeAttribute("disabled");
2005         this.filterButton.setAttribute("value", value);
2006         if (FBTrace.DBG_OPTIONS)
2007             FBTrace.sysout("debugger.filterMenuUpdate value: "+value+" label:"+this.filterButton.label+'\n');
2008     },
2009 });
2010 
2011 // ************************************************************************************************
2012 
2013 
2014 Firebug.ScriptPanel = function() {};
2015 
2016 
2017 /*
2018  * object used to markup Javascript source lines.
2019  * In the namespace Firebug.ScriptPanel.
2020  */
2021 
2022 Firebug.ScriptPanel.decorator = extend(new Firebug.SourceBoxDecorator,
2023 {
2024     decorate: function(sourceBox, sourceFile)
2025     {
2026         this.markExecutableLines(sourceBox);
2027         this.setLineBreakpoints(sourceBox.repObject, sourceBox)
2028     },
2029 
2030     markExecutableLines: function(sourceBox)
2031     {
2032         var sourceFile = sourceBox.repObject;
2033         if (FBTrace.DBG_BP || FBTrace.DBG_LINETABLE) FBTrace.sysout("debugger.markExecutableLines START: "+sourceFile.toString(), sourceFile.getLineRanges());
2034         var lineNo = sourceBox.firstViewableLine;
2035         while( lineNode = sourceBox.getLineNode(lineNo) )
2036         {
2037             var script = sourceFile.scriptsIfLineCouldBeExecutable(lineNo, true);
2038 
2039             if (FBTrace.DBG_LINETABLE) FBTrace.sysout("debugger.markExecutableLines ["+lineNo+"]="+(script?script.tag:"X")+"\n");
2040             if (script)
2041                 lineNode.setAttribute("executable", "true");
2042             else
2043                 lineNode.removeAttribute("executable");
2044 
2045             lineNo++;
2046         }
2047         if (FBTrace.DBG_BP || FBTrace.DBG_LINETABLE)
2048             FBTrace.sysout("debugger.markExecutableLines DONE: "+sourceFile.toString()+"\n");
2049     },
2050 
2051     setLineBreakpoints: function(sourceFile, sourceBox)
2052     {
2053         fbs.enumerateBreakpoints(sourceFile.href, {call: function(url, line, props, script)
2054         {
2055             var scriptRow = sourceBox.getLineNode(line);
2056             if (scriptRow)
2057             {
2058                 scriptRow.setAttribute("breakpoint", "true");
2059                 if (props.disabled)
2060                     scriptRow.setAttribute("disabledBreakpoint", "true");
2061                 if (props.condition)
2062                     scriptRow.setAttribute("condition", "true");
2063             }
2064             if (FBTrace.DBG_LINETABLE)
2065                 FBTrace.sysout("debugger.setLineBreakpoints found "+scriptRow+" for "+line+"@"+sourceFile.href+"\n");
2066         }});
2067     },
2068 });
2069 
2070 Firebug.ScriptPanel.prototype = extend(Firebug.SourceBoxPanel,
2071 {
2072     /*
2073     * Framework connection
2074     */
2075     updateSourceBox: function(sourceBox)
2076     {
2077         if (this.scrollInfo && (this.scrollInfo.location == this.location))
2078             this.scrollToLine(this.location, this.scrollInfo.previousCenterLine);
2079         delete this.scrollInfo;
2080     },
2081 
2082     /*
2083     * Framework connection
2084     */
2085     getSourceType: function()
2086     {
2087         return "js";
2088     },
2089 
2090     /*
2091      * Framework connection
2092      */
2093     getDecorator: function(sourceBox)
2094     {
2095         return Firebug.ScriptPanel.decorator;
2096     },
2097 
2098     initialize: function(context, doc)
2099     {
2100         this.location = null;
2101         Firebug.SourceBoxPanel.initialize.apply(this, arguments);
2102     },
2103 
2104     // *************************************************************************************
2105     showFunction: function(fn)
2106     {
2107         var sourceLink = findSourceForFunction(fn, this.context);
2108         if (sourceLink)
2109         {
2110             this.showSourceLink(sourceLink);
2111         }
2112         else
2113         {
2114             if (FBTrace.DBG_ERRORS) FBTrace.sysout("no sourcelink for function"); // want to avoid the debugger panel if possible
2115         }
2116     },
2117 
2118     showSourceLink: function(sourceLink)
2119     {
2120         var sourceFile = getSourceFileByHref(sourceLink.href, this.context);
2121         if (sourceFile)
2122         {
2123             this.navigate(sourceFile);
2124             if (sourceLink.line)
2125             {
2126                 this.scrollToLine(sourceLink.href, sourceLink.line, this.jumpHighlightFactory(sourceLink.line, this.context));
2127                 dispatch([Firebug.A11yModel], "onShowSourceLink", [this, sourceLink.line]);
2128             }
2129             if (sourceLink == this.selection)  // then clear it so the next link will scroll and highlight.
2130                 delete this.selection;
2131         }
2132     },
2133 
2134     showStackFrame: function(frame)
2135     {
2136         if (!frame || (frame && !frame.isValid))
2137         {
2138             if (FBTrace.DBG_STACK) FBTrace.sysout("showStackFrame no valid frame\n");
2139             this.showNoStackFrame();
2140             return;
2141         }
2142 
2143         this.context.currentFrame = frame;
2144         var sourceFile = Firebug.SourceFile.getSourceFileByScript(this.context, this.context.currentFrame.script);
2145         if (!sourceFile)
2146         {