1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/chrome/firefox",
  7     "firebug/chrome/reps",
  8     "firebug/lib/domplate",
  9     "arch/javascripttool",
 10     "arch/compilationunit",
 11     "firebug/lib/locale",
 12     "firebug/lib/events",
 13     "firebug/lib/url",
 14     "firebug/js/sourceLink",
 15     "firebug/js/stackFrame",
 16     "firebug/lib/css",
 17     "firebug/lib/dom",
 18     "firebug/chrome/window",
 19     "firebug/lib/search",
 20     "firebug/lib/persist",
 21     "firebug/lib/system",
 22     "firebug/chrome/menu",
 23     "firebug/trace/debug",
 24     "firebug/lib/keywords",
 25     "firebug/editor/editorSelector",
 26     "firebug/chrome/infotip",
 27     "firebug/chrome/searchBox",
 28     "firebug/js/sourceBox",
 29     "firebug/js/watchPanel",
 30 ],
 31 function (Obj, Firebug, Firefox, FirebugReps, Domplate, JavaScriptTool, CompilationUnit,
 32     Locale, Events, Url, SourceLink, StackFrame, Css, Dom, Win, Search, Persist,
 33     System, Menu, Debug, Keywords) {
 34 
 35 // ********************************************************************************************* //
 36 // Script panel
 37 
 38 Firebug.ScriptPanel = function() {};
 39 
 40 for (var p in Firebug.EditorSelector)
 41 {
 42     if (Firebug.EditorSelector.hasOwnProperty(p))
 43         Firebug.ScriptPanel[p] = Firebug.EditorSelector[p];
 44 }
 45 
 46 Firebug.ScriptPanel.getEditorOptionKey = function()
 47 {
 48     return "JSEditor";
 49 }
 50 
 51 Firebug.ScriptPanel.reLineNumber = /^[^\\]?#(\d*)$/;
 52 
 53 /**
 54  * object used to markup JavaScript source lines.
 55  * In the namespace Firebug.ScriptPanel.
 56  */
 57 Firebug.ScriptPanel.decorator = Obj.extend(new Firebug.SourceBoxDecorator,
 58 {
 59     decorate: function(sourceBox, unused)
 60     {
 61         this.markExecutableLines(sourceBox);
 62         this.setLineBreakpoints(sourceBox.repObject, sourceBox)
 63     },
 64 
 65     markExecutableLines: function(sourceBox)
 66     {
 67         var compilationUnit = sourceBox.repObject;
 68         if (FBTrace.DBG_BP || FBTrace.DBG_LINETABLE)
 69             FBTrace.sysout("script.markExecutableLines START: "+compilationUnit.toString());
 70 
 71         var lineNode;
 72         var lineNo = sourceBox.firstViewableLine;
 73         while (lineNode = sourceBox.getLineNode(lineNo))
 74         {
 75             if (lineNode.alreadyMarked)
 76             {
 77                 lineNo++;
 78                 continue;
 79             }
 80 
 81             var script = compilationUnit.isExecutableLine(lineNo);
 82 
 83             if (FBTrace.DBG_LINETABLE)
 84                 FBTrace.sysout("script.markExecutableLines [" + lineNo + "]=" + script);
 85 
 86             if (script)
 87                 lineNode.setAttribute("executable", "true");
 88             else
 89                 lineNode.removeAttribute("executable");
 90 
 91             lineNode.alreadyMarked = true;
 92 
 93             if (lineNo > sourceBox.lastViewableLine)
 94                 break;
 95 
 96             lineNo++;
 97         }
 98 
 99         if (FBTrace.DBG_BP || FBTrace.DBG_LINETABLE)
100             FBTrace.sysout("script.markExecutableLines DONE: " + compilationUnit.toString());
101     },
102 
103     setLineBreakpoints: function(compilationUnit, sourceBox)
104     {
105         compilationUnit.eachBreakpoint(function setAttributes(line, props)
106         {
107             var scriptRow = sourceBox.getLineNode(line);
108             if (scriptRow)
109             {
110                 scriptRow.setAttribute("breakpoint", "true");
111                 if (props.disabled)
112                     scriptRow.setAttribute("disabledBreakpoint", "true");
113 
114                 if (props.condition)
115                 {
116                     scriptRow.setAttribute("condition", "true");
117                     scriptRow.breakpointCondition = props.condition;
118                 }
119             }
120 
121             if (FBTrace.DBG_LINETABLE)
122             {
123                 FBTrace.sysout("script.setLineBreakpoints found " + scriptRow + " for " + line +
124                     "@" + compilationUnit.getURL(), props);
125             }
126         });
127     },
128 });
129 
130 // ********************************************************************************************* //
131 
132 Firebug.ScriptPanel.prototype = Obj.extend(Firebug.SourceBoxPanel,
133 {
134     dispatchName: "scriptPanel",
135 
136     /**
137      * Framework connection
138      */
139     updateSourceBox: function(sourceBox)
140     {
141         this.location = sourceBox.repObject;
142     },
143 
144     /**
145      * Framework connection
146      */
147     getSourceType: function()
148     {
149         return "js";
150     },
151 
152     /**
153      * Framework connection
154      */
155     getDecorator: function(sourceBox)
156     {
157         return Firebug.ScriptPanel.decorator;
158     },
159 
160     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
161     // TODO Class method
162 
163     onJavaScriptDebugging: function(active)
164     {
165         // If this panel is selected, the change in JSD causes a refresh
166         if (Firebug.chrome.getSelectedPanel() === this)
167             Firebug.chrome.syncPanel(this.name);
168 
169         // Front side UI mark
170         var firebugStatus = Firefox.getElementById("firebugStatus");
171         if (firebugStatus)
172         {
173             // Use enabled state for the status flag. JSD can be active even if
174             // the Script panel itself is deactivated (i.e. because the Console
175             // panel is enabled). See issue 2582 for more details.
176             var enabled = this.isEnabled();
177             firebugStatus.setAttribute("script", (enabled && active) ? "on" : "off");
178         }
179 
180         if (Firebug.StartButton)
181             Firebug.StartButton.resetTooltip();
182         else
183             FBTrace.sysout("No Firebug.StartButton in onJavaScriptDebugging?");
184 
185         // Front side state
186         Firebug.jsDebuggerOn = active;
187 
188         if (FBTrace.DBG_ACTIVATION)
189         {
190             FBTrace.sysout("script.onJavaScriptDebugging " + active + " icon attribute: " +
191                 Firefox.getElementById("firebugStatus").getAttribute("script"));
192         }
193     },
194 
195     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
196 
197     showFunction: function(fn)
198     {
199         var sourceLink = Firebug.SourceFile.findSourceForFunction(fn, this.context);
200         if (sourceLink)
201         {
202             this.showSourceLink(sourceLink);
203         }
204         else
205         {
206             // Want to avoid the Script panel if possible
207             if (FBTrace.DBG_ERRORS)
208                 FBTrace.sysout("no sourcelink for function");
209         }
210     },
211 
212     showSourceLink: function(sourceLink, noHighlight)
213     {
214         var compilationUnit = this.context.getCompilationUnit(sourceLink.href);
215         if (compilationUnit)
216         {
217             this.navigate(compilationUnit);
218             if (sourceLink.line)
219             {
220                 var highlighter = noHighlight ? null :
221                     this.jumpHighlightFactory(sourceLink.line, this.context);
222 
223                 this.scrollToLine(sourceLink.href, sourceLink.line, highlighter);
224 
225                 Events.dispatch(this.fbListeners, "onShowSourceLink", [this, sourceLink.line]);
226             }
227 
228             // If the source link is selected, clear it so the next link will scroll and highlight.
229             if (sourceLink == this.selection)
230                 delete this.selection;
231         }
232     },
233 
234     /**
235      * Some source files (compilation units) can be loaded asynchronously (e.g. when using
236      * RequireJS). If this case happens, this method tries it again after a short timeout.
237      *
238      * @param {Object} sourceLink  Link to the script and line to be displayed.
239      * @param {Boolean} noHighlight Do not highlight the line
240      * @param {Number} counter  Number of async attempts.
241      */
242     showSourceLinkAsync: function(sourceLink, noHighlight, counter)
243     {
244         var compilationUnit = this.context.getCompilationUnit(sourceLink.href);
245         if (compilationUnit)
246         {
247             this.showSourceLink(sourceLink, noHighlight);
248         }
249         else
250         {
251             if (typeof(counter) == "undefined")
252                 counter = 15;
253 
254             // Stop trying. The target script is probably not going to appear.
255             if (counter < 0)
256                 return;
257 
258             var self = this;
259             this.context.setTimeout(function()
260             {
261                 // If JS execution is stopped at a breakpoint, do not restore the previous
262                 // location. The user wants to see the breakpoint now.
263                 if (!self.context.stopped)
264                     self.showSourceLinkAsync(sourceLink, noHighlight, --counter);
265             }, 50);
266         }
267     },
268 
269     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
270 
271     highlightingAttribute: "exe_line",
272 
273     removeExeLineHighlight: function(sourceBox)
274     {
275         if (sourceBox.selectedLine)
276         {
277             sourceBox.selectedLine.removeAttribute(this.highlightingAttribute);
278 
279             // Make sure the highlighter for the selected line is removed, too (issue 4359).
280             sourceBox.highlighter = null;
281         }
282     },
283 
284     highlightLine: function(lineNumber, context)
285     {
286         var panel = this;
287         return function exeHighlightFactory(sourceBox)
288         {
289             panel.removeExeLineHighlight(sourceBox);
290 
291             // We close over lineNumber
292             var lineNode = sourceBox.getLineNode(lineNumber);
293             // If null, clears
294             sourceBox.selectedLine = lineNode;
295 
296             if (sourceBox.selectedLine)
297             {
298                 lineNode.setAttribute(panel.highlightingAttribute, "true");
299                 if (context.breakingCause && !context.breakingCause.shown)
300                 {
301                     context.breakingCause.shown = true;
302                     var cause = context.breakingCause;
303                     if (cause && Firebug.showBreakNotification)
304                     {
305                         var box = new Firebug.Breakpoint.BreakNotification(panel.document, cause);
306                         box.show(panel.panelNode);
307                         sourceBox.breakCauseBox = box;
308                     }
309                 }
310             }
311 
312             if (FBTrace.DBG_BP || FBTrace.DBG_STACK || FBTrace.DBG_COMPILATION_UNITS)
313             {
314                 FBTrace.sysout("sourceBox.highlightLine lineNo: "+lineNumber+
315                     " sourceBox.selectedLine="+sourceBox.selectedLine+" in "+
316                     sourceBox.repObject.getURL());
317             }
318 
319             // Sticky, if we have a valid line
320             return sourceBox.selectedLine;
321         };
322     },
323 
324     showStackFrameXB: function(frameXB)
325     {
326         if (this.context.stopped)
327             this.showStackFrameTrue(frameXB);
328         else
329             this.showNoStackFrame();
330     },
331 
332     showStackFrameTrue: function(frame)
333     {
334         // Make sure the current frame seen by the user is set (issue 4818)
335         // xxxHonza: Better solution (important for remoting)
336         // Set this.context.currentFrame = frame (meaning frameXB) and pass the value of
337         // frameXB during evaluation calls, causing the backend to select the appropriate
338         // frame for frame.eval().
339         this.context.currentFrame = frame.nativeFrame;
340 
341         var url = frame.getURL();
342         var lineNo = frame.getLineNumber();
343 
344         if (FBTrace.DBG_STACK)
345             FBTrace.sysout("showStackFrame: "+url+"@"+lineNo+"\n");
346 
347         if (this.context.breakingCause)
348             this.context.breakingCause.lineNo = lineNo;
349 
350         this.scrollToLine(url, lineNo, this.highlightLine(lineNo, this.context));
351         this.context.throttle(this.updateInfoTip, this);
352     },
353 
354     showNoStackFrame: function()
355     {
356         if (this.selectedSourceBox)
357         {
358             this.removeExeLineHighlight(this.selectedSourceBox);
359 
360             if (FBTrace.DBG_STACK)
361                 FBTrace.sysout("showNoStackFrame clear "+this.selectedSourceBox.repObject.url);
362         }
363 
364         var panelStatus = Firebug.chrome.getPanelStatusElements();
365         // Clear the stack on the panel toolbar
366         panelStatus.clear();
367         this.updateInfoTip();
368 
369         var watchPanel = this.context.getPanel("watches", true);
370         if (watchPanel)
371             watchPanel.showEmptyMembers();
372     },
373 
374     toggleBreakpoint: function(lineNo)
375     {
376         var href = this.getSourceBoxURL(this.selectedSourceBox);
377         var lineNode = this.selectedSourceBox.getLineNode(lineNo);
378 
379         if (!lineNode)
380         {
381             if (FBTrace.DBG_ERRORS)
382             {
383                 FBTrace.sysout("script.toggleBreakpoint no lineNode at " + lineNo +
384                     " in selectedSourceBox with URL " + href, this.selectedSourceBox);
385             }
386 
387             return;
388         }
389 
390         if (FBTrace.DBG_BP)
391         {
392             FBTrace.sysout("script.toggleBreakpoint lineNo=" + lineNo + " lineNode.breakpoint:" +
393                 (lineNode ? lineNode.getAttribute("breakpoint") : "(no lineNode)"),
394                 this.selectedSourceBox);
395         }
396 
397         if (lineNode.getAttribute("breakpoint") == "true")
398             JavaScriptTool.clearBreakpoint(this.context, href, lineNo);
399         else
400             JavaScriptTool.setBreakpoint(this.context, href, lineNo);
401     },
402 
403     toggleDisableBreakpoint: function(lineNo)
404     {
405         var href = this.getSourceBoxURL(this.selectedSourceBox);
406 
407         var lineNode = this.selectedSourceBox.getLineNode(lineNo);
408         if (lineNode.getAttribute("disabledBreakpoint") == "true")
409         {
410             JavaScriptTool.enableBreakpoint(this.context, href, lineNo);
411         }
412         else
413         {
414             if (lineNode.getAttribute("breakpoint") != "true")
415                 JavaScriptTool.setBreakpoint(this.context, href, lineNo);
416 
417             JavaScriptTool.disableBreakpoint(this.context, href, lineNo);
418         }
419     },
420 
421     editBreakpointCondition: function(lineNo)
422     {
423         var sourceRow = this.selectedSourceBox.getLineNode(lineNo);
424         var sourceLine = Dom.getChildByClass(sourceRow, "sourceLine");
425         var condition = JavaScriptTool.getBreakpointCondition(this.context,
426             this.location.getURL(), lineNo);
427 
428         if (condition)
429         {
430             var watchPanel = this.context.getPanel("watches", true);
431             watchPanel.removeWatch(condition);
432             watchPanel.rebuild();
433         }
434 
435         Firebug.Editor.startEditing(sourceLine, condition);
436     },
437 
438     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
439 
440     addSelectionWatch: function()
441     {
442         var watchPanel = this.context.getPanel("watches", true);
443         if (watchPanel)
444         {
445             var selection = this.document.defaultView.getSelection();
446             var source = this.getSourceLinesFrom(selection);
447             watchPanel.addWatch(source);
448         }
449     },
450 
451     copySource: function()
452     {
453         var selection = this.document.defaultView.getSelection();
454         var source = this.getSourceLinesFrom(selection);
455         System.copyToClipboard(source);
456     },
457 
458     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
459     // Info Tips
460 
461     updateInfoTip: function()
462     {
463         var infoTip = this.panelBrowser.infoTip;
464         if (infoTip && this.infoTipExpr)
465             this.populateInfoTip(infoTip, this.infoTipExpr);
466     },
467 
468     showInfoTip: function(infoTip, target, x, y, rangeParent, rangeOffset)
469     {
470         var sourceLine = Dom.getAncestorByClass(target, "sourceLine");
471         if (sourceLine)
472             return this.populateBreakpointInfoTip(infoTip, sourceLine);
473 
474         var frame = this.context.currentFrame;
475         if (!frame)
476             return;
477 
478         var sourceRowText = Dom.getAncestorByClass(target, "sourceRowText");
479         if (!sourceRowText)
480             return;
481 
482         // See http://code.google.com/p/fbug/issues/detail?id=889
483         // Idea from: Jonathan Zarate's rikaichan extension (http://www.polarcloud.com/rikaichan/)
484         if (!rangeParent)
485             return;
486 
487         rangeOffset = rangeOffset || 0;
488         var expr = getExpressionAt(rangeParent.data, rangeOffset);
489         if (!expr || !expr.expr)
490             return;
491 
492         if (expr.expr == this.infoTipExpr)
493             return true;
494         else
495             return this.populateInfoTip(infoTip, expr.expr);
496     },
497 
498     populateInfoTip: function(infoTip, expr)
499     {
500         if (!expr || Keywords.isJavaScriptKeyword(expr))
501             return false;
502 
503         var self = this;
504 
505         // If the evaluate fails, then we report an error and don't show the infotip
506         Firebug.CommandLine.evaluate(expr, this.context, null, this.context.getGlobalScope(),
507             function success(result, context)
508             {
509                 var rep = Firebug.getRep(result, context);
510                 var tag = rep.shortTag ? rep.shortTag : rep.tag;
511 
512                 if (FBTrace.DBG_STACK)
513                     FBTrace.sysout("populateInfoTip result is "+result, result);
514 
515                 tag.replace({object: result}, infoTip);
516 
517                 // If the menu is never displayed, the contextMenuObject is not reset
518                 // (back to null) and is reused at the next time the user opens the
519                 // context menu, which is wrong.
520                 // This line was appended when fixing:
521                 // http://code.google.com/p/fbug/issues/detail?id=1700
522                 // The object should be returned by getPopupObject(),
523                 // that is called when the context menu is showing.
524                 // The problem is, that the "onContextShowing" event doesn't have the
525                 // rangeParent field set and so it isn't possible to get the
526                 // expression under the cursor (see getExpressionAt).
527                 //Firebug.chrome.contextMenuObject = result;
528 
529                 self.infoTipExpr = expr;
530             },
531             function failed(result, context)
532             {
533                 self.infoTipExpr = "";
534             }
535         );
536         return (self.infoTipExpr == expr);
537     },
538 
539     populateBreakpointInfoTip: function(infoTip, sourceLine)
540     {
541         var sourceRow = Dom.getAncestorByClass(sourceLine, "sourceRow");
542         var condition = sourceRow.getAttribute("condition");
543         if (!condition)
544             return false;
545 
546         var expr = sourceRow.breakpointCondition;
547         if (!expr)
548             return false;
549 
550         if (expr == this.infoTipExpr)
551             return true;
552 
553         Firebug.ScriptPanel.BreakpointInfoTip.render(infoTip, expr);
554 
555         this.infoTipExpr = expr;
556 
557         return true;
558     },
559 
560     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
561     // UI event listeners
562 
563     onMouseDown: function(event)
564     {
565         // Don't interfere with clicks made into a notification editor.
566         if (Dom.getAncestorByClass(event.target, "breakNotification"))
567             return;
568 
569         var sourceLine = Dom.getAncestorByClass(event.target, "sourceLine");
570         if (!sourceLine)
571             return;
572 
573         var compilationUnit = Dom.getAncestorByClass(sourceLine, "sourceBox").repObject;
574         var lineNo = parseInt(sourceLine.textContent);
575 
576         if (Events.isLeftClick(event))
577         {
578             this.toggleBreakpoint(lineNo);
579         }
580         else if (Events.isShiftClick(event))
581         {
582             this.toggleDisableBreakpoint(lineNo);
583         }
584         else if (Events.isControlClick(event) || Events.isMiddleClick(event))
585         {
586             JavaScriptTool.runUntil(compilationUnit, lineNo);
587             Events.cancelEvent(event);
588         }
589     },
590 
591     onContextMenu: function(event)
592     {
593         var sourceLine = Dom.getAncestorByClass(event.target, "sourceLine");
594         if (!sourceLine)
595             return;
596 
597         var lineNo = parseInt(sourceLine.textContent);
598         this.editBreakpointCondition(lineNo);
599         Events.cancelEvent(event);
600     },
601 
602     onMouseOver: function(event)
603     {
604         var sourceLine = Dom.getAncestorByClass(event.target, "sourceLine");
605         if (sourceLine)
606         {
607             if (this.hoveredLine)
608                 Css.removeClass(this.hoveredLine.parentNode, "hovered");
609 
610             this.hoveredLine = sourceLine;
611 
612             if (Dom.getAncestorByClass(sourceLine, "sourceViewport"))
613                 Css.setClass(sourceLine.parentNode, "hovered");
614         }
615     },
616 
617     onMouseOut: function(event)
618     {
619         var sourceLine = Dom.getAncestorByClass(event.relatedTarget, "sourceLine");
620         if (!sourceLine)
621         {
622             if (this.hoveredLine)
623                 Css.removeClass(this.hoveredLine.parentNode, "hovered");
624 
625             delete this.hoveredLine;
626         }
627     },
628 
629     onScroll: function(event)
630     {
631         var scrollingElement = event.target;
632         this.reView(scrollingElement);
633         var searchBox = Firebug.chrome.$("fbSearchBox");
634         searchBox.placeholder = Locale.$STR("Use hash plus number to go to line");
635     },
636 
637     onKeyPress: function(event)
638     {
639         var ch = String.fromCharCode(event.charCode);
640         var searchBox = Firebug.chrome.$("fbSearchBox");
641 
642         if (ch == "l" && Events.isControl(event))
643         {
644             searchBox.value = "#";
645             searchBox.focus();
646 
647             Events.cancelEvent(event);
648         }
649     },
650 
651     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
652     // extends Panel
653 
654     name: "script",
655     searchable: true,
656     breakable: true,
657     enableA11y: true,
658     order: 40,
659 
660     initialize: function(context, doc)
661     {
662         this.location = null;
663 
664         this.onMouseDown = Obj.bind(this.onMouseDown, this);
665         this.onContextMenu = Obj.bind(this.onContextMenu, this);
666         this.onMouseOver = Obj.bind(this.onMouseOver, this);
667         this.onMouseOut = Obj.bind(this.onMouseOut, this);
668         this.onScroll = Obj.bind(this.onScroll, this);
669         this.onKeyPress = Obj.bind(this.onKeyPress, this);
670 
671         this.panelSplitter = Firebug.chrome.$("fbPanelSplitter");
672         this.sidePanelDeck = Firebug.chrome.$("fbSidePanelDeck");
673 
674         Firebug.SourceBoxPanel.initialize.apply(this, arguments);
675     },
676 
677     destroy: function(state)
678     {
679         // We want the location (compilationUnit) to persist, not the selection (eg stackFrame).
680         delete this.selection;
681 
682         var sourceBox = this.selectedSourceBox;
683         if (sourceBox)
684         {
685             if (sourceBox.centralLine)
686                 state.previousCentralLine = sourceBox.centralLine;
687             else
688                 state.scrollTop = sourceBox.scrollTop ? sourceBox.scrollTop : this.lastScrollTop;
689 
690             delete this.selectedSourceBox;
691         }
692 
693         Persist.persistObjects(this, state);
694 
695         if (this.location instanceof CompilationUnit)
696         {
697              state.location = this.location;
698         }
699         else
700         {
701             if (FBTrace.DBG_COMPILATION_UNITS)
702             {
703                 FBTrace.sysout("script.destroy had location not a CompilationUnit ",
704                     this.location);
705             }
706         }
707 
708         // xxxHonza: why this is here? I don't see addListener.
709         //Firebug.connection.removeListener(this);
710 
711         // Make sure listeners are removed.
712         this.detachListeners(this.context, Firebug.chrome);
713 
714         Firebug.SourceBoxPanel.destroy.apply(this, arguments);
715     },
716 
717     initializeNode: function(oldPanelNode)
718     {
719         this.tooltip = this.document.createElement("div");
720         Css.setClass(this.tooltip, "scriptTooltip");
721         this.tooltip.setAttribute('aria-live', 'polite')
722         Css.obscure(this.tooltip, true);
723         this.panelNode.appendChild(this.tooltip);
724 
725         Events.addEventListener(this.panelNode, "mousedown", this.onMouseDown, true);
726         Events.addEventListener(this.panelNode, "contextmenu", this.onContextMenu, false);
727         Events.addEventListener(this.panelNode, "mouseover", this.onMouseOver, false);
728         Events.addEventListener(this.panelNode, "mouseout", this.onMouseOut, false);
729         Events.addEventListener(this.panelNode, "scroll", this.onScroll, true);
730 
731         Firebug.SourceBoxPanel.initializeNode.apply(this, arguments);
732     },
733 
734     destroyNode: function()
735     {
736         if (this.tooltipTimeout)
737             clearTimeout(this.tooltipTimeout);
738 
739         Events.removeEventListener(this.panelNode, "mousedown", this.onMouseDown, true);
740         Events.removeEventListener(this.panelNode, "contextmenu", this.onContextMenu, false);
741         Events.removeEventListener(this.panelNode, "mouseover", this.onMouseOver, false);
742         Events.removeEventListener(this.panelNode, "mouseout", this.onMouseOut, false);
743         Events.removeEventListener(this.panelNode, "scroll", this.onScroll, true);
744 
745         Firebug.SourceBoxPanel.destroyNode.apply(this, arguments);
746     },
747 
748     clear: function()
749     {
750         Dom.clearNode(this.panelNode);
751     },
752 
753     showWarning: function()
754     {
755         // Fill the panel node with a warning if needed
756         var aLocation = this.getDefaultLocation();
757         var jsEnabled = Firebug.Options.getPref("javascript", "enabled");
758 
759         if (FBTrace.DBG_PANELS)
760         {
761             FBTrace.sysout("script.showWarning; " + this.context.getName(), {
762                 jsDebuggerOn: Firebug.jsDebuggerOn,
763                 jsDebuggerCalledUs: this.context.jsDebuggerCalledUs,
764                 jsEnabled: jsEnabled,
765                 aLocation: aLocation,
766                 activitySuspended: this.context.activitySuspended,
767                 stopped: this.context.stopped
768             });
769         }
770 
771         var currentURI = Firefox.getCurrentURI();
772         var activitySuspended = this.isActivitySuspended();
773         if (activitySuspended && !this.context.stopped)
774         {
775             // Make sure that the content of the panel is restored as soon as
776             // the debugger is resumed.
777             this.restored = false;
778             this.activeWarningTag = WarningRep.showActivitySuspended(this.panelNode);
779         }
780         else if (!jsEnabled)
781         {
782             this.activeWarningTag = WarningRep.showNotEnabled(this.panelNode);
783         }
784         else if (currentURI && (Url.isSystemURL(currentURI.spec) ||
785             currentURI.spec.match(Url.reChrome)))
786         {
787             this.activeWarningTag = WarningRep.showNoDebuggingForSystemSources(this.panelNode);
788         }
789         else if (this.context.allScriptsWereFiltered)
790         {
791             this.activeWarningTag = WarningRep.showFiltered(this.panelNode);
792         }
793         else if (aLocation && !this.context.jsDebuggerCalledUs)
794         {
795             this.activeWarningTag = WarningRep.showInactive(this.panelNode);
796         }
797         else if (!Firebug.jsDebuggerOn)  // set asynchronously by jsd in FF 4.0
798         {
799             this.activeWarningTag = WarningRep.showDebuggerInactive(this.panelNode);
800         }
801         else if (!aLocation) // they were not filtered, we just had none
802         {
803             this.activeWarningTag = WarningRep.showNoScript(this.panelNode);
804         }
805         else
806         {
807             return false;
808         }
809 
810         return true;
811     },
812 
813     isActivitySuspended: function()
814     {
815         return Win.iterateBrowserWindows("navigator:browser", function(win)
816         {
817             // Firebug doesn't have to be loaded in every browser window (see delayed load).
818             if (!win.Firebug.TabWatcher)
819                 return false;
820 
821             return win.Firebug.TabWatcher.iterateContexts(function(context)
822             {
823                 if (context.stopped)
824                      return true;
825             });
826         });
827     },
828 
829     show: function(state)
830     {
831         var enabled = this.isEnabled();
832         if (!enabled)
833             return;
834 
835         var active = !this.showWarning();
836 
837         if (active)
838         {
839             Events.addEventListener(this.panelNode.ownerDocument, "keypress", this.onKeyPress, true);
840             Events.addEventListener(this.resizeEventTarget, "resize", this.onResize, true);
841 
842             this.location = this.getDefaultLocation();
843 
844             if (this.context.loaded)
845             {
846                 if (!this.restored)
847                 {
848                     // remove the default location, if any
849                     delete this.location;
850                     Persist.restoreLocation(this, state);
851                     this.restored = true;
852                 }
853                 else
854                 {
855                     // we already restored
856                     if (!this.selectedSourceBox)
857                     {
858                         // but somehow we did not make a sourcebox?
859                         this.navigate(this.location);
860                     }
861                     else
862                     {
863                         // then we can sync the location to the sourcebox
864                         this.updateSourceBox(this.selectedSourceBox);
865                     }
866                 }
867 
868                 if (state)
869                 {
870                     if (state.location)
871                     {
872                         var sourceLink = new SourceLink.SourceLink(state.location.getURL(),
873                             state.previousCentralLine, "js");
874 
875                         // Causes the Script panel to show the proper location.
876                         // Do not highlight the line (second argument true), we just want
877                         // to restore the position.
878                         // Also do it asynchronously, the script doesn't have to be
879                         // available immediately.
880                         this.showSourceLinkAsync(sourceLink, true);
881 
882                         // Do not restore the location again, it could happen during
883                         // the single stepping and overwrite the debugger location.
884                         delete state.location;
885                     }
886 
887                     if (state.scrollTop)
888                     {
889                         this.selectedSourceBox.scrollTop = state.scrollTop;
890                     }
891                 }
892             }
893             else // show default
894             {
895                 this.navigate(this.location);
896             }
897 
898             this.highlight(this.context.stopped);
899 
900             var breakpointPanel = this.context.getPanel("breakpoints", true);
901             if (breakpointPanel)
902                 breakpointPanel.refresh();
903 
904             this.syncCommands(this.context);
905             this.ableWatchSidePanel(this.context);
906         }
907 
908         Dom.collapse(Firebug.chrome.$("fbToolbar"), !active);
909 
910         // These buttons are visible only, if debugger is enabled.
911         this.showToolbarButtons("fbLocationSeparator", active);
912         this.showToolbarButtons("fbDebuggerButtons", active);
913         this.showToolbarButtons("fbLocationButtons", active);
914         this.showToolbarButtons("fbScriptButtons", active);
915         this.showToolbarButtons("fbStatusButtons", active);
916         this.showToolbarButtons("fbLocationList", active);
917 
918         Firebug.chrome.$("fbRerunButton").setAttribute("tooltiptext",
919             Locale.$STRF("firebug.labelWithShortcut", [Locale.$STR("script.Rerun"), "Shift+F8"]));
920         Firebug.chrome.$("fbContinueButton").setAttribute("tooltiptext",
921             Locale.$STRF("firebug.labelWithShortcut", [Locale.$STR("script.Continue"), "F8"]));
922         Firebug.chrome.$("fbStepIntoButton").setAttribute("tooltiptext",
923             Locale.$STRF("firebug.labelWithShortcut", [Locale.$STR("script.Step_Into"), "F11"]));
924         Firebug.chrome.$("fbStepOverButton").setAttribute("tooltiptext",
925             Locale.$STRF("firebug.labelWithShortcut", [Locale.$STR("script.Step_Over"), "F10"]));
926         Firebug.chrome.$("fbStepOutButton").setAttribute("tooltiptext",
927             Locale.$STRF("firebug.labelWithShortcut",
928                 [Locale.$STR("script.Step_Out"), "Shift+F11"]));
929 
930         // Additional debugger panels are visible only, if debugger is active.
931         this.panelSplitter.collapsed = !active;
932         this.sidePanelDeck.collapsed = !active;
933     },
934 
935     hide: function(state)
936     {
937         if (this.selectedSourceBox)
938             this.lastScrollTop = this.selectedSourceBox.scrollTop;
939 
940         this.highlight(this.context.stopped);
941 
942         Events.removeEventListener(this.panelNode.ownerDocument, "keypress", this.onKeyPress,
943             true);
944         Events.removeEventListener(this.resizeEventTarget, "resize", this.onResize, true);
945 
946         if (FBTrace.DBG_PANELS)
947             FBTrace.sysout("script panel HIDE removed onResize eventhandler");
948 
949         var panelStatus = Firebug.chrome.getPanelStatusElements();
950         Dom.hide(panelStatus, false);
951 
952         delete this.infoTipExpr;
953     },
954 
955     ableWatchSidePanel: function(context)
956     {
957         // TODO if (commandline is not active, then we should not show the new watch feature)
958         var watchPanel = context.getPanel("watches", true);
959         if (watchPanel)
960             return watchPanel;
961     },
962 
963     search: function(text, reverse)
964     {
965         var sourceBox = this.selectedSourceBox;
966         if (!text || !sourceBox)
967         {
968             delete this.currentSearch;
969             return false;
970         }
971 
972         // Check, if the search is for a line number
973         var m = Firebug.ScriptPanel.reLineNumber.exec(text);
974         if (m)
975         {
976             if (!m[1])
977                 return true; // Don't beep, if only a # has been typed
978 
979             var lineNo = parseInt(m[1]);
980             if (!isNaN(lineNo) && (lineNo > 0) && (lineNo < sourceBox.lines.length) )
981             {
982                 this.scrollToLine(sourceBox.repObject.getURL(), lineNo,
983                     this.jumpHighlightFactory(lineNo, this.context))
984                 return true;
985             }
986         }
987 
988         var curDoc = this.searchCurrentDoc(!Firebug.searchGlobal, text, reverse);
989         if (!curDoc && Firebug.searchGlobal)
990         {
991             return this.searchOtherDocs(text, reverse) ||
992                 this.searchCurrentDoc(true, text, reverse);
993         }
994         return curDoc;
995     },
996 
997     searchOtherDocs: function(text, reverse)
998     {
999         var scanRE = Firebug.Search.getTestingRegex(text);
1000 
1001         var self = this;
1002 
1003         function scanDoc(compilationUnit)
1004         {
1005             var lines = null;
1006 
1007             // TODO The source lines arrive asynchronous in general
1008             compilationUnit.getSourceLines(-1, -1, function loadSource(unit, firstLineNumber,
1009                 lastLineNumber, linesRead)
1010             {
1011                 lines = linesRead;
1012             });
1013 
1014             if (!lines)
1015                 return;
1016 
1017             // We don't care about reverse here as we are just looking for existence.
1018             // If we do have a result, we will handle the reverse logic on display.
1019             for (var i = 0; i < lines.length; i++)
1020             {
1021                 if (scanRE.test(lines[i]))
1022                     return true;
1023             }
1024         }
1025 
1026         if (this.navigateToNextDocument(scanDoc, reverse))
1027             return this.searchCurrentDoc(true, text, reverse) && "wraparound";
1028     },
1029 
1030     searchCurrentDoc: function(wrapSearch, text, reverse)
1031     {
1032         var sourceBox = this.selectedSourceBox;
1033 
1034         var lineNo = null;
1035         if (this.currentSearch && text == this.currentSearch.text)
1036         {
1037             lineNo = this.currentSearch.findNext(wrapSearch, reverse,
1038                 Firebug.Search.isCaseSensitive(text));
1039         }
1040         else
1041         {
1042             if (!this.currentSearch || !this.currentSearch.tryToContinueSearch(sourceBox, text))
1043                 this.currentSearch = new Search.SourceBoxTextSearch(sourceBox);
1044 
1045             lineNo = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
1046         }
1047 
1048         if (lineNo || lineNo === 0)
1049         {
1050             // This lineNo is an zero-based index into sourceBox.lines.
1051             // Add one for user line numbers
1052             this.scrollToLine(sourceBox.repObject.getURL(), lineNo,
1053                 this.jumpHighlightFactory(lineNo+1, this.context));
1054 
1055             Events.dispatch(this.fbListeners, "onScriptSearchMatchFound",
1056                 [this, text, sourceBox.repObject, lineNo]);
1057 
1058             return this.currentSearch.wrapped ? "wraparound" : true;
1059         }
1060         else
1061         {
1062             Events.dispatch(this.fbListeners, "onScriptSearchMatchFound",
1063                 [this, text, null, null]);
1064 
1065             return false;
1066         }
1067     },
1068 
1069     getSearchOptionsMenuItems: function()
1070     {
1071         return [
1072             Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive",
1073                 "search.tip.Case_Sensitive"),
1074             Firebug.Search.searchOptionMenu("search.Multiple_Files", "searchGlobal",
1075                 "search.tip.Multiple_Files"),
1076             Firebug.Search.searchOptionMenu("search.Use_Regular_Expression",
1077                 "searchUseRegularExpression", "search.tip.Use_Regular_Expression")
1078         ];
1079     },
1080 
1081     supportsObject: function(object, type)
1082     {
1083         if (object instanceof CompilationUnit
1084             || (object instanceof SourceLink.SourceLink && object.type == "js")
1085             || typeof(object) == "function"
1086             || object instanceof StackFrame.StackFrame)
1087         {
1088             return 1;
1089         }
1090 
1091         return 0;
1092     },
1093 
1094     // Delete any sourceBoxes that are not in sync with compilationUnits
1095     refresh: function()
1096     {
1097         var previousCentralLine;
1098         var previousUrl;
1099 
1100         for (var url in this.sourceBoxes)
1101         {
1102             if (this.sourceBoxes.hasOwnProperty(url))
1103             {
1104                 var sourceBox = this.sourceBoxes[url];
1105                 var compilationUnit = this.context.getCompilationUnit(url);
1106 
1107                 // then out of sync
1108                 if (!compilationUnit || compilationUnit != sourceBox.repObject)
1109                 {
1110                     var victim = this.sourceBoxes[url];
1111                     delete this.sourceBoxes[url];
1112                     if (this.selectedSourceBox == victim)
1113                     {
1114                         previousCentralLine = this.selectedSourceBox.centralLine;
1115                         previousUrl = this.getSourceBoxURL(this.selectedSourceBox);
1116 
1117                         Dom.collapse(this.selectedSourceBox, true);
1118                         delete this.selectedSourceBox;
1119                     }
1120 
1121                     if (FBTrace.DBG_COMPILATION_UNITS)
1122                         FBTrace.sysout("script.refresh deleted sourceBox for " + url);
1123                 }
1124             }
1125         }
1126 
1127         // If selectedSourceBox is undefined, then show() has not run,
1128         // but we have to refresh, so do the default.
1129         if (!this.selectedSourceBox)
1130         {
1131             // If the current source-box has been deleted because it's out of sync
1132             // (the victim, see above), we need to navigate again to the same URL.
1133             // Otherwise the script panel would coincidentally switch to another script.
1134             // (see issue 5134)
1135             var object;
1136             if (previousUrl)
1137                 object = this.context.getCompilationUnit(previousUrl);
1138 
1139             this.navigate(object);
1140 
1141             // Restore the scroll position (issue 5111)
1142             if (this.selectedSourceBox)
1143             {
1144                 var url = this.getSourceBoxURL(this.selectedSourceBox);
1145                 if (this.selectedSourceBox && url == previousUrl)
1146                     this.scrollToLine(null, previousCentralLine);
1147             }
1148         }
1149     },
1150 
1151     updateLocation: function(compilationUnit)
1152     {
1153         // XXXjjb do we need to show a blank?
1154         if (!compilationUnit)
1155             return;
1156 
1157         if (!(compilationUnit instanceof CompilationUnit))
1158         {
1159             FBTrace.sysout("Script panel location not a CompilationUnit: ", compilationUnit);
1160             throw new Error("Script panel location not a CompilationUnit: " + compilationUnit);
1161         }
1162 
1163         // Since our last use of the compilationUnit we may have compiled or
1164         // recompiled the source
1165         var updatedCompilationUnit = this.context.getCompilationUnit(compilationUnit.getURL());
1166         if (!updatedCompilationUnit)
1167             updatedCompilationUnit = this.getDefaultLocation();
1168 
1169         if (!updatedCompilationUnit)
1170             return;
1171 
1172         if (this.activeWarningTag)
1173         {
1174             Dom.clearNode(this.panelNode);
1175             delete this.activeWarningTag;
1176 
1177             // The user was seeing the warning, but selected a file to show in the Script panel.
1178             // The removal of the warning leaves the panel without a clientHeight, so
1179             //  the old sourcebox will be out of sync. Just remove it and start over.
1180             this.removeAllSourceBoxes();
1181             // we are not passing state so I guess we could miss a restore
1182             this.show();
1183 
1184             // If show() reset the flag, obey it
1185             if (this.activeWarningTag)
1186                 return;
1187         }
1188 
1189         this.showSource(updatedCompilationUnit.getURL());
1190         Events.dispatch(this.fbListeners, "onUpdateScriptLocation", [this, updatedCompilationUnit]);
1191     },
1192 
1193     updateSelection: function(object)
1194     {
1195         if (FBTrace.DBG_PANELS)
1196         {
1197             FBTrace.sysout("script updateSelection object:" + object + " of type " +
1198                 typeof(object), object);
1199 
1200             if (object instanceof CompilationUnit)
1201                 FBTrace.sysout("script updateSelection this.navigate(object)", object);
1202             else if (object instanceof SourceLink.SourceLink)
1203                 FBTrace.sysout("script updateSelection this.showSourceLink(object)", object);
1204             else if (typeof(object) == "function")
1205                 FBTrace.sysout("script updateSelection this.showFunction(object)", object);
1206             else if (object instanceof StackFrame.StackFrame)
1207                 FBTrace.sysout("script updateSelection this.showStackFrameXB(object)", object);
1208             else
1209                 FBTrace.sysout("script updateSelection this.showStackFrame(null)", object);
1210         }
1211 
1212         if (object instanceof CompilationUnit)
1213             this.navigate(object);
1214         else if (object instanceof SourceLink.SourceLink)
1215             this.showSourceLink(object);
1216         else if (typeof(object) == "function")
1217             this.showFunction(object);
1218         else if (object instanceof StackFrame.StackFrame)
1219             this.showStackFrameXB(object);
1220     },
1221 
1222     showThisCompilationUnit: function(compilationUnit)
1223     {
1224         if (compilationUnit.getURL().substr(0, 9) == "chrome://")
1225             return false;
1226 
1227         if (compilationUnit.getKind() === CompilationUnit.EVAL && !this.showEvals)
1228             return false;
1229 
1230         if (compilationUnit.getKind() === CompilationUnit.BROWSER_GENERATED && !this.showEvents)
1231             return false;
1232 
1233         return true;
1234     },
1235 
1236     getLocationList: function()
1237     {
1238         var context = this.context;
1239 
1240         var allSources = context.getAllCompilationUnits();
1241 
1242         if (!allSources.length)
1243             return [];
1244 
1245         var filter = Firebug.Options.get("scriptsFilter");
1246         this.showEvents = (filter == "all" || filter == "events");
1247         this.showEvals = (filter == "all" || filter == "evals");
1248 
1249         var list = [];
1250         for (var i = 0; i < allSources.length; i++)
1251         {
1252             if (this.showThisCompilationUnit(allSources[i]))
1253             {
1254                 list.push(allSources[i]);
1255             }
1256             else if (FBTrace.DBG_COMPILATION_UNITS)
1257             {
1258                 FBTrace.sysout("scrpt.getLocationList filtered "+allSources[i].getURL(),
1259                     allSources[i]);
1260             }
1261         }
1262 
1263         if (!list.length && allSources.length)
1264             this.context.allScriptsWereFiltered = true;
1265         else
1266             delete this.context.allScriptsWereFiltered;
1267 
1268         if (FBTrace.DBG_COMPILATION_UNITS)
1269         {
1270             FBTrace.sysout("script.getLocationList enabledOnLoad:" + context.onLoadWindowContent +
1271                 " all:" + allSources.length + " filtered:" + list.length + " allFiltered: " +
1272                 this.context.allScriptsWereFiltered, list);
1273         }
1274 
1275         return list;
1276     },
1277 
1278     getDefaultLocation: function()
1279     {
1280         var compilationUnits = this.getLocationList();
1281         if (!compilationUnits.length)
1282             return null;
1283 
1284         if (this.context)
1285         {
1286             var url = this.context.getWindowLocation();
1287             for (var i = 0; i < compilationUnits.length; i++)
1288             {
1289                 if (url == compilationUnits[i].getURL())
1290                     return compilationUnits[i];
1291             }
1292         }
1293 
1294         return compilationUnits[0];
1295     },
1296 
1297     getDefaultSelection: function()
1298     {
1299         return this.getDefaultLocation();
1300     },
1301 
1302     getTooltipObject: function(target)
1303     {
1304         // Target should be an element with class = sourceLine
1305         if (Css.hasClass(target, "sourceLine"))
1306             return null; // TODO
1307 
1308         return null;
1309     },
1310 
1311     getPopupObject: function(target)
1312     {
1313         // Don't show the popup over the line numbers. We show the conditional breakpoint
1314         // editor there instead
1315         if (Dom.getAncestorByClass(target, "sourceLine"))
1316             return;
1317 
1318         var sourceRow = Dom.getAncestorByClass(target, "sourceRow");
1319         if (!sourceRow)
1320             return;
1321 
1322         var lineNo = parseInt(sourceRow.firstChild.textContent);
1323         var scripts = Firebug.SourceFile.findScripts(this.context, this.location.getURL(), lineNo);
1324 
1325         // Gee I wonder what will happen?
1326         return scripts;
1327     },
1328 
1329     getObjectPath: function(frame)
1330     {
1331         if (FBTrace.DBG_STACK)
1332             FBTrace.sysout("script.getObjectPath "+frame, frame);
1333 
1334         if (!frame || !frame.getStackNewestFrame) // then its probably not a frame after all
1335             return;
1336 
1337         var frames = [];
1338         frame = frame.getStackNewestFrame();
1339         for (; frame; frame = frame.getCallingFrame())
1340             frames.push(frame);
1341 
1342         return frames;
1343     },
1344 
1345     getObjectLocation: function(compilationUnit)
1346     {
1347         return compilationUnit.getURL();
1348     },
1349 
1350     // return.path: group/category label, return.name: item label
1351     getObjectDescription: function(compilationUnit)
1352     {
1353         return Url.splitURLBase(compilationUnit.getURL());
1354     },
1355 
1356     getSourceLink: function(target, object)
1357     {
1358         var sourceRow = Dom.getAncestorByClass(target, "sourceRow");
1359         if (!sourceRow)
1360             return;
1361 
1362         var sourceLine = Dom.getChildByClass(sourceRow, "sourceLine");
1363         var lineNo = parseInt(sourceLine.textContent);
1364         return new SourceLink.SourceLink(this.location.url, lineNo, "js");
1365     },
1366 
1367     getOptionsMenuItems: function()
1368     {
1369         var context = this.context;
1370 
1371         return [
1372             // 1.2: always check last line; optionMenu("UseLastLineForEvalName", "useLastLineForEvalName"),
1373             // 1.2: always use MD5 optionMenu("UseMD5ForEvalName", "useMD5ForEvalName")
1374             Menu.optionMenu("script.option.Track_Throw_Catch", "trackThrowCatch",
1375                 "script.option.tip.Track_Throw_Catch"),
1376             //"-",
1377             //1.2 option on toolbar this.optionMenu("DebuggerEnableAlways", enableAlwaysPref)
1378             Menu.optionMenu("firebug.breakpoint.showBreakNotifications", "showBreakNotification",
1379                 "firebug.breakpoint.tip.Show_Break_Notifications")
1380         ];
1381     },
1382 
1383     optionMenu: function(label, option)
1384     {
1385         var checked = Firebug.Options.get(option);
1386         return {
1387             label: label, type: "checkbox", checked: checked,
1388             command: function()
1389             {
1390                 var checked = this.hasAttribute("checked");
1391                 Firebug.Options.set(option, checked)
1392             }
1393         };
1394     },
1395 
1396     getContextMenuItems: function(fn, target)
1397     {
1398         if (Dom.getAncestorByClass(target, "sourceLine"))
1399             return;
1400 
1401         var sourceRow = Dom.getAncestorByClass(target, "sourceRow");
1402         if (!sourceRow)
1403             return;
1404 
1405         var sourceLine = Dom.getChildByClass(sourceRow, "sourceLine");
1406         var lineNo = parseInt(sourceLine.textContent);
1407 
1408         var items = [];
1409 
1410         var selection = this.document.defaultView.getSelection();
1411         if (selection.toString())
1412         {
1413             items.push(
1414                 {
1415                     label: "CopySourceCode",
1416                     tooltiptext: "script.tip.Copy_Source_Code",
1417                     command: Obj.bind(this.copySource, this)
1418                 },
1419                 "-",
1420                 {
1421                     label: "AddWatch",
1422                     tooltiptext: "watch.tip.Add_Watch",
1423                     command: Obj.bind(this.addSelectionWatch, this)
1424                 }
1425             );
1426         }
1427 
1428         var hasBreakpoint = sourceRow.getAttribute("breakpoint") == "true";
1429 
1430         items.push(
1431             "-",
1432             {
1433                 label: "SetBreakpoint",
1434                 tooltiptext: "script.tip.Set_Breakpoint",
1435                 type: "checkbox",
1436                 checked: hasBreakpoint,
1437                 command: Obj.bindFixed(this.toggleBreakpoint, this, lineNo)
1438             }
1439         );
1440 
1441         if (hasBreakpoint)
1442         {
1443             var isDisabled = JavaScriptTool.isBreakpointDisabled(this.context, this.location.href,
1444                 lineNo);
1445             items.push(
1446                 {
1447                     label: "breakpoints.Disable_Breakpoint",
1448                     tooltiptext: "breakpoints.tip.Disable_Breakpoint",
1449                     type: "checkbox",
1450                     checked: isDisabled,
1451                     command: Obj.bindFixed(this.toggleDisableBreakpoint, this, lineNo)
1452                 }
1453             );
1454         }
1455 
1456         items.push(
1457             {
1458                 label: "EditBreakpointCondition",
1459                 tooltiptext: "breakpoints.tip.Edit_Breakpoint_Condition",
1460                 command: Obj.bindFixed(this.editBreakpointCondition, this, lineNo)
1461             }
1462         );
1463 
1464         if (this.context.stopped)
1465         {
1466             var sourceRow = Dom.getAncestorByClass(target, "sourceRow");
1467             if (sourceRow)
1468             {
1469                 var compilationUnit = Dom.getAncestorByClass(sourceRow, "sourceBox").repObject;
1470                 var lineNo = parseInt(sourceRow.firstChild.textContent);
1471 
1472                 var debuggr = this;
1473                 items.push(
1474                     "-",
1475                     {
1476                         label: "script.Rerun",
1477                         tooltiptext: "script.tip.Rerun",
1478                         id: "contextMenuRerun",
1479                         command: Obj.bindFixed(debuggr.rerun, debuggr, this.context),
1480                         acceltext: "Shift+F8"
1481                     },
1482                     {
1483                         label: "script.Continue",
1484                         tooltiptext: "script.tip.Continue",
1485                         id: "contextMenuContinue",
1486                         command: Obj.bindFixed(debuggr.resume, debuggr, this.context),
1487                         acceltext: "F8"
1488                     },
1489                     {
1490                         label: "script.Step_Over",
1491                         tooltiptext: "script.tip.Step_Over",
1492                         id: "contextMenuStepOver",
1493                         command: Obj.bindFixed(debuggr.stepOver, debuggr, this.context),
1494                         acceltext: "F10"
1495                     },
1496                     {
1497                         label: "script.Step_Into",
1498                         tooltiptext: "script.tip.Step_Into",
1499                         id: "contextMenuStepInto",
1500                         command: Obj.bindFixed(debuggr.stepInto, debuggr, this.context),
1501                         acceltext: "F11"
1502                     },
1503                     {
1504                         label: "script.Step_Out",
1505                         tooltiptext: "script.tip.Step_Out",
1506                         id: "contextMenuStepOut",
1507                         command: Obj.bindFixed(debuggr.stepOut, debuggr, this.context),
1508                         acceltext: "Shift+F11"
1509                     },
1510                     {
1511                         label: "firebug.RunUntil",
1512                         tooltiptext: "script.tip.Run_Until",
1513                         id: "contextMenuRunUntil",
1514                         command: Obj.bindFixed(debuggr.runUntil, debuggr, this.context,
1515                             compilationUnit, lineNo)
1516                     }
1517                 );
1518             }
1519         }
1520 
1521         return items;
1522     },
1523 
1524     getEditor: function(target, value)
1525     {
1526         if (!this.conditionEditor)
1527             this.conditionEditor = new Firebug.Breakpoint.ConditionEditor(this.document);
1528 
1529         return this.conditionEditor;
1530     },
1531 
1532     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1533 
1534     supportsBreakOnNext: function()
1535     {
1536         return this.breakable && Firebug.jsDebuggerOn;
1537     },
1538 
1539     breakOnNext: function(enabled)
1540     {
1541         if (enabled)
1542             JavaScriptTool.breakOnNext(this.context, true);
1543         else
1544             JavaScriptTool.breakOnNext(this.context, false);
1545     },
1546 
1547     getBreakOnNextTooltip: function(armed)
1548     {
1549         return (armed ?
1550             Locale.$STR("script.Disable Break On Next") : Locale.$STR("script.Break On Next"));
1551     },
1552 
1553     shouldBreakOnNext: function()
1554     {
1555         return !!this.context.breakOnNextHook;  // TODO BTI
1556     },
1557 
1558     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1559     // extends ActivablePanel
1560 
1561     /**
1562      * Support for panel activation.
1563      */
1564     onActivationChanged: function(enable)
1565     {
1566         JavaScriptTool.setActivation(enable);
1567 
1568         if (enable)
1569             Firebug.TabCacheModel.addObserver(this);
1570         else
1571             Firebug.TabCacheModel.removeObserver(this);
1572 
1573         // If the Script is disabled make sure the BON tab flag (orange background)
1574         // is properly updated.
1575         Firebug.Breakpoint.updatePanelTabs(Firebug.currentContext);
1576     },
1577 
1578     // implements Tool
1579     onActiveTool: function(isActive)
1580     {
1581         this.onJavaScriptDebugging(isActive, "onActiveTool");
1582     },
1583 
1584     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1585     // Toolbar functions
1586 
1587     attachListeners: function(context, chrome)
1588     {
1589         this.keyListeners =
1590         [
1591             chrome.keyCodeListen("F8", Events.isShift, Obj.bind(this.rerun, this, context), true),
1592             chrome.keyCodeListen("F8", null, Obj.bind(this.resume, this, context), true),
1593             chrome.keyCodeListen("F10", null, Obj.bind(this.stepOver, this, context), true),
1594             chrome.keyCodeListen("F11", null, Obj.bind(this.stepInto, this, context)),
1595             chrome.keyCodeListen("F11", Events.isShift, Obj.bind(this.stepOut, this, context))
1596         ];
1597     },
1598 
1599     detachListeners: function(context, chrome)
1600     {
1601         if (this.keyListeners)
1602         {
1603             for (var i = 0; i < this.keyListeners.length; ++i)
1604                 chrome.keyIgnore(this.keyListeners[i]);
1605             delete this.keyListeners;
1606         }
1607     },
1608 
1609     syncListeners: function(context)
1610     {
1611         var chrome = Firebug.chrome;
1612 
1613         if (context.stopped)
1614             this.attachListeners(context, chrome);
1615         else
1616             this.detachListeners(context, chrome);
1617     },
1618 
1619     syncCommands: function(context)
1620     {
1621         var chrome = Firebug.chrome;
1622         if (!chrome)
1623         {
1624             if (FBTrace.DBG_ERRORS)
1625                 FBTrace.sysout("debugger.syncCommand, context with no chrome: " +
1626                     context.getGlobalScope());
1627 
1628             return;
1629         }
1630 
1631         if (context.stopped)
1632         {
1633             chrome.setGlobalAttribute("fbDebuggerButtons", "stopped", "true");
1634             chrome.setGlobalAttribute("cmd_firebug_rerun", "disabled", "false");
1635             chrome.setGlobalAttribute("cmd_firebug_resumeExecution", "disabled", "false");
1636             chrome.setGlobalAttribute("cmd_firebug_stepOver", "disabled", "false");
1637             chrome.setGlobalAttribute("cmd_firebug_stepInto", "disabled", "false");
1638             chrome.setGlobalAttribute("cmd_firebug_stepOut", "disabled", "false");
1639         }
1640         else
1641         {
1642             chrome.setGlobalAttribute("fbDebuggerButtons", "stopped", "false");
1643             chrome.setGlobalAttribute("cmd_firebug_rerun", "disabled", "true");
1644             chrome.setGlobalAttribute("cmd_firebug_stepOver", "disabled", "true");
1645             chrome.setGlobalAttribute("cmd_firebug_stepInto", "disabled", "true");
1646             chrome.setGlobalAttribute("cmd_firebug_stepOut", "disabled", "true");
1647             chrome.setGlobalAttribute("cmd_firebug_resumeExecution", "disabled", "true");
1648         }
1649     },
1650 
1651     rerun: function(context)
1652     {
1653         JavaScriptTool.rerun(context);
1654     },
1655 
1656     resume: function(context)
1657     {
1658         JavaScriptTool.resumeJavaScript(context);
1659     },
1660 
1661     stepOver: function(context)
1662     {
1663         JavaScriptTool.stepOver(context);
1664     },
1665 
1666     stepInto: function(context)
1667     {
1668         JavaScriptTool.stepInto(context);
1669     },
1670 
1671     stepOut: function(context)
1672     {
1673         JavaScriptTool.stepOut(context);
1674     },
1675 
1676     runUntil: function(context, compilationUnit, lineNo)
1677     {
1678         JavaScriptTool.runUntil(compilationUnit, lineNo);
1679     },
1680 
1681     onStartDebugging: function(frame)
1682     {
1683         if (FBTrace.DBG_UI_LOOP)
1684             FBTrace.sysout("script.startDebugging enter context: " + this.context.getName());
1685 
1686         try
1687         {
1688             var currentBreakable = Firebug.chrome.getGlobalAttribute("cmd_firebug_toggleBreakOn",
1689                 "breakable");
1690 
1691             if (FBTrace.DBG_BP)
1692             {
1693                 FBTrace.sysout("debugger.startDebugging; currentBreakable " + currentBreakable +
1694                     " in " + this.context.getName() + " currentContext " +
1695                     Firebug.currentContext.getName());
1696             }
1697 
1698             // If currentBreakable is false, then we are armed, but we broke
1699             if (currentBreakable == "false")
1700                 Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleBreakOn", "breakable", "true");
1701 
1702             // If Firebug is minimized, open the UI to show we are stopped
1703             if (Firebug.isMinimized())
1704                 Firebug.unMinimize();
1705 
1706             this.syncCommands(this.context);
1707             this.syncListeners(this.context);
1708 
1709             // Update Break on Next lightning
1710             Firebug.Breakpoint.updatePanelTab(this, false);
1711             Firebug.chrome.select(frame, "script", null, true);
1712             Firebug.chrome.syncPanel("script");  // issue 3463 and 4213
1713             Firebug.chrome.focus();
1714         }
1715         catch(exc)
1716         {
1717             if (FBTrace.DBG_ERRORS)
1718                 FBTrace.sysout("Resuming debugger: error during debugging loop: " + exc, exc);
1719 
1720             Firebug.Console.log("Resuming debugger: error during debugging loop: " + exc);
1721             this.resume(this.context);
1722         }
1723 
1724         if (FBTrace.DBG_UI_LOOP)
1725         {
1726             FBTrace.sysout("script.onStartDebugging exit context.stopped:" +
1727                 this.context.stopped + " for context: " + this.context.getName());
1728         }
1729     },
1730 
1731     onStopDebugging: function()
1732     {
1733         if (FBTrace.DBG_UI_LOOP)
1734             FBTrace.sysout("script.onStopDebugging enter context: " + this.context.getName());
1735 
1736         try
1737         {
1738             var chrome = Firebug.chrome;
1739 
1740             if (this.selectedSourceBox && this.selectedSourceBox.breakCauseBox)
1741             {
1742                 this.selectedSourceBox.breakCauseBox.hide();
1743                 delete this.selectedSourceBox.breakCauseBox;
1744             }
1745 
1746             this.syncCommands(this.context);
1747             this.syncListeners(this.context);
1748             this.highlight(false);
1749 
1750             // After main panel is completely updated
1751             chrome.syncSidePanels();
1752         }
1753         catch (exc)
1754         {
1755             if (FBTrace.DBG_UI_LOOP)
1756                 FBTrace.sysout("debugger.stopDebugging FAILS", exc);
1757 
1758             // If the window is closed while the debugger is stopped,
1759             // then all hell will break loose here
1760             Debug.ERROR(exc);
1761         }
1762     },
1763 
1764 });
1765 
1766 // ********************************************************************************************* //
1767 
1768 const reWord = /([A-Za-z_$0-9]+)(\.([A-Za-z_$0-9]+)|\[([A-Za-z_$0-9]+|["'].+?["'])\])*/;
1769 
1770 function getExpressionAt(text, charOffset)
1771 {
1772     var offset = 0;
1773     for (var m = reWord.exec(text); m; m = reWord.exec(text.substr(offset)))
1774     {
1775         var word = m[0];
1776         var wordOffset = offset+m.index;
1777         if (charOffset >= wordOffset && charOffset <= wordOffset+word.length)
1778         {
1779             var innerOffset = charOffset-wordOffset;
1780             m = word.substr(innerOffset+1).match(/\.|\]|\[|$/);
1781             var end = m.index + innerOffset + 1, start = 0;
1782 
1783             var openBr = word.lastIndexOf('[', innerOffset);
1784             var closeBr = word.lastIndexOf(']', innerOffset);
1785 
1786             if (openBr == innerOffset)
1787                 end++;
1788             else if (closeBr < openBr)
1789             {
1790                 if (/['"\d]/.test(word[openBr+1]))
1791                     end++;
1792                 else
1793                     start = openBr + 1;
1794             }
1795 
1796             word = word.substring(start, end);
1797 
1798             if (/^\d+$/.test(word) && word[0] != '0')
1799                 word = '';
1800 
1801             return {expr: word, offset: wordOffset-start};
1802         }
1803         offset = wordOffset+word.length;
1804     }
1805 
1806     return {expr: null, offset: -1};
1807 };
1808 
1809 // ********************************************************************************************* //
1810 // Domplate Templates
1811 
1812 with (Domplate) {
1813 
1814 /**
1815  * @domplate Displays various warning messages within the Script panel.
1816  */
1817 Firebug.ScriptPanel.WarningRep = domplate(Firebug.Rep,
1818 {
1819     tag:
1820         DIV({"class": "disabledPanelBox"},
1821             H1({"class": "disabledPanelHead"},
1822                 SPAN("$pageTitle")
1823             ),
1824             P({"class": "disabledPanelDescription", style: "margin-top: 15px;"},
1825                 SPAN("$suggestion")
1826             )
1827         ),
1828 
1829     enableScriptTag:
1830         SPAN({"class": "objectLink", onclick: "$onEnableScript", style: "color: blue"},
1831             Locale.$STR("script.button.enable_javascript")
1832         ),
1833 
1834     focusDebuggerTag:
1835         SPAN({"class": "objectLink", onclick: "$onFocusDebugger", style: "color: blue"},
1836             Locale.$STR("script.button.Go to that page")
1837         ),
1838 
1839     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1840 
1841     onEnableScript: function(event)
1842     {
1843         Firebug.Options.setPref("javascript", "enabled", true);
1844 
1845         Firebug.TabWatcher.reloadPageFromMemory(Firebug.currentContext);
1846     },
1847 
1848     onFocusDebugger: function(event)
1849     {
1850         Win.iterateBrowserWindows("navigator:browser", function(win)
1851         {
1852             return win.Firebug.TabWatcher.iterateContexts(function(context)
1853             {
1854                 if (context.stopped)
1855                 {
1856                     // Focus browser window with active debugger and select the Script panel
1857                     win.Firebug.focusBrowserTab(context.window);
1858                     win.Firebug.chrome.selectPanel("script");
1859                     return true;
1860                 }
1861             });
1862         });
1863 
1864         // No context is stopped
1865         if (FBTrace.DBG_UI_LOOP)
1866             FBTrace.sysout("script.onFocusDebugger FAILED");
1867     },
1868 
1869     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
1870 
1871     showInactive: function(parentNode)
1872     {
1873         var args = {
1874             pageTitle: Locale.$STR("script.warning.inactive_during_page_load"),
1875             suggestion: Locale.$STR("script.suggestion.inactive_during_page_load2")
1876         };
1877 
1878         var box = this.tag.replace(args, parentNode, this);
1879         var description = box.getElementsByClassName("disabledPanelDescription").item(0);
1880         FirebugReps.Description.render(args.suggestion, description,
1881             Obj.bindFixed(Firebug.TabWatcher.reloadPageFromMemory,  Firebug.TabWatcher,
1882             Firebug.currentContext));
1883 
1884         return box;
1885     },
1886 
1887     showNotEnabled: function(parentNode)
1888     {
1889         var args = {
1890             pageTitle: Locale.$STR("script.warning.javascript_not_enabled"),
1891             suggestion: Locale.$STR("script.suggestion.javascript_not_enabled")
1892         }
1893 
1894         var box = this.tag.replace(args, parentNode, this);
1895         this.enableScriptTag.append({}, box, this);
1896 
1897         return box;
1898     },
1899 
1900     showDebuggerInactive: function(parentNode)
1901     {
1902         var args = {
1903             pageTitle: Locale.$STR("script.warning.debugger_not_activated"),
1904             suggestion: Locale.$STR("script.suggestion.debugger_not_activated")
1905         }
1906 
1907         var box = this.tag.replace(args, parentNode, this);
1908 
1909         return box;
1910     },
1911 
1912     showFiltered: function(parentNode)
1913     {
1914         var args = {
1915             pageTitle: Locale.$STR("script.warning.all_scripts_filtered"),
1916             suggestion: Locale.$STR("script.suggestion.all_scripts_filtered")
1917         };
1918         return this.tag.replace(args, parentNode, this);
1919     },
1920 
1921     showNoScript: function(parentNode)
1922     {
1923         var args = {
1924             pageTitle: Locale.$STR("script.warning.no_javascript"),
1925             suggestion: Locale.$STR("script.suggestion.no_javascript2")
1926         }
1927         return this.tag.replace(args, parentNode, this);
1928     },
1929 
1930     showNoDebuggingForSystemSources: function(parentNode)
1931     {
1932         var args = {
1933             pageTitle: Locale.$STR("script.warning.no_system_source_debugging"),
1934             suggestion: Locale.$STR("script.suggestion.no_system_source_debugging")
1935         }
1936 
1937         var box = this.tag.replace(args, parentNode, this);
1938         var description = box.getElementsByClassName("disabledPanelDescription").item(0);
1939         FirebugReps.Description.render(args.suggestion, description,
1940             Obj.bindFixed(Firebug.chrome.visitWebsite, this, "issue5110"));
1941 
1942         return box;
1943     },
1944     
1945     showActivitySuspended: function(parentNode)
1946     {
1947         var args = {
1948             pageTitle: Locale.$STR("script.warning.debugger_active"),
1949             suggestion: Locale.$STR("script.suggestion.debugger_active")
1950         }
1951 
1952         var box = this.tag.replace(args, parentNode, this);
1953         this.focusDebuggerTag.append({}, box, this);
1954 
1955         return box;
1956     }
1957 });
1958 
1959 var WarningRep = Firebug.ScriptPanel.WarningRep;
1960 
1961 // ********************************************************************************************* //
1962 
1963 Firebug.ScriptPanel.BreakpointInfoTip = domplate(Firebug.Rep,
1964 {
1965     tag:
1966         DIV("$expr"),
1967 
1968     render: function(parentNode, expr)
1969     {
1970         this.tag.replace({expr: expr}, parentNode, this);
1971     }
1972 });
1973 
1974 // ********************************************************************************************* //
1975 
1976 }; // END with (Domplate)
1977 
1978 // ********************************************************************************************* //
1979 // Registration
1980 
1981 Firebug.registerPanel(Firebug.ScriptPanel);
1982 
1983 return Firebug.ScriptPanel;
1984 
1985 // ********************************************************************************************* //
1986 });
1987