1 /* See license.txt for terms of usage */ 2 3 FBL.ns(function() { with (FBL) { 4 5 // ************************************************************************************************ 6 // Constants 7 8 const Cc = Components.classes; 9 const Ci = Components.interfaces; 10 11 const MODIFICATION = MutationEvent.MODIFICATION; 12 const ADDITION = MutationEvent.ADDITION; 13 const REMOVAL = MutationEvent.REMOVAL; 14 15 const HTMLLib = Firebug.HTMLLib; 16 17 const BP_BREAKONATTRCHANGE = 1; 18 const BP_BREAKONCHILDCHANGE = 2; 19 const BP_BREAKONREMOVE = 3; 20 const BP_BREAKONTEXT = 4; 21 22 // ************************************************************************************************ 23 24 Firebug.HTMLModule = extend(Firebug.Module, 25 { 26 initialize: function(prefDomain, prefNames) 27 { 28 Firebug.Module.initialize.apply(this, arguments); 29 Firebug.Debugger.addListener(this.DebuggerListener); 30 }, 31 32 initContext: function(context, persistedState) 33 { 34 Firebug.Module.initContext.apply(this, arguments); 35 context.mutationBreakpoints = new MutationBreakpointGroup(); 36 }, 37 38 loadedContext: function(context, persistedState) 39 { 40 context.mutationBreakpoints.load(context); 41 }, 42 43 destroyContext: function(context, persistedState) 44 { 45 Firebug.Module.destroyContext.apply(this, arguments); 46 47 context.mutationBreakpoints.store(context); 48 }, 49 50 shutdown: function() 51 { 52 Firebug.Module.shutdown.apply(this, arguments); 53 Firebug.Debugger.removeListener(this.DebuggerListener); 54 }, 55 56 deleteNode: function(node, context) 57 { 58 dispatch(this.fbListeners, "onBeginFirebugChange", [node, context]); 59 node.parentNode.removeChild(node); 60 dispatch(this.fbListeners, "onEndFirebugChange", [node, context]); 61 }, 62 63 deleteAttribute: function(node, attr, context) 64 { 65 dispatch(this.fbListeners, "onBeginFirebugChange", [node, context]); 66 node.removeAttribute(attr); 67 dispatch(this.fbListeners, "onEndFirebugChange", [node, context]); 68 } 69 }); 70 71 // ************************************************************************************************ 72 73 Firebug.HTMLPanel = function() {}; 74 75 Firebug.HTMLPanel.prototype = extend(Firebug.Panel, 76 { 77 toggleEditing: function() 78 { 79 if (this.editing) 80 Firebug.Editor.stopEditing(); 81 else 82 this.editNode(this.selection); 83 }, 84 85 resetSearch: function() 86 { 87 delete this.lastSearch; 88 }, 89 90 selectNext: function() 91 { 92 var objectBox = this.ioBox.createObjectBox(this.selection); 93 var next = this.ioBox.getNextObjectBox(objectBox); 94 if (next) 95 { 96 this.select(next.repObject); 97 98 if (Firebug.Inspector.inspecting) 99 Firebug.Inspector.inspectNode(next.repObject); 100 101 } 102 }, 103 104 selectPrevious: function() 105 { 106 var objectBox = this.ioBox.createObjectBox(this.selection); 107 var previous = this.ioBox.getPreviousObjectBox(objectBox); 108 if (previous) 109 { 110 this.select(previous.repObject); 111 112 if (Firebug.Inspector.inspecting) 113 Firebug.Inspector.inspectNode(previous.repObject); 114 } 115 }, 116 117 selectNodeBy: function(dir) 118 { 119 if (dir == "up") 120 this.selectPrevious(); 121 else if (dir == "down") 122 this.selectNext(); 123 else if (dir == "left") 124 { 125 var box = this.ioBox.createObjectBox(this.selection); 126 if (!hasClass(box, "open")) 127 this.select(this.ioBox.getParentObjectBox(box).repObject); 128 else 129 this.ioBox.contractObject(this.selection); 130 } 131 else if (dir == "right") 132 { 133 var box = this.ioBox.createObjectBox(this.selection); 134 if (!hasClass(box, "open")) 135 this.ioBox.expandObject(this.selection); 136 else 137 this.selectNext(); 138 } 139 Firebug.Inspector.highlightObject(this.selection, this.context); 140 }, 141 142 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 143 144 editNewAttribute: function(elt) 145 { 146 var objectNodeBox = this.ioBox.findObjectBox(elt); 147 if (objectNodeBox) 148 { 149 var labelBox = objectNodeBox.firstChild.lastChild; 150 var bracketBox = labelBox.getElementsByClassName("nodeBracket").item(0); 151 Firebug.Editor.insertRow(bracketBox, "before"); 152 } 153 }, 154 155 editAttribute: function(elt, attrName) 156 { 157 var objectNodeBox = this.ioBox.findObjectBox(elt); 158 if (objectNodeBox) 159 { 160 var attrBox = HTMLLib.findNodeAttrBox(objectNodeBox, attrName); 161 if (attrBox) 162 { 163 var attrValueBox = attrBox.childNodes[3]; 164 var value = elt.getAttribute(attrName); 165 Firebug.Editor.startEditing(attrValueBox, value); 166 } 167 } 168 }, 169 170 deleteAttribute: function(elt, attrName) 171 { 172 Firebug.HTMLModule.deleteAttribute(elt, attrName, this.context); 173 }, 174 175 localEditors:{}, // instantiated editor cache 176 editNode: function(node) 177 { 178 var objectNodeBox = this.ioBox.findObjectBox(node); 179 if (objectNodeBox) 180 { 181 var type = getElementType(node); 182 var editor = this.localEditors[type]; 183 if (!editor) 184 { 185 // look for special purpose editor (inserted by an extension), otherwise use our html editor 186 var specializedEditor = Firebug.HTMLPanel.Editors[type] || Firebug.HTMLPanel.Editors['html']; 187 editor = this.localEditors[type] = new specializedEditor(this.document); 188 } 189 this.startEditingNode(node, objectNodeBox, editor, type); 190 } 191 }, 192 193 startEditingNode: function(node, box, editor, type) 194 { 195 switch (type) 196 { 197 case 'html': 198 case 'xhtml': 199 this.startEditingHTMLNode(node, box, editor); 200 break; 201 default: 202 this.startEditingXMLNode(node, box, editor); 203 } 204 }, 205 206 startEditingXMLNode: function(node, box, editor) 207 { 208 var xml = getElementXML(node); 209 Firebug.Editor.startEditing(box, xml, editor); 210 }, 211 212 startEditingHTMLNode: function(node, box, editor) 213 { 214 if ( nonEditableTags.hasOwnProperty(node.localName) ) 215 return; 216 editor.innerEditMode = node.localName in innerEditableTags; 217 218 var html = editor.innerEditMode ? node.innerHTML : getElementHTML(node); 219 Firebug.Editor.startEditing(box, html, editor); 220 }, 221 222 deleteNode: function(node, dir) 223 { 224 dir = dir || 'up'; 225 var box = this.ioBox.createObjectBox(node); 226 if (hasClass(box, "open")) 227 this.ioBox.contractObject(this.selection); 228 this.selectNodeBy(dir); 229 Firebug.HTMLModule.deleteNode(node, this.context); 230 }, 231 232 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 233 234 getElementSourceText: function(node) 235 { 236 if (this.sourceElements) 237 { 238 var index = this.sourceElementNodes.indexOf(node); 239 if (index != -1) 240 return this.sourceElements[index]; 241 } 242 243 var lines; 244 245 var url = HTMLLib.getSourceHref(node); 246 if (url) 247 lines = this.context.sourceCache.load(url); 248 else 249 { 250 var text = HTMLLib.getSourceText(node); 251 lines = splitLines(text); 252 } 253 254 var sourceElt = new SourceText(lines, node); 255 256 if (!this.sourceElements) 257 { 258 this.sourceElements = [sourceElt]; 259 this.sourceElementNodes = [node]; 260 } 261 else 262 { 263 this.sourceElements.push(sourceElt); 264 this.sourceElementNodes.push(node); 265 } 266 267 return sourceElt; 268 }, 269 270 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 271 272 mutateAttr: function(target, attrChange, attrName, attrValue) 273 { 274 // Every time the user scrolls we get this pointless mutation event, which 275 // is only bad for performance 276 if (attrName == "curpos") 277 return; 278 279 // Due to the delay call this may or may not exist in the tree anymore 280 if (!this.ioBox.isInExistingRoot(target)) 281 { 282 if (FBTrace.DBG_HTML) FBTrace.sysout("mutateAttr: different tree " + target, target); 283 return; 284 } 285 286 if (FBTrace.DBG_HTML) 287 FBTrace.sysout("html.mutateAttr target:"+target+" attrChange:"+attrChange+" attrName:"+attrName+" attrValue: "+attrValue, target); 288 289 this.markChange(); 290 291 var objectNodeBox = Firebug.scrollToMutations || Firebug.expandMutations 292 ? this.ioBox.createObjectBox(target) 293 : this.ioBox.findObjectBox(target); 294 295 if (!objectNodeBox) 296 return; 297 298 if (isVisible(objectNodeBox.repObject)) 299 removeClass(objectNodeBox, "nodeHidden"); 300 else 301 setClass(objectNodeBox, "nodeHidden"); 302 303 if (attrChange == MODIFICATION || attrChange == ADDITION) 304 { 305 var nodeAttr = HTMLLib.findNodeAttrBox(objectNodeBox, attrName); 306 if (FBTrace.DBG_HTML) 307 FBTrace.sysout("mutateAttr "+attrChange+" "+attrName+"="+attrValue+" node: "+nodeAttr, nodeAttr); 308 if (nodeAttr && nodeAttr.childNodes.length > 3) 309 { 310 var attrValueBox = nodeAttr.childNodes[3]; 311 var attrValueText = nodeAttr.childNodes[3].firstChild; 312 if (attrValueText) 313 attrValueText.nodeValue = attrValue; 314 315 this.highlightMutation(attrValueBox, objectNodeBox, "mutated"); 316 } 317 else 318 { 319 var attr = target.getAttributeNode(attrName); 320 if (FBTrace.DBG_HTML) 321 FBTrace.sysout("mutateAttr getAttributeNode "+attrChange+" "+attrName+"="+attrValue+" node: "+attr, attr); 322 if (attr) 323 { 324 var nodeAttr = Firebug.HTMLPanel.AttrNode.tag.replace({attr: attr}, 325 this.document); 326 327 var labelBox = objectNodeBox.firstChild.lastChild; 328 var bracketBox = labelBox.getElementsByClassName("nodeBracket").item(0); 329 labelBox.insertBefore(nodeAttr, bracketBox); 330 331 this.highlightMutation(nodeAttr, objectNodeBox, "mutated"); 332 } 333 } 334 } 335 else if (attrChange == REMOVAL) 336 { 337 var nodeAttr = HTMLLib.findNodeAttrBox(objectNodeBox, attrName); 338 if (nodeAttr) 339 { 340 nodeAttr.parentNode.removeChild(nodeAttr); 341 } 342 343 // We want to highlight regardless as the domplate may have been 344 // generated after the attribute was removed from the node 345 this.highlightMutation(objectNodeBox, objectNodeBox, "mutated"); 346 } 347 }, 348 349 mutateText: function(target, parent, textValue) 350 { 351 // Due to the delay call this may or may not exist in the tree anymore 352 if (!this.ioBox.isInExistingRoot(target)) 353 { 354 if (FBTrace.DBG_HTML) FBTrace.sysout("mutateText: different tree " + target, target); 355 return; 356 } 357 358 this.markChange(); 359 360 var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations 361 ? this.ioBox.createObjectBox(parent) 362 : this.ioBox.findObjectBox(parent); 363 364 if (!parentNodeBox) 365 { 366 if (FBTrace.DBG_HTML) FBTrace.sysout("html.mutateText failed to update text, parent node box does not exist"); 367 return; 368 } 369 370 if (!Firebug.showFullTextNodes) 371 textValue = cropMultipleLines(textValue); 372 373 var parentTag = getNodeBoxTag(parentNodeBox); 374 if (parentTag == Firebug.HTMLPanel.TextElement.tag) 375 { 376 if (FBTrace.DBG_HTML) 377 FBTrace.sysout("html.mutateText target: " + target + " parent: " + parent); 378 379 var nodeText = HTMLLib.getTextElementTextBox(parentNodeBox); 380 if (!nodeText.firstChild) 381 { 382 if (FBTrace.DBG_HTML) FBTrace.sysout("html.mutateText failed to update text, TextElement firstChild does not exist"); 383 return; 384 } 385 386 nodeText.firstChild.nodeValue = textValue; 387 388 this.highlightMutation(nodeText, parentNodeBox, "mutated"); 389 } 390 else 391 { 392 var childBox = this.ioBox.getChildObjectBox(parentNodeBox); 393 if (!childBox) 394 { 395 if (FBTrace.DBG_HTML) FBTrace.sysout("html.mutateText failed to update text, no child object box found"); 396 return; 397 } 398 399 var textNodeBox = this.ioBox.findChildObjectBox(childBox, target); 400 if (textNodeBox) 401 { 402 // structure for comment and cdata. Are there others? 403 textNodeBox.children[0].firstChild.nodeValue = textValue; 404 405 this.highlightMutation(textNodeBox, parentNodeBox, "mutated"); 406 } 407 else if (Firebug.scrollToMutations || Firebug.expandMutations) 408 { 409 // We are not currently rendered but we are set to highlight 410 var objectBox = this.ioBox.createObjectBox(target); 411 this.highlightMutation(objectBox, objectBox, "mutated"); 412 } 413 } 414 }, 415 416 mutateNode: function(target, parent, nextSibling, removal) 417 { 418 if (FBTrace.DBG_HTML) 419 FBTrace.sysout("\nhtml.mutateNode target:"+target+" parent:"+parent+(removal?"REMOVE":"")+"\n"); 420 421 // Due to the delay call this may or may not exist in the tree anymore 422 if (!removal && !this.ioBox.isInExistingRoot(target)) 423 { 424 if (FBTrace.DBG_HTML) FBTrace.sysout("mutateNode: different tree " + target, target); 425 return; 426 } 427 428 this.markChange(); // This invalidates the panels for every mutate 429 430 var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations 431 ? this.ioBox.createObjectBox(parent) 432 : this.ioBox.findObjectBox(parent); 433 434 if (FBTrace.DBG_HTML) 435 FBTrace.sysout("html.mutateNode parent:"+parent+" parentNodeBox:"+parentNodeBox+"\n"); 436 437 if (!parentNodeBox) 438 return; 439 440 if (!Firebug.showTextNodesWithWhitespace && this.isWhitespaceText(target)) 441 return; 442 443 // target is only whitespace 444 445 var newParentTag = getNodeTag(parent); 446 var oldParentTag = getNodeBoxTag(parentNodeBox); 447 448 if (newParentTag == oldParentTag) 449 { 450 if (parentNodeBox.populated) 451 { 452 if (removal) 453 { 454 this.ioBox.removeChildBox(parentNodeBox, target); 455 456 this.highlightMutation(parentNodeBox, parentNodeBox, "mutated"); 457 } 458 else 459 { 460 if (nextSibling) 461 { 462 while ( 463 (!Firebug.showTextNodesWithWhitespace && Firebug.HTMLLib.isWhitespaceText(nextSibling)) || 464 (!Firebug.showCommentNodes && nextSibling instanceof Comment) 465 ) 466 { 467 nextSibling = this.findNextSibling(nextSibling); 468 } 469 } 470 471 var objectBox = nextSibling 472 ? this.ioBox.insertChildBoxBefore(parentNodeBox, target, nextSibling) 473 : this.ioBox.appendChildBox(parentNodeBox, target); 474 475 this.highlightMutation(objectBox, objectBox, "mutated"); 476 } 477 } 478 else // !parentNodeBox.populated 479 { 480 var newParentNodeBox = newParentTag.replace({object: parent}, this.document); 481 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox); 482 483 if (this.selection && (!this.selection.parentNode || parent == this.selection)) 484 this.ioBox.select(parent, true); 485 486 this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated"); 487 488 if (!removal && (Firebug.scrollToMutations || Firebug.expandMutations)) 489 { 490 var objectBox = this.ioBox.createObjectBox(target); 491 this.highlightMutation(objectBox, objectBox, "mutated"); 492 } 493 } 494 } 495 else // newParentTag != oldParentTag 496 { 497 var newParentNodeBox = newParentTag.replace({object: parent}, this.document); 498 if (parentNodeBox.parentNode) 499 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox); 500 501 if (hasClass(parentNodeBox, "open")) 502 this.ioBox.toggleObjectBox(newParentNodeBox, true); 503 504 if (this.selection && (!this.selection.parentNode || parent == this.selection)) 505 this.ioBox.select(parent, true); 506 507 this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated"); 508 509 if (!removal && (Firebug.scrollToMutations || Firebug.expandMutations)) 510 { 511 var objectBox = this.ioBox.createObjectBox(target); 512 this.highlightMutation(objectBox, objectBox, "mutated"); 513 } 514 } 515 }, 516 517 highlightMutation: function(elt, objectBox, type) 518 { 519 if (!elt) 520 return; 521 522 if (Firebug.scrollToMutations || Firebug.expandMutations) 523 { 524 if (this.context.mutationTimeout) 525 { 526 this.context.clearTimeout(this.context.mutationTimeout); 527 delete this.context.mutationTimeout; 528 } 529 530 var ioBox = this.ioBox; 531 var panelNode = this.panelNode; 532 533 this.context.mutationTimeout = this.context.setTimeout(function() 534 { 535 ioBox.openObjectBox(objectBox); 536 537 if (Firebug.scrollToMutations) 538 scrollIntoCenterView(objectBox, panelNode); 539 }, 200); 540 } 541 542 if (Firebug.highlightMutations) 543 setClassTimed(elt, type, this.context); 544 }, 545 546 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 547 // SourceBox proxy 548 549 createObjectBox: function(object, isRoot) 550 { 551 if (FBTrace.DBG_HTML) FBTrace.sysout("html.createObjectBox("+(object.tagName?object.tagName:object)+", isRoot:"+(isRoot?"true":"false")+")\n"); 552 var tag = getNodeTag(object); 553 if (tag) 554 return tag.replace({object: object}, this.document); 555 }, 556 557 getParentObject: function(node) 558 { 559 if (node instanceof SourceText) 560 return node.owner; 561 562 var parentNode = node ? node.parentNode : null; 563 564 if (FBTrace.DBG_HTML) 565 FBTrace.sysout("ChromeBugPanel.getParentObject for "+node.nodeName+" parentNode:"+(parentNode?parentNode.nodeName:"null-or-false")+"\n"); 566 567 if (parentNode) 568 { 569 570 if (parentNode.nodeType == 9) // then parentNode is Document element 571 { 572 if (parentNode.defaultView) 573 { 574 if (parentNode.defaultView == this.context.window) // for chromebug to avoid climbing put to browser.xul 575 return null; 576 577 if (FBTrace.DBG_HTML) 578 FBTrace.sysout("getParentObject parentNode.nodeType 9, frameElement:"+parentNode.defaultView.frameElement+"\n"); /*@explore*/ 579 return parentNode.defaultView.frameElement; 580 } 581 else if (this.embeddedBrowserParents) 582 { 583 var skipParent = this.embeddedBrowserParents[node]; // better be HTML element, could be iframe 584 if (FBTrace.DBG_HTML) 585 FBTrace.sysout("getParentObject skipParent:"+(skipParent?skipParent.nodeName:"none")+"\n"); /*@explore*/ 586 if (skipParent) 587 return skipParent; 588 } 589 else // parent is document element, but no window at defaultView. 590 return null; 591 } 592 else if (!parentNode.localName) 593 { 594 if (FBTrace.DBG_HTML) 595 FBTrace.sysout("getParentObject: null localName must be window, no parentObject"); 596 return null; 597 } 598 else 599 return parentNode; 600 } 601 else // Documents have no parentNode; Attr, Document, DocumentFragment, Entity, and Notation. top level windows have no parentNode 602 { 603 if (node && node.nodeType == 9) // document type 604 { 605 if (node.defaultView) // generally a reference to the window object for the document, however that is not defined in the specification 606 { 607 var embeddingFrame = node.defaultView.frameElement; 608 if (embeddingFrame) 609 return embeddingFrame.parentNode; 610 } 611 else // a Document object without a parentNode or window 612 return null; // top level has no parent 613 } 614 } 615 }, 616 617 getChildObject: function(node, index, previousSibling) 618 { 619 if (!node) 620 { 621 FBTrace.sysout("getChildObject: null node"); 622 return; 623 } 624 if (FBTrace.DBG_HTML) 625 FBTrace.sysout("getChildObject "+node.tagName+" index "+index+" previousSibling: "+previousSibling, {node: node, previousSibling:previousSibling}); 626 627 if (this.isSourceElement(node)) 628 { 629 if (index == 0) 630 return this.getElementSourceText(node); 631 else 632 return null; // no siblings of source elements 633 } 634 else if (node.contentDocument) // then the node is a frame 635 { 636 if (index == 0) 637 { 638 if (!this.embeddedBrowserParents) 639 this.embeddedBrowserParents = {}; 640 var skipChild = node.contentDocument.documentElement; // unwrap 641 this.embeddedBrowserParents[skipChild] = node; 642 643 return skipChild; // (the node's).(type 9 document).(HTMLElement) 644 } 645 else 646 return null; 647 } 648 else if (node.getSVGDocument && node.getSVGDocument()) // then the node is a frame 649 { 650 if (index == 0) 651 { 652 if (!this.embeddedBrowserParents) 653 this.embeddedBrowserParents = {}; 654 var skipChild = node.getSVGDocument().documentElement; // unwrap 655 this.embeddedBrowserParents[skipChild] = node; 656 657 return skipChild; // (the node's).(type 9 document).(SVGElement) 658 } 659 else 660 return null; 661 } 662 663 if (previousSibling) // then we are walking 664 var child = this.getNextSibling(previousSibling); // may return null, meaning done with iteration. 665 else 666 var child = this.getFirstChild(node); // child is set to at the beginning of an iteration. 667 668 if (Firebug.showTextNodesWithWhitespace) // then the index is true to the node list 669 return child; 670 else 671 { 672 for (; child; child = this.getNextSibling(child)) 673 { 674 if (!this.isWhitespaceText(child)) 675 return child; 676 } 677 } 678 return null; // we have no children worth showing. 679 }, 680 681 isWhitespaceText: function(node) 682 { 683 return HTMLLib.isWhitespaceText(node); 684 }, 685 686 getFirstChild: function(node) 687 { 688 this.treeWalker = node.ownerDocument.createTreeWalker( 689 node, NodeFilter.SHOW_ALL, null, false); 690 return this.treeWalker.firstChild(); 691 }, 692 693 getNextSibling: function(node) 694 { 695 if (FBTrace.DBG_HTML || FBTrace.DBG_ERRORS) 696 { 697 if (node != this.treeWalker.currentNode) 698 FBTrace.sysout("getNextSibling FAILS treeWalker "+this.treeWalker.currentNode+" out of sync with node "+node, this.treeWalker); 699 } 700 var next = this.treeWalker.nextSibling(); 701 702 if (!next) 703 delete this.treeWalker; 704 705 return next; 706 }, 707 708 findNextSibling: function (node) 709 { 710 return HTMLLib.findNextSibling(node); 711 }, 712 713 isSourceElement: function(element) 714 { 715 return HTMLLib.isSourceElement(element); 716 }, 717 718 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 719 // Events 720 721 onMutateAttr: function(event) 722 { 723 var target = event.target; 724 if (unwrapObject(target).firebugIgnore) 725 return; 726 727 var attrChange = event.attrChange; 728 var attrName = event.attrName; 729 var newValue = event.newValue; 730 731 this.context.delay(function() 732 { 733 this.mutateAttr(target, attrChange, attrName, newValue); 734 }, this); 735 736 Firebug.HTMLModule.MutationBreakpoints.onMutateAttr(event, this.context); 737 }, 738 739 onMutateText: function(event) 740 { 741 if (FBTrace.DBG_HTML) 742 FBTrace.sysout("html.onMutateText; ", event); 743 744 var target = event.target; 745 var parent = target.parentNode; 746 747 var newValue = event.newValue; 748 749 this.context.delay(function() 750 { 751 this.mutateText(target, parent, newValue); 752 }, this); 753 754 Firebug.HTMLModule.MutationBreakpoints.onMutateText(event, this.context); 755 }, 756 757 onMutateNode: function(event) 758 { 759 var target = event.target; 760 if (unwrapObject(target).firebugIgnore) 761 return; 762 763 var parent = event.relatedNode; 764 var removal = event.type == "DOMNodeRemoved"; 765 var nextSibling = removal ? null : this.findNextSibling(target); 766 767 this.context.delay(function() 768 { 769 try 770 { 771 this.mutateNode(target, parent, nextSibling, removal); 772 } 773 catch (exc) 774 { 775 if (FBTrace.DBG_ERRORS || FBTrace.DBG_HTML) 776 FBTrace.sysout("html.onMutateNode FAILS:", exc); 777 } 778 }, this); 779 780 Firebug.HTMLModule.MutationBreakpoints.onMutateNode(event, this.context); 781 }, 782 783 onClick: function(event) 784 { 785 if (isLeftClick(event) && event.detail == 2) 786 { 787 this.toggleNode(event); 788 } 789 else if (isAltClick(event) && event.detail == 2 && !this.editing) 790 { 791 this.editNode(this.selection); 792 } 793 }, 794 795 onMouseDown: function(event) 796 { 797 if (!isLeftClick(event)) 798 return; 799 if (getAncestorByClass(event.target, "nodeTag")) 800 { 801 var node = Firebug.getRepObject(event.target); 802 this.noScrollIntoView = true; 803 this.select(node); 804 delete this.noScrollIntoView; 805 if (hasClass(event.target, "twisty")) 806 this.toggleNode(event); 807 } 808 }, 809 810 toggleNode: function(event) 811 { 812 var node = Firebug.getRepObject(event.target); 813 var box = this.ioBox.createObjectBox(node); 814 if (!hasClass(box, "open")) 815 this.ioBox.expandObject(node); 816 else 817 this.ioBox.contractObject(this.selection); 818 }, 819 820 onKeyPress: function(event) 821 { 822 if (this.editing || isControl(event) || isShift(event)) 823 return; 824 825 var node = this.selection; 826 if (!node) 827 return; 828 if (event.keyCode == KeyEvent.DOM_VK_UP) 829 this.selectNodeBy("up"); 830 else if (event.keyCode == KeyEvent.DOM_VK_DOWN) 831 this.selectNodeBy("down"); 832 else if (event.keyCode == KeyEvent.DOM_VK_LEFT) 833 this.selectNodeBy("left"); 834 else if (event.keyCode == KeyEvent.DOM_VK_RIGHT) 835 this.selectNodeBy("right"); 836 else if (event.keyCode == KeyEvent.DOM_VK_BACK_SPACE && !(node.localName in innerEditableTags) && !(nonEditableTags.hasOwnProperty(node.localName))) 837 this.deleteNode(node, "up"); 838 else if (event.keyCode == KeyEvent.DOM_VK_DELETE && !(node.localName in innerEditableTags) && !(nonEditableTags.hasOwnProperty(node.localName))) 839 this.deleteNode(node, "down"); 840 else 841 return; 842 843 cancelEvent(event); 844 }, 845 846 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 847 // extends Panel 848 849 name: "html", 850 searchable: true, 851 breakable: true, 852 dependents: ["css", "computed", "layout", "dom", "domSide", "watch"], 853 inspectorHistory: new Array(5), 854 855 initialize: function() 856 { 857 this.onMutateText = bind(this.onMutateText, this); 858 this.onMutateAttr = bind(this.onMutateAttr, this); 859 this.onMutateNode = bind(this.onMutateNode, this); 860 this.onClick = bind(this.onClick, this); 861 this.onMouseDown = bind(this.onMouseDown, this); 862 this.onKeyPress = bind(this.onKeyPress, this); 863 864 Firebug.Panel.initialize.apply(this, arguments); 865 }, 866 867 destroy: function(state) 868 { 869 persistObjects(this, state); 870 871 Firebug.Panel.destroy.apply(this, arguments); 872 }, 873 874 initializeNode: function(oldPanelNode) 875 { 876 if (!this.ioBox) 877 this.ioBox = new InsideOutBox(this, this.panelNode); 878 879 this.panelNode.addEventListener("click", this.onClick, false); 880 this.panelNode.addEventListener("mousedown", this.onMouseDown, false); 881 dispatch([Firebug.A11yModel], "onInitializeNode", [this]); 882 }, 883 884 destroyNode: function() 885 { 886 this.panelNode.removeEventListener("click", this.onClick, false); 887 this.panelNode.removeEventListener("mousedown", this.onMouseDown, false); 888 this.panelNode.ownerDocument.removeEventListener("keypress", this.onKeyPress, true); 889 890 if (this.ioBox) 891 { 892 this.ioBox.destroy(); 893 delete this.ioBox; 894 } 895 dispatch([Firebug.A11yModel], "onDestroyNode", [this]); 896 }, 897 898 show: function(state) 899 { 900 this.showToolbarButtons("fbHTMLButtons", true); 901 902 this.panelNode.ownerDocument.addEventListener("keypress", this.onKeyPress, true); 903 904 if (this.context.loaded) 905 { 906 if (!this.context.attachedMutation) 907 { 908 this.context.attachedMutation = true; 909 910 iterateWindows(this.context.window, bind(function(win) 911 { 912 var doc = win.document; 913 doc.addEventListener("DOMAttrModified", this.onMutateAttr, false); 914 doc.addEventListener("DOMCharacterDataModified", this.onMutateText, false); 915 doc.addEventListener("DOMNodeInserted", this.onMutateNode, false); 916 doc.addEventListener("DOMNodeRemoved", this.onMutateNode, false); 917 }, this)); 918 } 919 920 restoreObjects(this, state); 921 } 922 }, 923 924 hide: function() 925 { 926 this.showToolbarButtons("fbHTMLButtons", false); 927 delete this.infoTipURL; // clear the state that is tracking the infotip so it is reset after next show() 928 this.panelNode.ownerDocument.removeEventListener("keypress", this.onKeyPress, true); 929 }, 930 931 watchWindow: function(win) 932 { 933 if (this.context.window && this.context.window != win) // then I guess we are an embedded window 934 { 935 var htmlPanel = this; 936 iterateWindows(this.context.window, function(subwin) 937 { 938 if (win == subwin) 939 { 940 if (FBTrace.DBG_HTML) 941 FBTrace.sysout("html.watchWindow found subwin.location.href="+win.location.href+"\n"); 942 htmlPanel.mutateDocumentEmbedded(win, false); 943 } 944 }); 945 946 } 947 if (this.context.attachedMutation) 948 { 949 var doc = win.document; 950 doc.addEventListener("DOMAttrModified", this.onMutateAttr, false); 951 doc.addEventListener("DOMCharacterDataModified", this.onMutateText, false); 952 doc.addEventListener("DOMNodeInserted", this.onMutateNode, false); 953 doc.addEventListener("DOMNodeRemoved", this.onMutateNode, false); 954 } 955 }, 956 957 unwatchWindow: function(win) 958 { 959 if (this.context.window && this.context.window != win) // then I guess we are an embedded window 960 { 961 var htmlPanel = this; 962 iterateWindows(this.context.window, function(subwin) 963 { 964 if (win == subwin) 965 { 966 if (FBTrace.DBG_HTML) 967 FBTrace.sysout("html.unwatchWindow found subwin.location.href="+win.location.href+"\n"); 968 htmlPanel.mutateDocumentEmbedded(win, true); 969 } 970 }); 971 972 } 973 var doc = win.document; 974 doc.removeEventListener("DOMAttrModified", this.onMutateAttr, false); 975 doc.removeEventListener("DOMCharacterDataModified", this.onMutateText, false); 976 doc.removeEventListener("DOMNodeInserted", this.onMutateNode, false); 977 doc.removeEventListener("DOMNodeRemoved", this.onMutateNode, false); 978 }, 979 980 mutateDocumentEmbedded: function(win, remove) 981 { 982 // document.documentElement Returns the Element that is a direct child of document. For HTML documents, this normally the HTML element. 983 var target = win.document.documentElement; 984 var parent = win.frameElement; 985 var nextSibling = this.findNextSibling(target || parent); 986 this.mutateNode(target, parent, nextSibling, remove); 987 }, 988 989 supportsObject: function(object) 990 { 991 if (object instanceof Element || object instanceof Text || object instanceof CDATASection) 992 return 2; 993 else if (object instanceof SourceLink && object.type == "css" && !reCSS.test(object.href)) 994 return 2; 995 else 996 return 0; 997 }, 998 999 updateOption: function(name, value) 1000 { 1001 var viewOptionNames = { 1002 showCommentNodes:1, 1003 showTextNodesWithEntities:1, 1004 showTextNodesWithWhitespace:1, 1005 showFullTextNodes:1 1006 }; 1007 if (name in viewOptionNames) 1008 { 1009 this.resetSearch(); 1010 clearNode(this.panelNode); 1011 if (this.ioBox) 1012 this.ioBox.destroy(); 1013 1014 this.ioBox = new InsideOutBox(this, this.panelNode); 1015 this.ioBox.select(this.selection, true, true); 1016 } 1017 }, 1018 1019 updateSelection: function(object) 1020 { 1021 if (FBTrace.DBG_HTML) 1022 FBTrace.sysout("html.updateSelection "+object); 1023 if (this.ioBox.sourceRow) 1024 this.ioBox.sourceRow.removeAttribute("exe_line"); 1025 1026 if (object instanceof SourceLink) // && object.type == "css" and !reCSS(object.href) by supports 1027 { 1028 var sourceLink = object; 1029 var stylesheet = getStyleSheetByHref(sourceLink.href, this.context); 1030 if (stylesheet) 1031 { 1032 var ownerNode = stylesheet.ownerNode; 1033 if (FBTrace.DBG_CSS) 1034 FBTrace.sysout("html panel updateSelection stylesheet.ownerNode="+stylesheet.ownerNode 1035 +" href:"+sourceLink.href+"\n"); 1036 if (ownerNode) 1037 { 1038 var objectbox = this.ioBox.select(ownerNode, true, true, this.noScrollIntoView); 1039 1040 // XXXjjb seems like this could be bad for errors at the end of long files 1041 // 1042 var sourceRow = objectbox.getElementsByClassName("sourceRow").item(0); // first source row in style 1043 for (var lineNo = 1; lineNo < sourceLink.line; lineNo++) 1044 { 1045 if (!sourceRow) break; 1046 sourceRow = FBL.getNextByClass(sourceRow, "sourceRow"); 1047 } 1048 if (FBTrace.DBG_CSS) 1049 FBTrace.sysout("html panel updateSelection sourceLink.line="+sourceLink.line 1050 +" sourceRow="+(sourceRow?sourceRow.innerHTML:"undefined")+"\n"); 1051 if (sourceRow) 1052 { 1053 this.ioBox.sourceRow = sourceRow; 1054 this.ioBox.sourceRow.setAttribute("exe_line", "true"); 1055 scrollIntoCenterView(sourceRow); 1056 this.ioBox.selectObjectBox(sourceRow, false); // sourceRow isn't an objectBox, but the function should work anyway... 1057 } 1058 } 1059 } 1060 } 1061 else if (Firebug.Inspector.inspecting) 1062 { 1063 this.ioBox.highlight(object); 1064 } 1065 else 1066 { 1067 Firebug.chrome.getSelectedSidePanel().panelNode.scrollTop = 0; 1068 this.ioBox.select(object, true, false, this.noScrollIntoView); 1069 this.inspectorHistory.unshift(object); 1070 if (this.inspectorHistory.length > 5) 1071 this.inspectorHistory.pop(); 1072 } 1073 }, 1074 1075 stopInspecting: function(object, cancelled) 1076 { 1077 if (object != this.inspectorHistory) 1078 { 1079 // Manage history of selection for later access in the command line. 1080 this.inspectorHistory.unshift(object); 1081 if (this.inspectorHistory.length > 5) 1082 this.inspectorHistory.pop(); 1083 1084 if (FBTrace.DBG_HTML) 1085 FBTrace.sysout("html.stopInspecting: inspectoryHistory updated", this.inspectorHistory); 1086 } 1087 1088 this.ioBox.highlight(null); 1089 1090 if (!cancelled) 1091 this.ioBox.select(object, true); 1092 }, 1093 1094 search: function(text, reverse) 1095 { 1096 if (!text) 1097 return; 1098 1099 var search; 1100 if (text == this.searchText && this.lastSearch) 1101 search = this.lastSearch; 1102 else 1103 { 1104 var doc = this.context.window.document; 1105 search = this.lastSearch = new HTMLLib.NodeSearch(text, doc, this.panelNode, this.ioBox); 1106 } 1107 1108 var loopAround = search.find(reverse, Firebug.Search.isCaseSensitive(text)); 1109 if (loopAround) 1110 { 1111 this.resetSearch(); 1112 this.search(text, reverse); 1113 } 1114 1115 return !search.noMatch; 1116 }, 1117 1118 getSearchOptionsMenuItems: function() 1119 { 1120 return [ 1121 Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive") 1122 ]; 1123 }, 1124 1125 getDefaultSelection: function() 1126 { 1127 try 1128 { 1129 var doc = this.context.window.document; 1130 return doc.body ? doc.body : getPreviousElement(doc.documentElement.lastChild); 1131 } 1132 catch (exc) 1133 { 1134 return null; 1135 } 1136 }, 1137 1138 getObjectPath: function(element) 1139 { 1140 var path = []; 1141 for (; element; element = this.getParentObject(element)) 1142 path.push(element); 1143 1144 return path; 1145 }, 1146 1147 getPopupObject: function(target) 1148 { 1149 return Firebug.getRepObject(target); 1150 }, 1151 1152 getTooltipObject: function(target) 1153 { 1154 return null; 1155 }, 1156 1157 getOptionsMenuItems: function() 1158 { 1159 return [ 1160 optionMenu("ShowFullText", "showFullTextNodes"), 1161 optionMenu("ShowWhitespace", "showTextNodesWithWhitespace"), 1162 optionMenu("ShowComments", "showCommentNodes"), 1163 optionMenu("ShowTextNodesWithEntities", "showTextNodesWithEntities"), 1164 "-", 1165 optionMenu("HighlightMutations", "highlightMutations"), 1166 optionMenu("ExpandMutations", "expandMutations"), 1167 optionMenu("ScrollToMutations", "scrollToMutations"), 1168 "-", 1169 optionMenu("ShadeBoxModel", "shadeBoxModel"), 1170 optionMenu("ShowQuickInfoBox","showQuickInfoBox") 1171 ]; 1172 }, 1173 1174 getContextMenuItems: function(node, target) 1175 { 1176 if (!node) 1177 return null; 1178 1179 var items = []; 1180 1181 if (node && node.nodeType == 1) 1182 { 1183 items.push( 1184 "-", 1185 {label: "NewAttribute", command: bindFixed(this.editNewAttribute, this, node) } 1186 ); 1187 1188 var attrBox = getAncestorByClass(target, "nodeAttr"); 1189 if (getAncestorByClass(target, "nodeAttr")) 1190 { 1191 var attrName = attrBox.childNodes[1].textContent; 1192 1193 items.push( 1194 {label: $STRF("EditAttribute", [attrName]), nol10n: true, 1195 command: bindFixed(this.editAttribute, this, node, attrName) }, 1196 {label: $STRF("DeleteAttribute", [attrName]), nol10n: true, 1197 command: bindFixed(this.deleteAttribute, this, node, attrName) } 1198 ); 1199 } 1200 1201 if (!( nonEditableTags.hasOwnProperty(node.localName) )) 1202 { 1203 var EditElement = "EditHTMLElement"; 1204 1205 if (isElementMathML(node)) 1206 EditElement = "EditMathMLElement" 1207 else if (isElementSVG(node)) 1208 EditElement = "EditSVGElement"; 1209 1210 items.push("-", { label: EditElement, command: bindFixed(this.editNode, this, node)}, 1211 { label: "DeleteElement", command: bindFixed(this.deleteNode, this, node), disabled:(node.localName in innerEditableTags)} 1212 ); 1213 } 1214 } 1215 else 1216 { 1217 items.push( 1218 "-", 1219 {label: "EditNode", command: bindFixed(this.editNode, this, node) }, 1220 {label: "DeleteNode", command: bindFixed(this.deleteNode, this, node) } 1221 ); 1222 } 1223 1224 Firebug.HTMLModule.MutationBreakpoints.getContextMenuItems( 1225 this.context,node, target, items); 1226 1227 return items; 1228 }, 1229 1230 showInfoTip: function(infoTip, target, x, y) 1231 { 1232 if (!hasClass(target, "nodeValue")) 1233 return; 1234 1235 var targetNode = Firebug.getRepObject(target); 1236 if (targetNode && targetNode.nodeType == 1 && targetNode.localName.toUpperCase() == "IMG") 1237 { 1238 var url = targetNode.src; 1239 if (url == this.infoTipURL) // This state cleared in hide() 1240 return true; 1241 1242 this.infoTipURL = url; 1243 return Firebug.InfoTip.populateImageInfoTip(infoTip, url); 1244 } 1245 }, 1246 1247 getEditor: function(target, value) 1248 { 1249 if (hasClass(target, "nodeName") || hasClass(target, "nodeValue") || hasClass(target, "nodeBracket")) 1250 { 1251 if (!this.attrEditor) 1252 this.attrEditor = new Firebug.HTMLPanel.Editors.Attribute(this.document); 1253 1254 return this.attrEditor; 1255 } 1256 else if (hasClass(target, "nodeComment") || hasClass(target, "nodeCDATA")) 1257 { 1258 if (!this.textDataEditor) 1259 this.textDataEditor = new Firebug.HTMLPanel.Editors.TextData(this.document); 1260 1261 return this.textDataEditor; 1262 } 1263 else if (hasClass(target, "nodeText")) 1264 { 1265 if (!this.textNodeEditor) 1266 this.textNodeEditor = new Firebug.HTMLPanel.Editors.TextNode(this.document); 1267 1268 return this.textNodeEditor; 1269 } 1270 }, 1271 1272 getInspectorVars: function() 1273 { 1274 var vars = {}; 1275 for (var i=0; i<2; i++) 1276 vars["$"+i] = this.inspectorHistory[i]; 1277 1278 return vars; 1279 }, 1280 1281 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1282 // Break on Mutate 1283 1284 breakOnNext: function(breaking) 1285 { 1286 Firebug.HTMLModule.MutationBreakpoints.breakOnNext(this.context, breaking); 1287 }, 1288 1289 shouldBreakOnNext: function() 1290 { 1291 return this.context.breakOnNextMutate; 1292 }, 1293 1294 getBreakOnNextTooltip: function(enabled) 1295 { 1296 return (enabled ? $STR("html.Disable Break On Mutate") : $STR("html.Break On Mutate")); 1297 }, 1298 }); 1299 1300 // ************************************************************************************************ 1301 1302 var AttrTag = Firebug.HTMLPanel.AttrTag = 1303 SPAN({"class": "nodeAttr editGroup"}, 1304 " ", SPAN({"class": "nodeName editable"}, "$attr.nodeName"), "="", 1305 SPAN({"class": "nodeValue editable"}, "$attr.nodeValue"), """ 1306 ); 1307 1308 var TextTag = Firebug.HTMLPanel.TextTag = 1309 SPAN({"class": "nodeText editable"}, 1310 FOR("char", "$object|getNodeTextGroups", 1311 SPAN({"class": "$char.class $char.extra"}, "$char.str") 1312 ) 1313 ); 1314 1315 // ************************************************************************************************ 1316 1317 Firebug.HTMLPanel.CompleteElement = domplate(FirebugReps.Element, 1318 { 1319 tag: 1320 DIV({"class": "nodeBox open $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'}, 1321 DIV({"class": "nodeLabel", role: "presentation"}, 1322 SPAN({"class": "nodeLabelBox repTarget repTarget", role : 'treeitem', 'aria-expanded' : 'false'}, 1323 "<", 1324 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1325 FOR("attr", "$object|attrIterator", AttrTag), 1326 SPAN({"class": "nodeBracket"}, ">") 1327 ) 1328 ), 1329 DIV({"class": "nodeChildBox", role :"group"}, 1330 FOR("child", "$object|childIterator", 1331 TAG("$child|getNodeTag", {object: "$child"}) 1332 ) 1333 ), 1334 DIV({"class": "nodeCloseLabel", role:"presentation"}, 1335 "</", 1336 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1337 ">" 1338 ) 1339 ), 1340 1341 getNodeTag: function(node) 1342 { 1343 return getNodeTag(node, true); 1344 }, 1345 1346 childIterator: function(node) 1347 { 1348 if (node.contentDocument) 1349 return [node.contentDocument.documentElement]; 1350 1351 if (Firebug.showTextNodesWithWhitespace) 1352 return cloneArray(node.childNodes); 1353 else 1354 { 1355 var nodes = []; 1356 for (var child = node.firstChild; child; child = child.nextSibling) 1357 { 1358 if (child.nodeType != Node.TEXT_NODE || !HTMLLib.isWhitespaceText(child)) 1359 nodes.push(child); 1360 } 1361 return nodes; 1362 } 1363 } 1364 }); 1365 1366 Firebug.HTMLPanel.SoloElement = domplate(Firebug.HTMLPanel.CompleteElement, 1367 { 1368 tag: 1369 DIV({"class": "soloElement", onmousedown: "$onMouseDown"}, 1370 Firebug.HTMLPanel.CompleteElement.tag 1371 ), 1372 1373 onMouseDown: function(event) 1374 { 1375 for (var child = event.target; child; child = child.parentNode) 1376 { 1377 if (child.repObject) 1378 { 1379 var panel = Firebug.getElementPanel(child); 1380 Firebug.chrome.select(child.repObject); 1381 break; 1382 } 1383 } 1384 } 1385 }); 1386 1387 Firebug.HTMLPanel.Element = domplate(FirebugReps.Element, 1388 { 1389 tag: 1390 DIV({"class": "nodeBox containerNodeBox $object|getHidden repIgnore", _repObject: "$object", role :"presentation"}, 1391 DIV({"class": "nodeLabel", role: "presentation"}, 1392 IMG({"class": "twisty", role: "presentation"}), 1393 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem', 'aria-expanded' : 'false'}, 1394 "<", 1395 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1396 FOR("attr", "$object|attrIterator", AttrTag), 1397 SPAN({"class": "nodeBracket editable insertBefore"}, ">") 1398 ) 1399 ), 1400 DIV({"class": "nodeChildBox", role :"group"}), /* nodeChildBox is special signal in insideOutBox */ 1401 DIV({"class": "nodeCloseLabel", role : "presentation"}, 1402 SPAN({"class": "nodeCloseLabelBox repTarget"}, 1403 "</", 1404 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1405 ">" 1406 ) 1407 ) 1408 ) 1409 }); 1410 1411 Firebug.HTMLPanel.TextElement = domplate(FirebugReps.Element, 1412 { 1413 tag: 1414 DIV({"class": "nodeBox textNodeBox $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'}, 1415 DIV({"class": "nodeLabel", role: "presentation"}, 1416 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'}, 1417 "<", 1418 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1419 FOR("attr", "$object|attrIterator", AttrTag), 1420 SPAN({"class": "nodeBracket editable insertBefore"}, ">"), 1421 TextTag, 1422 "</", 1423 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1424 ">" 1425 ) 1426 ) 1427 ) 1428 }); 1429 1430 Firebug.HTMLPanel.EmptyElement = domplate(FirebugReps.Element, 1431 { 1432 tag: 1433 DIV({"class": "nodeBox emptyNodeBox $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'}, 1434 DIV({"class": "nodeLabel", role: "presentation"}, 1435 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'}, 1436 "<", 1437 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1438 FOR("attr", "$object|attrIterator", AttrTag), 1439 SPAN({"class": "nodeBracket editable insertBefore"}, ">") 1440 ) 1441 ) 1442 ) 1443 }); 1444 1445 Firebug.HTMLPanel.XEmptyElement = domplate(FirebugReps.Element, 1446 { 1447 tag: 1448 DIV({"class": "nodeBox emptyNodeBox $object|getHidden repIgnore", _repObject: "$object", role : 'presentation'}, 1449 DIV({"class": "nodeLabel", role: "presentation"}, 1450 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'}, 1451 "<", 1452 SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"), 1453 FOR("attr", "$object|attrIterator", AttrTag), 1454 SPAN({"class": "nodeBracket editable insertBefore"}, "/>") 1455 ) 1456 ) 1457 ) 1458 }); 1459 1460 Firebug.HTMLPanel.AttrNode = domplate(FirebugReps.Element, 1461 { 1462 tag: AttrTag 1463 }); 1464 1465 Firebug.HTMLPanel.TextNode = domplate(FirebugReps.Element, 1466 { 1467 tag: 1468 DIV({"class": "nodeBox", _repObject: "$object", role : 'presentation'}, 1469 TextTag 1470 ) 1471 }); 1472 1473 Firebug.HTMLPanel.CDATANode = domplate(FirebugReps.Element, 1474 { 1475 tag: 1476 DIV({"class": "nodeBox", _repObject: "$object", role : 'presentation'}, 1477 "<![CDATA[", 1478 SPAN({"class": "nodeText nodeCDATA editable"}, "$object.nodeValue"), 1479 "]]>" 1480 ) 1481 }); 1482 1483 Firebug.HTMLPanel.CommentNode = domplate(FirebugReps.Element, 1484 { 1485 tag: 1486 DIV({"class": "nodeBox nodeComment", _repObject: "$object", role : 'presentation'}, 1487 "<!--", 1488 SPAN({"class": "nodeComment editable"}, "$object.nodeValue"), 1489 "-->" 1490 ) 1491 }); 1492 1493 1494 // ************************************************************************************************ 1495 // TextDataEditor 1496 1497 /* 1498 * TextDataEditor deals with text of comments and cdata nodes 1499 */ 1500 1501 function TextDataEditor(doc) 1502 { 1503 this.initializeInline(doc); 1504 } 1505 1506 TextDataEditor.prototype = domplate(Firebug.InlineEditor.prototype, 1507 { 1508 1509 saveEdit: function(target, value, previousValue) 1510 { 1511 var node = Firebug.getRepObject(target); 1512 if (!node) 1513 return; 1514 target.data = value; 1515 node.data = value; 1516 } 1517 }); 1518 1519 //************************************************************************************************ 1520 // TextNodeEditor 1521 1522 /* 1523 * TextNodeEditor deals with text nodes that do and do not have sibling elements. If 1524 * there are no sibling elements, the parent is known as a TextElement. In other cases 1525 * we keep track of their position via a range (this is in part because as people type 1526 * html, the range will keep track of the text nodes and elements that the user 1527 * is creating as they type, and this range could be in the middle of the parent 1528 * elements children). 1529 */ 1530 1531 function TextNodeEditor(doc) 1532 { 1533 this.initializeInline(doc); 1534 } 1535 1536 TextNodeEditor.prototype = domplate(Firebug.InlineEditor.prototype, 1537 { 1538 1539 beginEditing: function(target, value) 1540 { 1541 var node = Firebug.getRepObject(target); 1542 if (!node || node instanceof Element) 1543 return; 1544 var document = node.ownerDocument; 1545 this.range = document.createRange(); 1546 this.range.setStartBefore(node); 1547 this.range.setEndAfter(node); 1548 }, 1549 1550 endEditing: function(target, value, cancel) 1551 { 1552 if (this.range) 1553 { 1554 this.range.detach(); 1555 delete this.range; 1556 } 1557 // Remove empty groups by default 1558 return true; 1559 }, 1560 1561 saveEdit: function(target, value, previousValue) 1562 { 1563 var node = Firebug.getRepObject(target); 1564 if (!node) 1565 return; 1566 value = unescapeForTextNode(value || ''); 1567 target.innerHTML = escapeForTextNode(value); 1568 if (node instanceof Element) 1569 { 1570 if (isElementMathML(node) || isElementSVG(node)) 1571 node.textContent=value; 1572 else 1573 node.innerHTML=value; 1574 } 1575 else 1576 { 1577 try 1578 { 1579 var documentFragment = this.range.createContextualFragment(value); 1580 var cnl=documentFragment.childNodes.length; 1581 this.range.deleteContents(); 1582 this.range.insertNode(documentFragment); 1583 var r = this.range, sc = r.startContainer, so = r.startOffset; 1584 this.range.setEnd(sc,so+cnl); 1585 } catch (e) {} 1586 } 1587 } 1588 }); 1589 1590 //************************************************************************************************ 1591 //AttributeEditor 1592 1593 function AttributeEditor(doc) 1594 { 1595 this.initializeInline(doc); 1596 } 1597 1598 AttributeEditor.prototype = domplate(Firebug.InlineEditor.prototype, 1599 { 1600 saveEdit: function(target, value, previousValue) 1601 { 1602 var element = Firebug.getRepObject(target); 1603 if (!element) 1604 return; 1605 1606 // XXXstr unescape value 1607 1608 target.innerHTML = escapeForElementAttribute(value); 1609 1610 if (hasClass(target, "nodeName")) 1611 { 1612 if (value != previousValue) 1613 element.removeAttribute(previousValue); 1614 if (value) 1615 { 1616 var attrValue = getNextByClass(target, "nodeValue").textContent; 1617 element.setAttribute(value, attrValue); 1618 } 1619 else 1620 element.removeAttribute(value); 1621 } 1622 else if (hasClass(target, "nodeValue")) 1623 { 1624 var attrName = getPreviousByClass(target, "nodeName").textContent; 1625 element.setAttribute(attrName, value); 1626 } 1627 //this.panel.markChange(); 1628 }, 1629 1630 advanceToNext: function(target, charCode) 1631 { 1632 if (charCode == 61 && hasClass(target, "nodeName")) 1633 return true; 1634 }, 1635 1636 insertNewRow: function(target, insertWhere) 1637 { 1638 var emptyAttr = {nodeName: "", nodeValue: ""}; 1639 var sibling = insertWhere == "before" ? target.previousSibling : target; 1640 return AttrTag.insertAfter({attr: emptyAttr}, sibling); 1641 } 1642 }); 1643 1644 //************************************************************************************************ 1645 //HTMLEditor 1646 1647 function HTMLEditor(doc) 1648 { 1649 this.box = this.tag.replace({}, doc, this); 1650 this.input = this.box.firstChild; 1651 1652 this.multiLine = true; 1653 this.tabNavigation = false; 1654 this.arrowCompletion = false; 1655 } 1656 1657 HTMLEditor.prototype = domplate(Firebug.BaseEditor, 1658 { 1659 tag: DIV( 1660 TEXTAREA({"class": "htmlEditor fullPanelEditor", oninput: "$onInput"}) 1661 ), 1662 1663 getValue: function() 1664 { 1665 return this.input.value; 1666 }, 1667 1668 setValue: function(value) 1669 { 1670 return this.input.value = value; 1671 }, 1672 1673 show: function(target, panel, value, textSize, targetSize) 1674 { 1675 this.target = target; 1676 this.panel = panel; 1677 this.editingElements = [target.repObject, null]; 1678 1679 this.panel.panelNode.appendChild(this.box); 1680 1681 this.input.value = value; 1682 this.input.focus(); 1683 1684 var command = Firebug.chrome.$("cmd_toggleHTMLEditing"); 1685 command.setAttribute("checked", true); 1686 }, 1687 1688 hide: function() 1689 { 1690 var command = Firebug.chrome.$("cmd_toggleHTMLEditing"); 1691 command.setAttribute("checked", false); 1692 1693 this.panel.panelNode.removeChild(this.box); 1694 1695 delete this.editingElements; 1696 delete this.target; 1697 delete this.panel; 1698 }, 1699 1700 saveEdit: function(target, value, previousValue) 1701 { 1702 // Remove all of the nodes in the last range we created, except for 1703 // the first one, because setOuterHTML will replace it 1704 var first = this.editingElements[0], last = this.editingElements[1]; 1705 if (last && last != first) 1706 { 1707 for (var child = first.nextSibling; child;) 1708 { 1709 var next = child.nextSibling; 1710 child.parentNode.removeChild(child); 1711 if (child == last) 1712 break; 1713 else 1714 child = next; 1715 } 1716 } 1717 1718 // Make sure that we create at least one node here, even if it's just 1719 // an empty space, because this code depends on having something to replace 1720 if (!value) 1721 value = " "; 1722 1723 if (this.innerEditMode) 1724 this.editingElements[0].innerHTML = value; 1725 else 1726 this.editingElements = setOuterHTML(this.editingElements[0], value); 1727 }, 1728 1729 endEditing: function() 1730 { 1731 //this.panel.markChange(); 1732 return true; 1733 }, 1734 1735 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1736 1737 onInput: function() 1738 { 1739 Firebug.Editor.update(); 1740 } 1741 }); 1742 1743 1744 // ************************************************************************************************ 1745 // Editors 1746 1747 Firebug.HTMLPanel.Editors = { 1748 html : HTMLEditor, 1749 Attribute : AttributeEditor, 1750 TextNode: TextNodeEditor, 1751 TextData: TextDataEditor 1752 }; 1753 1754 1755 // ************************************************************************************************ 1756 // Local Helpers 1757 1758 function getEmptyElementTag(node) 1759 { 1760 var isXhtml= isElementXHTML(node); 1761 if (isXhtml) 1762 return Firebug.HTMLPanel.XEmptyElement.tag; 1763 else 1764 return Firebug.HTMLPanel.EmptyElement.tag; 1765 } 1766 1767 function getNodeTag(node, expandAll) 1768 { 1769 if (node instanceof Element) 1770 { 1771 if (node instanceof HTMLAppletElement) 1772 return getEmptyElementTag(node); 1773 else if (unwrapObject(node).firebugIgnore) 1774 return null; 1775 else if (HTMLLib.isContainerElement(node)) 1776 return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag; 1777 else if (HTMLLib.isEmptyElement(node)) 1778 return getEmptyElementTag(node); 1779 else if (Firebug.showCommentNodes && HTMLLib.hasCommentChildren(node)) 1780 return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag; 1781 else if (HTMLLib.hasNoElementChildren(node)) 1782 return Firebug.HTMLPanel.TextElement.tag; 1783 else 1784 return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag; 1785 } 1786 else if (node instanceof Text) 1787 return Firebug.HTMLPanel.TextNode.tag; 1788 else if (node instanceof CDATASection) 1789 return Firebug.HTMLPanel.CDATANode.tag; 1790 else if (node instanceof Comment && (Firebug.showCommentNodes || expandAll)) 1791 return Firebug.HTMLPanel.CommentNode.tag; 1792 else if (node instanceof SourceText) 1793 return FirebugReps.SourceText.tag; 1794 else 1795 return FirebugReps.Nada.tag; 1796 } 1797 1798 function getNodeBoxTag(nodeBox) 1799 { 1800 var re = /([^\s]+)NodeBox/; 1801 var m = re.exec(nodeBox.className); 1802 if (!m) 1803 return null; 1804 1805 var nodeBoxType = m[1]; 1806 if (nodeBoxType == "container") 1807 return Firebug.HTMLPanel.Element.tag; 1808 else if (nodeBoxType == "text") 1809 return Firebug.HTMLPanel.TextElement.tag; 1810 else if (nodeBoxType == "empty") 1811 return Firebug.HTMLPanel.EmptyElement.tag; 1812 } 1813 1814 // ************************************************************************************************ 1815 // Mutation Breakpoints 1816 1817 /** 1818 * @class Represents {@link Firebug.Debugger} listener. This listener is reponsible for 1819 * providing a list of mutation-breakpoints into the Breakpoints side-panel. 1820 */ 1821 Firebug.HTMLModule.DebuggerListener = 1822 { 1823 getBreakpoints: function(context, groups) 1824 { 1825 if (!context.mutationBreakpoints.isEmpty()) 1826 groups.push(context.mutationBreakpoints); 1827 } 1828 }; 1829 1830 Firebug.HTMLModule.MutationBreakpoints = 1831 { 1832 breakOnNext: function(context, breaking) 1833 { 1834 context.breakOnNextMutate = breaking; 1835 }, 1836 1837 breakOnNextMutate: function(event, context, type) 1838 { 1839 if (!context.breakOnNextMutate) 1840 return false; 1841 1842 // Ignore changes in trees marked with firebugIgnore. 1843 if (isAncestorIgnored(event.target)) 1844 return false; 1845 1846 context.breakOnNextMutate = false; 1847 1848 this.breakWithCause(event, context, type); 1849 }, 1850 1851 breakWithCause: function(event, context, type) 1852 { 1853 var changeLabel = Firebug.HTMLModule.BreakpointRep.getChangeLabel({type: type}); 1854 context.breakingCause = { 1855 title: $STR("net.Break On Mutate"), 1856 message: changeLabel, 1857 type: event.type, 1858 target: event.target, 1859 relatedNode: event.relatedNode, // http://www.w3.org/TR/DOM-Level-2-Events/events.html 1860 prevValue: event.prevValue, 1861 newValue: event.newValue, 1862 attrName: event.attrName, 1863 attrChange: event.attrChange, 1864 }; 1865 1866 Firebug.Breakpoint.breakNow(context.getPanel("html", true)); 1867 return true; 1868 }, 1869 1870 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1871 // Mutation event handlers. 1872 1873 onMutateAttr: function(event, context) 1874 { 1875 if (this.breakOnNextMutate(event, context, BP_BREAKONATTRCHANGE)) 1876 return; 1877 1878 var breakpoints = context.mutationBreakpoints; 1879 var self = this; 1880 breakpoints.enumerateBreakpoints(function(bp) { 1881 if (bp.checked && bp.node == event.target && bp.type == BP_BREAKONATTRCHANGE) { 1882 self.breakWithCause(event, context, BP_BREAKONATTRCHANGE); 1883 return true; 1884 } 1885 }); 1886 }, 1887 1888 onMutateText: function(event, context) 1889 { 1890 if (this.breakOnNextMutate(event, context, BP_BREAKONTEXT)) 1891 return; 1892 }, 1893 1894 onMutateNode: function(event, context) 1895 { 1896 var node = event.target; 1897 var removal = event.type == "DOMNodeRemoved"; 1898 1899 if (this.breakOnNextMutate(event, context, removal ? BP_BREAKONREMOVE : BP_BREAKONCHILDCHANGE)) 1900 return; 1901 1902 var breakpoints = context.mutationBreakpoints; 1903 var breaked = false; 1904 1905 if (removal) 1906 { 1907 var self = this; 1908 breaked = breakpoints.enumerateBreakpoints(function(bp) { 1909 if (bp.checked && bp.node == node && bp.type == BP_BREAKONREMOVE) { 1910 self.breakWithCause(event, context, BP_BREAKONREMOVE); 1911 return true; 1912 } 1913 }); 1914 } 1915 1916 if (!breaked) 1917 { 1918 // Collect all parents of the mutated node. 1919 var parents = []; 1920 for (var parent = node.parentNode; parent; parent = parent.parentNode) 1921 parents.push(parent); 1922 1923 // Iterate over all parents and see if some of them has a breakpoint. 1924 var self = this; 1925 breakpoints.enumerateBreakpoints(function(bp) { 1926 for (var i=0; i<parents.length; i++) { 1927 if (bp.checked && bp.node == parents[i] && bp.type == BP_BREAKONCHILDCHANGE) { 1928 self.breakWithCause(event, context, BP_BREAKONCHILDCHANGE); 1929 return true; 1930 } 1931 } 1932 }); 1933 } 1934 1935 if (removal) 1936 { 1937 // Remove all breakpoints assocaited with removed node. 1938 var invalidate = false; 1939 breakpoints.enumerateBreakpoints(function(bp) { 1940 if (bp.node == node) { 1941 breakpoints.removeBreakpoint(bp); 1942 invalidate = true; 1943 } 1944 }); 1945 1946 if (invalidate) 1947 context.invalidatePanels("breakpoints"); 1948 } 1949 }, 1950 1951 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1952 // Context menu items 1953 1954 getContextMenuItems: function(context, node, target, items) 1955 { 1956 if (!(node && node.nodeType == 1)) 1957 return; 1958 1959 var breakpoints = context.mutationBreakpoints; 1960 1961 var attrBox = getAncestorByClass(target, "nodeAttr"); 1962 if (getAncestorByClass(target, "nodeAttr")) 1963 { 1964 } 1965 1966 if (!(nonEditableTags.hasOwnProperty(node.localName))) 1967 { 1968 items.push( 1969 "-", 1970 {label: "html.label.Break On Attribute Change", 1971 type: "checkbox", 1972 checked: breakpoints.findBreakpoint(node, BP_BREAKONATTRCHANGE), 1973 command: bindFixed(this.onModifyBreakpoint, this, context, node, 1974 BP_BREAKONATTRCHANGE)}, 1975 {label: "html.label.Break On Child Addition or Removal", 1976 type: "checkbox", 1977 checked: breakpoints.findBreakpoint(node, BP_BREAKONCHILDCHANGE), 1978 command: bindFixed(this.onModifyBreakpoint, this, context, node, 1979 BP_BREAKONCHILDCHANGE)}, 1980 {label: "html.label.Break On Element Removal", 1981 type: "checkbox", 1982 checked: breakpoints.findBreakpoint(node, BP_BREAKONREMOVE), 1983 command: bindFixed(this.onModifyBreakpoint, this, context, node, 1984 BP_BREAKONREMOVE)} 1985 ); 1986 } 1987 }, 1988 1989 onModifyBreakpoint: function(context, node, type) 1990 { 1991 if (FBTrace.DBG_HTML) 1992 FBTrace.sysout("html.onModifyBreakpoint " + getElementXPath(node)); 1993 1994 var breakpoints = context.mutationBreakpoints; 1995 var bp = breakpoints.findBreakpoint(node, type); 1996 1997 // Remove an existing or create new breakpoint. 1998 if (bp) 1999 breakpoints.removeBreakpoint(bp); 2000 else 2001 context.mutationBreakpoints.addBreakpoint(node, type); 2002 }, 2003 }; 2004 2005 Firebug.HTMLModule.Breakpoint = function(node, type) 2006 { 2007 this.node = node; 2008 this.xpath = getElementXPath(node); 2009 this.checked = true; 2010 this.type = type; 2011 } 2012 2013 Firebug.HTMLModule.BreakpointRep = domplate(Firebug.Rep, 2014 { 2015 inspectable: false, 2016 2017 tag: 2018 DIV({"class": "breakpointRow focusRow", _repObject: "$bp", 2019 role: "option", "aria-checked": "$bp.checked"}, 2020 DIV({"class": "breakpointBlockHead", onclick: "$onEnable"}, 2021 INPUT({"class": "breakpointCheckbox", type: "checkbox", 2022 _checked: "$bp.checked", tabindex : "-1"}), 2023 TAG("$bp.node|getNodeTag", {object: "$bp.node"}), 2024 DIV({"class": "breakpointMutationType"}, "$bp|getChangeLabel"), 2025 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"}) 2026 ), 2027 DIV({"class": "breakpointCode"}, 2028 TAG("$bp.node|getSourceLine", {object: "$bp.node"}) 2029 ) 2030 ), 2031 2032 getNodeTag: function(node) 2033 { 2034 var rep = Firebug.getRep(node); 2035 return rep.shortTag ? rep.shortTag : rep.tag; 2036 }, 2037 2038 getSourceLine: function(node) 2039 { 2040 return getNodeTag(node, false); 2041 }, 2042 2043 getChangeLabel: function(bp) 2044 { 2045 switch (bp.type) 2046 { 2047 case BP_BREAKONATTRCHANGE: 2048 return $STR("html.label.Break On Attribute Change"); 2049 case BP_BREAKONCHILDCHANGE: 2050 return $STR("html.label.Break On Child Addition or Removal"); 2051 case BP_BREAKONREMOVE: 2052 return $STR("html.label.Break On Element Removal"); 2053 case BP_BREAKONTEXT: 2054 return $STR("html.label.Break On Text Change"); 2055 } 2056 2057 return ""; 2058 }, 2059 2060 onRemove: function(event) 2061 { 2062 cancelEvent(event); 2063 2064 var bpPanel = Firebug.getElementPanel(event.target); 2065 var context = bpPanel.context; 2066 var htmlPanel = context.getPanel("html"); 2067 2068 if (hasClass(event.target, "closeButton")) 2069 { 2070 // Remove from list of breakpoints. 2071 var row = getAncestorByClass(event.target, "breakpointRow"); 2072 context.mutationBreakpoints.removeBreakpoint(row.repObject); 2073 2074 // Remove from the UI. 2075 bpPanel.noRefresh = true; 2076 bpPanel.removeRow(row); 2077 bpPanel.noRefresh = false; 2078 } 2079 }, 2080 2081 onEnable: function(event) 2082 { 2083 var checkBox = event.target; 2084 if (hasClass(checkBox, "breakpointCheckbox")) 2085 { 2086 var bp = getAncestorByClass(checkBox, "breakpointRow").repObject; 2087 bp.checked = checkBox.checked; 2088 } 2089 }, 2090 2091 supportsObject: function(object) 2092 { 2093 return object instanceof Firebug.HTMLModule.Breakpoint; 2094 } 2095 }); 2096 2097 // ************************************************************************************************ 2098 2099 function MutationBreakpointGroup() 2100 { 2101 this.breakpoints = []; 2102 } 2103 2104 MutationBreakpointGroup.prototype = extend(new Firebug.Breakpoint.BreakpointGroup(), 2105 { 2106 name: "mutationBreakpoints", 2107 title: $STR("html.label.HTML Breakpoints"), 2108 2109 addBreakpoint: function(node, type) 2110 { 2111 this.breakpoints.push(new Firebug.HTMLModule.Breakpoint(node, type)); 2112 }, 2113 2114 matchBreakpoint: function(bp, args) 2115 { 2116 var node = args[0]; 2117 var type = args[1]; 2118 return (bp.node == node) && (!bp.type || bp.type == type); 2119 }, 2120 2121 removeBreakpoint: function(bp) 2122 { 2123 remove(this.breakpoints, bp); 2124 }, 2125 2126 // Persistence 2127 load: function(context) 2128 { 2129 var panelState = getPersistedState(context, "html"); 2130 if (panelState.breakpoints) 2131 this.breakpoints = panelState.breakpoints; 2132 2133 this.enumerateBreakpoints(function(bp) 2134 { 2135 var elts = getElementsByXPath(context.window.document, bp.xpath); 2136 bp.node = elts && elts.length ? elts[0] : null; 2137 }); 2138 }, 2139 2140 store: function(context) 2141 { 2142 this.enumerateBreakpoints(function(bp) 2143 { 2144 bp.node = null; 2145 }); 2146 2147 var panelState = getPersistedState(context, "html"); 2148 panelState.breakpoints = this.breakpoints; 2149 }, 2150 }); 2151 2152 2153 2154 2155 // ************************************************************************************************ 2156 // Registration 2157 2158 Firebug.registerPanel(Firebug.HTMLPanel); 2159 Firebug.registerModule(Firebug.HTMLModule); 2160 Firebug.registerRep(Firebug.HTMLModule.BreakpointRep); 2161 2162 // ************************************************************************************************ 2163 }}); 2164