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