1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/lib/domplate", 7 "firebug/chrome/reps", 8 "firebug/lib/locale", 9 "firebug/lib/events", 10 "firebug/js/sourceLink", 11 "firebug/js/stackFrame", 12 "firebug/lib/css", 13 "firebug/lib/dom", 14 "firebug/lib/string", 15 "firebug/lib/array", 16 "firebug/lib/persist", 17 "firebug/chrome/menu", 18 "firebug/js/fbs", 19 "firebug/editor/editor", 20 "firebug/console/autoCompleter" 21 ], 22 function(Obj, Firebug, Domplate, FirebugReps, Locale, Events, SourceLink, 23 StackFrame, Css, Dom, Str, Arr, Persist, Menu, FBS) { 24 25 // ********************************************************************************************* // 26 // Constants 27 28 /* const animationDuration = 0.8; see issue 5618 */ 29 30 // ********************************************************************************************* // 31 // Breakpoints 32 33 Firebug.Breakpoint = Obj.extend(Firebug.Module, 34 { 35 dispatchName: "breakpoints", 36 37 toggleBreakOnNext: function(panel) 38 { 39 var breakable = Firebug.chrome.getGlobalAttribute("cmd_firebug_toggleBreakOn", "breakable"); 40 41 if (FBTrace.DBG_BP) 42 FBTrace.sysout("breakpoint.toggleBreakOnNext; currentBreakable "+breakable+ 43 " in " + panel.context.getName()); 44 45 // Toggle button's state. 46 breakable = (breakable == "true" ? "false" : "true"); 47 Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleBreakOn", "breakable", breakable); 48 49 // Call the current panel's logic related to break-on-next. 50 // If breakable == "true" the feature is currently disabled. 51 var enabled = (breakable == "true" ? false : true); 52 panel.breakOnNext(enabled); 53 54 // Make sure the correct tooltip (coming from the current panel) is used. 55 this.updateBreakOnNextTooltips(panel); 56 57 // Light up the tab whenever break on next is selected 58 this.updatePanelTab(panel, enabled); 59 60 return enabled; 61 }, 62 63 showPanel: function(browser, panel) 64 { 65 if (!panel) // there is no selectedPanel? 66 return; 67 68 var breakButton = Firebug.chrome.$("fbBreakOnNextButton"); 69 if (panel.name) 70 breakButton.setAttribute("panelName", panel.name); 71 72 breakButton.removeAttribute("type"); 73 Dom.collapse(Firebug.chrome.$("fbBonButtons"), !panel.breakable); 74 75 // The script panel can be created at this moment (the second parameter is false) 76 // It's needed for break on next to work (do not wait till the user actuall 77 // selectes the panel). 78 var scriptPanel = panel.context.getPanel("script"); 79 var scriptEnabled = scriptPanel && scriptPanel.isEnabled(); 80 var tool = Firebug.connection.getTool("script"); 81 var scriptActive = tool && tool.getActive(); 82 var supported = panel.supportsBreakOnNext(); 83 84 // Enable by default and disable if needed. 85 Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleBreakOn", "disabled", null); 86 87 // Disable BON if script is disabled or if BON isn't supported by the current panel. 88 if (!scriptEnabled || !scriptActive || !supported) 89 { 90 Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleBreakOn", "breakable", "disabled"); 91 Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleBreakOn", "disabled", "true"); 92 this.updateBreakOnNextTooltips(panel); 93 return; 94 } 95 96 // Set the tooltips and update break-on-next button's state. 97 var shouldBreak = panel.shouldBreakOnNext(); 98 this.updateBreakOnNextState(panel, shouldBreak); 99 this.updateBreakOnNextTooltips(panel); 100 this.updatePanelTab(panel, shouldBreak); 101 102 var menuItems = panel.getBreakOnMenuItems(); 103 if (!menuItems || !menuItems.length) 104 return; 105 106 breakButton.setAttribute("type", "menu-button"); 107 108 var menuPopup = Firebug.chrome.$("fbBreakOnNextOptions"); 109 Dom.eraseNode(menuPopup); 110 111 Menu.createMenuItems(menuPopup, menuItems); 112 }, 113 114 /* see issue 5618 115 toggleTabHighlighting: function(event) 116 { 117 // Don't continue if it's the wrong animation phase 118 if (Math.floor(event.elapsedTime * 10) % (animationDuration * 20) != 0) 119 return; 120 121 Events.removeEventListener(event.target, "animationiteration", 122 Firebug.Breakpoint.toggleTabHighlighting, true); 123 124 var panel = Firebug.currentContext.getPanel(event.target.panelType.prototype.name); 125 if (!panel) 126 return; 127 128 if (!panel.context.delayedArmedTab) 129 return; 130 131 panel.context.delayedArmedTab.setAttribute("breakOnNextArmed", "true"); 132 delete panel.context.delayedArmedTab; 133 }, 134 */ 135 136 updateBreakOnNextTooltips: function(panel) 137 { 138 var breakable = Firebug.chrome.getGlobalAttribute("cmd_firebug_toggleBreakOn", "breakable"); 139 140 // Get proper tooltip for the break-on-next button from the current panel. 141 // If breakable is set to "false" the feature is already activated (throbbing). 142 var armed = (breakable == "false"); 143 var tooltip = panel.getBreakOnNextTooltip(armed); 144 if (!tooltip) 145 tooltip = ""; 146 147 // The user should know that BON is disabled if the Script panel (debugger) is disabled. 148 if (breakable == "disabled") 149 tooltip += " " + Locale.$STR("firebug.bon.scriptPanelNeeded"); 150 151 Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleBreakOn", "tooltiptext", tooltip); 152 }, 153 154 updateBreakOnNextState: function(panel, armed) 155 { 156 // If the panel should break at the next chance, set the button to not breakable, 157 // which means already active (throbbing). 158 var breakable = armed ? "false" : "true"; 159 Firebug.chrome.setGlobalAttribute("cmd_firebug_toggleBreakOn", "breakable", breakable); 160 }, 161 162 updatePanelTab: function(panel, armed) 163 { 164 if (!panel) 165 return; 166 167 // If the script panels is disabled, BON can't be active. 168 if (!Firebug.PanelActivation.isPanelEnabled("script")) 169 armed = false; 170 171 var panelBar = Firebug.chrome.$("fbPanelBar1"); 172 var tab = panelBar.getTab(panel.name); 173 if (tab) 174 tab.setAttribute("breakOnNextArmed", armed ? "true" : "false"); 175 176 /* see issue 5618 177 { 178 if (armed) 179 { 180 // If there is already a panel armed synchronize highlighting of the panel tabs 181 var tabPanel = tab.parentNode; 182 var otherTabIsArmed = false; 183 for (var i = 0; i < tabPanel.children.length; ++i) 184 { 185 var panelTab = tabPanel.children[i]; 186 if (panelTab !== tab && panelTab.getAttribute("breakOnNextArmed") == "true") 187 { 188 panel.context.delayedArmedTab = tab; 189 Events.addEventListener(panelTab, "animationiteration", 190 this.toggleTabHighlighting, true); 191 otherTabIsArmed = true; 192 break; 193 } 194 } 195 196 if (!otherTabIsArmed) 197 tab.setAttribute("breakOnNextArmed", "true"); 198 } 199 else 200 { 201 delete panel.context.delayedArmedTab; 202 tab.setAttribute("breakOnNextArmed", "false"); 203 } 204 } 205 */ 206 }, 207 208 updatePanelTabs: function(context) 209 { 210 if (!context) 211 return; 212 213 var panelTypes = Firebug.getMainPanelTypes(context); 214 for (var i=0; i<panelTypes.length; ++i) 215 { 216 var panelType = panelTypes[i]; 217 var panel = context.getPanel(panelType.prototype.name); 218 var shouldBreak = (panel && panel.shouldBreakOnNext()) ? true : false; 219 this.updatePanelTab(panel, shouldBreak); 220 } 221 }, 222 223 // supports non-JS break on next 224 breakNow: function(panel) 225 { 226 this.updatePanelTab(panel, false); 227 Firebug.Debugger.breakNow(panel.context); // TODO BTI 228 }, 229 230 updateOption: function(name, value) 231 { 232 if (name == "showBreakNotification") 233 { 234 var panelBar1 = Firebug.chrome.$("fbPanelBar1"); 235 var doc = panelBar1.browser.contentDocument; 236 var checkboxes = doc.querySelectorAll(".doNotShowBreakNotification"); 237 238 for (var i=0; i<checkboxes.length; i++) 239 checkboxes[i].checked = !value; 240 } 241 }, 242 }); 243 244 // ************************************************************************************************ 245 246 with (Domplate) { 247 Firebug.Breakpoint.BreakpointListRep = domplate(Firebug.Rep, 248 { 249 tag: 250 DIV({role : "list"}, 251 FOR("group", "$groups", 252 DIV({"class": "breakpointBlock breakpointBlock-$group.name", role: "list", 253 $opened: "$group.opened", _repObject: "$group", onclick: "$onClick"}, 254 H1({"class": "breakpointHeader groupHeader"}, 255 DIV({"class": "twisty", role: "presentation"}), 256 SPAN({"class": "breakpointsHeaderLabel"}, "$group.title") 257 ), 258 DIV({"class": "breakpointsGroupListBox", role: "listbox"}, 259 FOR("bp", "$group.breakpoints", 260 TAG("$bp|getBreakpointRep", {bp: "$bp"}) 261 ) 262 ) 263 ) 264 ) 265 ), 266 267 getBreakpointRep: function(bp) 268 { 269 var rep = Firebug.getRep(bp, Firebug.currentContext); 270 return rep.tag; 271 }, 272 273 toggleGroup: function(node) 274 { 275 var panel = Firebug.getElementPanel(node); 276 var groupNode = Dom.getAncestorByClass(node, "breakpointBlock"); 277 var group = Firebug.getRepObject(groupNode); 278 279 Css.toggleClass(groupNode, "opened"); 280 var opened = Css.hasClass(groupNode, "opened"); 281 panel.groupOpened[group.name] = opened; 282 283 if (opened) 284 { 285 var offset = Dom.getClientOffset(node); 286 var titleAtTop = offset.y < panel.panelNode.scrollTop; 287 Dom.scrollTo(groupNode, panel.panelNode, null, 288 groupNode.offsetHeight > panel.panelNode.clientHeight || titleAtTop ? "top" : "bottom"); 289 } 290 }, 291 292 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 293 294 onClick: function(event) 295 { 296 if (!Events.isLeftClick(event)) 297 return; 298 299 var header = Dom.getAncestorByClass(event.target, "breakpointHeader"); 300 if (header) 301 { 302 this.toggleGroup(event.target); 303 return; 304 } 305 } 306 }); 307 308 // ********************************************************************************************* // 309 310 Firebug.Breakpoint.BreakpointRep = domplate(Firebug.Rep, 311 { 312 tag: 313 DIV({"class": "breakpointRow focusRow", $disabled: "$bp|isDisabled", role: "option", 314 "aria-checked": "$bp.checked", _repObject: "$bp", onclick: "$onClick"}, 315 DIV({"class": "breakpointBlockHead"}, 316 INPUT({"class": "breakpointCheckbox", type: "checkbox", 317 _checked: "$bp.checked", tabindex : '-1'}), 318 SPAN({"class": "breakpointName"}, "$bp.name"), 319 TAG(FirebugReps.SourceLink.tag, {object: "$bp|getSourceLink"}), 320 IMG({"class": "closeButton", src: "blank.gif"}) 321 ), 322 DIV({"class": "breakpointCode"}, "$bp.sourceLine") 323 ), 324 325 getSourceLink: function(bp) 326 { 327 return new SourceLink.SourceLink(bp.href, bp.lineNumber, "js"); 328 }, 329 330 removeBreakpoint: function(groupName, href, lineNumber) 331 { 332 if (groupName == "breakpoints") 333 FBS.clearBreakpoint(href, lineNumber); 334 else if (groupName == "errorBreakpoints") 335 FBS.clearErrorBreakpoint(href, lineNumber); 336 else if (groupName == "monitors") 337 FBS.unmonitor(href, lineNumber); 338 }, 339 340 enableBreakpoint: function(href, lineNumber) 341 { 342 FBS.enableBreakpoint(href, lineNumber); 343 }, 344 345 disableBreakpoint: function(href, lineNumber) 346 { 347 FBS.disableBreakpoint(href, lineNumber); 348 }, 349 350 isDisabled: function(bp) 351 { 352 return !bp.checked; 353 }, 354 355 getContextMenuItems: function(breakpoint, target) 356 { 357 var head = Dom.getAncestorByClass(target, "breakpointBlock"); 358 var groupName = Css.getClassValue(head, "breakpointBlock"); 359 360 var items = [{ 361 label: "breakpoints.Remove_Breakpoint", 362 tooltiptext: "breakpoints.tip.Remove_Breakpoint", 363 command: Obj.bindFixed(this.removeBreakpoint, this, groupName, 364 breakpoint.href, breakpoint.lineNumber) 365 }]; 366 367 if (groupName == "breakpoints") 368 { 369 if (breakpoint.checked) 370 { 371 items.push({ 372 label: "breakpoints.Disable_Breakpoint", 373 tooltiptext: "breakpoints.tip.Disable_Breakpoint", 374 command: Obj.bindFixed(this.disableBreakpoint, this, breakpoint.href, 375 breakpoint.lineNumber) 376 }); 377 } 378 else 379 { 380 items.push({ 381 label: "breakpoints.Enable_Breakpoint", 382 tooltiptext: "breakpoints.tip.Enable_Breakpoint", 383 command: Obj.bindFixed(this.enableBreakpoint, this, breakpoint.href, 384 breakpoint.lineNumber) 385 }); 386 } 387 } 388 389 items.push( 390 "-" 391 ); 392 393 return items; 394 }, 395 396 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 397 398 inspectable: false, 399 400 supportsObject: function(object, type) 401 { 402 return (object instanceof Firebug.Debugger.Breakpoint); // FIXME moz back end 403 }, 404 405 onClick: function(event) 406 { 407 var panel = Firebug.getElementPanel(event.target); 408 409 if (Dom.getAncestorByClass(event.target, "breakpointCheckbox")) 410 { 411 var node = event.target.parentNode.getElementsByClassName( 412 "objectLink-sourceLink").item(0); 413 414 if (!node) 415 return; 416 417 var sourceLink = node.repObject; 418 419 panel.noRefresh = true; 420 var checkBox = event.target; 421 var bpRow = Dom.getAncestorByClass(checkBox, "breakpointRow"); 422 423 if (checkBox.checked) 424 { 425 this.enableBreakpoint(sourceLink.href, sourceLink.line); 426 bpRow.setAttribute("aria-checked", "true"); 427 } 428 else 429 { 430 this.disableBreakpoint(sourceLink.href, sourceLink.line); 431 bpRow.setAttribute("aria-checked", "false"); 432 } 433 panel.noRefresh = false; 434 } 435 else if (Dom.getAncestorByClass(event.target, "closeButton")) 436 { 437 panel.noRefresh = true; 438 var sourceLink = event.target.parentNode.getElementsByClassName( 439 "objectLink-sourceLink").item(0).repObject; 440 441 var head = Dom.getAncestorByClass(event.target, "breakpointBlock"); 442 var groupName = Css.getClassValue(head, "breakpointBlock"); 443 444 this.removeBreakpoint(groupName, sourceLink.href, sourceLink.line); 445 446 panel.noRefresh = false; 447 } 448 449 panel.refresh(); 450 } 451 })}; 452 453 // ********************************************************************************************* // 454 455 Firebug.Breakpoint.BreakpointsPanel = function() {} 456 457 Firebug.Breakpoint.BreakpointsPanel.prototype = Obj.extend(Firebug.Panel, 458 { 459 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 460 // extends Panel 461 462 name: "breakpoints", 463 parentPanel: "script", 464 order: 2, 465 enableA11y: true, 466 deriveA11yFrom: "console", 467 468 initialize: function() 469 { 470 this.groupOpened = []; 471 472 Firebug.Panel.initialize.apply(this, arguments); 473 }, 474 475 destroy: function(state) 476 { 477 state.groupOpened = this.groupOpened; 478 479 Firebug.Panel.destroy.apply(this, arguments); 480 }, 481 482 show: function(state) 483 { 484 if (this.context.loaded) 485 { 486 var state; 487 Persist.restoreObjects(this, state); 488 489 if (state) 490 { 491 if (state.groupOpened) 492 this.groupOpened = state.groupOpened; 493 } 494 } 495 496 this.refresh(); 497 }, 498 499 refresh: function() 500 { 501 if (this.noRefresh) 502 return; 503 504 var extracted = this.extractBreakpoints(this.context); 505 506 var breakpoints = extracted.breakpoints; 507 var errorBreakpoints = extracted.errorBreakpoints; 508 var monitors = extracted.monitors; 509 510 if (FBTrace.DBG_BP) 511 FBTrace.sysout("breakpoints.refresh extracted " + 512 breakpoints.length+errorBreakpoints.length+monitors.length, 513 [breakpoints, errorBreakpoints, monitors]); 514 515 function sortBreakpoints(a, b) 516 { 517 if (a.href == b.href) 518 return a.lineNumber < b.lineNumber ? -1 : 1; 519 else 520 return a.href < b.href ? -1 : 1; 521 } 522 523 breakpoints.sort(sortBreakpoints); 524 errorBreakpoints.sort(sortBreakpoints); 525 monitors.sort(sortBreakpoints); 526 527 if (FBTrace.DBG_BP) 528 FBTrace.sysout("breakpoints.refresh sorted "+breakpoints.length+ 529 errorBreakpoints.length+monitors.length, [breakpoints, errorBreakpoints, monitors]); 530 531 var groups = []; 532 533 if (breakpoints.length) 534 groups.push({name: "breakpoints", title: Locale.$STR("Breakpoints"), 535 breakpoints: breakpoints}); 536 537 if (errorBreakpoints.length) 538 groups.push({name: "errorBreakpoints", title: Locale.$STR("ErrorBreakpoints"), 539 breakpoints: errorBreakpoints}); 540 541 if (monitors.length) 542 groups.push({name: "monitors", title: Locale.$STR("LoggedFunctions"), 543 breakpoints: monitors}); 544 545 Firebug.connection.dispatch("getBreakpoints", [this.context, groups]); 546 547 if (groups.length != 0) 548 { 549 for (var i = 0; i < groups.length; ++i) 550 { 551 groups[i].opened = typeof this.groupOpened[groups[i].name] != "undefined" ? 552 this.groupOpened[groups[i].name] : true; 553 } 554 555 Firebug.Breakpoint.BreakpointListRep.tag.replace({groups: groups}, this.panelNode); 556 } 557 else 558 { 559 FirebugReps.Warning.tag.replace({object: "NoBreakpointsWarning"}, this.panelNode); 560 } 561 562 if (FBTrace.DBG_BP) 563 { 564 FBTrace.sysout("breakpoints.refresh "+breakpoints.length+ 565 errorBreakpoints.length+monitors.length, [breakpoints, errorBreakpoints, monitors]); 566 } 567 568 Events.dispatch(this.fbListeners, "onBreakRowsRefreshed", [this, this.panelNode]); 569 }, 570 571 extractBreakpoints: function(context) 572 { 573 var breakpoints = []; 574 var errorBreakpoints = []; 575 var monitors = []; 576 577 var renamer = new SourceFileRenamer(context); 578 var self = this; 579 var Breakpoint = Firebug.Debugger.Breakpoint; 580 581 for (var url in context.sourceFileMap) 582 { 583 FBS.enumerateBreakpoints(url, {call: function(url, line, props, scripts) 584 { 585 if (FBTrace.DBG_BP) 586 FBTrace.sysout("breakpoints.extractBreakpoints type: "+props.type+" in url "+ 587 url+"@"+line+" context "+context.getName(), props); 588 589 // some url in this sourceFileMap has changed, we'll be back. 590 if (renamer.checkForRename(url, line, props)) 591 return; 592 593 if (scripts) // then this is a current (not future) breakpoint 594 { 595 var script = scripts[0]; 596 var analyzer = Firebug.SourceFile.getScriptAnalyzer(context, script); 597 if (FBTrace.DBG_BP) 598 FBTrace.sysout("breakpoints.refresh enumerateBreakpoints for script="+ 599 script.tag+(analyzer?" has analyzer":" no analyzer")+" in context "+ 600 context.getName()); 601 602 if (analyzer) 603 var name = analyzer.getFunctionDescription(script, context).name; 604 else 605 var name = StackFrame.guessFunctionName(url, 1, context); 606 607 var isFuture = false; 608 } 609 else 610 { 611 if (FBTrace.DBG_BP) 612 FBTrace.sysout("breakpoints.refresh enumerateBreakpoints future for url@line="+ 613 url+"@"+line+"\n"); 614 615 var isFuture = true; 616 } 617 618 var source = context.sourceCache.getLine(url, line); 619 breakpoints.push(new Breakpoint(name, url, line, !props.disabled, source, isFuture)); 620 }}); 621 622 FBS.enumerateErrorBreakpoints(url, {call: function(url, line, props) 623 { 624 // some url in this sourceFileMap has changed, we'll be back. 625 if (renamer.checkForRename(url, line, props)) 626 return; 627 628 var name = Firebug.SourceFile.guessEnclosingFunctionName(url, line, context); 629 var source = context.sourceCache.getLine(url, line); 630 errorBreakpoints.push(new Breakpoint(name, url, line, true, source)); 631 }}); 632 633 FBS.enumerateMonitors(url, {call: function(url, line, props) 634 { 635 // some url in this sourceFileMap has changed, we'll be back. 636 if (renamer.checkForRename(url, line, props)) 637 return; 638 639 var name = Firebug.SourceFile.guessEnclosingFunctionName(url, line, context); 640 monitors.push(new Breakpoint(name, url, line, true, "")); 641 }}); 642 } 643 644 var result = null; 645 646 if (renamer.needToRename(context)) 647 { 648 // since we renamed some sourceFiles we need to refresh the breakpoints again. 649 result = this.extractBreakpoints(context); 650 } 651 else 652 { 653 result = { 654 breakpoints: breakpoints, 655 errorBreakpoints: errorBreakpoints, 656 monitors: monitors 657 }; 658 } 659 660 // even if we did not rename, some bp may be dynamic 661 if (FBTrace.DBG_SOURCEFILES) 662 FBTrace.sysout("breakpoints.extractBreakpoints context.dynamicURLhasBP: "+ 663 context.dynamicURLhasBP, result); 664 665 return result; 666 }, 667 668 getOptionsMenuItems: function() 669 { 670 var items = []; 671 672 var context = this.context; 673 674 var bpCount = 0, disabledCount = 0; 675 var checkBoxes = this.panelNode.getElementsByClassName("breakpointCheckbox"); 676 for (var i=0; i<checkBoxes.length; i++) 677 { 678 ++bpCount; 679 if (!checkBoxes[i].checked) 680 ++disabledCount; 681 } 682 683 if (disabledCount) 684 { 685 items.push( 686 { 687 label: "EnableAllBreakpoints", 688 command: Obj.bindFixed(this.enableAllBreakpoints, this, context, true), 689 tooltiptext: "breakpoints.option.tip.Enable_All_Breakpoints" 690 } 691 ); 692 } 693 if (bpCount && disabledCount != bpCount) 694 { 695 items.push( 696 { 697 label: "DisableAllBreakpoints", 698 command: Obj.bindFixed(this.enableAllBreakpoints, this, context, false), 699 tooltiptext: "breakpoints.option.tip.Disable_All_Breakpoints" 700 } 701 ); 702 } 703 704 items.push( 705 "-", 706 { 707 label: "ClearAllBreakpoints", 708 disabled: !bpCount, 709 command: Obj.bindFixed(this.clearAllBreakpoints, this, context), 710 tooltiptext: "breakpoints.option.tip.Clear_All_Breakpoints" 711 } 712 ); 713 714 return items; 715 }, 716 717 getContextMenuItems: function(object, target, context) 718 { 719 return this.getOptionsMenuItems(); 720 }, 721 722 enableAllBreakpoints: function(context, status) 723 { 724 var checkBoxes = this.panelNode.getElementsByClassName("breakpointCheckbox"); 725 for (var i=0; i<checkBoxes.length; i++) 726 { 727 var box = checkBoxes[i]; 728 if (box.checked != status) 729 this.click(box); 730 } 731 }, 732 733 clearAllBreakpoints: function(context) 734 { 735 this.noRefresh = true; 736 737 try 738 { 739 // Remove regular JSD breakpoints 740 Firebug.Debugger.clearAllBreakpoints(context); 741 } 742 catch(exc) 743 { 744 FBTrace.sysout("breakpoint.clearAllBreakpoints FAILS "+exc, exc); 745 } 746 747 this.noRefresh = false; 748 this.refresh(); 749 750 // Remove the rest of all the other kinds of breakpoints (after refresh). 751 // These can come from various modules and perhaps extensions, so use 752 // the appropriate remove buttons. 753 var buttons = this.panelNode.getElementsByClassName("closeButton"); 754 while (buttons.length) 755 this.click(buttons[0]); 756 757 // Breakpoint group titles must also go away. 758 this.refresh(); 759 }, 760 761 click: function(node) 762 { 763 var doc = node.ownerDocument, event = doc.createEvent("MouseEvents"); 764 event.initMouseEvent("click", true, true, doc.defaultView, 0, 0, 0, 0, 0, 765 false, false, false, false, 0, null); 766 return node.dispatchEvent(event); 767 } 768 }); 769 770 // ********************************************************************************************* // 771 772 function countBreakpoints(context) 773 { 774 var count = 0; 775 for (var url in context.sourceFileMap) 776 { 777 FBS.enumerateBreakpoints(url, {call: function(url, lineNo) 778 { 779 ++count; 780 }}); 781 } 782 return count; 783 } 784 785 // ********************************************************************************************* // 786 787 Firebug.Breakpoint.BreakpointGroup = function() 788 { 789 this.breakpoints = []; 790 } 791 792 Firebug.Breakpoint.BreakpointGroup.prototype = 793 { 794 removeBreakpoint: function(bp) 795 { 796 Arr.remove(this.breakpoints, bp); 797 }, 798 799 enumerateBreakpoints: function(callback) 800 { 801 var breakpoints = Arr.cloneArray(this.breakpoints); 802 for (var i=0; i<breakpoints.length; i++) 803 { 804 var bp = breakpoints[i]; 805 if (callback(bp)) 806 return true; 807 } 808 return false; 809 }, 810 811 findBreakpoint: function() 812 { 813 for (var i=0; i<this.breakpoints.length; i++) 814 { 815 var bp = this.breakpoints[i]; 816 if (this.matchBreakpoint(bp, arguments)) 817 return bp; 818 } 819 return null; 820 }, 821 822 matchBreakpoint: function(bp, args) 823 { 824 // TODO: must be implemented in derived objects. 825 return false; 826 }, 827 828 isEmpty: function() 829 { 830 return !this.breakpoints.length; 831 } 832 }; 833 834 // ********************************************************************************************* // 835 // TODO move to mozilla back end 836 837 function SourceFileRenamer(context) 838 { 839 this.renamedSourceFiles = []; 840 this.context = context; 841 this.bps = []; 842 } 843 844 SourceFileRenamer.prototype.checkForRename = function(url, line, props) 845 { 846 var sourceFile = this.context.sourceFileMap[url]; 847 if (sourceFile.isEval() || sourceFile.isEvent()) 848 { 849 var segs = sourceFile.href.split('/'); 850 if (segs.length > 2) 851 { 852 if (segs[segs.length - 2] == "seq") 853 { 854 this.renamedSourceFiles.push(sourceFile); 855 this.bps.push(props); 856 } 857 } 858 859 // whether not we needed to rename, the dynamic sourceFile has a bp. 860 this.context.dynamicURLhasBP = true; 861 862 if (FBTrace.DBG_SOURCEFILES) 863 FBTrace.sysout("breakpoints.checkForRename found bp in "+sourceFile+" renamed files:", 864 this.renamedSourceFiles); 865 } 866 else 867 { 868 if (FBTrace.DBG_SOURCEFILES) 869 FBTrace.sysout("breakpoints.checkForRename found static bp in " + sourceFile + 870 " bp:", props); 871 } 872 873 return (this.renamedSourceFiles.length > 0); 874 }; 875 876 SourceFileRenamer.prototype.needToRename = function(context) 877 { 878 if (this.renamedSourceFiles.length > 0) 879 this.renameSourceFiles(context); 880 881 if (FBTrace.DBG_SOURCEFILES) 882 FBTrace.sysout("debugger renamed " + this.renamedSourceFiles.length + " sourceFiles", 883 context.sourceFileMap); 884 885 return this.renamedSourceFiles.length; 886 } 887 888 SourceFileRenamer.prototype.renameSourceFiles = function(context) 889 { 890 for (var i = 0; i < this.renamedSourceFiles.length; i++) 891 { 892 var sourceFile = this.renamedSourceFiles[i]; 893 var bp = this.bps[i]; 894 895 var oldURL = sourceFile.href; 896 var sameType = bp.type; 897 var sameLineNo = bp.lineNo; 898 899 // last is sequence #, next-last is "seq", next-next-last is kind 900 var segs = oldURL.split('/'); 901 var kind = segs.splice(segs.length - 3, 3)[0]; 902 var callerURL = segs.join('/'); 903 if (!sourceFile.source) 904 { 905 FBTrace.sysout("breakpoint.renameSourceFiles no source for " + oldURL + 906 " callerURL " + callerURL, sourceFile) 907 continue; 908 } 909 910 var newURL = Firebug.Debugger.getURLFromMD5(callerURL, sourceFile.source, kind); 911 sourceFile.href = newURL.href; 912 913 FBS.removeBreakpoint(bp.type, oldURL, bp.lineNo); 914 delete context.sourceFileMap[oldURL]; // SourceFile delete 915 916 if (FBTrace.DBG_SOURCEFILES) 917 FBTrace.sysout("breakpoints.renameSourceFiles type: "+bp.type, bp); 918 919 Firebug.Debugger.watchSourceFile(context, sourceFile); 920 var newBP = FBS.addBreakpoint(sameType, sourceFile, sameLineNo, bp, Firebug.Debugger); 921 922 var panel = context.getPanel("script", true); 923 if (panel) 924 { 925 panel.context.invalidatePanels("breakpoints"); 926 panel.renameSourceBox(oldURL, newURL.href); 927 } 928 929 if (context.sourceCache.isCached(oldURL)) 930 { 931 var lines = context.sourceCache.load(oldURL); 932 context.sourceCache.storeSplitLines(newURL.href, lines); 933 context.sourceCache.invalidate(oldURL); 934 } 935 936 if (FBTrace.DBG_SOURCEFILES) 937 FBTrace.sysout("SourceFileRenamer renamed " + oldURL + " to " + newURL, 938 { newBP: newBP, oldBP: bp}); 939 } 940 941 return this.renamedSourceFiles.length; 942 } 943 944 // ********************************************************************************************* // 945 946 Firebug.Breakpoint.ConditionEditor = function(doc) 947 { 948 this.initialize(doc); 949 } 950 951 with (Domplate) { 952 Firebug.Breakpoint.ConditionEditor.prototype = domplate(Firebug.JSEditor.prototype, 953 { 954 tag: 955 DIV({"class": "conditionEditor"}, 956 DIV({"class": "conditionCaption"}, Locale.$STR("ConditionInput")), 957 INPUT({"class": "conditionInput completionBox", type: "text", 958 tabindex: "-1"}), 959 INPUT({"class": "conditionInput completionInput", type: "text", 960 "aria-label": Locale.$STR("ConditionInput"), 961 oninput: "$onInput", onkeypress: "$onKeyPress"} 962 ) 963 ), 964 965 initialize: function(doc) 966 { 967 this.box = this.tag.replace({}, doc, this); 968 this.input = this.box.getElementsByClassName("completionInput").item(0); 969 970 var completionBox = this.box.getElementsByClassName("completionBox").item(0); 971 var options = { 972 tabWarnings: true 973 }; 974 this.setupCompleter(completionBox, options); 975 }, 976 977 show: function(sourceLine, panel, value) 978 { 979 this.target = sourceLine; 980 this.panel = panel; 981 982 this.getAutoCompleter().reset(); 983 984 Dom.hide(this.box, true); 985 panel.selectedSourceBox.appendChild(this.box); 986 987 this.input.value = value; 988 989 setTimeout(Obj.bindFixed(function() 990 { 991 var offset = Dom.getClientOffset(sourceLine); 992 993 var bottom = offset.y+sourceLine.offsetHeight; 994 var y = bottom - this.box.offsetHeight; 995 if (y < panel.selectedSourceBox.scrollTop) 996 { 997 y = offset.y; 998 Css.setClass(this.box, "upsideDown"); 999 } 1000 else 1001 { 1002 Css.removeClass(this.box, "upsideDown"); 1003 } 1004 1005 this.box.style.top = y + "px"; 1006 Dom.hide(this.box, false); 1007 1008 this.input.focus(); 1009 this.input.select(); 1010 }, this)); 1011 }, 1012 1013 hide: function() 1014 { 1015 this.box.parentNode.removeChild(this.box); 1016 1017 delete this.target; 1018 delete this.panel; 1019 }, 1020 1021 layout: function() 1022 { 1023 }, 1024 1025 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1026 1027 endEditing: function(target, value, cancel) 1028 { 1029 if (!cancel) 1030 { 1031 var compilationUnit = this.panel.location; 1032 var lineNo = parseInt(this.target.textContent); 1033 // TODO rest is mozilla backend 1034 var sourceFile = compilationUnit.sourceFile; 1035 FBS.setBreakpointCondition(sourceFile, lineNo, value, Firebug.Debugger); 1036 } 1037 }, 1038 }); 1039 1040 // ********************************************************************************************* // 1041 1042 /** 1043 * Construct a break notification popup 1044 * @param doc the document to contain the notification 1045 * @param cause info object for the popup, with these optional fields: 1046 * strings: title, message, attrName 1047 * elements: target, relatedTarget: element 1048 * objects: prevValue, newValue 1049 */ 1050 Firebug.Breakpoint.BreakNotification = function(doc, cause) 1051 { 1052 this.document = doc; 1053 this.cause = cause; 1054 } 1055 1056 Firebug.Breakpoint.BreakNotification.prototype = domplate(Firebug.Rep, 1057 /** @lends Firebug.ScriptPanel.Notification */ 1058 { 1059 tag: 1060 DIV({"class": "notificationBox"}, 1061 TABLE({"class": "notificationTable", onclick: "$onHide", 1062 onmouseover: "$onMouseOver", onmouseout: "$onMouseOut"}, 1063 TBODY( 1064 TR( 1065 TD({"class": "imageCol"}, 1066 IMG({"class": "notificationImage", 1067 src: "chrome://firebug/skin/breakpoint.png"}) 1068 ), 1069 TD({"class": "descCol"}, 1070 SPAN({"class": "notificationDesc"}, "$cause|getDescription"), 1071 SPAN(" "), 1072 SPAN({"class": "diff"}, "$cause|getDiff"), 1073 SPAN({"class": "targets"}), 1074 DIV({"class": "noNotificationDesc"}) 1075 ), 1076 TD({"class": "buttonsCol"}, 1077 BUTTON({"class": "notificationButton copyButton", 1078 onclick: "$onCopyAction", 1079 $collapsed: "$cause|hideCopyAction"}, 1080 Locale.$STR("Copy") 1081 ), 1082 BUTTON({"class": "notificationButton skipButton", 1083 onclick: "$onSkipAction", 1084 $collapsed: "$cause|hideSkipAction"}, 1085 Locale.$STR("script.balloon.Disable") 1086 ), 1087 BUTTON({"class": "notificationButton okButton", 1088 onclick: "$onOkAction", 1089 $collapsed: "$cause|hideOkAction"}, 1090 Locale.$STR("script.balloon.Continue") 1091 ) 1092 ), 1093 TD( 1094 DIV({"class": "notificationClose", onclick: "$onHide"}) 1095 ) 1096 ) 1097 ) 1098 ) 1099 ), 1100 1101 targets: 1102 SPAN( 1103 SPAN(" "), 1104 TAG("$cause|getTargetTag", {object: "$cause.target"}), 1105 SPAN(" "), 1106 TAG("$cause|getRelatedTargetTag", {object: "$cause.relatedNode"}) 1107 ), 1108 1109 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1110 1111 onMouseOver: function(event) 1112 { 1113 var target = event.target; 1114 var box = Dom.getAncestorByClass(target, "notificationBox"); 1115 var close = box.querySelector(".notificationClose"); 1116 1117 // The close button is "active" (red) if the mouse hovers over the notification 1118 // area except when it hovers over a button or link. 1119 var localName = target.localName ? target.localName.toLowerCase() : ""; 1120 if (Css.hasClass(target, "notificationButton") || localName == "a") 1121 close.removeAttribute("active"); 1122 else 1123 close.setAttribute("active", true); 1124 }, 1125 1126 onMouseOut: function(event) 1127 { 1128 var box = Dom.getAncestorByClass(event.target, "notificationBox"); 1129 var close = box.querySelector(".notificationClose"); 1130 close.removeAttribute("active"); 1131 }, 1132 1133 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1134 1135 onHide: function(event) 1136 { 1137 var notify = this.getNotifyObject(event.target); 1138 notify.hide(); 1139 }, 1140 1141 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1142 1143 getDescription: function(cause) 1144 { 1145 var str = cause.message + (cause.attrName ? (" '" + cause.attrName + "'") : ""); 1146 if (this.getDiff(cause)) 1147 str += ":"; 1148 1149 return str; 1150 }, 1151 1152 getTargetTag: function(cause) 1153 { 1154 return this.getElementTag(cause.target) || null; 1155 }, 1156 1157 getRelatedTargetTag: function(cause) 1158 { 1159 return this.getElementTag(cause.relatedNode) || null; 1160 }, 1161 1162 getElementTag: function(node) 1163 { 1164 if (node) 1165 { 1166 var rep = Firebug.getRep(node); 1167 if (rep) 1168 return rep.shortTag || rep.tag; 1169 } 1170 }, 1171 1172 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1173 // Button Handlers 1174 1175 hideCopyAction: function(cause) 1176 { 1177 return !cause.copyAction; 1178 }, 1179 1180 hideSkipAction: function(cause) 1181 { 1182 return !cause.skipAction; 1183 }, 1184 1185 hideOkAction: function(cause) 1186 { 1187 return !cause.okAction; 1188 }, 1189 1190 onCopyAction: function(event) 1191 { 1192 var notify = this.getNotifyObject(event.target); 1193 if (notify.cause.copyAction) 1194 notify.cause.copyAction(); 1195 }, 1196 1197 onSkipAction: function(event) 1198 { 1199 var notify = this.getNotifyObject(event.target); 1200 if (notify.cause.skipAction) 1201 notify.cause.skipAction(); 1202 }, 1203 1204 onOkAction: function(event) 1205 { 1206 var notify = this.getNotifyObject(event.target); 1207 if (notify.cause.okAction) 1208 notify.cause.okAction(); 1209 }, 1210 1211 onCloseAction: function(event) 1212 { 1213 var notify = this.getNotifyObject(event.target); 1214 if (notify.cause.onCloseAction) 1215 notify.cause.onCloseAction(); 1216 else 1217 notify.hide(event); // same as click on notify body 1218 }, 1219 1220 getNotifyObject: function(target) 1221 { 1222 var parentNode = Dom.getAncestorByClass(target, "notificationBox"); 1223 return parentNode.repObject; 1224 }, 1225 1226 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1227 // Action handlers from "do not show again" description 1228 1229 onClickLink: function(event) 1230 { 1231 this.showTabMenu(event); 1232 }, 1233 1234 disableNotifications: function(event) 1235 { 1236 Firebug.setPref(Firebug.prefDomain, "showBreakNotification", false); 1237 1238 // Hide the notification, but default processing of this event would hide it anyway. 1239 this.onHide(event); 1240 }, 1241 1242 showTabMenu: function(event) 1243 { 1244 // Open panel's tab menu to show the "Show Break Notifications" option 1245 // to teach the user where to enable it again. 1246 var panelBar = Firebug.chrome.$("fbPanelBar1"); 1247 var tab = panelBar.getTab("script"); 1248 tab.tabMenu.showMenu(); 1249 1250 // Avoid default processing that hides the notification popup. 1251 Events.cancelEvent(event); 1252 }, 1253 1254 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1255 // Helpers 1256 1257 getDiff: function(cause) 1258 { 1259 var str = ""; 1260 1261 if (cause.prevValue) 1262 str += Str.cropString(cause.prevValue, 40) + " -> "; 1263 1264 if (cause.newValue) 1265 str += Str.cropString(cause.newValue, 40); 1266 1267 if (!str.length) 1268 return ""; 1269 1270 if (!cause.target) 1271 return str; 1272 1273 return str; 1274 }, 1275 1276 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1277 // Public 1278 1279 show: function(parentNode) 1280 { 1281 if (FBTrace.DBG_BP) 1282 FBTrace.sysout("breakNotification.show; " + this.id); 1283 1284 // Reneder the entire notification box. 1285 this.box = this.tag.append(this.cause, parentNode, this); 1286 this.box.repObject = this; 1287 1288 // Appends the HTML targets dynamically. In case they are null, it breaks 1289 // click events. 1290 // xxxHonza: this problem would deserve clarification. 1291 if (this.cause.target || this.cause.relatedNode) 1292 { 1293 var targetsNode = this.box.querySelector(".targets"); 1294 this.targets.replace(this.cause, targetsNode, this); 1295 } 1296 1297 // Render "do not show again" text 1298 var descNode = this.box.querySelector(".noNotificationDesc"); 1299 FirebugReps.Description.render(Locale.$STR("firebug.breakpoint.doNotShowBreakNotification2"), 1300 descNode, Obj.bind(this.onClickLink, this)); 1301 1302 // Tooltips 1303 if (this.cause.skipActionTooltip) 1304 this.box.querySelector(".skipButton").setAttribute("title", this.cause.skipActionTooltip); 1305 if (this.cause.okActionTooltip) 1306 this.box.querySelector(".okButton").setAttribute("title", this.cause.okActionTooltip); 1307 if (this.cause.copyActionTooltip) 1308 this.box.querySelector(".copyButton").setAttribute("title", this.cause.copyActionTooltip); 1309 1310 // xxxHonza: disable the animation, the interval seems to be frozen during debugger break. 1311 this.box.style.top = "0"; 1312 return; 1313 1314 // Animation 1315 var self = this; 1316 var delta = Math.max(3, Math.floor(this.box.clientHeight/5)); 1317 var clientHeight = this.box.clientHeight; 1318 1319 this.box.style.top = -clientHeight + "px"; 1320 var interval = setInterval(function slide(event) 1321 { 1322 var top = parseInt(self.box.style.top, 10); 1323 if (top >= 0) 1324 { 1325 clearInterval(interval); 1326 } 1327 else 1328 { 1329 var newTop = (top + delta) > 0 ? 0 : (top + delta); 1330 self.box.style.top = newTop + "px"; 1331 } 1332 }, 15); 1333 1334 return this.box; 1335 }, 1336 1337 hide: function() 1338 { 1339 if (FBTrace.DBG_BP) 1340 FBTrace.sysout("breakNotification.hide;"); 1341 1342 // xxxHonza: disable the animation, the interval seems to be frozen during debugger break. 1343 if (this.box.parentNode) 1344 this.box.parentNode.removeChild(this.box); 1345 return; 1346 1347 // Animation 1348 var self = this; 1349 var delta = Math.max(3, Math.floor(this.box.clientHeight/5)); 1350 var clientHeight = this.box.clientHeight; 1351 var top = 0; 1352 1353 var interval = setInterval(function slide(event) 1354 { 1355 top = top - delta; 1356 if (top < -clientHeight) 1357 { 1358 clearInterval(interval); 1359 1360 if (self.box.parentNode) 1361 self.box.parentNode.removeChild(self.box); 1362 } 1363 else 1364 { 1365 self.box.style.top = top + "px"; 1366 } 1367 }, 15); 1368 } 1369 })}; 1370 1371 // ********************************************************************************************* // 1372 // Registration 1373 1374 Firebug.registerPanel(Firebug.Breakpoint.BreakpointsPanel); 1375 Firebug.registerRep(Firebug.Breakpoint.BreakpointRep); 1376 Firebug.registerModule(Firebug.Breakpoint); 1377 1378 return Firebug.Breakpoint; 1379 1380 // ********************************************************************************************* // 1381 }); 1382