1 /* See license.txt for terms of usage */ 2 3 FBL.ns(function() { with (FBL) { 4 5 // ************************************************************************************************ 6 // Constants 7 8 const inspectDelay = 100; 9 const edgeSize = 2; 10 const defaultPrimaryPanel = "html"; 11 const defaultSecondaryPanel = "dom"; 12 const highlightCSS = "chrome://firebug/content/highlighter.css"; 13 14 // ************************************************************************************************ 15 // Globals 16 17 var boxModelHighlighter = null, 18 frameHighlighter = null, 19 popupHighlighter = null, 20 mx, my; 21 22 // ************************************************************************************************ 23 24 Firebug.Inspector = extend(Firebug.Module, 25 { 26 dispatchName: "inspector", 27 inspecting: false, 28 29 highlightObject: function(element, context, highlightType, boxFrame) 30 { 31 if(context && context.window && context.window.document) 32 { 33 context.window.document.addEventListener("mousemove", function(event) 34 { 35 mx = event.clientX; 36 my = event.clientY; 37 }, true); 38 } 39 40 if (!element || !isElement(element) || !isVisible(element)) 41 { 42 if(element && element.nodeType == 3) 43 element = element.parentNode; 44 else 45 element = null; 46 } 47 48 if (element && context && context.highlightTimeout) 49 { 50 context.clearTimeout(context.highlightTimeout); 51 delete context.highlightTimeout; 52 } 53 54 var highlighter = highlightType ? getHighlighter(highlightType) : this.defaultHighlighter; 55 56 var oldContext = this.highlightedContext; 57 if (oldContext && highlighter != this.highlighter) 58 { 59 if (oldContext.window) 60 this.highlighter.unhighlight(oldContext); 61 } 62 63 this.highlighter = highlighter; 64 this.highlightedElement = element; 65 this.highlightedContext = context; 66 67 if (element) 68 { 69 if(!isVisibleElement(element)) 70 highlighter.unhighlight(context); 71 else if (context && context.window && context.window.document) 72 highlighter.highlight(context, element, boxFrame); 73 } 74 else if (oldContext) 75 { 76 oldContext.highlightTimeout = oldContext.setTimeout(function() 77 { 78 delete oldContext.highlightTimeout; 79 if (oldContext.window && oldContext.window.document) 80 highlighter.unhighlight(oldContext); 81 }, inspectDelay); 82 } 83 }, 84 85 toggleInspecting: function(context) 86 { 87 if (this.inspecting) 88 this.stopInspecting(true); 89 else 90 this.startInspecting(context); 91 }, 92 93 startInspecting: function(context) 94 { 95 if (this.inspecting || !context || !context.loaded) 96 return; 97 98 this.inspecting = true; 99 this.inspectingContext = context; 100 101 Firebug.chrome.setGlobalAttribute("cmd_toggleInspecting", "checked", "true"); 102 this.attachInspectListeners(context); 103 104 var htmlPanel = Firebug.chrome.switchToPanel(context, "html"); 105 106 if (Firebug.isDetached()) 107 context.window.focus(); 108 else if (Firebug.isMinimized()) 109 Firebug.showBar(true); 110 111 htmlPanel.panelNode.focus(); 112 htmlPanel.startInspecting(); 113 114 if (context.stopped) 115 Firebug.Debugger.thaw(context); 116 117 if (context.hoverNode) 118 this.inspectNode(context.hoverNode); 119 }, 120 121 inspectNode: function(node) 122 { 123 if (node && node.nodeType != 1) 124 node = node.parentNode; 125 126 if (node && node.firebugIgnore && !node.fbProxyFor) 127 return; 128 129 var context = this.inspectingContext; 130 131 if (this.inspectTimeout) 132 { 133 context.clearTimeout(this.inspectTimeout); 134 delete this.inspectTimeout; 135 } 136 137 if(node && node.fbProxyFor) 138 node = node.fbProxyFor; 139 140 this.highlightObject(node, context, "frame"); 141 142 this.inspectingNode = node; 143 144 if (node) 145 { 146 this.inspectTimeout = context.setTimeout(function() 147 { 148 Firebug.chrome.select(node); 149 }, inspectDelay); 150 dispatch(this.fbListeners, "onInspectNode", [context, node] ); 151 } 152 }, 153 154 stopInspecting: function(cancelled, waitForClick) 155 { 156 if (!this.inspecting) 157 return; 158 159 var context = this.inspectingContext; 160 161 if (context.stopped) 162 Firebug.Debugger.freeze(context); 163 164 if (this.inspectTimeout) 165 { 166 context.clearTimeout(this.inspectTimeout); 167 delete this.inspectTimeout; 168 } 169 170 this.detachInspectListeners(context); 171 if (!waitForClick) 172 this.detachClickInspectListeners(context.window); 173 174 Firebug.chrome.setGlobalAttribute("cmd_toggleInspecting", "checked", "false"); 175 176 this.inspecting = false; 177 178 var htmlPanel = Firebug.chrome.unswitchToPanel(context, "html", cancelled); 179 180 htmlPanel.stopInspecting(htmlPanel.selection, cancelled); 181 182 dispatch(this.fbListeners, "onStopInspecting", [context] ); 183 184 this.inspectNode(null); 185 }, 186 187 inspectFromContextMenu: function(elt) 188 { 189 var context, htmlPanel; 190 191 Firebug.toggleBar(true); 192 Firebug.chrome.select(elt, "html"); 193 context = this.inspectingContext || TabWatcher.getContextByWindow(elt.ownerDocument.defaultView); 194 htmlPanel = Firebug.chrome.unswitchToPanel(context, "html", false); 195 htmlPanel.panelNode.focus(); 196 }, 197 198 inspectNodeBy: function(dir) 199 { 200 var target; 201 var node = this.inspectingNode; 202 203 if (dir == "up") 204 target = Firebug.chrome.getNextObject(); 205 else if (dir == "down") 206 { 207 target = Firebug.chrome.getNextObject(true); 208 if (node && !target) 209 { 210 if (node.contentDocument) 211 target = node.contentDocument.documentElement; 212 else 213 target = getNextElement(node.firstChild); 214 } 215 } 216 217 if (target && isElement(target)) 218 this.inspectNode(target); 219 else 220 beep(); 221 }, 222 223 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 224 225 attachInspectListeners: function(context) 226 { 227 var win = context.window; 228 if (!win || !win.document) 229 return; 230 231 if (FBTrace.DBG_INSPECT) 232 FBTrace.sysout("inspector.attacheInspectListeners to alls subWindows of "+win.location); 233 234 var chrome = Firebug.chrome; 235 236 this.keyListeners = 237 [ 238 chrome.keyCodeListen("RETURN", null, bindFixed(this.stopInspecting, this)), 239 chrome.keyCodeListen("ESCAPE", null, bindFixed(this.stopInspecting, this, true)), 240 chrome.keyCodeListen("UP", isControl, bindFixed(this.inspectNodeBy, this, "up"), true), 241 chrome.keyCodeListen("DOWN", isControl, bindFixed(this.inspectNodeBy, this, "down"), true), 242 ]; 243 244 iterateWindows(win, bind(function(subWin) 245 { 246 if (FBTrace.DBG_INSPECT) 247 FBTrace.sysout("inspector.attacheInspectListeners to "+subWin.location+" subWindow of "+win.location); 248 subWin.document.addEventListener("mouseover", this.onInspectingMouseOver, true); 249 subWin.document.addEventListener("mousedown", this.onInspectingMouseDown, true); 250 subWin.document.addEventListener("click", this.onInspectingClick, true); 251 }, this)); 252 }, 253 254 detachInspectListeners: function(context) 255 { 256 var i, keyListenersLen, 257 win = context.window; 258 259 if (!win || !win.document) 260 return; 261 262 var chrome = Firebug.chrome; 263 264 if (this.keyListeners) // XXXjjb for some reason this is null some times. 265 { 266 keyListenersLen = this.keyListeners.length; 267 for (i = 0; i < keyListenersLen; ++i) 268 chrome.keyIgnore(this.keyListeners[i]); 269 delete this.keyListeners; 270 } 271 272 iterateWindows(win, bind(function(subWin) 273 { 274 subWin.document.removeEventListener("mouseover", this.onInspectingMouseOver, true); 275 subWin.document.removeEventListener("mousedown", this.onInspectingMouseDown, true); 276 }, this)); 277 }, 278 279 detachClickInspectListeners: function(win) 280 { 281 // We have to remove the click listener in a second phase because if we remove it 282 // after the mousedown, we won't be able to cancel clicked links 283 iterateWindows(win, bind(function(subWin) 284 { 285 subWin.document.removeEventListener("click", this.onInspectingClick, true); 286 }, this)); 287 }, 288 289 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 290 291 onInspectingMouseOver: function(event) 292 { 293 if (FBTrace.DBG_INSPECT) 294 FBTrace.sysout("onInspectingMouseOver event", event); 295 this.inspectNode(event.target); 296 cancelEvent(event); 297 }, 298 299 onInspectingMouseDown: function(event) 300 { 301 if (FBTrace.DBG_INSPECT) 302 FBTrace.sysout("onInspectingMouseDown event", event); 303 this.stopInspecting(false, true); 304 cancelEvent(event); 305 }, 306 307 onInspectingClick: function(event) 308 { 309 if (FBTrace.DBG_INSPECT) 310 FBTrace.sysout("onInspectingClick event", event); 311 var win = event.currentTarget.defaultView; 312 if (win) 313 { 314 win = getRootWindow(win); 315 this.detachClickInspectListeners(win); 316 } 317 cancelEvent(event); 318 }, 319 320 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 321 // extends Module 322 323 initialize: function() 324 { 325 Firebug.Module.initialize.apply(this, arguments); 326 327 this.onInspectingMouseOver = bind(this.onInspectingMouseOver, this); 328 this.onInspectingMouseDown = bind(this.onInspectingMouseDown, this); 329 this.onInspectingClick = bind(this.onInspectingClick, this); 330 331 this.updateOption("shadeBoxModel", Firebug.shadeBoxModel); 332 this.updateOption("showQuickInfoBox", Firebug.showQuickInfoBox); 333 }, 334 335 initContext: function(context) 336 { 337 context.onPreInspectMouseOver = function(event) { context.hoverNode = event.target; }; 338 }, 339 340 destroyContext: function(context) 341 { 342 if (context.highlightTimeout) 343 { 344 context.clearTimeout(context.highlightTimeout); 345 delete context.highlightTimeout; 346 } 347 348 if (this.inspecting) 349 this.stopInspecting(true); 350 }, 351 352 watchWindow: function(context, win) 353 { 354 win.addEventListener("mouseover", context.onPreInspectMouseOver, true); 355 }, 356 357 unwatchWindow: function(context, win) 358 { 359 try { 360 win.removeEventListener("mouseover", context.onPreInspectMouseOver, true); 361 this.hideQuickInfoBox(); 362 } catch (ex) { 363 // Get unfortunate errors here sometimes, so let's just ignore them 364 // since the window is going away anyhow 365 } 366 }, 367 368 showContext: function(browser, context) 369 { 370 if (this.inspecting) 371 this.stopInspecting(true); 372 }, 373 374 showPanel: function(browser, panel) 375 { 376 var chrome = Firebug.chrome; 377 var disabled = !panel || !panel.context.loaded; 378 379 chrome.setGlobalAttribute("cmd_toggleInspecting", "disabled", disabled); 380 //chrome.setGlobalAttribute("menu_firebugInspect", "disabled", disabled); 381 }, 382 383 loadedContext: function(context) 384 { 385 Firebug.chrome.setGlobalAttribute("cmd_toggleInspecting", "disabled", "false"); 386 //Firebug.chrome.setGlobalAttribute("menu_firebugInspect", "disabled", "false"); 387 }, 388 389 updateOption: function(name, value) 390 { 391 if (name == "shadeBoxModel") 392 { 393 this.highlightObject(null); 394 this.defaultHighlighter = value ? getHighlighter("boxModel") : getHighlighter("frame"); 395 } 396 else if(name == "showQuickInfoBox") 397 quickInfoBox.boxEnabled = value; 398 }, 399 400 getObjectByURL: function(context, url) 401 { 402 var styleSheet = getStyleSheetByHref(url, context); 403 if (styleSheet) 404 return styleSheet; 405 406 /*var path = getURLPath(url); 407 var xpath = "//*[contains(@src, '" + path + "')]"; 408 var elements = getElementsByXPath(context.window.document, xpath); 409 if (elements.length) 410 return elements[0];*/ 411 }, 412 413 toggleQuickInfoBox: function() 414 { 415 var qiBox = $('fbQuickInfoPanel'); 416 417 if (qiBox.state==="open") 418 quickInfoBox.hide(); 419 420 quickInfoBox.boxEnabled = !quickInfoBox.boxEnabled; 421 422 Firebug.setPref(Firebug.prefDomain, "showQuickInfoBox", quickInfoBox.boxEnabled); 423 }, 424 425 hideQuickInfoBox: function() 426 { 427 var qiBox = $('fbQuickInfoPanel'); 428 429 if (qiBox.state==="open") 430 quickInfoBox.hide(); 431 432 this.inspectNode(null); 433 }, 434 435 quickInfoBoxDragStart: function(event) 436 { 437 quickInfoBox.dragStart(event); 438 }, 439 440 quickInfoBoxDrag: function(event) 441 { 442 quickInfoBox.drag(event); 443 }, 444 445 quickInfoBoxDragEnd: function(event) 446 { 447 quickInfoBox.dragEnd(event); 448 } 449 }); 450 451 // ************************************************************************************************ 452 // Local Helpers 453 454 function getHighlighter(type) 455 { 456 if (type == "boxModel") 457 { 458 if (!boxModelHighlighter) 459 boxModelHighlighter = new BoxModelHighlighter(); 460 461 return boxModelHighlighter; 462 } 463 else if (type == "frame") 464 { 465 if (!frameHighlighter) 466 frameHighlighter = new Firebug.Inspector.FrameHighlighter(); 467 468 return frameHighlighter; 469 } 470 else if (type == "popup") 471 { 472 if (!popupHighlighter) 473 popupHighlighter = new PopupHighlighter(); 474 475 return popupHighlighter; 476 } 477 } 478 479 function pad(element, t, r, b, l) 480 { 481 element.style.padding = Math.abs(t) + "px " + Math.abs(r) + "px " 482 + Math.abs(b) + "px " + Math.abs(l) + "px"; 483 } 484 485 // ************************************************************************************************ 486 // Imagemap Inspector 487 488 function getImageMapHighlighter(context) 489 { 490 if(!context) 491 return; 492 493 var canvas, ctx, 494 doc = context.window.document, 495 init = function(elt) 496 { 497 if(elt) 498 doc = elt.ownerDocument; 499 500 canvas = doc.getElementById('firebugCanvas'); 501 502 if(!canvas) 503 { 504 canvas = doc.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); 505 canvas.wrappedJSObject.firebugIgnore = true; 506 canvas.id = "firebugCanvas"; 507 canvas.className = "firebugCanvas"; 508 canvas.width = context.window.innerWidth; 509 canvas.height = context.window.innerHeight; 510 canvas.addEventListener("mousemove", function(event){context.imageMapHighlighter.mouseMoved(event)}, true); 511 canvas.addEventListener("mouseout", function(){getImageMapHighlighter(context).destroy();}, true); 512 context.window.addEventListener("scroll", function(){context.imageMapHighlighter.show(false);}, true); 513 514 doc.body.appendChild(canvas); 515 } 516 }; 517 518 if (!context.imageMapHighlighter) 519 { 520 context.imageMapHighlighter = 521 { 522 show: function(state) 523 { 524 if(!canvas) 525 init(); 526 527 canvas.style.display = state?'block':'none'; 528 }, 529 530 getImages: function(mapName, multi) 531 { 532 var i, eltsLen, 533 elts = [], 534 images = [], 535 elts2 = doc.getElementsByTagName("img"), 536 elts3 = doc.getElementsByTagName("input"), 537 elts2Len = elts2.length, 538 elts3Len = elts3.length; 539 540 for(i = 0; i < elts2Len; i++) 541 elts.push(elts2[i]); 542 543 for(i = 0; i < elts3Len; i++) 544 elts.push(elts3[i]); 545 546 if(elts) 547 { 548 eltsLen = elts.length; 549 550 for(i = 0; i < eltsLen; i++) 551 { 552 if(elts[i].getAttribute('usemap') == mapName) 553 { 554 rect = getLTRBWH(elts[i]); 555 556 if(multi) 557 images.push(elts[i]); 558 else if(rect.left <= mx && rect.right >= mx && rect.top <= my && rect.bottom >= my) 559 { 560 images[0] = elts[i]; 561 break; 562 } 563 } 564 } 565 } 566 return images; 567 }, 568 569 highlight: function(eltArea, multi) 570 { 571 var i, j, v, vLen, images, imagesLen, rect, shape, clearForFirst; 572 573 if (eltArea && eltArea.coords) 574 { 575 images = this.getImages("#"+eltArea.parentNode.name, multi); 576 577 init(eltArea); 578 579 v = eltArea.coords.split(","); 580 581 if(!ctx) 582 ctx = canvas.getContext("2d"); 583 584 ctx.fillStyle = "rgba(135, 206, 235, 0.7)"; 585 ctx.strokeStyle = "rgb(29, 55, 95)"; 586 ctx.lineWidth = 2; 587 588 if(images.length === 0) 589 images[0] = eltArea; 590 591 imagesLen = images.length; 592 593 for(j = 0; j < imagesLen; j++) 594 { 595 rect = getLTRBWH(images[j], context); 596 597 ctx.beginPath(); 598 599 if(!multi || (multi && j===0)) 600 ctx.clearRect(0, 0, canvas.width, canvas.height); 601 602 shape = eltArea.shape.toLowerCase(); 603 604 if (shape === 'rect') 605 ctx.rect(rect.left + parseInt(v[0], 10), rect.top + parseInt(v[1], 10), v[2] - v[0], v[3] - v[1]); 606 else if (shape === 'circle') 607 ctx.arc(rect.left + parseInt(v[0], 10) + ctx.lineWidth / 2, rect.top + parseInt(v[1], 10) + ctx.lineWidth / 2, v[2], 0, Math.PI / 180 * 360, false); 608 else 609 { 610 vLen = v.length; 611 ctx.moveTo(rect.left + parseInt(v[0], 10), rect.top + parseInt(v[1], 10)); 612 for(i=2; i < vLen; i += 2) 613 ctx.lineTo(rect.left + parseInt(v[i], 10), rect.top + parseInt(v[i + 1], 10)); 614 } 615 616 ctx.fill(); 617 ctx.stroke(); 618 ctx.closePath(); 619 } 620 621 this.show(true); 622 } 623 else 624 return; 625 }, 626 627 mouseMoved: function(event) 628 { 629 var idata = ctx.getImageData(event.layerX, event.layerY, 1, 1); 630 631 mx = event.clientX; 632 my = event.clientY; 633 634 if (!idata || (idata.data[0] === 0 && idata.data[1] === 0 && idata.data[2] === 0 && idata.data[3] === 0)) 635 this.show(false); 636 }, 637 638 destroy: function() 639 { 640 canvas = null; 641 ctx = null; 642 } 643 } 644 } 645 646 return context.imageMapHighlighter; 647 } 648 649 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 650 quickInfoBox = 651 { 652 boxEnabled: undefined, 653 dragging: false, 654 storedX: null, 655 storedY: null, 656 prevX: null, 657 prevY: null, 658 659 show: function(element) 660 { 661 if (!this.boxEnabled || !element) 662 return; 663 664 var vbox, lab, 665 needsTitle = false, 666 needsTitle2 = false, 667 domAttribs = ['nodeName', 'id', 'name', 'offsetWidth', 'offsetHeight'], 668 cssAttribs = ['position'], 669 compAttribs = ['width', 'height', 'zIndex', 'position', 'top', 'right', 'bottom', 'left', 670 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'color', 'backgroundColor', 671 'fontFamily', 'cssFloat', 'display', 'visibility'], 672 qiBox = $('fbQuickInfoPanel'); 673 674 if (qiBox.state==="closed") 675 { 676 qiBox.hidePopup(); 677 678 this.storedX = this.storedX || $('content').tabContainer.boxObject.screenX + 5; 679 this.storedY = this.storedY || $('content').tabContainer.boxObject.screenY + 35; 680 681 qiBox.openPopupAtScreen(this.storedX, this.storedY, false); 682 } 683 684 qiBox.removeChild(qiBox.firstChild); 685 vbox = document.createElement("vbox"); 686 qiBox.appendChild(vbox); 687 688 needsTitle = this.addRows(element, vbox, domAttribs); 689 needsTitle2 = this.addRows(element.style, vbox, cssAttribs); 690 691 if (needsTitle || needsTitle2) 692 { 693 lab = document.createElement("label"); 694 lab.setAttribute("class", "fbQuickInfoBoxTitle"); 695 lab.setAttribute("value", $STR("quickInfo")); 696 vbox.insertBefore(lab, vbox.firstChild); 697 } 698 699 lab = document.createElement("label"); 700 lab.setAttribute("class", "fbQuickInfoBoxTitle"); 701 lab.setAttribute("value", $STR("computedStyle")); 702 vbox.appendChild(lab); 703 704 this.addRows(element, vbox, compAttribs, true); 705 }, 706 707 hide: function() 708 { 709 this.prevX = null; 710 this.prevY = null; 711 qiBox = $('fbQuickInfoPanel'); 712 qiBox.hidePopup(); 713 }, 714 715 dragStart: function(event) 716 { 717 this.dragging = true; 718 }, 719 720 dragEnd: function(event) 721 { 722 this.dragging = false; 723 this.prevX = null; 724 this.prevY = null; 725 }, 726 727 drag: function(event) 728 { 729 if(this.dragging) 730 { 731 var diffX, diffY, newX, newY, 732 qiBox = $('fbQuickInfoPanel'), 733 boxX = qiBox.boxObject.screenX, 734 boxY = qiBox.boxObject.screenY, 735 x = event.screenX, 736 y = event.screenY; 737 738 this.prevX = this.prevX || x; 739 this.prevY = this.prevY || y; 740 diffX = x - this.prevX; 741 diffY = y - this.prevY; 742 newX = boxX + diffX; 743 newY = boxY + diffY; 744 745 if(newX < 0) 746 newX = 0; 747 748 if(newY < 0) 749 newY = 0; 750 751 if(newY + qiBox.boxObject.height > window.screen.height - 5) 752 newY = window.screen.height - qiBox.boxObject.height - 5; 753 754 qiBox.hidePopup(); 755 qiBox.openPopupAtScreen(newX, newY, false); 756 757 this.prevX = x; 758 this.prevY = y; 759 this.storedX = boxX; 760 this.storedY = boxY; 761 } 762 }, 763 764 addRows: function(domBase, vbox, attribs, computedStyle) 765 { 766 if(!domBase) 767 return; 768 769 var i, cs, desc, hbox, lab, value, 770 needsTitle = false, 771 attribsLen = attribs.length; 772 773 for (i = 0; i < attribsLen; i++) 774 { 775 if(computedStyle) 776 { 777 cs = getNonFrameBody(domBase).ownerDocument.defaultView.getComputedStyle(domBase, null); 778 value = cs.getPropertyValue(attribs[i]); 779 780 if (value && /rgb\(\d+,\s\d+,\s\d+\)/.test(value)) 781 value = rgbToHex(value); 782 } 783 else 784 value = domBase[attribs[i]]; 785 786 if (value) 787 { 788 needsTitle = true; 789 hbox = document.createElement("hbox"); 790 lab = document.createElement("label"); 791 lab.setAttribute("class", "fbQuickInfoName"); 792 lab.setAttribute("value", attribs[i]); 793 hbox.appendChild(lab); 794 desc = document.createElement("description"); 795 desc.setAttribute("class", "fbQuickInfoValue"); 796 desc.appendChild(document.createTextNode(": " + value)); 797 hbox.appendChild(desc); 798 vbox.appendChild(hbox); 799 } 800 } 801 802 return needsTitle; 803 } 804 } 805 806 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 807 808 Firebug.Inspector.FrameHighlighter = function() 809 { 810 } 811 812 Firebug.Inspector.FrameHighlighter.prototype = 813 { 814 doNotHighlight: function(element) 815 { 816 return false; // (element instanceof XULElement); 817 }, 818 819 highlight: function(context, element) 820 { 821 if (this.doNotHighlight(element)) 822 return; 823 824 var offset = getLTRBWH(element); 825 offset = applyBodyOffsets(element, offset); 826 var x = offset.left, y = offset.top; 827 var w = offset.width, h = offset.height; 828 if (FBTrace.DBG_INSPECT) 829 FBTrace.sysout("FrameHighlighter HTML tag:"+element.tagName+" x:"+x+" y:"+y+" w:"+w+" h:"+h); 830 831 var wacked = isNaN(x) || isNaN(y) || isNaN(w) || isNaN(h); 832 if (FBTrace.DBG_INSPECT && wacked) 833 FBTrace.sysout("FrameHighlighter.highlight has bad boxObject for "+ element.tagName); 834 if (wacked) 835 return; 836 837 if(element.tagName !== "AREA") 838 { 839 quickInfoBox.show(element); 840 841 var nodes = this.getNodes(context, element); 842 843 move(nodes.top, x, y-edgeSize); 844 resize(nodes.top, w, edgeSize); 845 846 move(nodes.right, x+w, y-edgeSize); 847 resize(nodes.right, edgeSize, h+edgeSize*2); 848 849 move(nodes.bottom, x, y+h); 850 resize(nodes.bottom, w, edgeSize); 851 852 move(nodes.left, x-edgeSize, y-edgeSize); 853 resize(nodes.left, edgeSize, h+edgeSize*2); 854 if (FBTrace.DBG_INSPECT) 855 FBTrace.sysout("FrameHighlighter "+element.tagName); 856 var body = getNonFrameBody(element); 857 if (!body) 858 return this.unhighlight(context); 859 860 var needsAppend = !nodes.top.parentNode || nodes.top.ownerDocument != body.ownerDocument; 861 if (needsAppend) 862 { 863 if (FBTrace.DBG_INSPECT) 864 FBTrace.sysout("FrameHighlighter needsAppend: "+ nodes.top.ownerDocument.documentURI+" !?= "+body.ownerDocument.documentURI, nodes); 865 attachStyles(context, body); 866 for (var edge in nodes) 867 { 868 try 869 { 870 body.appendChild(nodes[edge]); 871 } 872 catch(exc) 873 { 874 if (FBTrace.DBG_INSPECT) 875 FBTrace.sysout("inspector.FrameHighlighter.highlight body.appendChild FAILS for body "+body+" "+exc, exc); 876 } 877 } 878 879 createProxiesForDisabledElements(body); 880 } 881 } 882 else 883 { 884 var ihl = getImageMapHighlighter(context); 885 ihl.highlight(element, false); 886 } 887 }, 888 889 unhighlight: function(context) 890 { 891 if (FBTrace.DBG_INSPECT) 892 FBTrace.sysout("FrameHighlighter unhightlight", context.window.location); 893 var nodes = this.getNodes(context); 894 var body = nodes.top.parentNode; 895 if (body) 896 { 897 for (var edge in nodes) 898 body.removeChild(nodes[edge]); 899 900 quickInfoBox.hide(); 901 } 902 }, 903 904 getNodes: function(context) 905 { 906 if (!context.frameHighlighter) 907 { 908 var doc = context.window.document; 909 910 function createEdge(name) 911 { 912 var div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div"); 913 unwrapObject(div).firebugIgnore = true; 914 div.className = "firebugHighlight"; 915 return div; 916 } 917 918 context.frameHighlighter = 919 { 920 top: createEdge("Top"), 921 right: createEdge("Right"), 922 bottom: createEdge("Bottom"), 923 left: createEdge("Left") 924 }; 925 } 926 927 return context.frameHighlighter; 928 } 929 }; 930 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 931 932 function PopupHighlighter() 933 { 934 } 935 936 PopupHighlighter.prototype = 937 { 938 highlight: function(context, element) 939 { 940 var doc = context.window.document; 941 var popup = doc.getElementById("inspectorPopup"); 942 popup.style.width = "200px"; 943 popup.style.height = "100px"; 944 popup.showPopup(element, element.boxObject.screenX, 945 element.boxObject.screenY, "popup", "none", "none"); 946 if (FBTrace.DBG_INSPECT) 947 { 948 FBTrace.sysout("PopupHighlighter for "+element.tagName, " at ("+element.boxObject.screenX+","+element.boxObject.screenY+")"); 949 FBTrace.sysout("PopupHighlighter popup=", popup); 950 } 951 }, 952 953 unhighlight: function(context) 954 { 955 }, 956 } 957 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 958 959 function BoxModelHighlighter() 960 { 961 } 962 963 BoxModelHighlighter.prototype = 964 { 965 highlight: function(context, element, boxFrame) 966 { 967 var nodes = this.getNodes(context); 968 var highlightFrame = boxFrame ? nodes[boxFrame] : null; 969 970 if (context.highlightFrame) 971 removeClass(context.highlightFrame, "firebugHighlightBox"); 972 973 if(element.tagName !== "AREA") 974 { 975 quickInfoBox.show(element); 976 context.highlightFrame = highlightFrame; 977 978 if (highlightFrame) 979 { 980 setClass(nodes.offset, "firebugHighlightGroup"); 981 setClass(highlightFrame, "firebugHighlightBox"); 982 } 983 else 984 removeClass(nodes.offset, "firebugHighlightGroup"); 985 986 var win = element.ownerDocument.defaultView; 987 if (!win) 988 return; 989 990 var style = win.getComputedStyle(element, ""); 991 if (!style) 992 { 993 if (FBTrace.DBG_INSPECT) 994 FBTrace.sysout("highlight: no style for element "+element, element); 995 return; 996 } 997 998 var styles = readBoxStyles(style); 999 1000 var offset = getLTRBWH(element); 1001 offset = applyBodyOffsets(element, offset); 1002 1003 var x = offset.left - Math.abs(styles.marginLeft); 1004 var y = offset.top - Math.abs(styles.marginTop); 1005 var w = offset.width - (styles.paddingLeft + styles.paddingRight 1006 + styles.borderLeft + styles.borderRight); 1007 var h = offset.height - (styles.paddingTop + styles.paddingBottom 1008 + styles.borderTop + styles.borderBottom); 1009 1010 move(nodes.offset, x, y); 1011 pad(nodes.margin, styles.marginTop, styles.marginRight, styles.marginBottom, 1012 styles.marginLeft); 1013 pad(nodes.border, styles.borderTop, styles.borderRight, styles.borderBottom, 1014 styles.borderLeft); 1015 pad(nodes.padding, styles.paddingTop, styles.paddingRight, styles.paddingBottom, 1016 styles.paddingLeft); 1017 resize(nodes.content, w, h); 1018 1019 // element.tagName !== "BODY" for issue 2447. hopefully temporary, robc 1020 var showLines = Firebug.showRulers && boxFrame && element.tagName !== "BODY"; 1021 if (showLines) 1022 { 1023 var offsetParent = element.offsetParent; 1024 if (offsetParent) 1025 this.setNodesByOffsetParent(win, offsetParent, nodes); 1026 else 1027 delete nodes.parent; 1028 1029 var left = x; 1030 var top = y; 1031 var width = w-1; 1032 var height = h-1; 1033 1034 if (boxFrame == "content") 1035 { 1036 left += Math.abs(styles.marginLeft) + Math.abs(styles.borderLeft) 1037 + Math.abs(styles.paddingLeft); 1038 top += Math.abs(styles.marginTop) + Math.abs(styles.borderTop) 1039 + Math.abs(styles.paddingTop); 1040 } 1041 else if (boxFrame == "padding") 1042 { 1043 left += Math.abs(styles.marginLeft) + Math.abs(styles.borderLeft); 1044 top += Math.abs(styles.marginTop) + Math.abs(styles.borderTop); 1045 width += Math.abs(styles.paddingLeft) + Math.abs(styles.paddingRight); 1046 height += Math.abs(styles.paddingTop) + Math.abs(styles.paddingBottom); 1047 } 1048 else if (boxFrame == "border") 1049 { 1050 left += Math.abs(styles.marginLeft); 1051 top += Math.abs(styles.marginTop); 1052 width += Math.abs(styles.paddingLeft) + Math.abs(styles.paddingRight) 1053 + Math.abs(styles.borderLeft) + Math.abs(styles.borderRight); 1054 height += Math.abs(styles.paddingTop) + Math.abs(styles.paddingBottom) 1055 + Math.abs(styles.borderTop) + Math.abs(styles.borderBottom); 1056 } 1057 else if (boxFrame == "margin") 1058 { 1059 width += Math.abs(styles.paddingLeft) + Math.abs(styles.paddingRight) 1060 + Math.abs(styles.borderLeft) + Math.abs(styles.borderRight) 1061 + Math.abs(styles.marginLeft) + Math.abs(styles.marginRight); 1062 height += Math.abs(styles.paddingTop) + Math.abs(styles.paddingBottom) 1063 + Math.abs(styles.borderTop) + Math.abs(styles.borderBottom) 1064 + Math.abs(styles.marginTop) + Math.abs(styles.marginBottom); 1065 } 1066 1067 move(nodes.lines.top, 0, top); 1068 move(nodes.lines.right, left+width, 0); 1069 move(nodes.lines.bottom, 0, top+height); 1070 move(nodes.lines.left, left, 0) 1071 } 1072 1073 var body = getNonFrameBody(element); 1074 if (!body) 1075 return this.unhighlight(context); 1076 1077 var needsAppend = !nodes.offset.parentNode 1078 || nodes.offset.parentNode.ownerDocument != body.ownerDocument; 1079 1080 if (needsAppend) 1081 { 1082 attachStyles(context, body); 1083 body.appendChild(nodes.offset); 1084 } 1085 1086 if (showLines) 1087 { 1088 if (!nodes.lines.top.parentNode) 1089 { 1090 if (nodes.parent) 1091 body.appendChild(nodes.parent); 1092 1093 for (var line in nodes.lines) 1094 body.appendChild(nodes.lines[line]); 1095 } 1096 } 1097 else if (nodes.lines.top.parentNode) 1098 { 1099 if (nodes.parent) 1100 body.removeChild(nodes.parent); 1101 1102 for (var line in nodes.lines) 1103 body.removeChild(nodes.lines[line]); 1104 } 1105 } 1106 else 1107 { 1108 var ihl = getImageMapHighlighter(context); 1109 ihl.highlight(element, true); 1110 } 1111 }, 1112 1113 unhighlight: function(context) 1114 { 1115 var nodes = this.getNodes(context); 1116 if (nodes.offset.parentNode) 1117 { 1118 var body = nodes.offset.parentNode; 1119 body.removeChild(nodes.offset); 1120 1121 if (nodes.lines.top.parentNode) 1122 { 1123 if (nodes.parent) 1124 body.removeChild(nodes.parent); 1125 1126 for (var line in nodes.lines) 1127 body.removeChild(nodes.lines[line]); 1128 } 1129 } 1130 1131 quickInfoBox.hide(); 1132 }, 1133 1134 getNodes: function(context) 1135 { 1136 if (!context.boxModelHighlighter) 1137 { 1138 var doc = context.window.document; 1139 if (FBTrace.DBG_ERRORS && !doc) FBTrace.sysout("inspector getNodes no document for window:"+window.location); 1140 if (FBTrace.DBG_INSPECT && doc) 1141 FBTrace.sysout("inspect.getNodes doc: "+doc.location); 1142 1143 function createRuler(name) 1144 { 1145 var div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div"); 1146 unwrapObject(div).firebugIgnore = true; 1147 div.className = "firebugRuler firebugRuler"+name; 1148 return div; 1149 } 1150 1151 function createBox(name) 1152 { 1153 var div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div"); 1154 unwrapObject(div).firebugIgnore = true; 1155 div.className = "firebugLayoutBox firebugLayoutBox"+name; 1156 return div; 1157 } 1158 1159 function createLine(name) 1160 { 1161 var div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div"); 1162 unwrapObject(div).firebugIgnore = true; 1163 div.className = "firebugLayoutLine firebugLayoutLine"+name; 1164 return div; 1165 } 1166 1167 var nodes = context.boxModelHighlighter = 1168 { 1169 parent: createBox("Parent"), 1170 rulerH: createRuler("H"), 1171 rulerV: createRuler("V"), 1172 offset: createBox("Offset"), 1173 margin: createBox("Margin"), 1174 border: createBox("Border"), 1175 padding: createBox("Padding"), 1176 content: createBox("Content"), 1177 lines: { 1178 top: createLine("Top"), 1179 right: createLine("Right"), 1180 bottom: createLine("Bottom"), 1181 left: createLine("Left") 1182 } 1183 }; 1184 1185 nodes.parent.appendChild(nodes.rulerH); 1186 nodes.parent.appendChild(nodes.rulerV); 1187 nodes.offset.appendChild(nodes.margin); 1188 nodes.margin.appendChild(nodes.border); 1189 nodes.border.appendChild(nodes.padding); 1190 nodes.padding.appendChild(nodes.content); 1191 } 1192 1193 return context.boxModelHighlighter; 1194 }, 1195 1196 setNodesByOffsetParent: function(win, offsetParent, nodes) 1197 { 1198 var parentStyle = win.getComputedStyle(offsetParent, ""); 1199 var parentOffset = getLTRBWH(offsetParent); 1200 parentOffset = applyBodyOffsets(offsetParent, parentOffset); 1201 var parentX = parentOffset.left + parseInt(parentStyle.borderLeftWidth); 1202 var parentY = parentOffset.top + parseInt(parentStyle.borderTopWidth); 1203 var parentW = offsetParent.offsetWidth-1; 1204 var parentH = offsetParent.offsetHeight-1; 1205 1206 move(nodes.parent, parentX, parentY); 1207 resize(nodes.parent, parentW, parentH); 1208 1209 if (parentX < 14) 1210 setClass(nodes.parent, "overflowRulerX"); 1211 else 1212 removeClass(nodes.parent, "overflowRulerX"); 1213 1214 if (parentY < 14) 1215 setClass(nodes.parent, "overflowRulerY"); 1216 else 1217 removeClass(nodes.parent, "overflowRulerY"); 1218 } 1219 }; 1220 1221 function getNonFrameBody(elt) 1222 { 1223 var body = getBody(elt.ownerDocument); 1224 return (body.localName && body.localName.toUpperCase() == "FRAMESET") ? null : body; 1225 } 1226 1227 function attachStyles(context, body) 1228 { 1229 var doc = body.ownerDocument; 1230 if (!context.highlightStyle) 1231 context.highlightStyle = createStyleSheet(doc, highlightCSS); 1232 1233 if (!context.highlightStyle.parentNode || context.highlightStyle.ownerDocument != doc) 1234 addStyleSheet(body.ownerDocument, context.highlightStyle); 1235 } 1236 1237 function createProxiesForDisabledElements(body) 1238 { 1239 var i, rect, div, 1240 doc = body.ownerDocument, 1241 nodes = doc.getElementsByTagName("*"); 1242 1243 for(i = 0; i < nodes.length; i++) 1244 { 1245 if(nodes[i].hasAttribute("disabled") && !nodes[i].fbHasProxyElement) 1246 { 1247 rect = nodes[i].getBoundingClientRect(); 1248 1249 div = doc.createElementNS("http://www.w3.org/1999/xhtml", "div"); 1250 div.className = "fbProxyElement"; 1251 div.style.left = rect.left + "px"; 1252 div.style.top = rect.top + body.scrollTop + "px"; 1253 div.style.width = rect.width + "px"; 1254 div.style.height = rect.height + "px"; 1255 unwrapObject(div).firebugIgnore = true; 1256 1257 div.fbProxyFor = nodes[i]; 1258 nodes[i].fbHasProxyElement = true; 1259 1260 body.appendChild(div); 1261 } 1262 } 1263 } 1264 1265 function rgbToHex(value) 1266 { 1267 return value.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, function(_, r, g, b) { 1268 return '#' + ((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase(); 1269 }); 1270 } 1271 1272 function isVisibleElement(elt) 1273 { 1274 var invisibleElements = 1275 { 1276 "head": true, 1277 "base": true, 1278 "basefont": true, 1279 "isindex": true, 1280 "link": true, 1281 "meta": true, 1282 "script": true, 1283 "style": true, 1284 "title": true, 1285 "isindex": true 1286 } 1287 1288 return !invisibleElements[elt.nodeName.toLowerCase()]; 1289 } 1290 // ************************************************************************************************ 1291 1292 Firebug.registerModule(Firebug.Inspector); 1293 1294 // ************************************************************************************************ 1295 1296 }}); 1297