1 /* See license.txt for terms of usage */ 2 3 FBL.ns(function() { with (FBL) { 4 5 // ************************************************************************************************ 6 // Constants 7 8 // ************************************************************************************************ 9 10 Firebug.Breakpoint = extend(Firebug.Module, 11 { 12 dispatchName: "breakpoints", 13 14 toggleBreakOnNext: function(panel) 15 { 16 var breakable = Firebug.chrome.getGlobalAttribute("cmd_breakOnNext", "breakable"); 17 18 if (FBTrace.DBG_BP) 19 FBTrace.sysout("breakpoint.toggleBreakOnNext; currentBreakable "+breakable+ 20 " in " + panel.context.getName()); 21 22 // Toggle button's state. 23 breakable = (breakable == "true" ? "false" : "true"); 24 Firebug.chrome.setGlobalAttribute("cmd_breakOnNext", "breakable", breakable); 25 26 // Call the current panel's logic related to break-on-next. 27 // If breakable == "true" the feature is currently disabled. 28 var enabled = (breakable == "true" ? false : true); 29 panel.breakOnNext(enabled); 30 31 // Make sure the correct tooltip (coming from the current panel) is used. 32 this.updateBreakOnNextTooltips(panel); 33 34 // Light up the tab whenever break on next is selected 35 this.updatePanelTab(panel, enabled); 36 37 return enabled; 38 }, 39 40 showPanel: function(browser, panel) 41 { 42 if(!panel) // there is no selectedPanel? 43 return; 44 45 var breakButton = Firebug.chrome.$("fbBreakOnNextButton"); 46 if (panel.name) 47 breakButton.setAttribute("panelName", panel.name); 48 49 breakButton.removeAttribute("type"); 50 51 // Disable break-on-next if it isn't supported by the current panel. 52 if (!panel.breakable) 53 { 54 Firebug.chrome.setGlobalAttribute("cmd_breakOnNext", "breakable", "disabled"); 55 Firebug.chrome.setGlobalAttribute("cmd_breakOnNext", "tooltiptext", ""); 56 return; 57 } 58 59 // Set the tooltips and update break-on-next button's state. 60 var shouldBreak = panel.shouldBreakOnNext(); 61 this.updateBreakOnNextState(panel, shouldBreak); 62 this.updateBreakOnNextTooltips(panel); 63 this.updatePanelTab(panel, shouldBreak); 64 65 var menuItems = panel.getBreakOnMenuItems(); 66 if (!menuItems || !menuItems.length) 67 return; 68 69 breakButton.setAttribute("type", "menu-button"); 70 71 var menuPopup = Firebug.chrome.$("fbBreakOnNextOptions"); 72 eraseNode(menuPopup); 73 74 for (var i=0; i<menuItems.length; ++i) 75 FBL.createMenuItem(menuPopup, menuItems[i]); 76 }, 77 78 updateBreakOnNextTooltips: function(panel) 79 { 80 var breakable = Firebug.chrome.getGlobalAttribute("cmd_breakOnNext", "breakable"); 81 82 // Get proper tooltip for the break-on-next button from the current panel. 83 // If breakable is set to "false" the feature is already activated (throbbing). 84 var armed = (breakable == "false"); 85 var tooltip = panel.getBreakOnNextTooltip(armed); 86 if (!tooltip) 87 tooltip = ""; 88 89 Firebug.chrome.setGlobalAttribute("cmd_breakOnNext", "tooltiptext", tooltip); 90 }, 91 92 updateBreakOnNextState: function(panel, armed) 93 { 94 // If the panel should break at the next chance, set the button to not breakable, 95 // which means already active (throbbing). 96 var breakable = armed ? "false" : "true"; 97 Firebug.chrome.setGlobalAttribute("cmd_breakOnNext", "breakable", breakable); 98 }, 99 100 updatePanelTab: function(panel, armed) 101 { 102 if (!panel) 103 return; 104 105 var panelBar = Firebug.chrome.$("fbPanelBar1"); 106 var tab = panelBar.getTab(panel.name); 107 if (tab) 108 tab.setAttribute("breakOnNextArmed", armed ? "true" : "false"); 109 }, 110 111 breakNow: function(panel) 112 { 113 this.updatePanelTab(panel, false); 114 Firebug.Debugger.breakNow(); 115 } 116 }); 117 118 // ************************************************************************************************ 119 120 Firebug.Breakpoint.BreakpointListRep = domplate(Firebug.Rep, 121 { 122 tag: 123 DIV({onclick: "$onClick", role : "list"}, 124 FOR("group", "$groups", 125 DIV({"class": "breakpointBlock breakpointBlock-$group.name", role: "listitem"}, 126 H1({"class": "breakpointHeader groupHeader"}, 127 "$group.title" 128 ), 129 DIV({"class": "breakpointsGroupListBox", role: "listbox"}, 130 FOR("bp", "$group.breakpoints", 131 TAG("$bp|getBreakpointRep", {bp: "$bp"}) 132 ) 133 ) 134 ) 135 ) 136 ), 137 138 getBreakpointRep: function(bp) 139 { 140 var rep = Firebug.getRep(bp); 141 return rep.tag; 142 }, 143 144 onClick: function(event) 145 { 146 var panel = Firebug.getElementPanel(event.target); 147 148 if (getAncestorByClass(event.target, "breakpointCheckbox")) 149 { 150 var node = event.target.parentNode.getElementsByClassName("objectLink-sourceLink").item(0); 151 if (!node) 152 return; 153 154 var sourceLink = node.repObject; 155 156 panel.noRefresh = true; 157 if (event.target.checked) 158 fbs.enableBreakpoint(sourceLink.href, sourceLink.line); 159 else 160 fbs.disableBreakpoint(sourceLink.href, sourceLink.line); 161 panel.noRefresh = false; 162 } 163 else if (getAncestorByClass(event.target, "closeButton")) 164 { 165 var sourceLink = 166 event.target.parentNode.getElementsByClassName("objectLink-sourceLink").item(0).repObject; 167 168 panel.noRefresh = true; 169 170 var head = getAncestorByClass(event.target, "breakpointBlock"); 171 var groupName = getClassValue(head, "breakpointBlock"); 172 if (groupName == "breakpoints") 173 fbs.clearBreakpoint(sourceLink.href, sourceLink.line); 174 else if (groupName == "errorBreakpoints") 175 fbs.clearErrorBreakpoint(sourceLink.href, sourceLink.line); 176 else if (groupName == "monitors") 177 { 178 fbs.unmonitor(sourceLink.href, sourceLink.line) 179 } 180 181 var row = getAncestorByClass(event.target, "breakpointRow"); 182 panel.removeRow(row); 183 184 panel.noRefresh = false; 185 } 186 } 187 }); 188 189 // ************************************************************************************************ 190 191 Firebug.Breakpoint.BreakpointRep = domplate(Firebug.Rep, 192 { 193 tag: 194 DIV({"class": "breakpointRow focusRow", role: "option", "aria-checked": "$bp.checked"}, 195 DIV({"class": "breakpointBlockHead"}, 196 INPUT({"class": "breakpointCheckbox", type: "checkbox", 197 _checked: "$bp.checked", tabindex : '-1'}), 198 SPAN({"class": "breakpointName"}, "$bp.name"), 199 TAG(FirebugReps.SourceLink.tag, {object: "$bp|getSourceLink"}), 200 IMG({"class": "closeButton", src: "blank.gif"}) 201 ), 202 DIV({"class": "breakpointCode"}, "$bp.sourceLine") 203 ), 204 205 getSourceLink: function(bp) 206 { 207 return new SourceLink(bp.href, bp.lineNumber, "js"); 208 }, 209 210 supportsObject: function(bp) 211 { 212 return (bp instanceof Firebug.Debugger.Breakpoint); 213 } 214 }); 215 216 // ************************************************************************************************ 217 218 Firebug.Breakpoint.BreakpointsPanel = function() {} 219 220 Firebug.Breakpoint.BreakpointsPanel.prototype = extend(Firebug.Panel, 221 { 222 name: "breakpoints", 223 parentPanel: "script", 224 order: 2, 225 226 initialize: function() 227 { 228 Firebug.Panel.initialize.apply(this, arguments); 229 }, 230 231 destroy: function(state) 232 { 233 Firebug.Panel.destroy.apply(this, arguments); 234 }, 235 236 initializeNode : function(oldPanelNode) 237 { 238 dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']); 239 }, 240 241 destroyNode : function() 242 { 243 dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']); 244 }, 245 246 show: function(state) 247 { 248 this.refresh(); 249 }, 250 251 refresh: function() 252 { 253 if (this.noRefresh) 254 return; 255 256 if (!Firebug.Debugger.isAlwaysEnabled(this.context)) 257 this.updateScriptFiles(this.context); 258 259 var extracted = this.extractBreakpoints(this.context, breakpoints, errorBreakpoints, monitors); 260 261 var breakpoints = extracted.breakpoints; 262 var errorBreakpoints = extracted.errorBreakpoints; 263 var monitors = extracted.monitors; 264 265 if (FBTrace.DBG_BP) 266 FBTrace.sysout("debugger.breakpoints.refresh extracted " + 267 breakpoints.length+errorBreakpoints.length+monitors.length, 268 [breakpoints, errorBreakpoints, monitors]); 269 270 function sortBreakpoints(a, b) 271 { 272 if (a.href == b.href) 273 return a.lineNumber < b.lineNumber ? -1 : 1; 274 else 275 return a.href < b.href ? -1 : 1; 276 } 277 278 breakpoints.sort(sortBreakpoints); 279 errorBreakpoints.sort(sortBreakpoints); 280 monitors.sort(sortBreakpoints); 281 282 if (FBTrace.DBG_BP) 283 FBTrace.sysout("debugger.breakpoints.refresh sorted "+breakpoints.length+ 284 errorBreakpoints.length+monitors.length, [breakpoints, errorBreakpoints, monitors]); 285 286 var groups = []; 287 288 if (breakpoints.length) 289 groups.push({name: "breakpoints", title: $STR("Breakpoints"), 290 breakpoints: breakpoints}); 291 if (errorBreakpoints.length) 292 groups.push({name: "errorBreakpoints", title: $STR("ErrorBreakpoints"), 293 breakpoints: errorBreakpoints}); 294 if (monitors.length) 295 groups.push({name: "monitors", title: $STR("LoggedFunctions"), 296 breakpoints: monitors}); 297 298 dispatch(Firebug.Debugger.fbListeners, "getBreakpoints", [this.context, groups]); 299 300 if (groups.length) 301 Firebug.Breakpoint.BreakpointListRep.tag.replace({groups: groups}, this.panelNode); 302 else 303 FirebugReps.Warning.tag.replace({object: "NoBreakpointsWarning"}, this.panelNode); 304 305 if (FBTrace.DBG_BP) 306 FBTrace.sysout("debugger.breakpoints.refresh "+breakpoints.length+ 307 errorBreakpoints.length+monitors.length, [breakpoints, errorBreakpoints, monitors]); 308 309 dispatch([Firebug.A11yModel], 'onBreakRowsRefreshed', [this, this.panelNode]); 310 }, 311 312 extractBreakpoints: function(context, breakpoints, errorBreakpoints, monitors) 313 { 314 var breakpoints = []; 315 var errorBreakpoints = []; 316 var monitors = []; 317 318 var renamer = new SourceFileRenamer(context); 319 var self = this; 320 var Breakpoint = Firebug.Debugger.Breakpoint; 321 322 for (var url in context.sourceFileMap) 323 { 324 fbs.enumerateBreakpoints(url, {call: function(url, line, props, script) 325 { 326 if (FBTrace.DBG_BP) FBTrace.sysout("debugger.extractBreakpoints type: "+props.type, props); 327 if (renamer.checkForRename(url, line, props)) // some url in this sourceFileMap has changed, we'll be back. 328 return; 329 330 if (script) // then this is a current (not future) breakpoint 331 { 332 var analyzer = Firebug.SourceFile.getScriptAnalyzer(context, script); 333 if (FBTrace.DBG_BP) FBTrace.sysout("debugger.refresh enumerateBreakpoints for script="+script.tag+(analyzer?" has analyzer":" no analyzer")+"\n"); 334 if (analyzer) 335 var name = analyzer.getFunctionDescription(script, context).name; 336 else 337 var name = FBL.guessFunctionName(url, 1, context); 338 var isFuture = false; 339 } 340 else 341 { 342 if (FBTrace.DBG_BP) FBTrace.sysout("debugger.refresh enumerateBreakpoints future for url@line="+url+"@"+line+"\n"); 343 var isFuture = true; 344 } 345 346 var source = context.sourceCache.getLine(url, line); 347 breakpoints.push(new Breakpoint(name, url, line, !props.disabled, source, isFuture)); 348 }}); 349 350 fbs.enumerateErrorBreakpoints(url, {call: function(url, line, props) 351 { 352 if (renamer.checkForRename(url, line, props)) // some url in this sourceFileMap has changed, we'll be back. 353 return; 354 355 var name = Firebug.SourceFile.guessEnclosingFunctionName(url, line, context); 356 var source = context.sourceCache.getLine(url, line); 357 errorBreakpoints.push(new Breakpoint(name, url, line, true, source)); 358 }}); 359 360 fbs.enumerateMonitors(url, {call: function(url, line, props) 361 { 362 if (renamer.checkForRename(url, line, props)) // some url in this sourceFileMap has changed, we'll be back. 363 return; 364 365 var name = Firebug.SourceFile.guessEnclosingFunctionName(url, line, context); 366 monitors.push(new Breakpoint(name, url, line, true, "")); 367 }}); 368 } 369 370 var result = null; 371 372 if (renamer.needToRename(context)) 373 result = this.extractBreakpoints(context); // since we renamed some sourceFiles we need to refresh the breakpoints again. 374 else 375 result = { breakpoints: breakpoints, errorBreakpoints: errorBreakpoints, monitors: monitors }; 376 377 // even if we did not rename, some bp may be dynamic 378 if (FBTrace.DBG_SOURCEFILES) 379 FBTrace.sysout("debugger.extractBreakpoints context.dynamicURLhasBP: "+context.dynamicURLhasBP, result); 380 381 return result; 382 }, 383 384 getOptionsMenuItems: function() 385 { 386 var items = []; 387 388 var context = this.context; 389 if (!Firebug.Debugger.isAlwaysEnabled(context)) 390 this.updateScriptFiles(context); 391 392 var bpCount = 0, disabledCount = 0; 393 var checkBoxes = this.panelNode.getElementsByClassName("breakpointCheckbox"); 394 for (var i=0; i<checkBoxes.length; i++) 395 { 396 ++bpCount; 397 if (!checkBoxes[i].checked) 398 ++disabledCount; 399 } 400 401 if (disabledCount) 402 { 403 items.push( 404 {label: "EnableAllBreakpoints", 405 command: bindFixed(this.enableAllBreakpoints, this, context, true) } 406 ); 407 } 408 if (bpCount && disabledCount != bpCount) 409 { 410 items.push( 411 {label: "DisableAllBreakpoints", 412 command: bindFixed(this.enableAllBreakpoints, this, context, false) } 413 ); 414 } 415 416 items.push( 417 "-", 418 {label: "ClearAllBreakpoints", disabled: !bpCount, 419 command: bindFixed(this.clearAllBreakpoints, this, context) } 420 ); 421 422 return items; 423 }, 424 425 enableAllBreakpoints: function(context, status) 426 { 427 var checkBoxes = this.panelNode.getElementsByClassName("breakpointCheckbox"); 428 for (var i=0; i<checkBoxes.length; i++) 429 { 430 var box = checkBoxes[i]; 431 if (box.checked != status) 432 this.click(box); 433 } 434 }, 435 436 clearAllBreakpoints: function(context) 437 { 438 this.noRefresh = true; 439 440 var buttons = this.panelNode.getElementsByClassName("closeButton"); 441 for (var i=0; i<buttons.length; i++) 442 this.click(buttons[i]); 443 444 this.noRefresh = false; 445 this.refresh(); 446 }, 447 448 click: function(node) 449 { 450 var doc = node.ownerDocument, event = doc.createEvent("MouseEvents"); 451 event.initMouseEvent("click", true, true, doc.defaultView, 0, 0, 0, 0, 0, 452 false, false, false, false, 0, null); 453 return node.dispatchEvent(event); 454 }, 455 456 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 457 458 removeRow: function(row) 459 { 460 row.parentNode.removeChild(row); 461 462 var bpCount = countBreakpoints(this.context); 463 if (!bpCount) 464 this.refresh(); 465 }, 466 }); 467 468 // ************************************************************************************************ 469 470 function countBreakpoints(context) 471 { 472 var count = 0; 473 for (var url in context.sourceFileMap) 474 { 475 fbs.enumerateBreakpoints(url, {call: function(url, lineNo) 476 { 477 ++count; 478 }}); 479 } 480 return count; 481 } 482 483 // ************************************************************************************************ 484 485 Firebug.Breakpoint.BreakpointGroup = function() 486 { 487 this.breakpoints = []; 488 } 489 490 Firebug.Breakpoint.BreakpointGroup.prototype = 491 { 492 removeBreakpoint: function(bp) 493 { 494 remove(this.breakpoints, bp); 495 }, 496 497 enumerateBreakpoints: function(callback) 498 { 499 var breakpoints = cloneArray(this.breakpoints); 500 for (var i=0; i<breakpoints.length; i++) 501 { 502 var bp = breakpoints[i]; 503 if (callback(bp)) 504 return true; 505 } 506 return false; 507 }, 508 509 findBreakpoint: function() 510 { 511 for (var i=0; i<this.breakpoints.length; i++) 512 { 513 var bp = this.breakpoints[i]; 514 if (this.matchBreakpoint(bp, arguments)) 515 return bp; 516 } 517 return null; 518 }, 519 520 matchBreakpoint: function(bp, args) 521 { 522 // TODO: must be implemented in derived objects. 523 return false; 524 }, 525 526 isEmpty: function() 527 { 528 return !this.breakpoints.length; 529 } 530 }; 531 532 // ************************************************************************************************ 533 534 function SourceFileRenamer(context) 535 { 536 this.renamedSourceFiles = []; 537 this.context = context; 538 this.bps = []; 539 } 540 541 SourceFileRenamer.prototype.checkForRename = function(url, line, props) 542 { 543 var sourceFile = this.context.sourceFileMap[url]; 544 if (sourceFile.isEval() || sourceFile.isEvent()) 545 { 546 var segs = sourceFile.href.split('/'); 547 if (segs.length > 2) 548 { 549 if (segs[segs.length - 2] == "seq") 550 { 551 this.renamedSourceFiles.push(sourceFile); 552 this.bps.push(props); 553 } 554 } 555 this.context.dynamicURLhasBP = true; // whether not we needed to rename, the dynamic sourceFile has a bp. 556 if (FBTrace.DBG_SOURCEFILES) 557 FBTrace.sysout("debugger.checkForRename found bp in "+sourceFile+" renamed files:", this.renamedSourceFiles); 558 } 559 else 560 { 561 if (FBTrace.DBG_SOURCEFILES) 562 FBTrace.sysout("debugger.checkForRename found static bp in "+sourceFile+" bp:", props); 563 } 564 565 return (this.renamedSourceFiles.length > 0); 566 }; 567 568 SourceFileRenamer.prototype.needToRename = function(context) 569 { 570 if (this.renamedSourceFiles.length > 0) 571 this.renameSourceFiles(context); 572 573 if (FBTrace.DBG_SOURCEFILES) 574 FBTrace.sysout("debugger renamed " + this.renamedSourceFiles.length + " sourceFiles", context.sourceFileMap); 575 576 return this.renamedSourceFiles.length; 577 } 578 579 SourceFileRenamer.prototype.renameSourceFiles = function(context) 580 { 581 for (var i = 0; i < this.renamedSourceFiles.length; i++) 582 { 583 var sourceFile = this.renamedSourceFiles[i]; 584 var bp = this.bps[i]; 585 FBTrace.sysout("debugger.renameSourceFiles type: "+bp.type, bp); 586 var oldURL = sourceFile.href; 587 var sameType = bp.type; 588 var sameLineNo = bp.lineNo; 589 var sameDebuggr = bp.debugger; 590 591 var segs = oldURL.split('/'); // last is sequence #, next-last is "seq", next-next-last is kind 592 var kind = segs.splice(segs.length - 3, 3)[0]; 593 var callerURL = segs.join('/'); 594 var newURL = Firebug.Debugger.getURLFromMD5(callerURL, sourceFile.source, kind); 595 sourceFile.href = newURL.href; 596 597 fbs.removeBreakpoint(bp.type, oldURL, bp.lineNo); 598 delete context.sourceFileMap[oldURL]; // SourceFile delete 599 600 Firebug.Debugger.watchSourceFile(context, sourceFile); 601 var newBP = fbs.addBreakpoint(sameType, sourceFile, sameLineNo, bp, sameDebuggr); 602 603 var panel = context.getPanel("script", true); 604 if (panel) 605 { 606 panel.context.invalidatePanels("breakpoints"); 607 panel.renameSourceBox(oldURL, newURL.href); 608 } 609 if (context.sourceCache.isCached(oldURL)) 610 { 611 var lines = context.sourceCache.load(oldURL); 612 context.sourceCache.storeSplitLines(newURL.href, lines); 613 context.sourceCache.invalidate(oldURL); 614 } 615 616 if (FBTrace.DBG_SOURCEFILES) 617 FBTrace.sysout("SourceFileRenamer renamed "+oldURL +" to "+newURL, { newBP: newBP, oldBP: bp}); 618 } 619 return this.renamedSourceFiles.length; 620 } 621 622 // ************************************************************************************************ 623 624 Firebug.Breakpoint.ConditionEditor = function(doc) 625 { 626 this.initialize(doc); 627 } 628 629 Firebug.Breakpoint.ConditionEditor.prototype = domplate(Firebug.InlineEditor.prototype, 630 { 631 tag: 632 DIV({"class": "conditionEditor"}, 633 DIV({"class": "conditionEditorTop1"}, 634 DIV({"class": "conditionEditorTop2"}) 635 ), 636 DIV({"class": "conditionEditorInner1"}, 637 DIV({"class": "conditionEditorInner2"}, 638 DIV({"class": "conditionEditorInner"}, 639 DIV({"class": "conditionCaption"}, $STR("ConditionInput")), 640 INPUT({"class": "conditionInput", type: "text", 641 "aria-label": $STR("ConditionInput")} 642 ) 643 ) 644 ) 645 ), 646 DIV({"class": "conditionEditorBottom1"}, 647 DIV({"class": "conditionEditorBottom2"}) 648 ) 649 ), 650 651 initialize: function(doc) 652 { 653 this.box = this.tag.replace({}, doc, this); 654 655 // XXXjjb we need childNode[1] always 656 this.input = this.box.childNodes[1].firstChild.firstChild.lastChild; 657 Firebug.InlineEditor.prototype.initialize.apply(this, arguments); 658 }, 659 660 show: function(sourceLine, panel, value) 661 { 662 this.target = sourceLine; 663 this.panel = panel; 664 665 if (this.getAutoCompleter) 666 this.getAutoCompleter().reset(); 667 668 hide(this.box, true); 669 panel.selectedSourceBox.appendChild(this.box); 670 671 if (this.input) 672 this.input.value = value; 673 674 setTimeout(bindFixed(function() 675 { 676 var offset = getClientOffset(sourceLine); 677 678 var bottom = offset.y+sourceLine.offsetHeight; 679 var y = bottom - this.box.offsetHeight; 680 if (y < panel.selectedSourceBox.scrollTop) 681 { 682 y = offset.y; 683 setClass(this.box, "upsideDown"); 684 } 685 else 686 removeClass(this.box, "upsideDown"); 687 688 this.box.style.top = y + "px"; 689 hide(this.box, false); 690 691 if (this.input) 692 { 693 this.input.focus(); 694 this.input.select(); 695 } 696 }, this)); 697 }, 698 699 hide: function() 700 { 701 this.box.parentNode.removeChild(this.box); 702 703 delete this.target; 704 delete this.panel; 705 }, 706 707 layout: function() 708 { 709 }, 710 711 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 712 713 endEditing: function(target, value, cancel) 714 { 715 if (!cancel) 716 { 717 var sourceFile = this.panel.location; 718 var lineNo = parseInt(this.target.textContent); 719 720 fbs.setBreakpointCondition(sourceFile, lineNo, value, Firebug.Debugger); 721 } 722 } 723 }); 724 725 // ************************************************************************************************ 726 /* 727 * Construct a break notification popup 728 * @param doc the document to contain the popup 729 * @param cause info object for the popup, with these optional fields: 730 * strings: title, message, attrName 731 * elements: target, relatedTarget: element 732 * objects: prevValue, newValue 733 */ 734 Firebug.Breakpoint.BreakNotification = function(doc, cause) 735 { 736 this.initialize(doc, cause); 737 } 738 739 Firebug.Breakpoint.BreakNotification.prototype = domplate(Firebug.InlineEditor.prototype, 740 { 741 tag: 742 DIV({"class": "conditionEditor breakNotification", onclick: "$hide"}, 743 DIV({"class": "notationEditorTop1"}, 744 DIV({"class": "notationEditorTop2"}) 745 ), 746 DIV({"class": "notationEditorInner1"}, 747 DIV({"class": "notationEditorInner2"}, 748 DIV({"class": "conditionEditorInner"}, 749 DIV({"class": "notationCaption"}, 750 SPAN({"class": "notationTitle"}, "$cause.title"), 751 BUTTON({"class": "notationButton", onclick: "$onCopyAction", 752 $collapsed: "$cause|hideCopyAction"}, 753 $STR("Copy") 754 ) 755 ), 756 DIV({"class": "notationCaption"}, 757 SPAN({"class": "notationTitle"}, "$cause|getTitle"), 758 SPAN(" "), 759 SPAN({"class": "notationTitle diff"}, "$cause|getDiff"), 760 SPAN(" "), 761 TAG("$cause|getTargetTag", {object: "$cause.target"}), 762 SPAN(" "), 763 TAG("$cause|getRelatedTargetTag", {object: "$cause.relatedNode"}) 764 ) 765 ) 766 ) 767 ), 768 DIV({"class": "notationEditorBottom1"}, 769 DIV({"class": "notationEditorBottom2"}) 770 ) 771 ), 772 773 getTargetTag: function(cause) 774 { 775 return cause.target ? FirebugReps.Element.shortTag : null; 776 }, 777 778 getRelatedTargetTag: function(cause) 779 { 780 return cause.relatedTarget ? FirebugReps.Element.shortTag : null; 781 }, 782 783 getDiff: function(cause) 784 { 785 var str = ""; 786 if (cause.prevValue) 787 str += cropString(cause.prevValue, 40) + " -> "; 788 if (cause.newValue) 789 str += cropString(cause.newValue, 40); 790 791 if (!str.length) 792 return ""; 793 794 if (!cause.target) 795 return str; 796 797 return str; 798 }, 799 800 getTitle: function(cause) 801 { 802 var str = cause.message + (cause.attrName ? (" '"+cause.attrName+"'") : ""); 803 if (this.getDiff(cause)) 804 str += ":"; 805 return str; 806 }, 807 808 initialize: function(doc, cause) 809 { 810 this.cause = cause; 811 this.box = this.tag.replace({cause: cause}, doc, this); 812 }, 813 814 show: function(sourceLine, panel, value) 815 { 816 this.target = sourceLine; 817 this.panel = panel; 818 819 hide(this.box, true); 820 panel.selectedSourceBox.appendChild(this.box); 821 822 setTimeout(bindFixed(function() 823 { 824 var offset = getClientOffset(sourceLine); 825 826 var bottom = offset.y+sourceLine.offsetHeight; 827 var y = bottom - this.box.offsetHeight; 828 if (y < panel.selectedSourceBox.scrollTop) 829 { 830 y = offset.y; 831 setClass(this.box, "upsideDown"); 832 } 833 else 834 removeClass(this.box, "upsideDown"); 835 836 this.box.style.top = y + "px"; 837 hide(this.box, false); 838 }, this)); 839 }, 840 841 hide: function(event) // the argument event does not come thru?? 842 { 843 if (this.panel) 844 { 845 var guts = this.box.getElementsByClassName("conditionEditorInner").item(0); 846 collapse(guts, true); // as the box shrinks you don't want text to spill 847 848 var msg = this.cause.message; 849 if (msg) 850 { 851 var self = this; 852 var delta = Math.max(20,Math.floor(self.box.clientWidth/20)); 853 var interval = setInterval(function slide(event) 854 { 855 if (self.box.clientWidth < delta) 856 { 857 clearNode(guts); 858 859 clearInterval(interval); 860 self.box.parentNode.removeChild(self.box); 861 self.target.setAttribute('title', msg); 862 setClass(self.target, "noteInToolTip"); 863 delete self.target; 864 delete self.panel; 865 } 866 else 867 self.box.style.width = (self.box.clientWidth - delta)+"px"; 868 }, 15); 869 } 870 else 871 { 872 delete this.target; 873 delete this.panel; 874 } 875 } 876 // else we already called hide 877 }, 878 879 hideCopyAction: function(cause) 880 { 881 return !cause.copyAction; 882 }, 883 884 onCopyAction: function(event) 885 { 886 if (this.cause.copyAction) 887 this.cause.copyAction(); 888 } 889 }); 890 891 // ************************************************************************************************ 892 // Registration 893 894 Firebug.registerPanel(Firebug.Breakpoint.BreakpointsPanel); 895 Firebug.registerRep(Firebug.Breakpoint.BreakpointRep); 896 Firebug.registerModule(Firebug.Breakpoint); 897 898 // ************************************************************************************************ 899 }}); 900