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/html/htmlLib", 10 "firebug/lib/events", 11 "firebug/js/sourceLink", 12 "firebug/lib/css", 13 "firebug/lib/dom", 14 "firebug/chrome/window", 15 "firebug/lib/options", 16 "firebug/lib/xpath", 17 "firebug/lib/string", 18 "firebug/lib/xml", 19 "firebug/lib/array", 20 "firebug/lib/persist", 21 "firebug/chrome/menu", 22 "firebug/lib/url", 23 "firebug/css/cssModule", 24 "firebug/css/cssReps", 25 "firebug/js/breakpoint", 26 "firebug/editor/editor", 27 "firebug/chrome/searchBox", 28 "firebug/html/insideOutBox", 29 "firebug/html/inspector", 30 "firebug/html/layout" 31 ], 32 function(Obj, Firebug, Domplate, FirebugReps, Locale, HTMLLib, Events, 33 SourceLink, Css, Dom, Win, Options, Xpath, Str, Xml, Arr, Persist, Menu, 34 Url, CSSModule, CSSInfoTip) { 35 36 with (Domplate) { 37 38 // ********************************************************************************************* // 39 // Constants 40 41 const Cc = Components.classes; 42 const Ci = Components.interfaces; 43 44 const MODIFICATION = window.MutationEvent.MODIFICATION; 45 const ADDITION = window.MutationEvent.ADDITION; 46 const REMOVAL = window.MutationEvent.REMOVAL; 47 48 const BP_BREAKONATTRCHANGE = 1; 49 const BP_BREAKONCHILDCHANGE = 2; 50 const BP_BREAKONREMOVE = 3; 51 const BP_BREAKONTEXT = 4; 52 53 var KeyEvent = window.KeyEvent; 54 55 // ********************************************************************************************* // 56 57 Firebug.HTMLModule = Obj.extend(Firebug.Module, 58 { 59 dispatchName: "htmlModule", 60 61 initialize: function(prefDomain, prefNames) 62 { 63 Firebug.Module.initialize.apply(this, arguments); 64 Firebug.connection.addListener(this.DebuggerListener); 65 }, 66 67 shutdown: function() 68 { 69 Firebug.Module.shutdown.apply(this, arguments); 70 Firebug.connection.removeListener(this.DebuggerListener); 71 }, 72 73 initContext: function(context, persistedState) 74 { 75 Firebug.Module.initContext.apply(this, arguments); 76 context.mutationBreakpoints = new MutationBreakpointGroup(); 77 }, 78 79 loadedContext: function(context, persistedState) 80 { 81 context.mutationBreakpoints.load(context); 82 83 // If there are mutation breakpoints, make sure the HTML panel 84 // is automatically created and mutation listeners registered. 85 // Mutation breakpoints should work even if the HTML panel has 86 // never been selected by the user since the page load. 87 if (!context.mutationBreakpoints.isEmpty()) 88 { 89 var panel = context.getPanel("html"); 90 panel.registerMutationListeners(); 91 } 92 }, 93 94 destroyContext: function(context, persistedState) 95 { 96 Firebug.Module.destroyContext.apply(this, arguments); 97 98 context.mutationBreakpoints.store(context); 99 }, 100 101 deleteNode: function(node, context) 102 { 103 Events.dispatch(this.fbListeners, "onBeginFirebugChange", [node, context]); 104 node.parentNode.removeChild(node); 105 Events.dispatch(this.fbListeners, "onEndFirebugChange", [node, context]); 106 }, 107 108 deleteAttribute: function(node, attr, context) 109 { 110 Events.dispatch(this.fbListeners, "onBeginFirebugChange", [node, context]); 111 node.removeAttribute(attr); 112 Events.dispatch(this.fbListeners, "onEndFirebugChange", [node, context]); 113 } 114 }); 115 116 // ********************************************************************************************* // 117 118 Firebug.HTMLPanel = function() {}; 119 120 var WalkingPanel = Obj.extend(Firebug.Panel, HTMLLib.ElementWalkerFunctions); 121 122 Firebug.HTMLPanel.prototype = Obj.extend(WalkingPanel, 123 { 124 inspectable: true, 125 126 toggleEditing: function() 127 { 128 if (this.editing) 129 this.stopEditing(); 130 else 131 this.editNode(this.selection); 132 }, 133 134 stopEditing: function() 135 { 136 Firebug.Editor.stopEditing(); 137 138 // After mutation listeners have made the element appear in the panel, 139 // re-select it (and also update the disable state of the "Edit" button). 140 this.context.delay(function() 141 { 142 this.select(this.selection, true); 143 }.bind(this)); 144 }, 145 146 isEditing: function() 147 { 148 var editButton = Firebug.chrome.$("fbToggleHTMLEditing"); 149 return (this.editing && editButton.getAttribute("checked") === "true"); 150 }, 151 152 resetSearch: function() 153 { 154 delete this.lastSearch; 155 }, 156 157 select: function(object, forceUpdate, noEditChange) 158 { 159 if (!object) 160 object = this.getDefaultSelection(); 161 162 if (FBTrace.DBG_PANELS) 163 { 164 FBTrace.sysout("firebug.select " + this.name + " forceUpdate: " + forceUpdate + " " + 165 object + ((object == this.selection) ? "==" : "!=") + this.selection); 166 } 167 168 if (forceUpdate || object != this.selection) 169 { 170 this.selection = object; 171 this.updateSelection(object); 172 173 // Update the Edit button to reflect editability of the selection. 174 // (Except during editing, when it should always be possible to click it.) 175 var editButton = Firebug.chrome.$("fbToggleHTMLEditing"); 176 editButton.disabled = (this.selection && !this.isEditing() && 177 Css.nonEditableTags.hasOwnProperty(this.selection.localName)); 178 179 // Distribute selection change further to listeners. 180 Events.dispatch(Firebug.uiListeners, "onObjectSelected", [object, this]); 181 182 // If the 'free text' edit mode is active change the current markup 183 // displayed in the editor (textarea) so that it corresponds to the current 184 // selection. This typically happens when the user clicks on object-status-path 185 // buttons in the toolbar. 186 // For the case when the selection is changed from within the editor, don't 187 // change the edited element. 188 if (this.isEditing() && !noEditChange) 189 this.editNode(object); 190 } 191 }, 192 193 selectNext: function() 194 { 195 var objectBox = this.ioBox.createObjectBox(this.selection); 196 var next = this.ioBox.getNextObjectBox(objectBox); 197 if (next) 198 { 199 this.select(next.repObject); 200 201 if (Firebug.Inspector.inspecting) 202 Firebug.Inspector.inspectNode(next.repObject); 203 } 204 }, 205 206 selectPrevious: function() 207 { 208 var objectBox = this.ioBox.createObjectBox(this.selection); 209 var previous = this.ioBox.getPreviousObjectBox(objectBox); 210 if (previous) 211 { 212 this.select(previous.repObject); 213 214 if (Firebug.Inspector.inspecting) 215 Firebug.Inspector.inspectNode(previous.repObject); 216 } 217 }, 218 219 selectNodeBy: function(dir) 220 { 221 if (dir == "up") 222 { 223 this.selectPrevious(); 224 } 225 else if (dir == "down") 226 { 227 this.selectNext(); 228 } 229 else if (dir == "left") 230 { 231 var box = this.ioBox.createObjectBox(this.selection); 232 if (Css.hasClass(box, "open")) 233 { 234 this.ioBox.contractObjectBox(box); 235 } 236 else 237 { 238 var parentBox = this.ioBox.getParentObjectBox(box); 239 if (parentBox && parentBox.repObject instanceof window.Element) 240 this.select(parentBox.repObject); 241 } 242 } 243 else if (dir == "right") 244 { 245 var box = this.ioBox.createObjectBox(this.selection); 246 if (!Css.hasClass(box, "open")) 247 this.ioBox.expandObject(this.selection); 248 else 249 this.selectNext(); 250 } 251 252 Firebug.Inspector.highlightObject(this.selection, this.context); 253 }, 254 255 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 256 257 editNewAttribute: function(elt) 258 { 259 var objectNodeBox = this.ioBox.findObjectBox(elt); 260 if (objectNodeBox) 261 { 262 var labelBox = objectNodeBox.querySelector("*> .nodeLabel > .nodeLabelBox"); 263 var bracketBox = labelBox.querySelector("*> .nodeBracket"); 264 Firebug.Editor.insertRow(bracketBox, "before"); 265 } 266 }, 267 268 editAttribute: function(elt, attrName) 269 { 270 var objectNodeBox = this.ioBox.findObjectBox(elt); 271 if (objectNodeBox) 272 { 273 var attrBox = HTMLLib.findNodeAttrBox(objectNodeBox, attrName); 274 if (attrBox) 275 { 276 var attrValueBox = attrBox.childNodes[3]; 277 var value = elt.getAttribute(attrName); 278 Firebug.Editor.startEditing(attrValueBox, value); 279 } 280 } 281 }, 282 283 deleteAttribute: function(elt, attrName) 284 { 285 Firebug.HTMLModule.deleteAttribute(elt, attrName, this.context); 286 }, 287 288 localEditors:{}, // instantiated editor cache 289 editNode: function(node) 290 { 291 var objectNodeBox = this.ioBox.findObjectBox(node); 292 if (objectNodeBox) 293 { 294 var type = Xml.getElementType(node); 295 var editor = this.localEditors[type]; 296 if (!editor) 297 { 298 // look for special purpose editor (inserted by an extension), 299 // otherwise use our html editor 300 var specializedEditor = Firebug.HTMLPanel.Editors[type] || 301 Firebug.HTMLPanel.Editors["html"]; 302 editor = this.localEditors[type] = new specializedEditor(this.document); 303 } 304 305 this.startEditingNode(node, objectNodeBox, editor, type); 306 } 307 }, 308 309 startEditingNode: function(node, box, editor, type) 310 { 311 switch (type) 312 { 313 case "html": 314 case "xhtml": 315 this.startEditingHTMLNode(node, box, editor); 316 break; 317 default: 318 this.startEditingXMLNode(node, box, editor); 319 } 320 }, 321 322 startEditingXMLNode: function(node, box, editor) 323 { 324 var xml = Xml.getElementXML(node); 325 Firebug.Editor.startEditing(box, xml, editor); 326 }, 327 328 startEditingHTMLNode: function(node, box, editor) 329 { 330 if (Css.nonEditableTags.hasOwnProperty(node.localName)) 331 return; 332 333 editor.innerEditMode = node.localName in Css.innerEditableTags; 334 335 var html = editor.innerEditMode ? node.innerHTML : Xml.getElementHTML(node); 336 html = Str.escapeForHtmlEditor(html); 337 Firebug.Editor.startEditing(box, html, editor); 338 }, 339 340 deleteNode: function(node, dir) 341 { 342 var box = this.ioBox.createObjectBox(node); 343 if (Css.hasClass(box, "open")) 344 this.ioBox.contractObjectBox(box); 345 346 if (dir === "up") 347 { 348 // We want a "backspace"-like behavior, including traversing parents. 349 this.selectPrevious(); 350 } 351 else 352 { 353 // Move to the next sibling if there is one, else backwards. 354 var nextSelection = this.ioBox.getNextSiblingObjectBox(box); 355 if (nextSelection) 356 this.select(nextSelection.repObject); 357 else 358 this.selectPrevious(); 359 } 360 361 Firebug.HTMLModule.deleteNode(node, this.context); 362 363 Firebug.Inspector.highlightObject(this.selection, this.context); 364 }, 365 366 toggleAll: function(event, node) 367 { 368 var expandExternalContentNodes = Events.isShift(event); 369 this.ioBox.toggleObject(node, true, expandExternalContentNodes ? 370 null : ["link", "script", "style"]); 371 }, 372 373 updateNodeVisibility: function(node) 374 { 375 var wasHidden = node.classList.contains("nodeHidden"); 376 if (!Xml.isVisible(node.repObject)) 377 { 378 // Hide this node and, through CSS, every descendant. 379 node.classList.add("nodeHidden"); 380 } 381 else if (wasHidden) 382 { 383 // The node has changed state from hidden to shown. While in the 384 // hidden state, some descendants may have been explicitly marked 385 // with .nodeHidden (not just through CSS inheritance), so we need 386 // to recheck the visibility of those. 387 node.classList.remove("nodeHidden"); 388 var desc = Arr.cloneArray(node.getElementsByClassName("nodeHidden")); 389 for (var i = 0; i < desc.length; ++i) 390 { 391 if (Xml.isVisible(desc[i].repObject)) 392 desc[i].classList.remove("nodeHidden"); 393 } 394 } 395 }, 396 397 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 398 399 getElementSourceText: function(node) 400 { 401 if (this.sourceElements) 402 { 403 var index = this.sourceElementNodes.indexOf(node); 404 if (index != -1) 405 return this.sourceElements[index]; 406 } 407 408 var lines; 409 410 var url = HTMLLib.getSourceHref(node); 411 if (url) 412 { 413 lines = this.context.sourceCache.load(url); 414 } 415 else 416 { 417 var text = HTMLLib.getSourceText(node); 418 lines = Str.splitLines(text); 419 } 420 421 var sourceElt = new Firebug.HTMLModule.SourceText(lines, node); 422 423 if (!this.sourceElements) 424 { 425 this.sourceElements = [sourceElt]; 426 this.sourceElementNodes = [node]; 427 } 428 else 429 { 430 this.sourceElements.push(sourceElt); 431 this.sourceElementNodes.push(node); 432 } 433 434 return sourceElt; 435 }, 436 437 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 438 439 registerMutationListeners: function(win) 440 { 441 // The 'attachedMutation' flag should be maintained per window. Otherwise 442 // we can miss some registration. Events.addEventListener is safe for multiple 443 // calls so, let's remove the condition for now as part of issue 5761 fix. 444 // This should be improved together with issue 5490 445 //if (this.context.attachedMutation) 446 // return; 447 448 this.context.attachedMutation = true; 449 450 var self = this; 451 function addListeners(win) 452 { 453 var doc = win.document; 454 455 // xxxHonza: an iframe doesn't have to be loaded yet, so do not 456 // register mutation elements in such cases since they wouldn't 457 // be removed. 458 // The listeners can be registered later in watchWindowDelayed, 459 // but it's also risky. Mutation listeners should be registered 460 // at the moment when it's clear that the window/frame has been 461 // loaded. 462 463 // This break HTML panel for about:blank pages (see issue 5120). 464 //if (doc.location == "about:blank") 465 // return; 466 467 Events.addEventListener(doc, "DOMAttrModified", self.onMutateAttr, false); 468 Events.addEventListener(doc, "DOMCharacterDataModified", self.onMutateText, false); 469 Events.addEventListener(doc, "DOMNodeInserted", self.onMutateNode, false); 470 Events.addEventListener(doc, "DOMNodeRemoved", self.onMutateNode, false); 471 } 472 473 // If a window is specified use it, otherwise register listeners for all 474 // context windows (including the main window and all embedded iframes). 475 if (win) 476 addListeners(win); 477 else 478 Win.iterateWindows(this.context.window, addListeners); 479 }, 480 481 unregisterMutationListeners: function(win) 482 { 483 var self = this; 484 function removeListeners(win) 485 { 486 var doc = win.document; 487 Events.removeEventListener(doc, "DOMAttrModified", self.onMutateAttr, false); 488 Events.removeEventListener(doc, "DOMCharacterDataModified", self.onMutateText, false); 489 Events.removeEventListener(doc, "DOMNodeInserted", self.onMutateNode, false); 490 Events.removeEventListener(doc, "DOMNodeRemoved", self.onMutateNode, false); 491 } 492 493 if (win) 494 removeListeners(win); 495 else 496 Win.iterateWindows(this.context.window, removeListeners); 497 }, 498 499 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 500 501 mutateAttr: function(target, attrChange, attrName, attrValue) 502 { 503 // Every time the user scrolls we get this pointless mutation event, which 504 // is only bad for performance 505 if (attrName == "curpos") 506 return; 507 508 // Due to the delay call this may or may not exist in the tree anymore 509 if (!this.ioBox.isInExistingRoot(target)) 510 { 511 if (FBTrace.DBG_HTML) 512 FBTrace.sysout("mutateAttr: different tree " + target, target); 513 return; 514 } 515 516 if (FBTrace.DBG_HTML) 517 { 518 FBTrace.sysout("html.mutateAttr target:"+target+" attrChange:"+attrChange+ 519 " attrName:"+attrName+" attrValue: "+attrValue, target); 520 } 521 522 this.markChange(); 523 524 var objectNodeBox = Firebug.scrollToMutations || Firebug.expandMutations ? 525 this.ioBox.createObjectBox(target) : this.ioBox.findObjectBox(target); 526 527 if (!objectNodeBox) 528 return; 529 530 this.updateNodeVisibility(objectNodeBox); 531 532 if (attrChange == MODIFICATION || attrChange == ADDITION) 533 { 534 var nodeAttr = HTMLLib.findNodeAttrBox(objectNodeBox, attrName); 535 536 if (FBTrace.DBG_HTML) 537 FBTrace.sysout("mutateAttr " + attrChange + " " + attrName + "=" + attrValue + 538 " node: " + nodeAttr, nodeAttr); 539 540 if (nodeAttr && nodeAttr.childNodes.length > 3) 541 { 542 var attrValueBox = nodeAttr.querySelector("*> .nodeValue"); 543 var attrValueText = attrValueBox.firstChild; 544 if (attrValueText) 545 attrValueText.nodeValue = attrValue; 546 else 547 attrValueBox.innerHTML = Str.escapeForTextNode(attrValue); 548 549 this.highlightMutation(attrValueBox, objectNodeBox, "mutated"); 550 } 551 else 552 { 553 var attr = target.getAttributeNode(attrName); 554 555 if (FBTrace.DBG_HTML) 556 FBTrace.sysout("mutateAttr getAttributeNode " + attrChange + " " + attrName + 557 "=" + attrValue + " node: " + attr, attr); 558 559 if (attr) 560 { 561 var nodeAttr = Firebug.HTMLPanel.AttrNode.tag.replace({attr: attr}, 562 this.document); 563 564 var labelBox = objectNodeBox.querySelector("*> .nodeLabel > .nodeLabelBox"); 565 var bracketBox = labelBox.querySelector("*> .nodeBracket"); 566 labelBox.insertBefore(nodeAttr, bracketBox); 567 568 this.highlightMutation(nodeAttr, objectNodeBox, "mutated"); 569 } 570 } 571 } 572 else if (attrChange == REMOVAL) 573 { 574 var nodeAttr = HTMLLib.findNodeAttrBox(objectNodeBox, attrName); 575 if (nodeAttr) 576 nodeAttr.parentNode.removeChild(nodeAttr); 577 578 // We want to highlight regardless as the domplate may have been 579 // generated after the attribute was removed from the node 580 this.highlightMutation(objectNodeBox, objectNodeBox, "mutated"); 581 } 582 583 Firebug.Inspector.repaint(); 584 }, 585 586 mutateText: function(target, parent, textValue) 587 { 588 // Due to the delay call this may or may not exist in the tree anymore 589 if (!this.ioBox.isInExistingRoot(target)) 590 { 591 if (FBTrace.DBG_HTML) 592 FBTrace.sysout("mutateText: different tree " + target, target); 593 return; 594 } 595 596 this.markChange(); 597 598 var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations ? 599 this.ioBox.createObjectBox(parent) : this.ioBox.findObjectBox(parent); 600 601 if (!parentNodeBox) 602 { 603 if (FBTrace.DBG_HTML) 604 FBTrace.sysout("html.mutateText failed to update text, parent node " + 605 "box does not exist"); 606 return; 607 } 608 609 if (!Firebug.showFullTextNodes) 610 textValue = Str.cropMultipleLines(textValue); 611 612 var parentTag = getNodeBoxTag(parentNodeBox); 613 if (parentTag == Firebug.HTMLPanel.TextElement.tag) 614 { 615 if (FBTrace.DBG_HTML) 616 FBTrace.sysout("html.mutateText target: " + target + " parent: " + parent); 617 618 // Rerender the entire parentNodeBox. Proper entity-display logic will 619 // be automatically applied according to the preferences. 620 var newParentNodeBox = parentTag.replace({object: parentNodeBox.repObject}, this.document); 621 if (parentNodeBox.parentNode) 622 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox); 623 624 // Reselect if the element was selected before. 625 if (this.selection && (!this.selection.parentNode || parent == this.selection)) 626 this.ioBox.select(parent, true); 627 628 var nodeText = HTMLLib.getTextElementTextBox(newParentNodeBox); 629 if (!nodeText.firstChild) 630 { 631 if (FBTrace.DBG_HTML) 632 { 633 FBTrace.sysout("html.mutateText failed to update text, " + 634 "TextElement firstChild does not exist"); 635 } 636 return; 637 } 638 639 // Highlight the text box only (not the entire parentNodeBox/element). 640 this.highlightMutation(nodeText, newParentNodeBox, "mutated"); 641 } 642 else 643 { 644 var childBox = this.ioBox.getChildObjectBox(parentNodeBox); 645 if (!childBox) 646 { 647 if (FBTrace.DBG_HTML) 648 { 649 FBTrace.sysout("html.mutateText failed to update text, " + 650 "no child object box found"); 651 } 652 return; 653 } 654 655 var textNodeBox = this.ioBox.findChildObjectBox(childBox, target); 656 if (textNodeBox) 657 { 658 // structure for comment and cdata. Are there others? 659 textNodeBox.firstChild.firstChild.nodeValue = textValue; 660 661 this.highlightMutation(textNodeBox, parentNodeBox, "mutated"); 662 } 663 else if (Firebug.scrollToMutations || Firebug.expandMutations) 664 { 665 // We are not currently rendered but we are set to highlight 666 var objectBox = this.ioBox.createObjectBox(target); 667 this.highlightMutation(objectBox, objectBox, "mutated"); 668 } 669 } 670 }, 671 672 mutateNode: function(target, parent, nextSibling, removal) 673 { 674 if (FBTrace.DBG_HTML) 675 FBTrace.sysout("html.mutateNode target:" + target + " parent:" + parent + 676 (removal ? "REMOVE" : "")); 677 678 // Due to the delay call this may or may not exist in the tree anymore 679 if (!removal && !this.ioBox.isInExistingRoot(target)) 680 { 681 if (FBTrace.DBG_HTML) 682 FBTrace.sysout("mutateNode: different tree " + target, target); 683 return; 684 } 685 686 this.markChange(); // This invalidates the panels for every mutate 687 688 var parentNodeBox = Firebug.scrollToMutations || Firebug.expandMutations 689 ? this.ioBox.createObjectBox(parent) 690 : this.ioBox.findObjectBox(parent); 691 692 if (FBTrace.DBG_HTML) 693 FBTrace.sysout("html.mutateNode parent:" + parent + " parentNodeBox:" + 694 parentNodeBox); 695 696 if (!parentNodeBox) 697 return; 698 699 if (!Firebug.showTextNodesWithWhitespace && this.isWhitespaceText(target)) 700 return; 701 702 // target is only whitespace 703 704 var newParentTag = getNodeTag(parent); 705 var oldParentTag = getNodeBoxTag(parentNodeBox); 706 707 if (newParentTag == oldParentTag) 708 { 709 if (parentNodeBox.populated) 710 { 711 if (removal) 712 { 713 this.ioBox.removeChildBox(parentNodeBox, target); 714 715 // Special case for docType. 716 if (target instanceof HTMLHtmlElement) 717 this.ioBox.removeChildBox(parentNodeBox, target.parentNode.doctype); 718 719 this.highlightMutation(parentNodeBox, parentNodeBox, "mutated"); 720 } 721 else 722 { 723 var childBox = this.ioBox.getChildObjectBox(parentNodeBox); 724 725 var comments = Firebug.showCommentNodes; 726 var whitespaces = Firebug.showTextNodesWithWhitespace; 727 728 // Get the right next sibling that match following criteria: 729 // 1) It's not a whitespace text node in case 'show whitespaces' is false. 730 // 2) It's not a comment in case 'show comments' is false. 731 // 3) There is a child box already created for it in the HTML panel UI. 732 // The new node will then be inserted before that sibling's child box, or 733 // appended at the end (issue 5255). 734 while (nextSibling && ( 735 (!whitespaces && HTMLLib.isWhitespaceText(nextSibling)) || 736 (!comments && nextSibling instanceof window.Comment) || 737 (!this.ioBox.findChildObjectBox(childBox, nextSibling)))) 738 { 739 nextSibling = this.findNextSibling(nextSibling); 740 } 741 742 var objectBox = nextSibling ? 743 this.ioBox.insertChildBoxBefore(parentNodeBox, target, nextSibling) : 744 this.ioBox.appendChildBox(parentNodeBox, target); 745 746 // Special case for docType. 747 if (target instanceof HTMLHtmlElement) 748 { 749 this.ioBox.insertChildBoxBefore(parentNodeBox, 750 target.parentNode.doctype, target); 751 } 752 753 this.highlightMutation(objectBox, objectBox, "mutated"); 754 } 755 } 756 else // !parentNodeBox.populated 757 { 758 var newParentNodeBox = newParentTag.replace({object: parent}, this.document); 759 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox); 760 761 if (this.selection && (!this.selection.parentNode || parent == this.selection)) 762 this.ioBox.select(parent, true); 763 764 this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated"); 765 766 if (!removal && (Firebug.scrollToMutations || Firebug.expandMutations)) 767 { 768 var objectBox = this.ioBox.createObjectBox(target); 769 this.highlightMutation(objectBox, objectBox, "mutated"); 770 } 771 } 772 } 773 else // newParentTag != oldParentTag 774 { 775 var newParentNodeBox = newParentTag.replace({object: parent}, this.document); 776 if (parentNodeBox.parentNode) 777 parentNodeBox.parentNode.replaceChild(newParentNodeBox, parentNodeBox); 778 779 if (Css.hasClass(parentNodeBox, "open")) 780 this.ioBox.toggleObjectBox(newParentNodeBox, true); 781 782 if (this.selection && (!this.selection.parentNode || parent == this.selection)) 783 this.ioBox.select(parent, true); 784 785 this.highlightMutation(newParentNodeBox, newParentNodeBox, "mutated"); 786 787 if (!removal && (Firebug.scrollToMutations || Firebug.expandMutations)) 788 { 789 var objectBox = this.ioBox.createObjectBox(target); 790 this.highlightMutation(objectBox, objectBox, "mutated"); 791 } 792 } 793 }, 794 795 highlightMutation: function(elt, objectBox, type) 796 { 797 if (FBTrace.DBG_HTML) 798 FBTrace.sysout("html.highlightMutation Firebug.highlightMutations:" + 799 Firebug.highlightMutations, {elt: elt, objectBox: objectBox, type: type}); 800 801 if (!elt) 802 return; 803 804 if (Firebug.scrollToMutations || Firebug.expandMutations) 805 { 806 if (this.context.mutationTimeout) 807 { 808 this.context.clearTimeout(this.context.mutationTimeout); 809 delete this.context.mutationTimeout; 810 } 811 812 var ioBox = this.ioBox; 813 var panelNode = this.panelNode; 814 815 this.context.mutationTimeout = this.context.setTimeout(function() 816 { 817 ioBox.openObjectBox(objectBox); 818 819 if (Firebug.scrollToMutations) 820 Dom.scrollIntoCenterView(objectBox, panelNode); 821 }, 200); 822 } 823 824 if (Firebug.highlightMutations) 825 Css.setClassTimed(elt, type, this.context); 826 }, 827 828 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 829 // InsideOutBoxView implementation 830 831 createObjectBox: function(object, isRoot) 832 { 833 if (FBTrace.DBG_HTML) 834 { 835 FBTrace.sysout("html.createObjectBox(" + Css.getElementCSSSelector(object) + 836 ", isRoot:" + (isRoot? "true" : "false")+")"); 837 } 838 839 var tag = getNodeTag(object); 840 if (tag) 841 return tag.replace({object: object}, this.document); 842 }, 843 844 getParentObject: function(node) 845 { 846 if (node instanceof Firebug.HTMLModule.SourceText) 847 return node.owner; 848 849 var parentNode = this.getParentNode(node); 850 851 // for chromebug to avoid climbing out to browser.xul 852 if (node.nodeName == "#document") 853 return null; 854 855 //if (FBTrace.DBG_HTML) 856 // FBTrace.sysout("html.getParentObject for "+node.nodeName+" parentNode:"+ 857 // Css.getElementCSSSelector(parentNode)); 858 859 if (parentNode) 860 { 861 if (parentNode.nodeType == Node.DOCUMENT_NODE) 862 { 863 if (parentNode.defaultView) 864 { 865 if (parentNode.defaultView == this.context.window) 866 return parentNode; 867 868 if (FBTrace.DBG_HTML) 869 { 870 FBTrace.sysout("getParentObject; node is document node"+ 871 ", frameElement:" + parentNode.defaultView.frameElement); 872 } 873 874 return parentNode.defaultView.frameElement; 875 } 876 else 877 { 878 var skipParent = this.getEmbedConnection(parentNode); 879 if (FBTrace.DBG_HTML) 880 FBTrace.sysout("getParentObject skipParent:" + 881 (skipParent ? skipParent.nodeName : "none")); 882 883 if (skipParent) 884 return skipParent; 885 else 886 return null; // parent is document element, but no window at defaultView. 887 } 888 } 889 else if (!parentNode.localName) 890 { 891 if (FBTrace.DBG_HTML) 892 FBTrace.sysout("getParentObject: null localName must be window, no parentObject"); 893 return null; 894 } 895 else 896 { 897 return parentNode; 898 } 899 } 900 else 901 { 902 // Documents have no parentNode; Attr, Document, DocumentFragment, Entity, 903 // and Notation. top level windows have no parentNode 904 if (node && node.nodeType == Node.DOCUMENT_NODE) 905 { 906 // generally a reference to the window object for the document, however 907 // that is not defined in the specification 908 if (node.defaultView) 909 { 910 var embeddingFrame = node.defaultView.frameElement; 911 if (embeddingFrame) 912 return embeddingFrame.contentDocument; 913 } 914 else 915 { 916 // a Document object without a parentNode or window 917 return null; // top level has no parent 918 } 919 } 920 } 921 }, 922 923 setEmbedConnection: function(node, skipChild) 924 { 925 if (!this.embeddedBrowserParents) 926 { 927 this.embeddedBrowserParents = []; 928 this.embeddedBrowserDocument = []; 929 } 930 931 this.embeddedBrowserDocument.push(skipChild); 932 933 // store our adopted child in a side table 934 this.embeddedBrowserParents.push(node); 935 936 if (FBTrace.DBG_HTML) 937 FBTrace.sysout("Found skipChild " + Css.getElementCSSSelector(skipChild) + 938 " for " + Css.getElementCSSSelector(node) + " with node.contentDocument " + 939 node.contentDocument); 940 941 return skipChild; 942 }, 943 944 getEmbedConnection: function(node) 945 { 946 if (this.embeddedBrowserParents) 947 { 948 var index = this.embeddedBrowserParents.indexOf(node); 949 if (index !== -1) 950 return this.embeddedBrowserDocument[index]; 951 } 952 }, 953 954 /** 955 * @param: node a DOM node from the Web page 956 * @param: index counter for important children, may skip whitespace 957 * @param: previousSibling a node from the web page 958 */ 959 getChildObject: function(node, index, previousSibling) 960 { 961 if (!node) 962 { 963 FBTrace.sysout("getChildObject: null node"); 964 return; 965 } 966 967 if (FBTrace.DBG_HTML) 968 FBTrace.sysout("getChildObject " + node.tagName + " index " + index + 969 " previousSibling: " + 970 (previousSibling ? Css.getElementCSSSelector(previousSibling) : "null"), 971 {node: node, previousSibling:previousSibling}); 972 973 if (this.isSourceElement(node)) 974 { 975 if (index == 0) 976 return this.getElementSourceText(node); 977 else 978 return null; // no siblings of source elements 979 } 980 else if (node instanceof window.Document) 981 { 982 if (previousSibling !== null) 983 return this.getNextSibling(previousSibling); 984 else 985 return this.getFirstChild(node); 986 } 987 else if (node.contentDocument) // then the node is a frame 988 { 989 if (index == 0) 990 { 991 // punch thru and adopt the document node as our child 992 var skipChild = node.contentDocument.firstChild; 993 994 // (the node's).(type 9 document).(HTMLElement) 995 return this.setEmbedConnection(node, skipChild); 996 } 997 else if (previousSibling) 998 { 999 // Next child of a document (after doc-type) is <html>. 1000 return this.getNextSibling(previousSibling); 1001 } 1002 } 1003 else if (node.getSVGDocument && node.getSVGDocument()) // then the node is a frame 1004 { 1005 if (index == 0) 1006 { 1007 var skipChild = node.getSVGDocument().documentElement; // unwrap 1008 1009 // (the node's).(type 9 document).(HTMLElement) 1010 return this.setEmbedConnection(node, skipChild); 1011 } 1012 else 1013 { 1014 return null; 1015 } 1016 } 1017 1018 var child; 1019 if (previousSibling) // then we are walking 1020 child = this.getNextSibling(previousSibling); // may return null, meaning done with iteration. 1021 else 1022 child = this.getFirstChild(node); // child is set to at the beginning of an iteration. 1023 1024 if (FBTrace.DBG_HTML) 1025 FBTrace.sysout("getChildObject firstChild " + Css.getElementCSSSelector(child) + 1026 " with Firebug.showTextNodesWithWhitespace " + 1027 Firebug.showTextNodesWithWhitespace); 1028 1029 if (Firebug.showTextNodesWithWhitespace) // then the index is true to the node list 1030 { 1031 return child; 1032 } 1033 else 1034 { 1035 for (; child; child = this.getNextSibling(child)) 1036 { 1037 if (!this.isWhitespaceText(child)) 1038 return child; 1039 } 1040 } 1041 1042 return null; // we have no children worth showing. 1043 }, 1044 1045 isWhitespaceText: function(node) 1046 { 1047 return HTMLLib.isWhitespaceText(node); 1048 }, 1049 1050 findNextSibling: function (node) 1051 { 1052 return HTMLLib.findNextSibling(node); 1053 }, 1054 1055 isSourceElement: function(element) 1056 { 1057 return HTMLLib.isSourceElement(element); 1058 }, 1059 1060 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1061 // Events 1062 1063 onMutateAttr: function(event) 1064 { 1065 var target = event.target; 1066 if (Firebug.shouldIgnore(target)) 1067 return; 1068 1069 var attrChange = event.attrChange; 1070 var attrName = event.attrName; 1071 var newValue = event.newValue; 1072 1073 this.context.delay(function() 1074 { 1075 this.mutateAttr(target, attrChange, attrName, newValue); 1076 }, this); 1077 1078 Firebug.HTMLModule.MutationBreakpoints.onMutateAttr(event, this.context); 1079 }, 1080 1081 onMutateText: function(event) 1082 { 1083 if (FBTrace.DBG_HTML) 1084 FBTrace.sysout("html.onMutateText; ", event); 1085 1086 var target = event.target; 1087 var parent = target.parentNode; 1088 1089 var newValue = event.newValue; 1090 1091 this.context.delay(function() 1092 { 1093 this.mutateText(target, parent, newValue); 1094 }, this); 1095 1096 Firebug.HTMLModule.MutationBreakpoints.onMutateText(event, this.context); 1097 }, 1098 1099 onMutateNode: function(event) 1100 { 1101 var target = event.target; 1102 if (Firebug.shouldIgnore(target)) 1103 return; 1104 1105 var parent = event.relatedNode; 1106 var removal = event.type == "DOMNodeRemoved"; 1107 var nextSibling = removal ? null : this.findNextSibling(target); 1108 1109 this.context.delay(function() 1110 { 1111 try 1112 { 1113 this.mutateNode(target, parent, nextSibling, removal); 1114 } 1115 catch (exc) 1116 { 1117 if (FBTrace.DBG_ERRORS && FBTrace.DBG_HTML) 1118 FBTrace.sysout("html.onMutateNode FAILS:", exc); 1119 } 1120 }, this); 1121 1122 Firebug.HTMLModule.MutationBreakpoints.onMutateNode(event, this.context); 1123 }, 1124 1125 onClick: function(event) 1126 { 1127 if (Events.isLeftClick(event) && Events.isDoubleClick(event)) 1128 { 1129 // The double-click (detail == 2) expands an HTML element, but the user must click 1130 // on the element itself not on the twisty. 1131 // The logic should be as follow: 1132 // - click on the twisty expands/collapses the element 1133 // - double click on the element name expands/collapses it 1134 // - click on the element name selects it 1135 if (!Css.hasClass(event.target, "twisty") && !Css.hasClass(event.target, "nodeLabel")) 1136 this.toggleNode(event); 1137 } 1138 else if (Events.isAltClick(event) && Events.isDoubleClick(event) && !this.editing) 1139 { 1140 var node = Firebug.getRepObject(event.target); 1141 this.editNode(node); 1142 } 1143 }, 1144 1145 onMouseDown: function(event) 1146 { 1147 if (!Events.isLeftClick(event)) 1148 return; 1149 1150 if (Dom.getAncestorByClass(event.target, "nodeTag")) 1151 { 1152 var node = Firebug.getRepObject(event.target); 1153 this.noScrollIntoView = true; 1154 this.select(node); 1155 1156 delete this.noScrollIntoView; 1157 1158 if (Css.hasClass(event.target, "twisty")) 1159 this.toggleNode(event); 1160 } 1161 }, 1162 1163 toggleNode: function(event) 1164 { 1165 var node = Firebug.getRepObject(event.target); 1166 var box = this.ioBox.createObjectBox(node); 1167 if (!Css.hasClass(box, "open")) 1168 this.ioBox.expandObject(node); 1169 else 1170 this.ioBox.contractObject(this.selection); 1171 }, 1172 1173 onKeyPress: function(event) 1174 { 1175 if (this.editing) 1176 return; 1177 1178 var node = this.selection; 1179 if (!node) 1180 return; 1181 1182 // * expands the node with all its children 1183 // + expands the node 1184 // - collapses the node 1185 var ch = String.fromCharCode(event.charCode); 1186 if (ch == "*") 1187 this.toggleAll(event, node); 1188 1189 if (!Events.noKeyModifiers(event)) 1190 return; 1191 1192 if (ch == "+") 1193 this.ioBox.expandObject(node); 1194 else if (ch == "-") 1195 this.ioBox.contractObject(node); 1196 1197 if (event.keyCode == KeyEvent.DOM_VK_UP) 1198 this.selectNodeBy("up"); 1199 else if (event.keyCode == KeyEvent.DOM_VK_DOWN) 1200 this.selectNodeBy("down"); 1201 else if (event.keyCode == KeyEvent.DOM_VK_LEFT) 1202 this.selectNodeBy("left"); 1203 else if (event.keyCode == KeyEvent.DOM_VK_RIGHT) 1204 this.selectNodeBy("right"); 1205 else if (event.keyCode == KeyEvent.DOM_VK_BACK_SPACE) 1206 { 1207 if (!Css.nonDeletableTags.hasOwnProperty(node.localName)) 1208 this.deleteNode(node, "up"); 1209 } 1210 else if (event.keyCode == KeyEvent.DOM_VK_DELETE) 1211 { 1212 if (!Css.nonDeletableTags.hasOwnProperty(node.localName)) 1213 this.deleteNode(node, "down"); 1214 } 1215 else 1216 return; 1217 1218 Events.cancelEvent(event); 1219 }, 1220 1221 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1222 // CSS Listener 1223 1224 updateVisibilitiesForSelectorInSheet: function(sheet, selector) 1225 { 1226 if (!selector) 1227 return; 1228 var doc = (sheet && sheet.ownerNode && sheet.ownerNode.ownerDocument); 1229 if (!doc) 1230 return; 1231 1232 var affected = doc.querySelectorAll(selector); 1233 if (!affected.length || !this.ioBox.isInExistingRoot(affected[0])) 1234 return; 1235 1236 for (var i = 0; i < affected.length; ++i) 1237 { 1238 var node = this.ioBox.findObjectBox(affected[i]); 1239 if (node) 1240 this.updateNodeVisibility(node); 1241 } 1242 }, 1243 1244 updateVisibilitiesForRule: function(rule) 1245 { 1246 this.updateVisibilitiesForSelectorInSheet(rule.parentStyleSheet, rule.selectorText); 1247 }, 1248 1249 cssPropAffectsVisibility: function(propName) 1250 { 1251 // Pretend that "display" is the only property which affects visibility, 1252 // which is a half-truth. We could make this more technically correct 1253 // by unconditionally returning true, but forcing a synchronous reflow 1254 // and computing offsetWidth/Height on up to every element on the page 1255 // isn't worth it. 1256 return (propName === "display"); 1257 }, 1258 1259 cssTextAffectsVisibility: function(cssText) 1260 { 1261 return (cssText.indexOf("display:") !== -1); 1262 }, 1263 1264 onAfterCSSDeleteRule: function(styleSheet, cssText, selector) 1265 { 1266 if (this.cssTextAffectsVisibility(cssText)) 1267 this.updateVisibilitiesForSelectorInSheet(styleSheet, selector); 1268 }, 1269 1270 onCSSInsertRule: function(styleSheet, cssText, ruleIndex) 1271 { 1272 if (this.cssTextAffectsVisibility(cssText)) 1273 this.updateVisibilitiesForRule(styleSheet.cssRules[ruleIndex]); 1274 }, 1275 1276 onCSSSetProperty: function(style, propName, propValue, propPriority, prevValue, 1277 prevPriority, rule, baseText) 1278 { 1279 if (this.cssPropAffectsVisibility(propName)) 1280 this.updateVisibilitiesForRule(rule); 1281 }, 1282 1283 onCSSRemoveProperty: function(style, propName, prevValue, prevPriority, rule, baseText) 1284 { 1285 if (this.cssPropAffectsVisibility(propName)) 1286 this.updateVisibilitiesForRule(rule); 1287 }, 1288 1289 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1290 // extends Panel 1291 1292 name: "html", 1293 searchable: true, 1294 breakable: true, 1295 dependents: ["css", "computed", "layout", "dom", "domSide", "watch"], 1296 inspectorHistory: new Array(5), 1297 enableA11y: true, 1298 order: 20, 1299 1300 initialize: function() 1301 { 1302 this.onMutateText = Obj.bind(this.onMutateText, this); 1303 this.onMutateAttr = Obj.bind(this.onMutateAttr, this); 1304 this.onMutateNode = Obj.bind(this.onMutateNode, this); 1305 this.onClick = Obj.bind(this.onClick, this); 1306 this.onMouseDown = Obj.bind(this.onMouseDown, this); 1307 this.onKeyPress = Obj.bind(this.onKeyPress, this); 1308 1309 Firebug.Panel.initialize.apply(this, arguments); 1310 Firebug.CSSModule.addListener(this); 1311 }, 1312 1313 destroy: function(state) 1314 { 1315 Persist.persistObjects(this, state); 1316 1317 Firebug.Panel.destroy.apply(this, arguments); 1318 1319 delete this.embeddedBrowserParents; 1320 delete this.embeddedBrowserDocument; 1321 1322 // xxxHonza: I don't know why this helps, but it helps to release the 1323 // page compartment (at least by observing about:memory); 1324 // Note that inspectorHistory holds references to page elements. 1325 for (var i=0; i<this.inspectorHistory.length; i++) 1326 delete this.inspectorHistory[i]; 1327 delete this.inspectorHistory; 1328 1329 Firebug.CSSModule.removeListener(this); 1330 this.unregisterMutationListeners(); 1331 }, 1332 1333 initializeNode: function(oldPanelNode) 1334 { 1335 if (!this.ioBox) 1336 this.ioBox = new Firebug.InsideOutBox(this, this.panelNode); 1337 1338 Events.addEventListener(this.panelNode, "click", this.onClick, false); 1339 Events.addEventListener(this.panelNode, "mousedown", this.onMouseDown, false); 1340 1341 Firebug.Panel.initializeNode.apply(this, arguments); 1342 }, 1343 1344 destroyNode: function() 1345 { 1346 Events.removeEventListener(this.panelNode, "click", this.onClick, false); 1347 Events.removeEventListener(this.panelNode, "mousedown", this.onMouseDown, false); 1348 1349 Events.removeEventListener(this.panelNode.ownerDocument, "keypress", 1350 this.onKeyPress, true); 1351 1352 if (this.ioBox) 1353 { 1354 this.ioBox.destroy(); 1355 delete this.ioBox; 1356 } 1357 1358 Firebug.Panel.destroyNode.apply(this, arguments); 1359 }, 1360 1361 show: function(state) 1362 { 1363 this.showToolbarButtons("fbHTMLButtons", true); 1364 this.showToolbarButtons("fbStatusButtons", true); 1365 1366 Events.addEventListener(this.panelNode.ownerDocument, "keypress", this.onKeyPress, true); 1367 1368 if (this.context.loaded) 1369 { 1370 this.registerMutationListeners(); 1371 1372 Persist.restoreObjects(this, state); 1373 } 1374 }, 1375 1376 hide: function() 1377 { 1378 // clear the state that is tracking the infotip so it is reset after next show() 1379 delete this.infoTipURL; 1380 1381 Events.removeEventListener(this.panelNode.ownerDocument, "keypress", this.onKeyPress, true); 1382 }, 1383 1384 watchWindow: function(context, win) 1385 { 1386 var self = this; 1387 setTimeout(function() { 1388 self.watchWindowDelayed(context, win); 1389 }, 100); 1390 }, 1391 1392 watchWindowDelayed: function(context, win) 1393 { 1394 if (this.context.window && this.context.window != win) 1395 { 1396 // then I guess we are an embedded window 1397 var htmlPanel = this; 1398 Win.iterateWindows(this.context.window, function(subwin) 1399 { 1400 if (win == subwin) 1401 { 1402 if (FBTrace.DBG_HTML) 1403 FBTrace.sysout("html.watchWindow found subwin.location.href="+ 1404 win.location.href); 1405 1406 htmlPanel.mutateDocumentEmbedded(win, false); 1407 } 1408 }); 1409 } 1410 1411 if (this.context.attachedMutation) 1412 this.registerMutationListeners(win); 1413 }, 1414 1415 unwatchWindow: function(context, win) 1416 { 1417 if (this.context.window && this.context.window != win) 1418 { 1419 // then I guess we are an embedded window 1420 var htmlPanel = this; 1421 Win.iterateWindows(this.context.window, function(subwin) 1422 { 1423 if (win == subwin) 1424 { 1425 if (FBTrace.DBG_HTML) 1426 FBTrace.sysout("html.unwatchWindow found subwin.location.href="+ 1427 win.location.href); 1428 1429 htmlPanel.mutateDocumentEmbedded(win, true); 1430 } 1431 }); 1432 } 1433 1434 this.unregisterMutationListeners(win); 1435 }, 1436 1437 mutateDocumentEmbedded: function(win, remove) 1438 { 1439 //xxxHonza: win.document.documentElement is null if this method is synchronously 1440 // called after watchWindow. This is why watchWindowDelayed is introduced. 1441 // See issue 3342 1442 1443 // document.documentElement - Returns the Element that is a direct child of document. 1444 // For HTML documents, this normally the HTML element. 1445 var self = this; 1446 var target = win.document.documentElement; 1447 var parent = win.frameElement; 1448 var nextSibling = self.findNextSibling(target || parent); 1449 self.mutateNode(target, parent, nextSibling, remove); 1450 }, 1451 1452 supportsObject: function(object, type) 1453 { 1454 if (object instanceof window.Element || object instanceof window.Text || 1455 object instanceof window.CDATASection) 1456 { 1457 return 2; 1458 } 1459 else if (object instanceof SourceLink.SourceLink && object.type == "css" && 1460 !Url.reCSS.test(object.href)) 1461 { 1462 return 2; 1463 } 1464 else 1465 { 1466 return 0; 1467 } 1468 }, 1469 1470 updateOption: function(name, value) 1471 { 1472 var options = new Set(); 1473 options.add("showCommentNodes"); 1474 options.add("entityDisplay"); 1475 options.add("showTextNodesWithWhitespace"); 1476 options.add("showFullTextNodes"); 1477 1478 if (options.has(name)) 1479 { 1480 this.resetSearch(); 1481 Dom.clearNode(this.panelNode); 1482 if (this.ioBox) 1483 this.ioBox.destroy(); 1484 1485 this.ioBox = new Firebug.InsideOutBox(this, this.panelNode); 1486 this.ioBox.select(this.selection, true, true); 1487 } 1488 }, 1489 1490 updateSelection: function(object) 1491 { 1492 if (FBTrace.DBG_HTML) 1493 FBTrace.sysout("html.updateSelection " + object, object); 1494 1495 if (this.ioBox.sourceRow) 1496 this.ioBox.sourceRow.removeAttribute("exe_line"); 1497 1498 // && object.type == "css" and !Url.reCSS(object.href) by supports 1499 if (object instanceof SourceLink.SourceLink) 1500 { 1501 var sourceLink = object; 1502 var stylesheet = Css.getStyleSheetByHref(sourceLink.href, this.context); 1503 if (stylesheet) 1504 { 1505 var ownerNode = stylesheet.ownerNode; 1506 1507 if (FBTrace.DBG_CSS) 1508 { 1509 FBTrace.sysout("html panel updateSelection stylesheet.ownerNode=" + 1510 stylesheet.ownerNode + " href:" + sourceLink.href); 1511 } 1512 1513 if (ownerNode) 1514 { 1515 var objectbox = this.ioBox.select(ownerNode, true, true, this.noScrollIntoView); 1516 1517 // XXXjjb seems like this could be bad for errors at the end of long files 1518 // first source row in style 1519 var sourceRow = objectbox.getElementsByClassName("sourceRow").item(0); 1520 for (var lineNo = 1; lineNo < sourceLink.line; lineNo++) 1521 { 1522 if (!sourceRow) break; 1523 sourceRow = Dom.getNextByClass(sourceRow, "sourceRow"); 1524 } 1525 1526 if (FBTrace.DBG_CSS) 1527 { 1528 FBTrace.sysout("html panel updateSelection sourceLink.line=" + 1529 sourceLink.line + " sourceRow=" + 1530 (sourceRow ? sourceRow.innerHTML : "undefined")); 1531 } 1532 1533 if (sourceRow) 1534 { 1535 this.ioBox.sourceRow = sourceRow; 1536 this.ioBox.sourceRow.setAttribute("exe_line", "true"); 1537 1538 Dom.scrollIntoCenterView(sourceRow); 1539 1540 // sourceRow isn't an objectBox, but the function should work anyway... 1541 this.ioBox.selectObjectBox(sourceRow, false); 1542 } 1543 } 1544 } 1545 } 1546 else if (Firebug.Inspector.inspecting) 1547 { 1548 this.ioBox.highlight(object); 1549 } 1550 else 1551 { 1552 var found = this.ioBox.select(object, true, false, this.noScrollIntoView); 1553 if (!found) 1554 { 1555 // Look up for an enclosing parent. NB this will mask failures in createObjectBoxes 1556 var parentNode = this.getParentObject(object); 1557 1558 if (FBTrace.DBG_ERRORS && FBTrace.DBG_HTML) 1559 FBTrace.sysout("html.updateSelect no objectBox for object:"+ 1560 Css.getElementCSSSelector(object) + " trying "+ 1561 Css.getElementCSSSelector(parentNode)); 1562 1563 this.updateSelection(parentNode); 1564 return; 1565 } 1566 1567 this.inspectorHistory.unshift(object); 1568 if (this.inspectorHistory.length > 5) 1569 this.inspectorHistory.pop(); 1570 } 1571 }, 1572 1573 stopInspecting: function(object, canceled) 1574 { 1575 if (object != this.inspectorHistory) 1576 { 1577 // Manage history of selection for later access in the command line. 1578 this.inspectorHistory.unshift(object); 1579 if (this.inspectorHistory.length > 5) 1580 this.inspectorHistory.pop(); 1581 1582 if (FBTrace.DBG_HTML) 1583 FBTrace.sysout("html.stopInspecting: inspectoryHistory updated", 1584 this.inspectorHistory); 1585 } 1586 1587 this.ioBox.highlight(null); 1588 1589 if (!canceled) 1590 this.ioBox.select(object, true); 1591 }, 1592 1593 search: function(text, reverse) 1594 { 1595 if (!text) 1596 return; 1597 1598 var search; 1599 if (text == this.searchText && this.lastSearch) 1600 { 1601 search = this.lastSearch; 1602 } 1603 else 1604 { 1605 var doc = this.context.window.document; 1606 search = this.lastSearch = new HTMLLib.NodeSearch(text, doc, this.panelNode, this.ioBox); 1607 } 1608 1609 var loopAround = search.find(reverse, Firebug.Search.isCaseSensitive(text)); 1610 if (loopAround) 1611 { 1612 this.resetSearch(); 1613 this.search(text, reverse); 1614 } 1615 1616 return !search.noMatch && (loopAround ? "wraparound" : true); 1617 }, 1618 1619 getSearchOptionsMenuItems: function() 1620 { 1621 return [ 1622 Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive", 1623 "search.tip.Case_Sensitive") 1624 ]; 1625 }, 1626 1627 getDefaultSelection: function() 1628 { 1629 try 1630 { 1631 var doc = this.context.window.document; 1632 return doc.body ? doc.body : Dom.getPreviousElement(doc.documentElement.lastChild); 1633 } 1634 catch (exc) 1635 { 1636 return null; 1637 } 1638 }, 1639 1640 getObjectPath: function(element) 1641 { 1642 var path = []; 1643 for (; element; element = this.getParentObject(element)) 1644 { 1645 // Ignore the document itself, it shouldn't be displayed in 1646 // the object path (aka breadcrumbs). 1647 if (element instanceof window.Document) 1648 continue; 1649 1650 // Ignore elements without parent 1651 if (!element.parentNode) 1652 continue; 1653 1654 path.push(element); 1655 } 1656 return path; 1657 }, 1658 1659 getPopupObject: function(target) 1660 { 1661 return Firebug.getRepObject(target); 1662 }, 1663 1664 getTooltipObject: function(target) 1665 { 1666 if (Dom.getAncestorByClass(target, "nodeLabelBox") || 1667 Dom.getAncestorByClass(target, "nodeCloseLabelBox")) 1668 { 1669 return Firebug.getRepObject(target); 1670 } 1671 }, 1672 1673 getOptionsMenuItems: function() 1674 { 1675 return [ 1676 Menu.optionMenu("ShowFullText", "showFullTextNodes", 1677 "html.option.tip.Show_Full_Text"), 1678 Menu.optionMenu("ShowWhitespace", "showTextNodesWithWhitespace", 1679 "html.option.tip.Show_Whitespace"), 1680 Menu.optionMenu("ShowComments", "showCommentNodes", 1681 "html.option.tip.Show_Comments"), 1682 "-", 1683 { 1684 label: "html.option.Show_Entities_As_Symbols", 1685 tooltiptext: "html.option.tip.Show_Entities_As_Symbols", 1686 type: "radio", 1687 name: "entityDisplay", 1688 id: "entityDisplaySymbols", 1689 command: Obj.bind(this.setEntityDisplay, this, "symbols"), 1690 checked: Options.get("entityDisplay") == "symbols" 1691 }, 1692 { 1693 label: "html.option.Show_Entities_As_Names", 1694 tooltiptext: "html.option.tip.Show_Entities_As_Names", 1695 type: "radio", 1696 name: "entityDisplay", 1697 id: "entityDisplayNames", 1698 command: Obj.bind(this.setEntityDisplay, this, "names"), 1699 checked: Options.get("entityDisplay") == "names" 1700 }, 1701 { 1702 label: "html.option.Show_Entities_As_Unicode", 1703 tooltiptext: "html.option.tip.Show_Entities_As_Unicode", 1704 type: "radio", 1705 name: "entityDisplay", 1706 id: "entityDisplayUnicode", 1707 command: Obj.bind(this.setEntityDisplay, this, "unicode"), 1708 checked: Options.get("entityDisplay") == "unicode" 1709 }, 1710 "-", 1711 Menu.optionMenu("HighlightMutations", "highlightMutations", 1712 "html.option.tip.Highlight_Mutations"), 1713 Menu.optionMenu("ExpandMutations", "expandMutations", 1714 "html.option.tip.Expand_Mutations"), 1715 Menu.optionMenu("ScrollToMutations", "scrollToMutations", 1716 "html.option.tip.Scroll_To_Mutations"), 1717 "-", 1718 Menu.optionMenu("ShadeBoxModel", "shadeBoxModel", 1719 "inspect.option.tip.Shade_Box_Model"), 1720 Menu.optionMenu("ShowQuickInfoBox","showQuickInfoBox", 1721 "inspect.option.tip.Show_Quick_Info_Box") 1722 ]; 1723 }, 1724 1725 getContextMenuItems: function(node, target) 1726 { 1727 if (!node) 1728 return null; 1729 1730 var items = []; 1731 1732 if (node.nodeType == Node.ELEMENT_NODE) 1733 { 1734 items.push( 1735 "-", 1736 { 1737 label: "NewAttribute", 1738 id: "htmlNewAttribute", 1739 tooltiptext: "html.tip.New_Attribute", 1740 command: Obj.bindFixed(this.editNewAttribute, this, node) 1741 } 1742 ); 1743 1744 var attrBox = Dom.getAncestorByClass(target, "nodeAttr"); 1745 if (Dom.getAncestorByClass(target, "nodeAttr")) 1746 { 1747 var attrName = attrBox.childNodes[1].textContent; 1748 1749 items.push( 1750 { 1751 label: Locale.$STRF("EditAttribute", [attrName]), 1752 tooltiptext: Locale.$STRF("html.tip.Edit_Attribute", [attrName]), 1753 nol10n: true, 1754 command: Obj.bindFixed(this.editAttribute, this, node, attrName) 1755 }, 1756 { 1757 label: Locale.$STRF("DeleteAttribute", [attrName]), 1758 tooltiptext: Locale.$STRF("html.tip.Delete_Attribute", [attrName]), 1759 nol10n: true, 1760 command: Obj.bindFixed(this.deleteAttribute, this, node, attrName) 1761 } 1762 ); 1763 } 1764 1765 if (!(Css.nonEditableTags.hasOwnProperty(node.localName))) 1766 { 1767 var type; 1768 1769 if (Xml.isElementHTML(node) || Xml.isElementXHTML(node)) 1770 type = "HTML"; 1771 else if (Xml.isElementMathML(node)) 1772 type = "MathML"; 1773 else if (Xml.isElementSVG(node)) 1774 type = "SVG"; 1775 else if (Xml.isElementXUL(node)) 1776 type = "XUL"; 1777 else 1778 type = "XML"; 1779 1780 items.push("-", 1781 { 1782 label: Locale.$STRF("html.Edit_Node", [type]), 1783 tooltiptext: Locale.$STRF("html.tip.Edit_Node", [type]), 1784 nol10n: true, 1785 command: Obj.bindFixed(this.editNode, this, node) 1786 }, 1787 { 1788 label: "DeleteElement", 1789 tooltiptext: "html.Delete_Element", 1790 1791 // xxxsz: 'Del' needs to be translated, but therefore customizeShortcuts 1792 // must be turned into a module 1793 acceltext: "Del", 1794 command: Obj.bindFixed(this.deleteNode, this, node), 1795 disabled:(node.localName in Css.innerEditableTags) 1796 }); 1797 } 1798 1799 var objectBox = Dom.getAncestorByClass(target, "nodeBox"); 1800 var nodeChildBox = this.ioBox.getChildObjectBox(objectBox); 1801 if (nodeChildBox) 1802 { 1803 items.push( 1804 "-", 1805 { 1806 label: "html.label.Expand/Contract_All", 1807 tooltiptext: "html.tip.Expand/Contract_All", 1808 acceltext: "*", 1809 command: Obj.bind(this.toggleAll, this, node) 1810 } 1811 ); 1812 } 1813 } 1814 else 1815 { 1816 var nodeLabel = Locale.$STR("html.Node"); 1817 items.push( 1818 "-", 1819 { 1820 label: Locale.$STRF("html.Edit_Node", [nodeLabel]), 1821 tooltiptext: Locale.$STRF("html.tip.Edit_Node", [nodeLabel]), 1822 nol10n: true, 1823 command: Obj.bindFixed(this.editNode, this, node) 1824 }, 1825 { 1826 label: "DeleteNode", 1827 tooltiptext: "html.Delete_Node", 1828 command: Obj.bindFixed(this.deleteNode, this, node) 1829 } 1830 ); 1831 } 1832 1833 Firebug.HTMLModule.MutationBreakpoints.getContextMenuItems( 1834 this.context, node, target, items); 1835 1836 return items; 1837 }, 1838 1839 showInfoTip: function(infoTip, target, x, y) 1840 { 1841 if (!Css.hasClass(target, "nodeValue")) 1842 return; 1843 1844 var node = Firebug.getRepObject(target); 1845 if (node && node.nodeType == Node.ELEMENT_NODE) 1846 { 1847 var nodeName = node.localName.toUpperCase(); 1848 var attribute = Dom.getAncestorByClass(target, "nodeAttr"); 1849 var attributeName = attribute.getElementsByClassName("nodeName").item(0).textContent; 1850 1851 if ((nodeName == "IMG" || nodeName == "INPUT") && attributeName == "src") 1852 { 1853 var url = node.src; 1854 1855 // This state cleared in hide() 1856 if (url == this.infoTipURL) 1857 return true; 1858 1859 this.infoTipURL = url; 1860 return CSSInfoTip.populateImageInfoTip(infoTip, url); 1861 } 1862 } 1863 }, 1864 1865 getEditor: function(target, value) 1866 { 1867 if (Css.hasClass(target, "nodeName") || Css.hasClass(target, "nodeValue") || 1868 Css.hasClass(target, "nodeBracket")) 1869 { 1870 if (!this.attrEditor) 1871 this.attrEditor = new Firebug.HTMLPanel.Editors.Attribute(this.document); 1872 1873 return this.attrEditor; 1874 } 1875 else if (Css.hasClass(target, "nodeComment") || Css.hasClass(target, "nodeCDATA")) 1876 { 1877 if (!this.textDataEditor) 1878 this.textDataEditor = new Firebug.HTMLPanel.Editors.TextData(this.document); 1879 1880 return this.textDataEditor; 1881 } 1882 else if (Css.hasClass(target, "nodeText")) 1883 { 1884 if (!this.textNodeEditor) 1885 this.textNodeEditor = new Firebug.HTMLPanel.Editors.TextNode(this.document); 1886 1887 return this.textNodeEditor; 1888 } 1889 }, 1890 1891 getInspectorVars: function() 1892 { 1893 var vars = {}; 1894 for (var i=0; i<2; i++) 1895 vars["$"+i] = this.inspectorHistory[i]; 1896 1897 return vars; 1898 }, 1899 1900 setEntityDisplay: function(event, type) 1901 { 1902 Options.set("entityDisplay", type); 1903 1904 var menuItem = event.target; 1905 menuItem.setAttribute("checked", "true"); 1906 }, 1907 1908 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 1909 // Break on Mutate 1910 1911 breakOnNext: function(breaking) 1912 { 1913 Firebug.HTMLModule.MutationBreakpoints.breakOnNext(this.context, breaking); 1914 }, 1915 1916 shouldBreakOnNext: function() 1917 { 1918 return this.context.breakOnNextMutate; 1919 }, 1920 1921 getBreakOnNextTooltip: function(enabled) 1922 { 1923 return (enabled ? Locale.$STR("html.Disable Break On Mutate") : 1924 Locale.$STR("html.Break On Mutate")); 1925 } 1926 }); 1927 1928 // ********************************************************************************************* // 1929 1930 var AttrTag = Firebug.HTMLPanel.AttrTag = 1931 SPAN({"class": "nodeAttr editGroup"}, 1932 " ", SPAN({"class": "nodeName editable"}, "$attr.name"), "="", 1933 SPAN({"class": "nodeValue editable"}, "$attr|getAttrValue"), """ 1934 ); 1935 1936 var TextTag = Firebug.HTMLPanel.TextTag = 1937 SPAN({"class": "nodeText editable"}, 1938 FOR("char", "$object|getNodeTextGroups", 1939 SPAN({"class": "$char.class $char.extra"}, "$char.str") 1940 ) 1941 ); 1942 1943 // ********************************************************************************************* // 1944 1945 Firebug.HTMLPanel.CompleteElement = domplate(FirebugReps.Element, 1946 { 1947 tag: 1948 DIV({"class": "nodeBox open $object|getHidden", _repObject: "$object", role : 'presentation'}, 1949 DIV({"class": "nodeLabel", role: "presentation"}, 1950 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem', 'aria-expanded' : 'false'}, 1951 "<", 1952 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 1953 FOR("attr", "$object|attrIterator", AttrTag), 1954 SPAN({"class": "nodeBracket"}, ">") 1955 ) 1956 ), 1957 DIV({"class": "nodeChildBox", role :"group"}, 1958 FOR("child", "$object|childIterator", 1959 TAG("$child|getNodeTag", {object: "$child"}) 1960 ) 1961 ), 1962 DIV({"class": "nodeCloseLabel", role:"presentation"}, 1963 "</", 1964 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 1965 ">" 1966 ) 1967 ), 1968 1969 getNodeTag: function(node) 1970 { 1971 return getNodeTag(node, true); 1972 }, 1973 1974 childIterator: function(node) 1975 { 1976 if (node.contentDocument) 1977 return [node.contentDocument.documentElement]; 1978 1979 if (Firebug.showTextNodesWithWhitespace) 1980 { 1981 return Arr.cloneArray(node.childNodes); 1982 } 1983 else 1984 { 1985 var nodes = []; 1986 var walker = new HTMLLib.ElementWalker(); 1987 1988 for (var child = walker.getFirstChild(node); child; child = walker.getNextSibling(child)) 1989 { 1990 if (child.nodeType != Node.TEXT_NODE || !HTMLLib.isWhitespaceText(child)) 1991 nodes.push(child); 1992 } 1993 1994 return nodes; 1995 } 1996 } 1997 }); 1998 1999 Firebug.HTMLPanel.SoloElement = domplate(Firebug.HTMLPanel.CompleteElement, 2000 { 2001 tag: 2002 DIV({"class": "soloElement", onmousedown: "$onMouseDown"}, 2003 Firebug.HTMLPanel.CompleteElement.tag 2004 ), 2005 2006 onMouseDown: function(event) 2007 { 2008 for (var child = event.target; child; child = child.parentNode) 2009 { 2010 if (child.repObject) 2011 { 2012 var panel = Firebug.getElementPanel(child); 2013 Firebug.chrome.select(child.repObject); 2014 break; 2015 } 2016 } 2017 } 2018 }); 2019 2020 Firebug.HTMLPanel.Element = domplate(FirebugReps.Element, 2021 { 2022 tag: 2023 DIV({"class": "nodeBox containerNodeBox $object|getHidden", _repObject: "$object", 2024 role: "presentation"}, 2025 DIV({"class": "nodeLabel", role: "presentation"}, 2026 DIV({"class": "twisty", role: "presentation"}), 2027 SPAN({"class": "nodeLabelBox repTarget", role: "treeitem", "aria-expanded": "false"}, 2028 "<", 2029 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2030 FOR("attr", "$object|attrIterator", AttrTag), 2031 SPAN({"class": "nodeBracket editable insertBefore"}, ">") 2032 ) 2033 ), 2034 DIV({"class": "nodeChildBox", role: "group"}), /* nodeChildBox is special signal in insideOutBox */ 2035 DIV({"class": "nodeCloseLabel", role: "presentation"}, 2036 SPAN({"class": "nodeCloseLabelBox repTarget"}, 2037 "</", 2038 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2039 ">" 2040 ) 2041 ) 2042 ) 2043 }); 2044 2045 Firebug.HTMLPanel.HTMLDocument = domplate(FirebugReps.Element, 2046 { 2047 tag: 2048 DIV({"class": "nodeBox documentNodeBox containerNodeBox", 2049 _repObject: "$object", role: "presentation"}, 2050 DIV({"class": "nodeChildBox", role: "group"}) 2051 ) 2052 }); 2053 2054 Firebug.HTMLPanel.HTMLDocType = domplate(FirebugReps.Element, 2055 { 2056 tag: 2057 DIV({"class": "nodeBox docTypeNodeBox containerNodeBox", 2058 _repObject: "$object", role: "presentation"}, 2059 DIV({"class": "docType"}, 2060 "$object|getDocType" 2061 ) 2062 ), 2063 2064 getDocType: function(doctype) 2065 { 2066 return '<!DOCTYPE ' + doctype.name + (doctype.publicId ? ' PUBLIC "' + doctype.publicId + 2067 '"': '') + (doctype.systemId ? ' "' + doctype.systemId + '"' : '') + '>'; 2068 } 2069 }); 2070 2071 Firebug.HTMLPanel.HTMLHtmlElement = domplate(FirebugReps.Element, 2072 { 2073 tag: 2074 DIV({"class": "nodeBox htmlNodeBox containerNodeBox $object|getHidden", 2075 _repObject: "$object", role: "presentation"}, 2076 DIV({"class": "nodeLabel", role: "presentation"}, 2077 DIV({"class": "twisty", role: "presentation"}), 2078 SPAN({"class": "nodeLabelBox repTarget", role: "treeitem", 2079 "aria-expanded": "false"}, 2080 "<", 2081 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2082 FOR("attr", "$object|attrIterator", AttrTag), 2083 SPAN({"class": "nodeBracket editable insertBefore"}, ">") 2084 ) 2085 ), 2086 DIV({"class": "nodeChildBox", role: "group"}), /* nodeChildBox is special signal in insideOutBox */ 2087 DIV({"class": "nodeCloseLabel", role: "presentation"}, 2088 SPAN({"class": "nodeCloseLabelBox repTarget"}, 2089 "</", 2090 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2091 ">" 2092 ) 2093 ) 2094 ) 2095 }); 2096 2097 Firebug.HTMLPanel.TextElement = domplate(FirebugReps.Element, 2098 { 2099 tag: 2100 DIV({"class": "nodeBox textNodeBox $object|getHidden", _repObject: "$object", role : 'presentation'}, 2101 DIV({"class": "nodeLabel", role: "presentation"}, 2102 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'}, 2103 "<", 2104 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2105 FOR("attr", "$object|attrIterator", AttrTag), 2106 SPAN({"class": "nodeBracket editable insertBefore"}, ">"), 2107 TextTag, 2108 "</", 2109 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2110 ">" 2111 ) 2112 ) 2113 ) 2114 }); 2115 2116 Firebug.HTMLPanel.EmptyElement = domplate(FirebugReps.Element, 2117 { 2118 tag: 2119 DIV({"class": "nodeBox emptyNodeBox $object|getHidden", _repObject: "$object", role : 'presentation'}, 2120 DIV({"class": "nodeLabel", role: "presentation"}, 2121 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'}, 2122 "<", 2123 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2124 FOR("attr", "$object|attrIterator", AttrTag), 2125 SPAN({"class": "nodeBracket editable insertBefore"}, ">") 2126 ) 2127 ) 2128 ) 2129 }); 2130 2131 Firebug.HTMLPanel.XEmptyElement = domplate(FirebugReps.Element, 2132 { 2133 tag: 2134 DIV({"class": "nodeBox emptyNodeBox $object|getHidden", _repObject: "$object", role : 'presentation'}, 2135 DIV({"class": "nodeLabel", role: "presentation"}, 2136 SPAN({"class": "nodeLabelBox repTarget", role : 'treeitem'}, 2137 "<", 2138 SPAN({"class": "nodeTag"}, "$object|getNodeName"), 2139 FOR("attr", "$object|attrIterator", AttrTag), 2140 SPAN({"class": "nodeBracket editable insertBefore"}, "/>") 2141 ) 2142 ) 2143 ) 2144 }); 2145 2146 Firebug.HTMLPanel.AttrNode = domplate(FirebugReps.Element, 2147 { 2148 tag: AttrTag 2149 }); 2150 2151 Firebug.HTMLPanel.TextNode = domplate(FirebugReps.Element, 2152 { 2153 tag: 2154 DIV({"class": "nodeBox", _repObject: "$object", role : 'presentation'}, 2155 TextTag 2156 ) 2157 }); 2158 2159 Firebug.HTMLPanel.CDATANode = domplate(FirebugReps.Element, 2160 { 2161 tag: 2162 DIV({"class": "nodeBox", _repObject: "$object", role : 'presentation'}, 2163 "<![CDATA[", 2164 SPAN({"class": "nodeText nodeCDATA editable"}, "$object.nodeValue"), 2165 "]]>" 2166 ) 2167 }); 2168 2169 Firebug.HTMLPanel.CommentNode = domplate(FirebugReps.Element, 2170 { 2171 tag: 2172 DIV({"class": "nodeBox nodeComment", _repObject: "$object", role : 'presentation'}, 2173 "<!--", 2174 SPAN({"class": "nodeComment editable"}, "$object.nodeValue"), 2175 "-->" 2176 ) 2177 }); 2178 2179 // ********************************************************************************************* // 2180 // TextDataEditor 2181 2182 /** 2183 * TextDataEditor deals with text of comments and cdata nodes 2184 */ 2185 function TextDataEditor(doc) 2186 { 2187 this.initializeInline(doc); 2188 } 2189 2190 TextDataEditor.prototype = domplate(Firebug.InlineEditor.prototype, 2191 { 2192 saveEdit: function(target, value, previousValue) 2193 { 2194 var node = Firebug.getRepObject(target); 2195 if (!node) 2196 return; 2197 2198 target.data = value; 2199 node.data = value; 2200 } 2201 }); 2202 2203 // ********************************************************************************************* // 2204 // TextNodeEditor 2205 2206 /** 2207 * TextNodeEditor deals with text nodes that do and do not have sibling elements. If 2208 * there are no sibling elements, the parent is known as a TextElement. In other cases 2209 * we keep track of their position via a range (this is in part because as people type 2210 * html, the range will keep track of the text nodes and elements that the user 2211 * is creating as they type, and this range could be in the middle of the parent 2212 * elements children). 2213 */ 2214 function TextNodeEditor(doc) 2215 { 2216 this.initializeInline(doc); 2217 } 2218 2219 TextNodeEditor.prototype = domplate(Firebug.InlineEditor.prototype, 2220 { 2221 getInitialValue: function(target, value) 2222 { 2223 // The text displayed within the HTML panel can be shortened if the 'Show Full Text' 2224 // option is false, so get the original textContent from the associated page element 2225 // (issue 2183). 2226 var repObject = Firebug.getRepObject(target); 2227 if (repObject) 2228 return repObject.textContent; 2229 2230 return value; 2231 }, 2232 2233 beginEditing: function(target, value) 2234 { 2235 var node = Firebug.getRepObject(target); 2236 if (!node || node instanceof window.Element) 2237 return; 2238 2239 var document = node.ownerDocument; 2240 this.range = document.createRange(); 2241 this.range.setStartBefore(node); 2242 this.range.setEndAfter(node); 2243 }, 2244 2245 endEditing: function(target, value, cancel) 2246 { 2247 if (this.range) 2248 { 2249 this.range.detach(); 2250 delete this.range; 2251 } 2252 2253 // Remove empty groups by default 2254 return true; 2255 }, 2256 2257 saveEdit: function(target, value, previousValue) 2258 { 2259 var node = Firebug.getRepObject(target); 2260 if (!node) 2261 return; 2262 2263 value = Str.unescapeForTextNode(value || ""); 2264 target.innerHTML = Str.escapeForTextNode(value); 2265 2266 if (node instanceof window.Element) 2267 { 2268 if (Xml.isElementMathML(node) || Xml.isElementSVG(node)) 2269 node.textContent = value; 2270 else 2271 node.innerHTML = value; 2272 } 2273 else 2274 { 2275 try 2276 { 2277 var documentFragment = this.range.createContextualFragment(value); 2278 var cnl = documentFragment.childNodes.length; 2279 this.range.deleteContents(); 2280 this.range.insertNode(documentFragment); 2281 var r = this.range, sc = r.startContainer, so = r.startOffset; 2282 this.range.setEnd(sc,so+cnl); 2283 } 2284 catch (e) 2285 { 2286 if (FBTrace.DBG_ERRORS) 2287 FBTrace.sysout("TextNodeEditor.saveEdit; EXCEPTION " + e, e); 2288 } 2289 } 2290 } 2291 }); 2292 2293 // ********************************************************************************************* // 2294 // AttributeEditor 2295 2296 function AttributeEditor(doc) 2297 { 2298 this.initializeInline(doc); 2299 } 2300 2301 AttributeEditor.prototype = domplate(Firebug.InlineEditor.prototype, 2302 { 2303 saveEdit: function(target, value, previousValue) 2304 { 2305 var element = Firebug.getRepObject(target); 2306 if (!element) 2307 return; 2308 2309 // XXXstr unescape value 2310 target.innerHTML = Str.escapeForElementAttribute(value); 2311 2312 if (Css.hasClass(target, "nodeName")) 2313 { 2314 if (value != previousValue) 2315 element.removeAttribute(previousValue); 2316 2317 if (value) 2318 { 2319 var attrValue = Dom.getNextByClass(target, "nodeValue").textContent; 2320 element.setAttribute(value, attrValue); 2321 } 2322 else 2323 { 2324 element.removeAttribute(value); 2325 } 2326 } 2327 else if (Css.hasClass(target, "nodeValue")) 2328 { 2329 var attrName = Dom.getPreviousByClass(target, "nodeName").textContent; 2330 element.setAttribute(attrName, value); 2331 } 2332 2333 var panel = Firebug.getElementPanel(target); 2334 Events.dispatch(Firebug.uiListeners, "onObjectChanged", [element, panel]); 2335 2336 //this.panel.markChange(); 2337 }, 2338 2339 advanceToNext: function(target, charCode) 2340 { 2341 if (charCode == 61 /* '=' */ && Css.hasClass(target, "nodeName")) 2342 { 2343 return true; 2344 } 2345 else if ((charCode == 34 /* '"' */ || charCode == 39 /* ''' */) && 2346 Css.hasClass(target, "nodeValue")) 2347 { 2348 var nonRestrictiveAttributes = 2349 [ 2350 "onabort", 2351 "onblur", 2352 "onchange", 2353 "onclick", 2354 "ondblclick", 2355 "onerror", 2356 "onfocus", 2357 "onkeydown", 2358 "onkeypress", 2359 "onkeyup", 2360 "onload", 2361 "onmousedown", 2362 "onmousemove", 2363 "onmouseout", 2364 "onmouseover", 2365 "onmouseup", 2366 "onreset", 2367 "onselect", 2368 "onsubmit", 2369 "onunload", 2370 "title", 2371 "alt", 2372 "style" 2373 ] 2374 2375 var attrName = Dom.getPreviousByClass(target, "nodeName").textContent; 2376 2377 // This should cover most of the cases where quotes are allowed inside the value 2378 // See issue 4542 2379 for (var i = 0; i < nonRestrictiveAttributes.length; i++) 2380 { 2381 if (attrName == nonRestrictiveAttributes[i]) 2382 return false; 2383 } 2384 return true; 2385 } 2386 }, 2387 2388 insertNewRow: function(target, insertWhere) 2389 { 2390 var emptyAttr = {name: "", value: ""}; 2391 var sibling = insertWhere == "before" ? target.previousSibling : target; 2392 return AttrTag.insertAfter({attr: emptyAttr}, sibling); 2393 }, 2394 2395 getInitialValue: function(target, value) 2396 { 2397 if (value == "") 2398 return value; 2399 2400 var element = Firebug.getRepObject(target); 2401 if (element && element instanceof window.Element) 2402 { 2403 // If object that was clicked to edit was 2404 // attribute value, not attribute name. 2405 if (Css.hasClass(target, "nodeValue")) 2406 { 2407 var attributeName = Dom.getPreviousByClass(target, "nodeName").textContent; 2408 return element.getAttribute(attributeName); 2409 } 2410 } 2411 return value; 2412 } 2413 }); 2414 2415 // ********************************************************************************************* // 2416 // HTMLEditor 2417 2418 function HTMLEditor(doc) 2419 { 2420 this.box = this.tag.replace({}, doc, this); 2421 this.input = this.box.firstChild; 2422 this.multiLine = true; 2423 this.tabNavigation = false; 2424 this.arrowCompletion = false; 2425 } 2426 2427 HTMLEditor.prototype = domplate(Firebug.BaseEditor, 2428 { 2429 tag: 2430 DIV( 2431 TEXTAREA({"class": "htmlEditor fullPanelEditor", oninput: "$onInput"}) 2432 ), 2433 2434 getValue: function() 2435 { 2436 return this.input.value; 2437 }, 2438 2439 setValue: function(value) 2440 { 2441 return this.input.value = value; 2442 }, 2443 2444 show: function(target, panel, value, textSize) 2445 { 2446 this.target = target; 2447 this.panel = panel; 2448 var el = target.repObject; 2449 if (this.innerEditMode) 2450 { 2451 this.editingParent = el; 2452 } 2453 else 2454 { 2455 this.editingRange = el.ownerDocument.createRange(); 2456 this.editingRange.selectNode(el); 2457 this.originalLocalName = el.localName; 2458 } 2459 2460 this.panel.panelNode.appendChild(this.box); 2461 2462 this.input.value = value; 2463 this.input.focus(); 2464 2465 var command = Firebug.chrome.$("cmd_firebug_toggleHTMLEditing"); 2466 command.setAttribute("checked", true); 2467 }, 2468 2469 hide: function() 2470 { 2471 var command = Firebug.chrome.$("cmd_firebug_toggleHTMLEditing"); 2472 command.setAttribute("checked", false); 2473 2474 this.panel.panelNode.removeChild(this.box); 2475 2476 delete this.editingParent; 2477 delete this.editingRange; 2478 delete this.originalLocalName; 2479 delete this.target; 2480 delete this.panel; 2481 }, 2482 2483 getNewSelection: function(fragment) 2484 { 2485 // Get a new element to select in the HTML panel. An element with the 2486 // same localName is preferred, or just any element. If there is none, 2487 // we choose the parent instead. 2488 var found = null; 2489 var nodes = fragment.childNodes; 2490 for (var i = 0; i < nodes.length; ++i) 2491 { 2492 var n = nodes[i]; 2493 if (n.nodeType === Node.ELEMENT_NODE) 2494 { 2495 if (n.localName === this.originalLocalName) 2496 return n; 2497 if (!found) 2498 found = n; 2499 } 2500 } 2501 if (found) 2502 return found; 2503 return this.editingRange.startContainer; 2504 }, 2505 2506 saveEdit: function(target, value, previousValue) 2507 { 2508 if (this.innerEditMode) 2509 { 2510 try 2511 { 2512 // xxxHonza: Catch "can't access dead object" exception. 2513 this.editingParent.innerHTML = value; 2514 } 2515 catch (e) 2516 { 2517 FBTrace.sysout("htmlPanel.saveEdit; EXCEPTION " + e, e); 2518 } 2519 } 2520 else 2521 { 2522 try 2523 { 2524 var range = this.editingRange; 2525 var fragment = range.createContextualFragment(value); 2526 var sel = this.getNewSelection(fragment); 2527 2528 var cnl = fragment.childNodes.length; 2529 range.deleteContents(); 2530 range.insertNode(fragment); 2531 var sc = range.startContainer, so = range.startOffset; 2532 range.setEnd(sc, so + cnl); 2533 2534 this.panel.select(sel, false, true); 2535 2536 // Clear and update the status path, to make sure it doesn't 2537 // show elements no longer in the DOM. 2538 Firebug.chrome.clearStatusPath(); 2539 Firebug.chrome.syncStatusPath(); 2540 } 2541 catch (e) 2542 { 2543 if (FBTrace.DBG_ERRORS) 2544 FBTrace.sysout("HTMLEditor.saveEdit; EXCEPTION " + e, e); 2545 } 2546 } 2547 }, 2548 2549 endEditing: function() 2550 { 2551 //this.panel.markChange(); 2552 return true; 2553 }, 2554 2555 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 2556 2557 onInput: function() 2558 { 2559 Firebug.Editor.update(); 2560 } 2561 }); 2562 2563 // ********************************************************************************************* // 2564 // Editors 2565 2566 Firebug.HTMLPanel.Editors = { 2567 html : HTMLEditor, 2568 Attribute : AttributeEditor, 2569 TextNode: TextNodeEditor, 2570 TextData: TextDataEditor 2571 }; 2572 2573 // ********************************************************************************************* // 2574 // Local Helpers 2575 2576 function getEmptyElementTag(node) 2577 { 2578 var isXhtml= Xml.isElementXHTML(node); 2579 if (isXhtml) 2580 return Firebug.HTMLPanel.XEmptyElement.tag; 2581 else 2582 return Firebug.HTMLPanel.EmptyElement.tag; 2583 } 2584 2585 function getNodeTag(node, expandAll) 2586 { 2587 if (node instanceof window.Element) 2588 { 2589 if (node instanceof window.HTMLHtmlElement && node.ownerDocument && node.ownerDocument.doctype) 2590 return Firebug.HTMLPanel.HTMLHtmlElement.tag; 2591 else if (node instanceof window.HTMLAppletElement) 2592 return getEmptyElementTag(node); 2593 else if (Firebug.shouldIgnore(node)) 2594 return null; 2595 else if (HTMLLib.isContainerElement(node)) 2596 return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag; 2597 else if (HTMLLib.isEmptyElement(node)) 2598 return getEmptyElementTag(node); 2599 else if (Firebug.showCommentNodes && HTMLLib.hasCommentChildren(node)) 2600 return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag; 2601 else if (HTMLLib.hasNoElementChildren(node)) 2602 return Firebug.HTMLPanel.TextElement.tag; 2603 else 2604 return expandAll ? Firebug.HTMLPanel.CompleteElement.tag : Firebug.HTMLPanel.Element.tag; 2605 } 2606 else if (node instanceof window.Text) 2607 return Firebug.HTMLPanel.TextNode.tag; 2608 else if (node instanceof window.CDATASection) 2609 return Firebug.HTMLPanel.CDATANode.tag; 2610 else if (node instanceof window.Comment && (Firebug.showCommentNodes || expandAll)) 2611 return Firebug.HTMLPanel.CommentNode.tag; 2612 else if (node instanceof Firebug.HTMLModule.SourceText) 2613 return FirebugReps.SourceText.tag; 2614 else if (node instanceof window.Document) 2615 return Firebug.HTMLPanel.HTMLDocument.tag; 2616 else if (node instanceof window.DocumentType) 2617 return Firebug.HTMLPanel.HTMLDocType.tag; 2618 else 2619 return FirebugReps.Nada.tag; 2620 } 2621 2622 function getNodeBoxTag(nodeBox) 2623 { 2624 var re = /([^\s]+)NodeBox/; 2625 var m = re.exec(nodeBox.className); 2626 if (!m) 2627 return null; 2628 2629 var nodeBoxType = m[1]; 2630 if (nodeBoxType == "container") 2631 return Firebug.HTMLPanel.Element.tag; 2632 else if (nodeBoxType == "text") 2633 return Firebug.HTMLPanel.TextElement.tag; 2634 else if (nodeBoxType == "empty") 2635 return Firebug.HTMLPanel.EmptyElement.tag; 2636 } 2637 2638 // ********************************************************************************************* // 2639 2640 Firebug.HTMLModule.SourceText = function(lines, owner) 2641 { 2642 this.lines = lines; 2643 this.owner = owner; 2644 }; 2645 2646 Firebug.HTMLModule.SourceText.getLineAsHTML = function(lineNo) 2647 { 2648 return Str.escapeForSourceLine(this.lines[lineNo-1]); 2649 }; 2650 2651 // ********************************************************************************************* // 2652 // Mutation Breakpoints 2653 2654 /** 2655 * @class Represents {@link Firebug.Debugger} listener. This listener is reponsible for 2656 * providing a list of mutation-breakpoints into the Breakpoints side-panel. 2657 */ 2658 Firebug.HTMLModule.DebuggerListener = 2659 { 2660 getBreakpoints: function(context, groups) 2661 { 2662 if (!context.mutationBreakpoints.isEmpty()) 2663 groups.push(context.mutationBreakpoints); 2664 } 2665 }; 2666 2667 Firebug.HTMLModule.MutationBreakpoints = 2668 { 2669 breakOnNext: function(context, breaking) 2670 { 2671 context.breakOnNextMutate = breaking; 2672 }, 2673 2674 breakOnNextMutate: function(event, context, type) 2675 { 2676 if (!context.breakOnNextMutate) 2677 return false; 2678 2679 // Ignore changes in ignored branches 2680 if (isAncestorIgnored(event.target)) 2681 return false; 2682 2683 context.breakOnNextMutate = false; 2684 2685 this.breakWithCause(event, context, type); 2686 }, 2687 2688 breakWithCause: function(event, context, type) 2689 { 2690 var changeLabel = Firebug.HTMLModule.BreakpointRep.getChangeLabel({type: type}); 2691 context.breakingCause = { 2692 title: Locale.$STR("html.Break On Mutate"), 2693 message: changeLabel, 2694 type: event.type, 2695 target: event.target, 2696 relatedNode: event.relatedNode, // http://www.w3.org/TR/DOM-Level-2-Events/events.html 2697 prevValue: event.prevValue, 2698 newValue: event.newValue, 2699 attrName: event.attrName, 2700 attrChange: event.attrChange, 2701 }; 2702 2703 Firebug.Breakpoint.breakNow(context.getPanel("html", true)); 2704 return true; 2705 }, 2706 2707 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 2708 // Mutation event handlers. 2709 2710 onMutateAttr: function(event, context) 2711 { 2712 if (this.breakOnNextMutate(event, context, BP_BREAKONATTRCHANGE)) 2713 return; 2714 2715 var breakpoints = context.mutationBreakpoints; 2716 var self = this; 2717 breakpoints.enumerateBreakpoints(function(bp) { 2718 if (bp.checked && bp.node == event.target && bp.type == BP_BREAKONATTRCHANGE) { 2719 self.breakWithCause(event, context, BP_BREAKONATTRCHANGE); 2720 return true; 2721 } 2722 }); 2723 }, 2724 2725 onMutateText: function(event, context) 2726 { 2727 if (this.breakOnNextMutate(event, context, BP_BREAKONTEXT)) 2728 return; 2729 }, 2730 2731 onMutateNode: function(event, context) 2732 { 2733 var node = event.target; 2734 var removal = event.type == "DOMNodeRemoved"; 2735 2736 if (this.breakOnNextMutate(event, context, removal ? 2737 BP_BREAKONREMOVE : BP_BREAKONCHILDCHANGE)) 2738 { 2739 return; 2740 } 2741 2742 var breakpoints = context.mutationBreakpoints; 2743 var breaked = false; 2744 2745 if (removal) 2746 { 2747 var self = this; 2748 breaked = breakpoints.enumerateBreakpoints(function(bp) { 2749 if (bp.checked && bp.node == node && bp.type == BP_BREAKONREMOVE) { 2750 self.breakWithCause(event, context, BP_BREAKONREMOVE); 2751 return true; 2752 } 2753 }); 2754 } 2755 2756 if (!breaked) 2757 { 2758 // Collect all parents of the mutated node. 2759 var parents = []; 2760 for (var parent = node.parentNode; parent; parent = parent.parentNode) 2761 parents.push(parent); 2762 2763 // Iterate over all parents and see if some of them has a breakpoint. 2764 var self = this; 2765 breakpoints.enumerateBreakpoints(function(bp) 2766 { 2767 for (var i=0; i<parents.length; i++) 2768 { 2769 if (bp.checked && bp.node == parents[i] && bp.type == BP_BREAKONCHILDCHANGE) 2770 { 2771 self.breakWithCause(event, context, BP_BREAKONCHILDCHANGE); 2772 return true; 2773 } 2774 } 2775 }); 2776 } 2777 2778 if (removal) 2779 { 2780 // Remove all breakpoints assocaited with removed node. 2781 var invalidate = false; 2782 breakpoints.enumerateBreakpoints(function(bp) 2783 { 2784 if (bp.node == node) 2785 { 2786 breakpoints.removeBreakpoint(bp); 2787 invalidate = true; 2788 } 2789 }); 2790 2791 if (invalidate) 2792 context.invalidatePanels("breakpoints"); 2793 } 2794 }, 2795 2796 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 2797 // Context menu items 2798 2799 getContextMenuItems: function(context, node, target, items) 2800 { 2801 if (!(node && node.nodeType == Node.ELEMENT_NODE)) 2802 return; 2803 2804 var breakpoints = context.mutationBreakpoints; 2805 2806 var attrBox = Dom.getAncestorByClass(target, "nodeAttr"); 2807 if (Dom.getAncestorByClass(target, "nodeAttr")) 2808 { 2809 } 2810 2811 if (!(Css.nonEditableTags.hasOwnProperty(node.localName))) 2812 { 2813 items.push( 2814 "-", 2815 { 2816 label: "html.label.Break_On_Attribute_Change", 2817 tooltiptext: "html.tip.Break_On_Attribute_Change", 2818 type: "checkbox", 2819 checked: breakpoints.findBreakpoint(node, BP_BREAKONATTRCHANGE), 2820 command: Obj.bindFixed(this.onModifyBreakpoint, this, context, node, 2821 BP_BREAKONATTRCHANGE) 2822 }, 2823 { 2824 label: "html.label.Break_On_Child_Addition_or_Removal", 2825 tooltiptext: "html.tip.Break_On_Child_Addition_or_Removal", 2826 type: "checkbox", 2827 checked: breakpoints.findBreakpoint(node, BP_BREAKONCHILDCHANGE), 2828 command: Obj.bindFixed(this.onModifyBreakpoint, this, context, node, 2829 BP_BREAKONCHILDCHANGE) 2830 }, 2831 { 2832 label: "html.label.Break_On_Element_Removal", 2833 tooltiptext: "html.tip.Break_On_Element_Removal", 2834 type: "checkbox", 2835 checked: breakpoints.findBreakpoint(node, BP_BREAKONREMOVE), 2836 command: Obj.bindFixed(this.onModifyBreakpoint, this, context, node, 2837 BP_BREAKONREMOVE) 2838 } 2839 ); 2840 } 2841 }, 2842 2843 onModifyBreakpoint: function(context, node, type) 2844 { 2845 var xpath = Xpath.getElementXPath(node); 2846 if (FBTrace.DBG_HTML) 2847 FBTrace.sysout("html.onModifyBreakpoint " + xpath ); 2848 2849 var breakpoints = context.mutationBreakpoints; 2850 var bp = breakpoints.findBreakpoint(node, type); 2851 2852 // Remove an existing or create new breakpoint. 2853 if (bp) 2854 breakpoints.removeBreakpoint(bp); 2855 else 2856 context.mutationBreakpoints.addBreakpoint(node, type); 2857 2858 Events.dispatch( Firebug.HTMLModule.fbListeners, "onModifyBreakpoint", 2859 [context, xpath, type]); 2860 }, 2861 }; 2862 2863 Firebug.HTMLModule.Breakpoint = function(node, type) 2864 { 2865 this.node = node; 2866 this.xpath = Xpath.getElementXPath(node); 2867 this.checked = true; 2868 this.type = type; 2869 }; 2870 2871 Firebug.HTMLModule.BreakpointRep = domplate(Firebug.Rep, 2872 { 2873 inspectable: false, 2874 2875 tag: 2876 DIV({"class": "breakpointRow focusRow", $disabled: "$bp|isDisabled", _repObject: "$bp", 2877 role: "option", "aria-checked": "$bp.checked"}, 2878 DIV({"class": "breakpointBlockHead"}, 2879 INPUT({"class": "breakpointCheckbox", type: "checkbox", 2880 _checked: "$bp.checked", tabindex : "-1", onclick: "$onEnable"}), 2881 TAG("$bp.node|getNodeTag", {object: "$bp.node"}), 2882 DIV({"class": "breakpointMutationType"}, "$bp|getChangeLabel"), 2883 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"}) 2884 ), 2885 DIV({"class": "breakpointCode"}, 2886 TAG("$bp.node|getSourceLine", {object: "$bp.node"}) 2887 ) 2888 ), 2889 2890 getNodeTag: function(node) 2891 { 2892 var rep = Firebug.getRep(node, Firebug.currentContext); 2893 return rep.shortTag ? rep.shortTag : rep.tag; 2894 }, 2895 2896 getSourceLine: function(node) 2897 { 2898 return getNodeTag(node, false); 2899 }, 2900 2901 getChangeLabel: function(bp) 2902 { 2903 switch (bp.type) 2904 { 2905 case BP_BREAKONATTRCHANGE: 2906 return Locale.$STR("html.label.Break On Attribute Change"); 2907 case BP_BREAKONCHILDCHANGE: 2908 return Locale.$STR("html.label.Break On Child Addition or Removal"); 2909 case BP_BREAKONREMOVE: 2910 return Locale.$STR("html.label.Break On Element Removal"); 2911 case BP_BREAKONTEXT: 2912 return Locale.$STR("html.label.Break On Text Change"); 2913 } 2914 2915 return ""; 2916 }, 2917 2918 isDisabled: function(bp) 2919 { 2920 return !bp.checked; 2921 }, 2922 2923 onRemove: function(event) 2924 { 2925 Events.cancelEvent(event); 2926 2927 var bpPanel = Firebug.getElementPanel(event.target); 2928 var context = bpPanel.context; 2929 2930 if (Css.hasClass(event.target, "closeButton")) 2931 { 2932 // Remove from list of breakpoints. 2933 var row = Dom.getAncestorByClass(event.target, "breakpointRow"); 2934 context.mutationBreakpoints.removeBreakpoint(row.repObject); 2935 2936 bpPanel.refresh(); 2937 } 2938 }, 2939 2940 onEnable: function(event) 2941 { 2942 var checkBox = event.target; 2943 var bpRow = Dom.getAncestorByClass(checkBox, "breakpointRow"); 2944 2945 if (checkBox.checked) 2946 { 2947 Css.removeClass(bpRow, "disabled"); 2948 bpRow.setAttribute("aria-checked", "true"); 2949 } 2950 else 2951 { 2952 Css.setClass(bpRow, "disabled"); 2953 bpRow.setAttribute("aria-checked", "false"); 2954 } 2955 2956 var bp = bpRow.repObject; 2957 bp.checked = checkBox.checked; 2958 2959 var bpPanel = Firebug.getElementPanel(event.target); 2960 var context = bpPanel.context; 2961 }, 2962 2963 supportsObject: function(object, type) 2964 { 2965 return object instanceof Firebug.HTMLModule.Breakpoint; 2966 } 2967 }); 2968 2969 // ********************************************************************************************* // 2970 2971 function MutationBreakpointGroup() 2972 { 2973 this.breakpoints = []; 2974 } 2975 2976 MutationBreakpointGroup.prototype = Obj.extend(new Firebug.Breakpoint.BreakpointGroup(), 2977 { 2978 name: "mutationBreakpoints", 2979 title: Locale.$STR("html.label.HTML Breakpoints"), 2980 2981 addBreakpoint: function(node, type) 2982 { 2983 this.breakpoints.push(new Firebug.HTMLModule.Breakpoint(node, type)); 2984 }, 2985 2986 matchBreakpoint: function(bp, args) 2987 { 2988 var node = args[0]; 2989 var type = args[1]; 2990 return (bp.node == node) && (!bp.type || bp.type == type); 2991 }, 2992 2993 removeBreakpoint: function(bp) 2994 { 2995 Arr.remove(this.breakpoints, bp); 2996 }, 2997 2998 // Persistence 2999 load: function(context) 3000 { 3001 var panelState = Persist.getPersistedState(context, "html"); 3002 if (panelState.breakpoints) 3003 this.breakpoints = panelState.breakpoints; 3004 3005 this.enumerateBreakpoints(function(bp) 3006 { 3007 var elts = Xpath.getElementsByXPath(context.window.document, bp.xpath); 3008 bp.node = elts && elts.length ? elts[0] : null; 3009 }); 3010 }, 3011 3012 store: function(context) 3013 { 3014 this.enumerateBreakpoints(function(bp) 3015 { 3016 bp.node = null; 3017 }); 3018 3019 var panelState = Persist.getPersistedState(context, "html"); 3020 panelState.breakpoints = this.breakpoints; 3021 }, 3022 }); 3023 3024 function isAncestorIgnored(node) 3025 { 3026 for (var parent = node; parent; parent = parent.parentNode) 3027 { 3028 if (Firebug.shouldIgnore(parent)) 3029 return true; 3030 } 3031 3032 return false; 3033 } 3034 3035 // ********************************************************************************************* // 3036 // Registration 3037 3038 Firebug.registerPanel(Firebug.HTMLPanel); 3039 Firebug.registerModule(Firebug.HTMLModule); 3040 Firebug.registerRep(Firebug.HTMLModule.BreakpointRep); 3041 3042 return Firebug.HTMLModule; 3043 3044 // ********************************************************************************************* // 3045 }}); 3046