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